Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ endif()

set(FDB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/bindings/c)
set(SWIFT_SDK_DIR "/usr/lib/swift/linux/")
set(SWIFT_SDK_STATIC_DIR "/usr/libexec/swift/6.0.3/lib/swift_static/linux")
set(SWIFT_SDK_STATIC_DIR "/usr/lib/swift_static/linux")

file(GLOB_RECURSE SWIFT_BINDING_SOURCES "Sources/**/*.swift")
add_library(FoundationDB-Swift ${SWIFT_BINDING_SOURCES})
Expand Down Expand Up @@ -71,10 +71,22 @@ set_target_properties(FoundationDB-Swift-Tests PROPERTIES
Swift_MODULE_NAME "FoundationDBTests"
)

install(TARGETS FoundationDB-Swift
add_executable(stacktester_swift
Tests/StackTester/Sources/StackTester/StackTester.swift
Tests/StackTester/Sources/StackTester/main.swift
)
add_dependencies(stacktester_swift FoundationDB-Swift)
target_link_libraries(stacktester_swift PRIVATE FoundationDB-Swift)

set_target_properties(stacktester_swift PROPERTIES
Swift_MODULE_NAME "StackTester"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bindings/swift/bin"
)

install(TARGETS FoundationDB-Swift stacktester_swift
RUNTIME DESTINATION bindings/swift/bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)

# Configure test to run with Swift Testing
add_test(NAME FoundationDB-Swift-Tests COMMAND FoundationDB-Swift-Tests)
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ let package = Package(
.macOS(.v14)
],
products: [
.library(name: "FoundationDB", targets: ["FoundationDB"])
.library(name: "FoundationDB", targets: ["FoundationDB"]),
.executable(name: "stacktester", targets: ["StackTester"])
],
targets: [
.systemLibrary(
Expand All @@ -45,5 +46,10 @@ let package = Package(
name: "FoundationDBTests",
dependencies: ["FoundationDB"]
),
.executableTarget(
name: "StackTester",
dependencies: ["FoundationDB"],
path: "Tests/StackTester/Sources/StackTester"
),
]
)
2 changes: 1 addition & 1 deletion Sources/CFoundationDB/fdb_c_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#ifndef FDB_C_WRAPPER_H
#define FDB_C_WRAPPER_H

#define FDB_API_VERSION 730
#define FDB_API_VERSION FDB_LATEST_API_VERSION
#include <foundationdb/fdb_c.h>

#endif
4 changes: 2 additions & 2 deletions Sources/FoundationDB/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import CFoundationDB
public class FdbClient {
/// FoundationDB API version constants.
public enum APIVersion {
/// The current supported API version (730).
public static let current: Int32 = 730
/// The current supported API version (710).
public static let current: Int32 = 710
}

/// Initializes the FoundationDB client with the specified API version.
Expand Down
49 changes: 49 additions & 0 deletions Sources/FoundationDB/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,53 @@ public class FdbDatabase: IDatabase {

return FdbTransaction(transaction: tr)
}

/// Sets a database option with a byte array value.
///
/// - Parameters:
/// - option: The database option to set.
/// - value: The value for the option (optional).
/// - Throws: `FdbError` if the option cannot be set.
public func setOption(_ option: Fdb.DatabaseOption, value: Fdb.Value? = nil) throws {
let error: Int32
if let value = value {
error = value.withUnsafeBytes { bytes in
fdb_database_set_option(
database,
FDBDatabaseOption(option.rawValue),
bytes.bindMemory(to: UInt8.self).baseAddress,
Int32(value.count)
)
}
} else {
error = fdb_database_set_option(database, FDBDatabaseOption(option.rawValue), nil, 0)
}

if error != 0 {
throw FdbError(code: error)
}
}

/// Sets a database option with a string value.
///
/// - Parameters:
/// - option: The database option to set.
/// - value: The string value for the option.
/// - Throws: `FdbError` if the option cannot be set.
public func setOption(_ option: Fdb.DatabaseOption, value: String) throws {
try setOption(option, value: Array(value.utf8))
}

/// Sets a database option with an integer value.
///
/// - Parameters:
/// - option: The database option to set.
/// - value: The integer value for the option.
/// - Throws: `FdbError` if the option cannot be set.
public func setOption(_ option: Fdb.DatabaseOption, value: Int) throws {
var val = Int64(value).littleEndian
try withUnsafeBytes(of: &val) { bytes in
try setOption(option, value: Array(bytes))
}
}
}
2 changes: 1 addition & 1 deletion Sources/FoundationDB/Fdb+Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ public extension Fdb {
}

/** Conflict range types used internally by the C API. */
enum ConflictRangeType: UInt32, @unchecked Sendable {
public enum ConflictRangeType: UInt32, @unchecked Sendable {
/** Used to add a read conflict range */
case read = 0

Expand Down
62 changes: 62 additions & 0 deletions Sources/FoundationDB/FoundationdDB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,56 @@ public protocol ITransaction: Sendable {
/// - Throws: `FdbError` if the operation fails.
func getReadVersion() async throws -> Int64

/// Handles transaction errors and implements retry logic with exponential backoff.
///
/// If this method returns successfully, the transaction has been reset and can be retried.
/// If it throws an error, the transaction should not be retried.
///
/// - Parameter error: The error encountered during transaction execution.
/// - Throws: `FdbError` if the error is not retryable or retry limits have been exceeded.
func onError(_ error: FdbError) async throws

/// Returns an estimated byte size of the specified key range.
///
/// The estimate is calculated based on sampling done by FDB server. Larger key-value pairs
/// are more likely to be sampled. For accuracy, use on large ranges (>3MB recommended).
///
/// - Parameters:
/// - beginKey: The start of the range (inclusive).
/// - endKey: The end of the range (exclusive).
/// - Returns: The estimated size in bytes.
/// - Throws: `FdbError` if the operation fails.
func getEstimatedRangeSizeBytes(beginKey: Fdb.Key, endKey: Fdb.Key) async throws -> Int64

/// Returns a list of keys that can split the given range into roughly equal chunks.
///
/// The returned split points include the start and end keys of the range.
///
/// - Parameters:
/// - beginKey: The start of the range.
/// - endKey: The end of the range.
/// - chunkSize: The desired size of each chunk in bytes.
/// - Returns: An array of keys representing split points.
/// - Throws: `FdbError` if the operation fails.
func getRangeSplitPoints(beginKey: Fdb.Key, endKey: Fdb.Key, chunkSize: Int64) async throws -> [[UInt8]]

/// Returns the version number at which a committed transaction modified the database.
///
/// Must only be called after a successful commit. Read-only transactions return -1.
///
/// - Returns: The committed version number.
/// - Throws: `FdbError` if called before commit or if the operation fails.
func getCommittedVersion() throws -> Int64

/// Returns the approximate transaction size so far.
///
/// This is the sum of estimated sizes of mutations, read conflict ranges, and write conflict ranges.
/// Can be called multiple times before commit.
///
/// - Returns: The approximate size in bytes.
/// - Throws: `FdbError` if the operation fails.
func getApproximateSize() async throws -> Int64

/// Performs an atomic operation on a key.
///
/// - Parameters:
Expand All @@ -226,6 +276,18 @@ public protocol ITransaction: Sendable {
/// - mutationType: The type of atomic operation to perform.
func atomicOp(key: Fdb.Key, param: Fdb.Value, mutationType: Fdb.MutationType)

/// Adds a conflict range to the transaction.
///
/// Conflict ranges are used to manually declare the read and write sets of the transaction.
/// This can be useful for ensuring serializability when certain keys are accessed indirectly.
///
/// - Parameters:
/// - beginKey: The start of the range (inclusive) as a byte array.
/// - endKey: The end of the range (exclusive) as a byte array.
/// - type: The type of conflict range (read or write).
/// - Throws: `FdbError` if the operation fails.
func addConflictRange(beginKey: Fdb.Key, endKey: Fdb.Key, type: Fdb.ConflictRangeType) throws

// MARK: - Transaction option methods

/// Sets a transaction option with an optional value.
Expand Down
62 changes: 60 additions & 2 deletions Sources/FoundationDB/Future.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ struct ResultVersion: FutureResult {
}
}

/// A result type for futures that return 64-bit integer values.
///
/// Used for operations that return size estimates or counts.
struct ResultInt64: FutureResult {
/// The extracted integer value.
let value: Int64

/// Extracts an Int64 from the future.
///
/// - Parameter fromFuture: The C future containing the integer.
/// - Returns: A `ResultInt64` with the extracted value.
/// - Throws: `FdbError` if the future contains an error.
static func extract(fromFuture: CFuturePtr) throws -> Self? {
var value: Int64 = 0
let err = fdb_future_get_int64(fromFuture, &value)
if err != 0 {
throw FdbError(code: err)
}
return Self(value: value)
}
}

/// A result type for futures that return key data.
///
/// Used for operations like key selectors that resolve to actual keys.
Expand Down Expand Up @@ -230,9 +252,9 @@ struct ResultValue: FutureResult {
/// with information about whether more data is available.
public struct ResultRange: FutureResult {
/// The array of key-value pairs returned by the range operation.
let records: Fdb.KeyValueArray
public let records: Fdb.KeyValueArray
/// Indicates whether there are more records beyond this result.
let more: Bool
public let more: Bool

/// Extracts key-value pairs from a range future.
///
Expand Down Expand Up @@ -264,3 +286,39 @@ public struct ResultRange: FutureResult {
return Self(records: keyValueArray, more: more > 0)
}
}

/// A result type for futures that return arrays of keys.
///
/// Used for operations like get range split points that return multiple keys.
struct ResultKeyArray: FutureResult {
/// The array of keys returned by the operation.
let value: [[UInt8]]

/// Extracts an array of keys from the future.
///
/// - Parameter fromFuture: The C future containing the key array.
/// - Returns: A `ResultKeyArray` with the extracted keys.
/// - Throws: `FdbError` if the future contains an error.
static func extract(fromFuture: CFuturePtr) throws -> Self? {
var keysPtr: UnsafePointer<FDBKey>?
var count: Int32 = 0

let err = fdb_future_get_key_array(fromFuture, &keysPtr, &count)
if err != 0 {
throw FdbError(code: err)
}

guard let keysPtr = keysPtr, count > 0 else {
return Self(value: [])
}

var keyArray: [[UInt8]] = []
for i in 0 ..< Int(count) {
let fdbKey = keysPtr[i]
let key = Array(UnsafeBufferPointer(start: fdbKey.key, count: Int(fdbKey.key_length)))
keyArray.append(key)
}

return Self(value: keyArray)
}
}
38 changes: 20 additions & 18 deletions Sources/FoundationDB/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ import CFoundationDB
/// let network = FdbNetwork.shared
/// try network.initialize(version: 740)
/// ```
// TODO: stopNetwork at deinit.
@MainActor
class FdbNetwork {
/// The shared singleton instance of the network manager.
static let shared = FdbNetwork()

/// Indicates whether the network has been set up.
private var networkSetup = false
private nonisolated(unsafe) var networkSetup = false

/// The pthread handle for the network thread.
private var networkThread: pthread_t? = nil
private nonisolated(unsafe) var networkThread: pthread_t? = nil

/// Initializes the FoundationDB network with the specified API version.
///
Expand All @@ -65,6 +65,23 @@ class FdbNetwork {
startNetwork()
}

/// Stops the FoundationDB network and waits for the network thread to complete.
deinit {
if !networkSetup {
return
}

// Call stop_network and wait for network thread to complete
let error = fdb_stop_network()
if error != 0 {
print("Failed to stop network in deinit: \(FdbError(code: error).description)")
}

if let thread = networkThread {
pthread_join(thread, nil)
}
}

/// Selects the FoundationDB API version.
///
/// - Parameter version: The API version to select.
Expand Down Expand Up @@ -117,21 +134,6 @@ class FdbNetwork {
}
}

/// Stops the FoundationDB network and waits for the network thread to complete.
///
/// - Throws: `FdbError` if the network cannot be stopped cleanly.
func stopNetwork() throws {
let error = fdb_stop_network()
if error != 0 {
throw FdbError(code: error)
}

if networkSetup {
pthread_join(networkThread!, nil)
}
networkSetup = false
}

/// Sets a network option with an optional byte array value.
///
/// - Parameters:
Expand Down
Loading