Low-level C API for ACID transactional key-value database operations.
foundationdb/fdb_c.hfoundationdb/fdb_c_types.hbindings/c/The C API is the foundation layer for all FoundationDB client bindings. It provides complete control over database operations with manual memory management and explicit network thread control. All other language bindings (Python, Go, Java, Ruby) wrap this core C API.
Control API version selection and compatibility.
// Select API version (required first call before any other FDB functions)
fdb_error_t fdb_select_api_version_impl(
int version,
int header_version
);
// Get maximum supported API version
int fdb_get_max_api_version(void);
// Get client library version information
const char* fdb_get_client_version(void);Usage Pattern:
// Always the first FDB call in your application
fdb_error_t err = fdb_select_api_version_impl(740, FDB_API_VERSION);
if (err) {
fprintf(stderr, "Failed to select API version: %s\n", fdb_get_error(err));
return 1;
}The API version must be selected before calling any other FoundationDB functions. This ensures forward compatibility when the client library is upgraded. The header_version parameter should always be the FDB_API_VERSION constant from your header file.
Initialize and control the FoundationDB network thread.
// Initialize network thread (call after fdb_select_api_version_impl)
fdb_error_t fdb_setup_network(void);
// Run network event loop (blocking call, typically run in dedicated thread)
fdb_error_t fdb_run_network(void);
// Stop network thread (causes fdb_run_network to return)
fdb_error_t fdb_stop_network(void);
// Configure network options before setup
fdb_error_t fdb_network_set_option(
FDBNetworkOption option,
uint8_t const* value,
int value_length
);
// Add cleanup hook executed when network thread completes
fdb_error_t fdb_add_network_thread_completion_hook(
void (*hook)(void*),
void* hook_parameter
);Network Initialization Example:
// Setup network
fdb_error_t err = fdb_setup_network();
if (err) {
fprintf(stderr, "Failed to setup network: %s\n", fdb_get_error(err));
return 1;
}
// Run network in separate thread
pthread_t network_thread;
pthread_create(&network_thread, NULL, (void*(*)(void*))fdb_run_network, NULL);
// ... perform database operations ...
// Cleanup
fdb_stop_network();
pthread_join(network_thread, NULL);The network thread handles all communication with the FoundationDB cluster. You must call fdb_setup_network() once after version selection, then start fdb_run_network() in a dedicated thread before performing any database operations.
Core types representing FoundationDB objects.
// Asynchronous operation handle
typedef struct FDB_future FDBFuture;
// Synchronous result handle (for certain operations)
typedef struct FDB_result FDBResult;
// Database connection handle
typedef struct FDB_database FDBDatabase;
// Tenant handle for multi-tenancy
typedef struct FDB_tenant FDBTenant;
// Transaction context handle
typedef struct FDB_transaction FDBTransaction;
// Cluster handle (deprecated in API version 610+)
typedef struct FDB_cluster FDBCluster;All handles are opaque pointers managed by the library. They must be explicitly destroyed when no longer needed to prevent memory leaks.
Key data structures for working with FoundationDB data.
// Key representation
typedef struct {
const uint8_t* key;
int key_length;
} FDBKey;
// Key-value pair
typedef struct {
const uint8_t* key;
int key_length;
const uint8_t* value;
int value_length;
} FDBKeyValue;
// Key selector for range boundaries
typedef struct {
const uint8_t* key;
int key_length;
fdb_bool_t or_equal;
int offset;
} FDBKeySelector;
// Key range specification
typedef struct {
const uint8_t* begin_key;
int begin_key_length;
const uint8_t* end_key;
int end_key_length;
} FDBKeyRange;
// Context for reading blob granules (time-travel queries)
typedef struct {
// User context to pass along to functions
void* userContext;
// Returns a unique id for the load (asynchronous to support queueing multiple in parallel)
int64_t (*start_load_f)(
const char* filename,
int filenameLength,
int64_t offset,
int64_t length,
int64_t fullFileLength,
void* context
);
// Returns data for the load (pass the loadId returned by start_load_f)
uint8_t* (*get_load_f)(int64_t loadId, void* context);
// Frees data from load (pass the loadId returned by start_load_f)
void (*free_load_f)(int64_t loadId, void* context);
// Set to true for testing if you don't want to read granule files
fdb_bool_t debugNoMaterialize;
// Number of granules to load in parallel
int granuleParallelism;
} FDBReadBlobGranuleContext;
// Granule summary metadata
typedef struct {
FDBKeyRange key_range; // Key range covered by this granule
int64_t snapshot_version; // Version of the snapshot file
int64_t snapshot_size; // Size of snapshot data in bytes
int64_t delta_version; // Latest delta file version
int64_t delta_size; // Total size of delta files in bytes
} FDBGranuleSummary;
// Tenant prefix for blob granule operations
typedef struct {
fdb_bool_t present; // Whether a tenant prefix is present
FDBKey prefix; // The tenant prefix key
} FDBBGTenantPrefix;
// Blob granule encryption key
typedef struct {
int64_t domain_id; // Encryption domain identifier
uint64_t base_key_id; // Base key identifier
uint32_t base_kcv; // Base key check value
uint64_t random_salt; // Random salt for key derivation
FDBKey base_key; // The base encryption key
} FDBBGEncryptionKey;
// Blob granule encryption context
typedef struct {
fdb_bool_t present; // Whether encryption is enabled
FDBBGEncryptionKey textKey; // Key for encrypting text/data
uint32_t textKCV; // Text key check value
FDBBGEncryptionKey headerKey; // Key for encrypting headers
uint32_t headerKCV; // Header key check value
FDBKey iv; // Initialization vector
} FDBBGEncryptionCtx;
// Blob granule file pointer
typedef struct {
const uint8_t* filename_ptr; // Pointer to filename string
int filename_length; // Length of filename
int64_t file_offset; // Offset within the file
int64_t file_length; // Length of data to read
int64_t full_file_length; // Total file size
int64_t file_version; // Version of the file
FDBBGEncryptionCtx encryption_ctx; // Encryption context for this file
} FDBBGFilePointer;
// Blob granule mutation types
typedef enum {
FDB_BG_MUTATION_TYPE_SET_VALUE = 0,
FDB_BG_MUTATION_TYPE_CLEAR_RANGE = 1
} FDBBGMutationType;
// Blob granule mutation
typedef struct {
uint8_t type; // Mutation type (FDBBGMutationType)
int64_t version; // Version at which mutation occurred
const uint8_t* param1_ptr; // First parameter (key for SET_VALUE, begin for CLEAR_RANGE)
int param1_length; // Length of param1
const uint8_t* param2_ptr; // Second parameter (value for SET_VALUE, end for CLEAR_RANGE)
int param2_length; // Length of param2
} FDBBGMutation;
// Blob granule file description
typedef struct {
FDBKeyRange key_range; // Key range covered by these files
fdb_bool_t snapshot_present; // Whether a snapshot file is present
FDBBGFilePointer snapshot_file_pointer; // Pointer to snapshot file
int delta_file_count; // Number of delta files
FDBBGFilePointer* delta_files; // Array of delta file pointers
int memory_mutation_count; // Number of in-memory mutations
FDBBGMutation* memory_mutations; // Array of in-memory mutations
FDBBGTenantPrefix tenant_prefix; // Tenant prefix if applicable
} FDBBGFileDescription;These structures are used to pass data to and from FoundationDB. Keys and values are always byte arrays with explicit lengths, allowing storage of arbitrary binary data including null bytes.
Create and manage database connections.
// Create database connection from cluster file
fdb_error_t fdb_create_database(
const char* cluster_file_path,
FDBDatabase** out_database
);
// Create database connection from connection string
fdb_error_t fdb_create_database_from_connection_string(
const char* connection_string,
FDBDatabase** out_database
);
// Configure database options
fdb_error_t fdb_database_set_option(
FDBDatabase* database,
FDBDatabaseOption option,
uint8_t const* value,
int value_length
);
// Create new transaction
fdb_error_t fdb_database_create_transaction(
FDBDatabase* database,
FDBTransaction** out_transaction
);
// Get client status information
FDBFuture* fdb_database_get_client_status(FDBDatabase* database);
// Release database handle
void fdb_database_destroy(FDBDatabase* database);Database Connection Example:
FDBDatabase* db;
fdb_error_t err = fdb_create_database(NULL, &db); // NULL uses default cluster file
if (err) {
fprintf(stderr, "Failed to create database: %s\n", fdb_get_error(err));
return 1;
}
// Use database...
fdb_database_destroy(db);Advanced database management operations.
// Reboot worker process at specified address
FDBFuture* fdb_database_reboot_worker(
FDBDatabase* database,
uint8_t const* address,
int address_length,
fdb_bool_t check,
int duration
);
// Force recovery with data loss (EMERGENCY ONLY)
FDBFuture* fdb_database_force_recovery_with_data_loss(
FDBDatabase* database,
uint8_t const* dcid,
int dcid_length
);
// Create database snapshot
FDBFuture* fdb_database_create_snapshot(
FDBDatabase* database,
uint8_t const* uid,
int uid_length,
uint8_t const* snap_command,
int snap_command_length
);
// Purge blob granules in key range
FDBFuture* fdb_database_purge_blob_granules(
FDBDatabase* database,
uint8_t const* begin_key,
int begin_key_length,
uint8_t const* end_key,
int end_key_length,
int64_t purge_version,
fdb_bool_t force
);
// Wait for blob granule purge to complete
FDBFuture* fdb_database_wait_purge_granules_complete(
FDBDatabase* database,
uint8_t const* purge_key,
int purge_key_length
);These functions are for advanced administrative tasks and should be used with caution. The force_recovery_with_data_loss function is particularly dangerous and should only be used in disaster recovery scenarios.
Multi-tenancy support for isolated data namespaces.
// Open tenant connection
fdb_error_t fdb_database_open_tenant(
FDBDatabase* database,
uint8_t const* tenant_name,
int tenant_name_length,
FDBTenant** out_tenant
);
// Create transaction in tenant context
fdb_error_t fdb_tenant_create_transaction(
FDBTenant* tenant,
FDBTransaction** out_transaction
);
// Get tenant ID
FDBFuture* fdb_tenant_get_id(FDBTenant* tenant);
// Release tenant handle
void fdb_tenant_destroy(FDBTenant* tenant);
// Purge blob granules in tenant
FDBFuture* fdb_tenant_purge_blob_granules(
FDBTenant* tenant,
uint8_t const* begin_key,
int begin_key_length,
uint8_t const* end_key,
int end_key_length,
int64_t version,
fdb_bool_t force
);
// Wait for tenant blob granule purge
FDBFuture* fdb_tenant_wait_purge_granules_complete(
FDBTenant* tenant,
uint8_t const* purge_key,
int purge_key_length
);Tenant Usage Example:
FDBTenant* tenant;
fdb_error_t err = fdb_database_open_tenant(db, (uint8_t*)"my_tenant", 9, &tenant);
if (err) {
fprintf(stderr, "Failed to open tenant: %s\n", fdb_get_error(err));
return 1;
}
// Create transaction in tenant context
FDBTransaction* tr;
fdb_tenant_create_transaction(tenant, &tr);
// ... perform operations ...
fdb_transaction_destroy(tr);
fdb_tenant_destroy(tenant);Tenants provide data isolation within a single database. All keys accessed through a tenant transaction are automatically prefixed with the tenant's namespace.
Core transaction management functions.
// Reset transaction to initial state (keeps read version if set)
void fdb_transaction_reset(FDBTransaction* transaction);
// Cancel transaction (all pending operations will fail)
void fdb_transaction_cancel(FDBTransaction* transaction);
// Commit transaction (returns future that completes when committed)
FDBFuture* fdb_transaction_commit(FDBTransaction* transaction);
// Handle error with retry logic (returns future for retry delay)
FDBFuture* fdb_transaction_on_error(
FDBTransaction* transaction,
fdb_error_t error
);
// Release transaction handle
void fdb_transaction_destroy(FDBTransaction* transaction);Transaction Retry Pattern:
FDBTransaction* tr;
fdb_database_create_transaction(db, &tr);
while (1) {
// Perform operations...
fdb_transaction_set(tr, key, key_len, value, value_len);
// Commit
FDBFuture* f = fdb_transaction_commit(tr);
fdb_error_t err = fdb_future_block_until_ready(f);
fdb_future_destroy(f);
if (err) {
// Handle error with retry logic
FDBFuture* f_retry = fdb_transaction_on_error(tr, err);
fdb_error_t retry_err = fdb_future_block_until_ready(f_retry);
fdb_future_destroy(f_retry);
if (retry_err) {
// Non-retryable error
fprintf(stderr, "Transaction failed: %s\n", fdb_get_error(retry_err));
break;
}
// Retry loop continues
} else {
// Success
break;
}
}
fdb_transaction_destroy(tr);The on_error function implements automatic retry logic with exponential backoff. It returns an error if the transaction cannot be retried.
Functions for reading data from the database.
// Read single key value
FDBFuture* fdb_transaction_get(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length,
fdb_bool_t snapshot
);
// Resolve key selector to actual key
FDBFuture* fdb_transaction_get_key(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length,
fdb_bool_t or_equal,
int offset,
fdb_bool_t snapshot
);
// Read range of key-value pairs
FDBFuture* fdb_transaction_get_range(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
fdb_bool_t begin_or_equal,
int begin_offset,
uint8_t const* end_key_name,
int end_key_name_length,
fdb_bool_t end_or_equal,
int end_offset,
int limit,
int target_bytes,
FDBStreamingMode mode,
int iteration,
fdb_bool_t snapshot,
fdb_bool_t reverse
);
// Read mapped range (experimental)
FDBFuture* fdb_transaction_get_mapped_range(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
fdb_bool_t begin_or_equal,
int begin_offset,
uint8_t const* end_key_name,
int end_key_name_length,
fdb_bool_t end_or_equal,
int end_offset,
uint8_t const* mapper_name,
int mapper_name_length,
int limit,
int target_bytes,
FDBStreamingMode mode,
int iteration,
fdb_bool_t snapshot,
fdb_bool_t reverse
);
// Get server addresses storing a key (locality information)
FDBFuture* fdb_transaction_get_addresses_for_key(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length
);
// Estimate storage size of key range in bytes
FDBFuture* fdb_transaction_get_estimated_range_size_bytes(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
// Get suggested split points for parallel processing
FDBFuture* fdb_transaction_get_range_split_points(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int64_t chunk_size
);Reading Example:
// Simple get
FDBFuture* f = fdb_transaction_get(tr, (uint8_t*)"mykey", 5, 0);
fdb_error_t err = fdb_future_block_until_ready(f);
if (!err) {
fdb_bool_t present;
const uint8_t* value;
int value_length;
err = fdb_future_get_value(f, &present, &value, &value_length);
if (!err && present) {
printf("Value: %.*s\n", value_length, value);
}
}
fdb_future_destroy(f);
// Range read
FDBFuture* f_range = fdb_transaction_get_range(
tr,
(uint8_t*)"a", 1, 1, 0, // begin >= "a"
(uint8_t*)"z", 1, 0, 0, // end < "z"
0, // no limit
0, // no target_bytes
FDB_STREAMING_MODE_WANT_ALL,
1, // iteration
0, // not snapshot
0 // not reverse
);
err = fdb_future_block_until_ready(f_range);
if (!err) {
const FDBKeyValue* kvs;
int count;
fdb_bool_t more;
err = fdb_future_get_keyvalue_array(f_range, &kvs, &count, &more);
if (!err) {
for (int i = 0; i < count; i++) {
printf("Key: %.*s, Value: %.*s\n",
kvs[i].key_length, kvs[i].key,
kvs[i].value_length, kvs[i].value);
}
}
}
fdb_future_destroy(f_range);The snapshot parameter controls isolation. When true, reads don't add conflict ranges, allowing concurrent writes.
Functions for modifying data in the database.
// Write key-value pair
void fdb_transaction_set(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length,
uint8_t const* value,
int value_length
);
// Delete single key
void fdb_transaction_clear(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length
);
// Delete range of keys [begin_key, end_key)
void fdb_transaction_clear_range(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
// Perform atomic mutation
void fdb_transaction_atomic_op(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length,
uint8_t const* param,
int param_length,
FDBMutationType operation_type
);Write Examples:
// Simple set
fdb_transaction_set(tr, (uint8_t*)"key", 3, (uint8_t*)"value", 5);
// Clear
fdb_transaction_clear(tr, (uint8_t*)"key", 3);
// Clear range
fdb_transaction_clear_range(tr, (uint8_t*)"a", 1, (uint8_t*)"z", 1);
// Atomic add (increment counter)
int64_t delta = 1;
fdb_transaction_atomic_op(tr, (uint8_t*)"counter", 7,
(uint8_t*)&delta, sizeof(delta),
FDB_MUTATION_TYPE_ADD);
// Atomic set with versionstamp (10-byte incomplete versionstamp + data)
uint8_t incomplete_versionstamp[10] = {0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff};
fdb_transaction_atomic_op(tr, (uint8_t*)"versioned_key", 13,
incomplete_versionstamp, 10,
FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE);Write operations are buffered locally and sent to the database during commit. They return immediately without network round-trips.
Configure transactions and access metadata.
// Configure transaction option
fdb_error_t fdb_transaction_set_option(
FDBTransaction* transaction,
FDBTransactionOption option,
uint8_t const* value,
int value_length
);
// Get read version for transaction
FDBFuture* fdb_transaction_get_read_version(
FDBTransaction* transaction
);
// Set explicit read version for snapshot reads
void fdb_transaction_set_read_version(
FDBTransaction* transaction,
int64_t version
);
// Get committed version (call after successful commit)
fdb_error_t fdb_transaction_get_committed_version(
FDBTransaction* transaction,
int64_t* out_version
);
// Get approximate transaction size in bytes
FDBFuture* fdb_transaction_get_approximate_size(
FDBTransaction* transaction
);
// Get transaction versionstamp (call after commit)
FDBFuture* fdb_transaction_get_versionstamp(
FDBTransaction* transaction
);Options and Versioning Example:
// Set transaction timeout
const int timeout_ms = 5000;
fdb_transaction_set_option(tr, FDB_TR_OPTION_TIMEOUT,
(uint8_t*)&timeout_ms, sizeof(timeout_ms));
// Set retry limit
const int retry_limit = 10;
fdb_transaction_set_option(tr, FDB_TR_OPTION_RETRY_LIMIT,
(uint8_t*)&retry_limit, sizeof(retry_limit));
// Get read version
FDBFuture* f = fdb_transaction_get_read_version(tr);
fdb_future_block_until_ready(f);
int64_t version;
fdb_future_get_int64(f, &version);
printf("Read version: %lld\n", version);
fdb_future_destroy(f);
// After commit, get committed version
int64_t committed_version;
fdb_transaction_get_committed_version(tr, &committed_version);
printf("Committed at version: %lld\n", committed_version);Transaction options control behavior like timeouts, retry limits, and read modes. The committed version is only available after a successful commit.
Advanced transaction capabilities.
// Watch for changes to a key
FDBFuture* fdb_transaction_watch(
FDBTransaction* transaction,
uint8_t const* key_name,
int key_name_length
);
// Add conflict range for custom conflict detection
fdb_error_t fdb_transaction_add_conflict_range(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
FDBConflictRangeType type
);
// Get blob granule ranges (for time-travel queries)
FDBFuture* fdb_transaction_get_blob_granule_ranges(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int row_limit
);
// Read blob granules at specific version (time-travel)
FDBResult* fdb_transaction_read_blob_granules(
FDBTransaction* transaction,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int64_t begin_version,
int64_t read_version,
FDBReadBlobGranuleContext context
);
// Get summary information about blob granules in a range
FDBFuture* fdb_transaction_summarize_blob_granules(
FDBTransaction* tr,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int64_t summaryVersion,
int rangeLimit
);Operations for managing blob storage of data ranges.
// Convert a range to use blob storage
FDBFuture* fdb_database_blobbify_range(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
// Convert range to blob storage and wait for completion
FDBFuture* fdb_database_blobbify_range_blocking(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
// Convert blob range back to regular storage
FDBFuture* fdb_database_unblobbify_range(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
// List all ranges that have been converted to blob storage
FDBFuture* fdb_database_list_blobbified_ranges(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int rangeLimit
);
// Verify integrity of a blob range
FDBFuture* fdb_database_verify_blob_range(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int64_t version
);
// Flush blob data to persistent storage
FDBFuture* fdb_database_flush_blob_range(
FDBDatabase* db,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
fdb_bool_t compact,
int64_t version
);
// Tenant versions of blob management operations
FDBFuture* fdb_tenant_blobbify_range(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
FDBFuture* fdb_tenant_blobbify_range_blocking(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
FDBFuture* fdb_tenant_unblobbify_range(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length
);
FDBFuture* fdb_tenant_list_blobbified_ranges(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int rangeLimit
);
FDBFuture* fdb_tenant_verify_blob_range(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
int64_t version
);
FDBFuture* fdb_tenant_flush_blob_range(
FDBTenant* tenant,
uint8_t const* begin_key_name,
int begin_key_name_length,
uint8_t const* end_key_name,
int end_key_name_length,
fdb_bool_t compact,
int64_t version
);
// Get transaction cost and throttling info
FDBFuture* fdb_transaction_get_tag_throttled_duration(FDBTransaction* tr);
FDBFuture* fdb_transaction_get_total_cost(FDBTransaction* tr);Note: Blob granule operations are advanced features for managing data storage. See FoundationDB documentation for detailed usage and blob storage architecture.
Watch Example:
// Watch for changes to a key
FDBFuture* watch = fdb_transaction_watch(tr, (uint8_t*)"watched_key", 11);
// Commit the transaction
FDBFuture* commit = fdb_transaction_commit(tr);
fdb_future_block_until_ready(commit);
fdb_future_destroy(commit);
// Watch future completes when key changes
printf("Waiting for key to change...\n");
fdb_error_t err = fdb_future_block_until_ready(watch);
if (!err) {
printf("Key has changed!\n");
}
fdb_future_destroy(watch);Conflict Range Example:
// Add custom read conflict range
fdb_transaction_add_conflict_range(
tr,
(uint8_t*)"conflict_start", 14,
(uint8_t*)"conflict_end", 12,
FDB_CONFLICT_RANGE_TYPE_READ
);
// Add custom write conflict range
fdb_transaction_add_conflict_range(
tr,
(uint8_t*)"write_start", 11,
(uint8_t*)"write_end", 9,
FDB_CONFLICT_RANGE_TYPE_WRITE
);Watches allow applications to be notified when a key changes. Conflict ranges provide fine-grained control over transaction isolation.
Work with asynchronous operations.
// Check if future completed with error
fdb_error_t fdb_future_get_error(FDBFuture* future);
// Block until future completes
fdb_error_t fdb_future_block_until_ready(FDBFuture* future);
// Check if future is ready without blocking
fdb_bool_t fdb_future_is_ready(FDBFuture* future);
// Cancel future operation
void fdb_future_cancel(FDBFuture* future);
// Release memory associated with future result
void fdb_future_release_memory(FDBFuture* future);
// Release future handle
void fdb_future_destroy(FDBFuture* future);
// Set completion callback
fdb_error_t fdb_future_set_callback(
FDBFuture* future,
FDBCallback callback,
void* callback_parameter
);Callback Type:
typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter);Callback Example:
void my_callback(FDBFuture* future, void* param) {
fdb_error_t err = fdb_future_get_error(future);
if (err) {
fprintf(stderr, "Future failed: %s\n", fdb_get_error(err));
return;
}
// Extract result...
fdb_bool_t present;
const uint8_t* value;
int value_length;
fdb_future_get_value(future, &present, &value, &value_length);
printf("Got value: %.*s\n", value_length, value);
}
// Use callback
FDBFuture* f = fdb_transaction_get(tr, (uint8_t*)"key", 3, 0);
fdb_future_set_callback(f, my_callback, NULL);
// Callback will be invoked when future completes
// Don't forget to destroy the future laterCallbacks are invoked on the network thread. They should be fast and non-blocking.
Extract typed results from completed futures.
// Extract int64 result (used for versions, sizes, etc.)
fdb_error_t fdb_future_get_int64(
FDBFuture* future,
int64_t* out_value
);
// Extract uint64 result (used for tenant IDs, etc.)
fdb_error_t fdb_future_get_uint64(
FDBFuture* future,
uint64_t* out_value
);
// Extract double result (used for metrics, etc.)
fdb_error_t fdb_future_get_double(
FDBFuture* future,
double* out_value
);
// Extract boolean result
fdb_error_t fdb_future_get_bool(
FDBFuture* future,
fdb_bool_t* out_value
);
// Extract key result (from get_key operations)
fdb_error_t fdb_future_get_key(
FDBFuture* future,
uint8_t const** out_key,
int* out_key_length
);
// Extract value result (from get operations)
fdb_error_t fdb_future_get_value(
FDBFuture* future,
fdb_bool_t* out_present,
uint8_t const** out_value,
int* out_value_length
);
// Extract string array (for addresses, boundary keys, etc.)
fdb_error_t fdb_future_get_string_array(
FDBFuture* future,
const char*** out_strings,
int* out_count
);
// Extract key-value array (from get_range operations)
fdb_error_t fdb_future_get_keyvalue_array(
FDBFuture* future,
FDBKeyValue const** out_kv,
int* out_count,
fdb_bool_t* out_more
);
// Extract mapped key-value array (from get_mapped_range)
fdb_error_t fdb_future_get_mappedkeyvalue_array(
FDBFuture* future,
FDBMappedKeyValue const** out_kv,
int* out_count,
fdb_bool_t* out_more
);
// Extract key array (from locality operations)
fdb_error_t fdb_future_get_key_array(
FDBFuture* future,
FDBKey const** out_key_array,
int* out_count
);
// Extract key range array (from blob granule operations)
fdb_error_t fdb_future_get_keyrange_array(
FDBFuture* future,
FDBKeyRange const** out_ranges,
int* out_count
);
// Extract granule summary array (from summarize_blob_granules)
fdb_error_t fdb_future_get_granule_summary_array(
FDBFuture* future,
FDBGranuleSummary const** out_summaries,
int* out_count
);Result Extraction Pattern:
FDBFuture* f = fdb_transaction_get(tr, (uint8_t*)"key", 3, 0);
fdb_error_t err = fdb_future_block_until_ready(f);
if (!err) {
fdb_bool_t present;
const uint8_t* value;
int value_length;
err = fdb_future_get_value(f, &present, &value, &value_length);
if (!err) {
if (present) {
printf("Value: %.*s\n", value_length, value);
} else {
printf("Key not found\n");
}
}
}
fdb_future_destroy(f);The out_present parameter in fdb_future_get_value indicates whether the key exists. If false, out_value will be NULL.
Error handling and diagnostics.
// Get human-readable error description
const char* fdb_get_error(fdb_error_t code);
// Test error predicates
fdb_bool_t fdb_error_predicate(
int predicate_test,
fdb_error_t code
);Error Handling Example:
fdb_error_t err = some_fdb_operation();
if (err) {
const char* description = fdb_get_error(err);
fprintf(stderr, "Error %d: %s\n", err, description);
// Check if error is retryable
if (fdb_error_predicate(FDB_ERROR_PREDICATE_RETRYABLE, err)) {
printf("Error is retryable\n");
}
}Error codes are negative integers. Zero indicates success. The fdb_get_error function returns a static string that doesn't need to be freed.
Atomic operation types for fdb_transaction_atomic_op.
// Arithmetic operations
FDB_MUTATION_TYPE_ADD // Add integer (little-endian)
FDB_MUTATION_TYPE_MAX // Set to maximum value
FDB_MUTATION_TYPE_MIN // Set to minimum value
// Bitwise operations
FDB_MUTATION_TYPE_BIT_AND // Bitwise AND
FDB_MUTATION_TYPE_BIT_OR // Bitwise OR
FDB_MUTATION_TYPE_BIT_XOR // Bitwise XOR
// Byte-level operations
FDB_MUTATION_TYPE_BYTE_MIN // Byte-wise minimum
FDB_MUTATION_TYPE_BYTE_MAX // Byte-wise maximum
// Append operations
FDB_MUTATION_TYPE_APPEND_IF_FITS // Append if fits in value
// Versionstamp operations
FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY // Set with versionstamp in key
FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE // Set with versionstamp in value
// Conditional operations
FDB_MUTATION_TYPE_COMPARE_AND_CLEAR // Clear if value matches
// And additional specialized mutation types...Atomic Operation Examples:
// Increment counter
int64_t one = 1;
fdb_transaction_atomic_op(tr, (uint8_t*)"counter", 7,
(uint8_t*)&one, 8, FDB_MUTATION_TYPE_ADD);
// Set maximum value
uint32_t max_val = 100;
fdb_transaction_atomic_op(tr, (uint8_t*)"max_value", 9,
(uint8_t*)&max_val, 4, FDB_MUTATION_TYPE_MAX);
// Bitwise OR flags
uint64_t flags = 0x0F;
fdb_transaction_atomic_op(tr, (uint8_t*)"flags", 5,
(uint8_t*)&flags, 8, FDB_MUTATION_TYPE_BIT_OR);
// Append data
const char* append_data = "suffix";
fdb_transaction_atomic_op(tr, (uint8_t*)"text", 4,
(uint8_t*)append_data, 6,
FDB_MUTATION_TYPE_APPEND_IF_FITS);Atomic operations are performed on the server side during commit, ensuring atomicity even with concurrent transactions.
Types for manual conflict range specification.
FDB_CONFLICT_RANGE_TYPE_READ // Read conflict range
FDB_CONFLICT_RANGE_TYPE_WRITE // Write conflict rangeRead conflicts detect when other transactions write to keys you've read. Write conflicts detect when other transactions read or write keys you've written.
Control data fetching behavior for range reads.
FDB_STREAMING_MODE_WANT_ALL // Fetch entire range (best for small ranges)
FDB_STREAMING_MODE_ITERATOR // Balanced iteration (default)
FDB_STREAMING_MODE_EXACT // Fetch exactly 'limit' rows
FDB_STREAMING_MODE_SMALL // Small data size hint
FDB_STREAMING_MODE_MEDIUM // Medium data size hint
FDB_STREAMING_MODE_LARGE // Large data size hint
FDB_STREAMING_MODE_SERIAL // Minimize latency (fetch one at a time)Streaming Mode Usage:
// For small ranges, fetch everything at once
FDBFuture* f = fdb_transaction_get_range(
tr,
begin_key, begin_key_len, 1, 0,
end_key, end_key_len, 0, 0,
0, 0, FDB_STREAMING_MODE_WANT_ALL,
1, 0, 0
);
// For large ranges, use iterator mode with pagination
int iteration = 1;
fdb_bool_t more = 1;
while (more) {
FDBFuture* f = fdb_transaction_get_range(
tr,
begin_key, begin_key_len, 1, 0,
end_key, end_key_len, 0, 0,
1000, 0, FDB_STREAMING_MODE_ITERATOR,
iteration++, 0, 0
);
fdb_future_block_until_ready(f);
const FDBKeyValue* kvs;
int count;
fdb_future_get_keyvalue_array(f, &kvs, &count, &more);
// Process kvs...
if (more && count > 0) {
// Update begin_key to continue after last key
begin_key = kvs[count - 1].key;
begin_key_len = kvs[count - 1].key_length;
// Continue from next key
}
fdb_future_destroy(f);
}Streaming modes hint to FoundationDB how to fetch data efficiently. WANT_ALL is best for small ranges, while ITERATOR provides balanced performance for ranges of unknown size.
Test error characteristics.
FDB_ERROR_PREDICATE_RETRYABLE // Error can be retried with on_errorThe retryable predicate indicates whether fdb_transaction_on_error can handle the error. If true, the transaction may succeed on retry after the appropriate delay.
typedef int fdb_bool_t; // 0 = false, non-zero = truetypedef int fdb_error_t; // 0 = success, negative = error codetypedef int64_t fdb_version_t; // Database version number_destroy() functions for all FDB objects// Good pattern
FDBFuture* f = fdb_transaction_get(tr, key, key_len, 0);
fdb_future_block_until_ready(f);
// ... use results ...
fdb_future_destroy(f); // Always destroy
// Bad pattern
FDBFuture* f = fdb_transaction_get(tr, key, key_len, 0);
fdb_future_destroy(f); // WRONG: Destroyed before using resultfdb_error_t should be checked// Good error handling
FDBFuture* f = fdb_transaction_commit(tr);
fdb_error_t err = fdb_future_block_until_ready(f);
fdb_future_destroy(f);
if (err) {
FDBFuture* retry_f = fdb_transaction_on_error(tr, err);
fdb_error_t retry_err = fdb_future_block_until_ready(retry_f);
fdb_future_destroy(retry_f);
if (retry_err) {
// Non-retryable error - handle appropriately
fprintf(stderr, "Transaction failed: %s\n", fdb_get_error(retry_err));
return retry_err;
}
// Retryable - loop and try again
}fdb_run_network() oncefdb_stop_network() to cleanly shut down// Good pattern
pthread_t network_thread;
void* run_network(void* arg) {
fdb_run_network();
return NULL;
}
int main() {
fdb_select_api_version_impl(740, FDB_API_VERSION);
fdb_setup_network();
pthread_create(&network_thread, NULL, run_network, NULL);
// ... application code ...
fdb_stop_network();
pthread_join(network_thread, NULL);
return 0;
}// Good: Short transaction
FDBTransaction* tr;
fdb_database_create_transaction(db, &tr);
fdb_transaction_set(tr, key, key_len, value, value_len);
FDBFuture* f = fdb_transaction_commit(tr);
fdb_future_block_until_ready(f);
fdb_future_destroy(f);
fdb_transaction_destroy(tr);
// Bad: Long-running transaction
FDBTransaction* tr;
fdb_database_create_transaction(db, &tr);
sleep(60); // DON'T DO THIS
fdb_transaction_set(tr, key, key_len, value, value_len);
// ... transaction likely to conflict or timeout// Efficient: Single range read
FDBFuture* f = fdb_transaction_get_range(tr, ...);
// Inefficient: Multiple individual gets
for (int i = 0; i < 100; i++) {
FDBFuture* f = fdb_transaction_get(tr, keys[i], key_lens[i], 0);
// ...
}Here's a complete example demonstrating proper usage of the C API:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <foundationdb/fdb_c.h>
void* run_network(void* arg) {
fdb_run_network();
return NULL;
}
int main() {
// 1. Select API version
fdb_error_t err = fdb_select_api_version_impl(740, FDB_API_VERSION);
if (err) {
fprintf(stderr, "Failed to select API version: %s\n", fdb_get_error(err));
return 1;
}
// 2. Setup and start network
err = fdb_setup_network();
if (err) {
fprintf(stderr, "Failed to setup network: %s\n", fdb_get_error(err));
return 1;
}
pthread_t network_thread;
pthread_create(&network_thread, NULL, run_network, NULL);
// 3. Open database
FDBDatabase* db;
err = fdb_create_database(NULL, &db);
if (err) {
fprintf(stderr, "Failed to create database: %s\n", fdb_get_error(err));
fdb_stop_network();
pthread_join(network_thread, NULL);
return 1;
}
// 4. Run transaction with retry loop
FDBTransaction* tr;
fdb_database_create_transaction(db, &tr);
while (1) {
// Write operation
const uint8_t* key = (uint8_t*)"hello";
const uint8_t* value = (uint8_t*)"world";
fdb_transaction_set(tr, key, 5, value, 5);
// Read operation
FDBFuture* get_future = fdb_transaction_get(tr, key, 5, 0);
err = fdb_future_block_until_ready(get_future);
if (!err) {
fdb_bool_t present;
const uint8_t* read_value;
int read_value_length;
err = fdb_future_get_value(get_future, &present, &read_value, &read_value_length);
if (!err && present) {
printf("Read value: %.*s\n", read_value_length, read_value);
}
}
fdb_future_destroy(get_future);
if (err) {
// Handle read error
FDBFuture* retry_future = fdb_transaction_on_error(tr, err);
err = fdb_future_block_until_ready(retry_future);
fdb_future_destroy(retry_future);
if (err) {
fprintf(stderr, "Transaction failed: %s\n", fdb_get_error(err));
break;
}
continue; // Retry
}
// Commit transaction
FDBFuture* commit_future = fdb_transaction_commit(tr);
err = fdb_future_block_until_ready(commit_future);
fdb_future_destroy(commit_future);
if (err) {
// Handle commit error
FDBFuture* retry_future = fdb_transaction_on_error(tr, err);
err = fdb_future_block_until_ready(retry_future);
fdb_future_destroy(retry_future);
if (err) {
fprintf(stderr, "Transaction failed: %s\n", fdb_get_error(err));
break;
}
continue; // Retry
}
// Success
printf("Transaction committed successfully\n");
break;
}
// 5. Cleanup
fdb_transaction_destroy(tr);
fdb_database_destroy(db);
fdb_stop_network();
pthread_join(network_thread, NULL);
return 0;
}This example demonstrates: