or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

c-api.mddocs/reference/

FoundationDB C API

Low-level C API for ACID transactional key-value database operations.

Package Information

  • Header: foundationdb/fdb_c.h
  • Type Definitions: foundationdb/fdb_c_types.h
  • Version: 7.4.5
  • Location: bindings/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.

Capabilities

API Version Management

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.

Network Management

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.

Opaque Handle Types

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.

Data Structures

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.

Database Operations

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);

Database Administrative Functions

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.

Tenant Operations

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.

Transaction Lifecycle

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.

Transaction Read Operations

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.

Transaction Write Operations

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.

Transaction Options and Metadata

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.

Transaction Advanced Features

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
);

Blob Granule Management

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.

Future Operations

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 later

Callbacks are invoked on the network thread. They should be fast and non-blocking.

Future Result Extraction

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 Functions

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.

Mutation Types

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.

Conflict Range Types

Types for manual conflict range specification.

FDB_CONFLICT_RANGE_TYPE_READ    // Read conflict range
FDB_CONFLICT_RANGE_TYPE_WRITE   // Write conflict range

Read conflicts detect when other transactions write to keys you've read. Write conflicts detect when other transactions read or write keys you've written.

Streaming Modes

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.

Error Predicates

Test error characteristics.

FDB_ERROR_PREDICATE_RETRYABLE   // Error can be retried with on_error

The retryable predicate indicates whether fdb_transaction_on_error can handle the error. If true, the transaction may succeed on retry after the appropriate delay.

Type Reference

Boolean Type

typedef int fdb_bool_t;  // 0 = false, non-zero = true

Error Type

typedef int fdb_error_t;  // 0 = success, negative = error code

Version Type

typedef int64_t fdb_version_t;  // Database version number

Best Practices

Memory Management

  1. Always destroy handles: Call appropriate _destroy() functions for all FDB objects
  2. Future lifecycle: Futures must be destroyed even if their results aren't used
  3. No premature destruction: Don't destroy objects while they're in use by active operations
// 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 result

Error Handling

  1. Check all errors: Every FDB function that returns fdb_error_t should be checked
  2. Use on_error for retries: Don't implement custom retry logic
  3. Handle non-retryable errors: Not all errors can be retried
// 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
}

Network Thread Management

  1. Single network thread: Only call fdb_run_network() once
  2. Dedicated thread: Run network in its own thread
  3. Proper shutdown: Call fdb_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;
}

Transaction Patterns

  1. Use snapshot reads when appropriate: Reduces conflicts for read-heavy workloads
  2. Keep transactions small: Long-running transactions are more likely to conflict
  3. Don't hold transactions open: Create, use, and commit/destroy promptly
// 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

Key Design

  1. Use tuple layer: Prefer structured key encoding over raw bytes
  2. Plan for range operations: Design key schemas that support efficient range queries
  3. Avoid hot keys: Distribute writes across many keys to avoid bottlenecks

Performance Optimization

  1. Batch operations: Combine multiple operations in single transaction when possible
  2. Use appropriate streaming modes: Match to your data access pattern
  3. Leverage snapshot reads: Avoid conflicts for read-mostly workloads
  4. Pre-fetch with get_range: More efficient than individual gets for multiple keys
// 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);
    // ...
}

Complete Example

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:

  • Proper initialization sequence
  • Network thread management
  • Transaction creation and retry loop
  • Read and write operations
  • Future handling
  • Complete error handling
  • Proper cleanup

Related Documentation

  • Python API: Higher-level interface with automatic retry and Pythonic conveniences
  • Go API: Type-safe interface with idiomatic Go patterns
  • Java API: CompletableFuture-based async API
  • Ruby API: Ruby-style interface with blocks and iterators