diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 5cc605a0695..be422f601bc 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -26,10 +26,10 @@ jobs: sudo apt-get install clang-format-19 - name: clang-format - # skip contrib dir, find all files named *.c/h/cpp/hpp, + # Skip contrib and historical Rust C-header snapshots, find *.c/h/cpp/hpp, # run clang-format 19.x on each file, in-place modifications run: | - find . -name contrib -prune -or -type f \ + find . '(' -name contrib -or -path ./bindings/rust/foundationdb-sys/include ')' -prune -or -type f \ '(' -name \*.c -or -name \*.cpp -or -name \*.h -or -name \*.hpp ')' \ -exec clang-format-19 -i '{}' \; diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000000..f74de41e667 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,119 @@ +name: Rust bindings + +on: + pull_request: + branches: + - main + - release-* + paths: + - bindings/rust/** + - bindings/c/** + - bindings/bindingtester/** + - contrib/local_cluster/binding_test.py + - cmake/AddFdbTest.cmake + - cmake/FDBComponents.cmake + - fdbclient/vexillographer/** + - flow/ApiVersions.cmake + - .github/workflows/rust.yml + push: + branches: + - main + - release-* + paths: + - bindings/rust/** + - bindings/c/** + - bindings/bindingtester/** + - contrib/local_cluster/binding_test.py + - cmake/AddFdbTest.cmake + - cmake/FDBComponents.cmake + - fdbclient/vexillographer/** + - flow/ApiVersions.cmake + - .github/workflows/rust.yml + workflow_dispatch: + +permissions: + contents: read + +defaults: + run: + working-directory: bindings/rust + +env: + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + check: + name: Check and unit tests (${{ matrix.toolchain }}) + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + toolchain: ["1.85.1", stable] + env: + RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }} + steps: + - name: Checkout code + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Install Rust and libclang + run: | + rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal + sudo apt-get update + sudo apt-get install --yes libclang-dev + + - name: Generate current C headers and options + run: cmake -DOUTPUT_DIR="$RUNNER_TEMP/rust-fdb-include" -P tests/current_headers.cmake + + - name: Check current headers and options + env: + FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include + run: | + cargo check --locked -p foundationdb --lib --features fdb-7_4 + + - name: Check client targets against current headers + # API version features are mutually exclusive; do not use --all-features. + env: + FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include + run: >- + cargo check --locked --workspace --exclude foundationdb-simulation --all-targets + --features foundationdb/fdb-7_4,foundationdb/num-bigint,foundationdb/trace,foundationdb-gen/fdb-7_4 + + - name: Check simulator C ABI and examples + env: + FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include + run: >- + cargo check --locked --package foundationdb-simulation --all-targets + --features fdb-7_4 + + - name: Check supported historical APIs + # Some examples require newer APIs; historical compatibility covers the library. + run: | + for version in 6_1 6_2 6_3 7_0 7_1 7_3 7_4; do + cargo check --locked -p foundationdb --lib --no-default-features \ + --features "embedded-fdb-include,fdb-${version},uuid" + done + + - name: Test tuple encoding and macros + # These tests do not require libfdb_c or a running FoundationDB cluster. + run: | + cargo test --locked -p foundationdb-tuple --all-features + cargo test --locked -p foundationdb-macros + + format: + name: Rustfmt + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + RUSTUP_TOOLCHAIN: "1.85.1" + steps: + - name: Checkout code + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Install Rustfmt + run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --component rustfmt + + - name: Check formatting + run: cargo fmt --all -- --check diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index 9efd516071c..f792c67074d 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -88,6 +88,9 @@ jobs: flow/include/flow/sse2neon.h|flow/include/flow/xxhash.h) continue ;; + bindings/rust/foundationdb-sys/include/*) + continue # historical C API snapshots for compatibility builds + ;; esac # These inputs are not parseable as standalone clang-tidy translation units in this workflow: # the public C headers require C consumer setup and intentionally use C typedefs, AWS headers are diff --git a/README.md b/README.md index d4c691921c1..127685d8073 100755 --- a/README.md +++ b/README.md @@ -177,6 +177,8 @@ The language bindings that CMake supports will have a corresponding `README.md` Generally, CMake will build all language bindings for which it can find all necessary dependencies. After each successful CMake run, CMake will tell you which language bindings it is going to build. +The [Rust bindings](bindings/rust/README.md) are opt-in with `-DBUILD_RUST_BINDING=ON`. They retain the imported Rust API and currently select FoundationDB API 740; see their README for build requirements and compatibility limits. + ### Generating `compile_commands.json` diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 94a03d4cba7..9073f3bf0ea 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -12,6 +12,9 @@ endif() if(WITH_GO_BINDING AND NOT OPEN_FOR_IDE) add_subdirectory(go) endif() +if(WITH_RUST_BINDING) + add_subdirectory(rust) +endif() if(WITH_RUBY_BINDING) add_subdirectory(ruby) endif() diff --git a/bindings/bindingtester/bindingtester.py b/bindings/bindingtester/bindingtester.py index 23f8d159094..5b853534ffc 100755 --- a/bindings/bindingtester/bindingtester.py +++ b/bindings/bindingtester/bindingtester.py @@ -273,6 +273,10 @@ def __init__(self, args): min_api_version = max([tester.min_api_version for tester in self.testers]) max_api_version = min([tester.max_api_version for tester in self.testers]) + # Scripted cases exercise the newest common API; randomized suites also + # sample older versions. A tester's ceiling need not match the harness's. + if self.args.test_name == "scripted" and self.args.api_version is None: + self.args.api_version = min(max_api_version, self.test.max_api_version) self.args.api_version = choose_api_version( self.args.api_version, min_api_version, @@ -646,8 +650,8 @@ def parse_args(argv): "--api-version", default=None, type=int, - help="The API version that the testers should use. Not supported in scripted mode. (default = random version supported by " - "all testers)", + help="The API version that the testers should use. (default = newest common version in scripted mode, " + "random version supported by all testers otherwise)", ) parser.add_argument( "--cluster-file", diff --git a/bindings/bindingtester/known_testers.py b/bindings/bindingtester/known_testers.py index 615d173a6a8..adfe4a62cf4 100644 --- a/bindings/bindingtester/known_testers.py +++ b/bindings/bindingtester/known_testers.py @@ -117,6 +117,15 @@ def _absolute_path(path): MAX_API_VERSION, types=ALL_TYPES, ), + "rust": Tester( + "rust", + os.environ.get("FDB_RUST_BINDINGTESTER", _absolute_path("rust/bin/bindingtester")), + 2040, + 610, + 740, + types=ALL_TYPES, + directory_snapshot_ops_enabled=False, + ), "flow": Tester( "flow", _absolute_path("flow/bin/fdb_flow_tester"), diff --git a/bindings/bindingtester/run_binding_tester.sh b/bindings/bindingtester/run_binding_tester.sh index dbec3314ab4..ed49477a8d7 100644 --- a/bindings/bindingtester/run_binding_tester.sh +++ b/bindings/bindingtester/run_binding_tester.sh @@ -25,8 +25,11 @@ BREAKONERROR="${BREAKONERROR:-0}" RUNSCRIPTS="${RUNSCRIPTS:-1}" RUNTESTS="${RUNTESTS:-1}" RANDOMTEST="${RANDOMTEST:-0}" -# BINDINGTESTS="${BINDINGTESTS:-python python3 java java_async ruby go flow}" -BINDINGTESTS="${BINDINGTESTS:-python python3 java java_async go flow swift}" +DEFAULT_BINDINGTESTS="python java java_async go flow swift" +if [[ -x "${FDB_RUST_BINDINGTESTER:-${SCRIPTDIR}/../rust/bin/bindingtester}" ]]; then + DEFAULT_BINDINGTESTS+=" rust" +fi +BINDINGTESTS="${BINDINGTESTS:-${DEFAULT_BINDINGTESTS}}" LOGLEVEL="${LOGLEVEL:-INFO}" _BINDINGTESTS=(${BINDINGTESTS}) DISABLEDTESTS=() @@ -41,7 +44,7 @@ VERSION="1.6" # Display syntax if [ "${#}" -lt 2 ] then - echo 'run_binding_tester.sh ' + echo 'run_binding_tester.sh [bindingtester options...]' echo ' cycles: number of cycles to run test (0 => unlimitted)' echo '' echo ' Modifiable Environment Variables:' @@ -61,6 +64,7 @@ fi # Read arguments MAXCYCLES="${1}" ERRORFILE="${2}" +BINDINGTESTER_OPTIONS=("${@:3}") function logError() { @@ -187,7 +191,7 @@ function runScriptedTest() else local test="${1}" - if ! runCommand "Scripting ${test} ..." 'python3' '-u' "${TESTFILE}" "${test}" --test-name scripted --logging-level "${LOGLEVEL}" + if ! runCommand "Scripting ${test} ..." 'python3' '-u' "${TESTFILE}" "${test}" --test-name scripted --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}" then let status="${status} + 1" fi @@ -212,25 +216,25 @@ function runTest() fi # API - if ([[ "${TESTINDEX}" -eq 0 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[0]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" + if ([[ "${TESTINDEX}" -eq 0 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[0]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}" then let status="${status} + 1" fi # Concurrent API - if ([[ "${TESTINDEX}" -eq 1 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[1]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --concurrency "${CONCURRENCY}" --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" + if ([[ "${TESTINDEX}" -eq 1 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[1]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --concurrency "${CONCURRENCY}" --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}" then let status="${status} + 1" fi # Directory - if ([[ "${TESTINDEX}" -eq 2 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[2]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" + if ([[ "${TESTINDEX}" -eq 2 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[2]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}" then let status="${status} + 1" fi # Directory HCA - if ([[ "${TESTINDEX}" -eq 3 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[3]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory_hca --concurrency "${CONCURRENCY}" --num-ops "${HCAOPERATIONS}" --logging-level "${LOGLEVEL}" + if ([[ "${TESTINDEX}" -eq 3 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[3]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory_hca --concurrency "${CONCURRENCY}" --num-ops "${HCAOPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}" then let status="${status} + 1" fi diff --git a/bindings/bindingtester/tests/scripted.py b/bindings/bindingtester/tests/scripted.py index f5cc68b4905..754e445f069 100644 --- a/bindings/bindingtester/tests/scripted.py +++ b/bindings/bindingtester/tests/scripted.py @@ -39,7 +39,7 @@ class ScriptedTest(Test): def __init__(self, subspace): - super(ScriptedTest, self).__init__(subspace, FDB_API_VERSION, FDB_API_VERSION) + super(ScriptedTest, self).__init__(subspace, 510, FDB_API_VERSION) self.workspace = self.subspace["workspace"] self.results_subspace = self.subspace["results"] # self.thread_subspace = self.subspace['threads'] # TODO: update START_THREAD so that we can create threads in subspaces diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 9f54ade1f74..2cfe4977579 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -20,7 +20,9 @@ #include "fdbclient/FDBTypes.h" #include "flow/ProtocolVersion.h" +#include #include +#include #define FDB_USE_LATEST_API_VERSION #define FDB_INCLUDE_LEGACY_TYPES @@ -32,6 +34,7 @@ #include "foundationdb/fdb_c_internal.h" int g_api_version = 0; +static std::atomic g_header_api_version{ 0 }; /* * Our clients might share these ThreadSafe types between threads. It is therefore @@ -1108,6 +1111,13 @@ extern "C" DLLEXPORT FDBFuture* fdb_transaction_get_range_split_points_with_limi fdb_api_ptr_##func = (void*)&fdb_api_ptr_removed; extern "C" DLLEXPORT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version) { + std::unique_lock selectionLock; + RETURN_ON_ERROR( + // Shared by every caller of this C library, including independently loaded language bindings. + // Retain the mutex through process exit so late calls cannot encounter a destroyed lock. + static auto* const selectionMutex = new std::mutex; + selectionLock = std::unique_lock(*selectionMutex);); + /* Can only call this once */ if (g_api_version != 0) return error_code_api_version_already_set; @@ -1157,9 +1167,17 @@ extern "C" DLLEXPORT fdb_error_t fdb_select_api_version_impl(int runtime_version FDB_API_CHANGED(fdb_transaction_set_option, 14); /* End versioned API changes */ + // Publish only after the runtime version and versioned entry points are final. + g_header_api_version.store(header_version, std::memory_order_release); return error_code_success; } +extern "C" DLLEXPORT void fdb_get_selected_api_versions(int* runtime_version, int* header_version) { + const int headerVersion = g_header_api_version.load(std::memory_order_acquire); + *runtime_version = headerVersion == 0 ? 0 : g_api_version; + *header_version = headerVersion; +} + extern "C" DLLEXPORT int fdb_get_max_api_version() { return FDB_API_VERSION; } diff --git a/bindings/c/foundationdb/fdb_c.h b/bindings/c/foundationdb/fdb_c.h index 2ddd63f1b02..0fc53952e63 100644 --- a/bindings/c/foundationdb/fdb_c.h +++ b/bindings/c/foundationdb/fdb_c.h @@ -669,8 +669,17 @@ DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points_w #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 +/* Concurrent selection calls are serialized; calls after successful selection return api_version_already_set. */ DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); +/* + * Returns the runtime and header versions selected by fdb_select_api_version_impl, + * or zero for both until selection completes successfully. Both output pointers + * must be non-null. This query is thread-safe; API selection must still complete + * before other threads use the FoundationDB API. + */ +DLLEXPORT void fdb_get_selected_api_versions(int* runtime_version, int* header_version); + DLLEXPORT int fdb_get_max_api_version(void); DLLEXPORT const char* fdb_get_client_version(void); diff --git a/bindings/rust/.cargo/config.toml b/bindings/rust/.cargo/config.toml new file mode 100644 index 00000000000..a0257424105 --- /dev/null +++ b/bindings/rust/.cargo/config.toml @@ -0,0 +1,13 @@ +[alias] +# Common development aliases for contributors + +# Build/test/check with latest FDB version (7.4) using embedded headers +# Linking requires an installed FoundationDB client library +build-fdb-latest = "build -p foundationdb --features embedded-fdb-include,fdb-7_4" +test-fdb-latest = "test -p foundationdb --features embedded-fdb-include,fdb-7_4" +check-fdb-latest = "check -p foundationdb --features embedded-fdb-include,fdb-7_4" +clippy-fdb-latest = "clippy -p foundationdb --features embedded-fdb-include,fdb-7_4" +doc-fdb-latest = "doc -p foundationdb --features embedded-fdb-include,fdb-7_4" + +# Lint retained crates and examples with the current supported API. +clippy-all = "clippy --workspace --all-targets --features foundationdb/num-bigint,foundationdb/embedded-fdb-include,foundationdb/fdb-7_4,foundationdb/trace,foundationdb-gen/fdb-7_4,foundationdb-gen/embedded-fdb-include,foundationdb-simulation/fdb-7_4 -- -D warnings" diff --git a/bindings/rust/.gitignore b/bindings/rust/.gitignore new file mode 100644 index 00000000000..400d85a06dc --- /dev/null +++ b/bindings/rust/.gitignore @@ -0,0 +1,11 @@ +/target +**/*.rs.bk +*.swp + +.vscode +.direnv +.idea + +# Files generated by the simulation +simfdb +trace.*.json \ No newline at end of file diff --git a/bindings/rust/CMakeLists.txt b/bindings/rust/CMakeLists.txt new file mode 100644 index 00000000000..078a92d033e --- /dev/null +++ b/bindings/rust/CMakeLists.txt @@ -0,0 +1,198 @@ +option(RUN_RUST_INTEGRATION_TESTS "Compile and run Rust tests against a temporary cluster" OFF) +option(BUILD_RUST_SIMULATION "Build Rust simulator workloads and their tests" OFF) + +# bindgen and the Rust option generator consume the same headers and options as +# the other bindings. Packaged historical headers remain available to Cargo users. +set(rust_include_dir ${CMAKE_CURRENT_BINARY_DIR}/include) +set(rust_header_inputs + ${CMAKE_SOURCE_DIR}/bindings/c/foundationdb/fdb_c.h + ${CMAKE_SOURCE_DIR}/bindings/c/foundationdb/fdb_c_types.h + ${CMAKE_BINARY_DIR}/bindings/c/foundationdb/fdb_c_options.g.h + ${CMAKE_BINARY_DIR}/bindings/c/foundationdb/fdb_c_apiversion.g.h + ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options) +if(BUILD_RUST_SIMULATION) + list(APPEND rust_header_inputs ${CMAKE_SOURCE_DIR}/bindings/c/foundationdb/CWorkload.h) +endif() +set(rust_headers) +foreach(header IN LISTS rust_header_inputs) + get_filename_component(name ${header} NAME) + list(APPEND rust_headers ${rust_include_dir}/${name}) +endforeach() +add_custom_command( + OUTPUT ${rust_headers} + COMMAND ${CMAKE_COMMAND} -E make_directory ${rust_include_dir} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${rust_header_inputs} ${rust_include_dir} + DEPENDS ${rust_header_inputs} fdb_c_options + COMMENT "Stage current FoundationDB headers and options for Rust" + VERBATIM) +add_custom_target(fdb_rust_headers DEPENDS ${rust_headers}) + +set(rust_target_dir ${CMAKE_CURRENT_BINARY_DIR}/target) +set(rust_env + FDB_INCLUDE_DIR=${rust_include_dir} + FDB_CLIENT_LIB_PATH=$ + CARGO_TARGET_DIR=${rust_target_dir}) +set(rust_profile $,debug,release>) +# API 740 is the newest API supported by the imported Rust binding. Building +# against current headers does not imply support for every current C API. +set(rust_cargo_base_args + --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml + --locked + --profile $,dev,release>) +set(rust_cargo_args ${rust_cargo_base_args} + --features foundationdb/fdb-7_4,foundationdb-gen/fdb-7_4) + +# Cargo owns Rust dependency tracking, including changes to build-script inputs. +# Always invoking Cargo avoids maintaining a second Rust source list in CMake. +add_custom_target(fdb_rust ALL + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} build ${rust_cargo_args} + --package foundationdb --package bindingtester + DEPENDS fdb_c fdb_rust_headers + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Build Rust client and binding tester" + COMMAND_EXPAND_LISTS + VERBATIM) + +set(rust_tester ${CMAKE_CURRENT_BINARY_DIR}/bin/bindingtester) +add_custom_target(fdb_rust_tester ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/bin + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${rust_target_dir}/${rust_profile}/bindingtester ${rust_tester} + DEPENDS fdb_rust + BYPRODUCTS ${rust_tester} + COMMENT "Stage Rust binding tester" + VERBATIM) + +set(rust_unit_packages + --package foundationdb + --package foundationdb-tuple + --package foundationdb-gen + --package foundationdb-macros) +add_custom_target(fdb_rust_tests ALL + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_args} ${rust_unit_packages} --lib --no-run + DEPENDS fdb_rust + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Compile Rust library unit tests" + COMMAND_EXPAND_LISTS + VERBATIM) + +if(APPLE) + set(rust_library_path DYLD_LIBRARY_PATH) +else() + set(rust_library_path LD_LIBRARY_PATH) +endif() +set(rust_test_env ${rust_env} + "${rust_library_path}=$:$ENV{${rust_library_path}}") + +add_test(NAME rust_unit_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_args} ${rust_unit_packages} --lib --offline + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND_EXPAND_LISTS) +set_tests_properties(rust_unit_tests PROPERTIES TIMEOUT 300 LABELS "rust") + +# This compile-fail doctest checks the public raw-pointer ownership boundary. +# Other client doctests may require a running cluster. +add_test(NAME rust_future_safety_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_args} --package foundationdb --doc --offline + future::FdbFuture + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND_EXPAND_LISTS) +set_tests_properties(rust_future_safety_tests PROPERTIES TIMEOUT 300 LABELS "rust") + +if(BUILD_RUST_SIMULATION) + set(rust_simulation_args ${rust_cargo_base_args} + --package foundationdb-simulation --features foundationdb-simulation/fdb-7_4) + add_custom_target(fdb_rust_simulation ALL + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} build ${rust_simulation_args} --lib --examples + DEPENDS fdb_rust + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Build Rust simulator workloads" + COMMAND_EXPAND_LISTS + VERBATIM) + add_custom_target(fdb_rust_simulation_tests ALL + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} test ${rust_simulation_args} --lib --no-run + DEPENDS fdb_rust_simulation + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Compile Rust simulator unit tests" + COMMAND_EXPAND_LISTS + VERBATIM) + add_test(NAME rust_simulation_unit_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_simulation_args} --lib --offline + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND_EXPAND_LISTS) + set_tests_properties(rust_simulation_unit_tests PROPERTIES + TIMEOUT 300 LABELS "rust;simulation") + + foreach(symbol str_from_c WorkloadContext::new WorkloadContext::set_process_id register_workload_context WrappedWorkload Metrics) + string(REPLACE "::" "_" test_suffix ${symbol}) + add_test(NAME rust_simulation_${test_suffix}_safety_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_simulation_args} --doc --offline ${symbol} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND_EXPAND_LISTS) + set_tests_properties(rust_simulation_${test_suffix}_safety_tests PROPERTIES + TIMEOUT 300 LABELS "rust;simulation") + endforeach() + + add_test(NAME rust_simulation_wake_order_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + "RUSTFLAGS=$ENV{RUSTFLAGS} --cfg fdb_simulation_loom" + CARGO_TARGET_DIR=${rust_target_dir}-loom + ${CARGO_EXECUTABLE} test ${rust_simulation_args} --lib --offline + fdb_rt::loom_tests::coalesced_wake_publishes_before_poll -- --exact + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND_EXPAND_LISTS) + set_tests_properties(rust_simulation_wake_order_tests PROPERTIES + TIMEOUT 300 LABELS "rust;simulation") +endif() + +if(RUN_RUST_INTEGRATION_TESTS) + if(NOT WITH_PYTHON_BINDING) + message(FATAL_ERROR "Rust integration tests require BUILD_PYTHON_BINDING=ON") + endif() + add_custom_target(fdb_rust_integration_tests ALL + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_args} --package foundationdb --package bindingtester --tests --no-run + COMMAND ${CMAKE_COMMAND} -E env ${rust_env} + ${CARGO_EXECUTABLE} build ${rust_cargo_args} --package foundationdb --example api_version_library + DEPENDS fdb_rust_tests fdb_rust_tester python_binding + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Compile Rust client integration tests" + COMMAND_EXPAND_LISTS + VERBATIM) + add_fdbclient_test(NAME rust_integration_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_args} --package foundationdb --tests --offline -- --test-threads=1 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + TEST_TIMEOUT 1200) + add_fdbclient_test(NAME rust_bindingtester_tests + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/scripts/run_bindingtester.sh ${CMAKE_BINARY_DIR} 1 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + TEST_TIMEOUT 1800) + add_fdbclient_test(NAME rust_bindingtester_on_error_tests + COMMAND ${CMAKE_COMMAND} -E env ${rust_test_env} + ${CARGO_EXECUTABLE} test ${rust_cargo_base_args} --features foundationdb/fdb-7_4 + --package bindingtester --bin bindingtester --offline + tests::on_error_restores_transaction_before_next_instruction -- --exact --ignored + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + TEST_TIMEOUT 300) + add_fdbclient_test(NAME rust_api_version_libraries_tests + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/api_version_libraries.py + --client-library $ + --rust-library ${rust_target_dir}/${rust_profile}/examples/${CMAKE_SHARED_LIBRARY_PREFIX}api_version_library${CMAKE_SHARED_LIBRARY_SUFFIX} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + TEST_TIMEOUT 120) + # tmp_cluster otherwise preserves an inherited default cluster. These tests + # must always use the disposable cluster created by the test runner. + set_tests_properties(rust_integration_tests rust_bindingtester_tests + rust_bindingtester_on_error_tests rust_api_version_libraries_tests PROPERTIES + LABELS "rust;integration" + ENVIRONMENT_MODIFICATION "FDB_CLUSTER_FILE=unset:") +endif() diff --git a/bindings/rust/CONTRIBUTING.md b/bindings/rust/CONTRIBUTING.md new file mode 100644 index 00000000000..63f7390241b --- /dev/null +++ b/bindings/rust/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Developing the Rust bindings + +Use Rust 1.85.1 or later, Cargo, and libclang. Follow the FoundationDB repository +contribution process and preserve the imported licenses and contributor notices. + +## Standalone Cargo + +Run commands from `bindings/rust`. For an installed FoundationDB client: + +```sh +cargo build-fdb-latest +cargo fmt --all -- --check +cargo clippy-all +cargo test -p foundationdb-tuple --all-features --locked +cargo test -p foundationdb-macros --locked +``` + +The `*-fdb-latest` aliases select `fdb-7_4` and `embedded-fdb-include`. Embedded +headers support standalone builds, but linking and execution still require +`libfdb_c`. Set `FDB_CLIENT_LIB_PATH` and the platform's runtime library path when +the library is outside the normal search paths. Client API features are mutually +exclusive; do not use `--all-features` for the client. + +`FDB_INCLUDE_DIR` overrides embedded headers. For client builds, it must contain +`fdb_c.h`, its companion headers, and `fdb.options`; for simulator builds it must +also contain `CWorkload.h`. CMake stages these inputs from the owning source tree. +Standalone simulator builds otherwise use this repository's canonical +`bindings/c/foundationdb/CWorkload.h`. + +## Tests + +Configure `BUILD_RUST_BINDING=ON` and build `fdb_rust_tests` for client, tuple, +option-generator, and macro unit tests. `rust_future_safety_tests` also checks +that raw future construction cannot bypass the ownership contract. + +Enable `RUN_RUST_INTEGRATION_TESTS=ON` and `BUILD_PYTHON_BINDING=ON` for tests +against a disposable local cluster: + +```sh +cmake --build build --target fdb_rust_integration_tests fdbserver fdbcli fdbmonitor python_binding +ctest --test-dir build -R '^rust_' --output-on-failure +``` + +These commands run from the repository root. Direct Cargo client integration tests +also need a disposable cluster: set `FDB_CLUSTER_FILE`, then run +`cargo test-fdb-latest --locked`. They write test keys and exercise database-wide +behavior, so do not use an application cluster. + +The shared [binding tester](foundationdb-bindingtester/README.md) runs fixed +regression seeds plus scripted and randomized comparisons against Python. Client +coverage includes directory-prefix allocation, native retry/error ownership, +commit uncertainty, and runtime-version-specific versionstamp behavior. Separate +library subprocess tests cover compatible and incompatible API selection across +independently loaded Rust libraries. + +## Simulator support + +Configure `BUILD_RUST_SIMULATION=ON` explicitly and build +`fdb_rust_simulation_tests` to compile the C-ABI adapter, examples, and unit tests. +The `rust_simulation_` CTests check context validity, callback metrics lifetimes, +thread ownership, phase cancellation, and native teardown. The Loom test explores +wake/dequeue memory ordering in a separate Cargo target directory. These checks do +not execute the simulator; workload smoke tests use a compatible server as +described in [foundationdb-simulation/README.md](foundationdb-simulation/README.md). + +Hosted checks cover all retained crates against generated current headers and +check historical client API configurations against embedded snapshots separately. +State the actual API feature, client/server versions, tests, and bindingtester +seeds when reporting validation. Upstream release and scheduled simulation +campaigns are not imported. diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock new file mode 100644 index 00000000000..2faff1f1af5 --- /dev/null +++ b/bindings/rust/Cargo.lock @@ -0,0 +1,1034 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bindgen" +version = "0.73.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "787ef8ef523575546b106a58213d6e6b06198a05c2f757258c68a74273670cfa" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 3.0.5", +] + +[[package]] +name = "bindingtester" +version = "0.9.0" +dependencies = [ + "async-trait", + "env_logger", + "foundationdb", + "foundationdb-sys", + "futures", + "log", + "num-bigint", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "foundationdb" +version = "0.11.0" +dependencies = [ + "async-recursion", + "async-trait", + "byteorder", + "foundationdb-gen", + "foundationdb-macros", + "foundationdb-sys", + "foundationdb-tuple", + "futures", + "futures-util", + "lazy_static", + "libc", + "num-bigint", + "rand", + "static_assertions", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "foundationdb-gen" +version = "0.11.0" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "foundationdb-macros" +version = "0.4.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", + "try_map", +] + +[[package]] +name = "foundationdb-simulation" +version = "0.3.0" +dependencies = [ + "bindgen", + "foundationdb", + "foundationdb-sys", + "futures-util", + "loom", +] + +[[package]] +name = "foundationdb-sys" +version = "0.11.0" +dependencies = [ + "bindgen", + "libc", +] + +[[package]] +name = "foundationdb-tuple" +version = "0.11.0" +dependencies = [ + "memchr", + "num-bigint", + "uuid", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.5", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try_map" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1626d07cb5c1bb2cf17d94c0be4852e8a7c02b041acec9a8c5bdda99f9d580" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "xml" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f45bb2c13fec6a6cb4c0f76a7e94839e110a14ec803ec2940777a94c347bc52" + +[[package]] +name = "xml-rs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a56132a0d6ecbe77352edc10232f788fc4ceefefff4cab784a98e0e16b6b51" +dependencies = [ + "xml", +] diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 00000000000..8205f8c5a7d --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +resolver = "2" +members = [ + "foundationdb", + "foundationdb-tuple", + "foundationdb-sys", + "foundationdb-gen", + "foundationdb-bindingtester", + "foundationdb-macros", + "foundationdb-simulation", +] +default-members = [ + "foundationdb", + "foundationdb-tuple", + "foundationdb-sys", + "foundationdb-gen", + "foundationdb-bindingtester", + "foundationdb-macros", +] + +[profile.release] +debug = true diff --git a/bindings/rust/LICENSE-APACHE b/bindings/rust/LICENSE-APACHE new file mode 100644 index 00000000000..8f71f43fee3 --- /dev/null +++ b/bindings/rust/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/bindings/rust/LICENSE-MIT b/bindings/rust/LICENSE-MIT new file mode 100644 index 00000000000..b497b098ee8 --- /dev/null +++ b/bindings/rust/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright (c) 2015 The trust-dns Developers +Copyright (c) 2017 Google LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bindings/rust/README.md b/bindings/rust/README.md new file mode 100644 index 00000000000..608f05603ed --- /dev/null +++ b/bindings/rust/README.md @@ -0,0 +1,84 @@ +# FoundationDB Rust bindings + +This Cargo workspace provides an async Rust interface to the FoundationDB C API, +including transactions, tuples, subspaces, the directory layer, and the shared +binding tester. It was imported from +[foundationdb-rs](https://github.com/foundationdb-rs/foundationdb-rs). +[UPSTREAM.md](UPSTREAM.md) records the source revision and licensing provenance. + +## Crates + +| Crate | Purpose | +| --- | --- | +| [`foundationdb`](foundationdb/README.md) | Async client, transactions, and directory layer | +| [`foundationdb-sys`](foundationdb-sys/README.md) | Raw C API bindings | +| [`foundationdb-gen`](foundationdb-gen/README.md) | Rust options generated from `fdb.options` | +| [`foundationdb-tuple`](foundationdb-tuple/README.md) | Tuple encoding, subspaces, and versionstamps | +| [`foundationdb-macros`](foundationdb-macros/README.md) | API-version conditional compilation | +| [`bindingtester`](foundationdb-bindingtester/README.md) | Shared binding tester protocol | +| [`foundationdb-simulation`](foundationdb-simulation/README.md) | Optional C-ABI simulator workloads | + +The six client and tester crates are the default workspace members. Simulator +support is selected explicitly. Application layers, client-budget policies, +profiling decoders, and upstream development/release tooling are outside this +import. + +## Building with FoundationDB + +Rust is optional and requires Rust 1.85.1 or later, Cargo, and libclang for +bindgen. Enable it when configuring the repository: + +```sh +cmake -S . -B build -G Ninja -DBUILD_RUST_BINDING=ON +cmake --build build --target fdb_rust fdb_rust_tester fdb_rust_tests +ctest --test-dir build -R '^rust_(unit_tests|future_safety_tests)$' --output-on-failure +``` + +CMake uses the in-tree `fdb_c` library, generated C headers, and canonical +`fdb.options`. Cargo artifacts stay in the build directory. +[CONTRIBUTING.md](CONTRIBUTING.md) covers standalone Cargo and integration tests. + +To build the optional simulator adapter and its examples, also configure +`BUILD_RUST_SIMULATION=ON` and build `fdb_rust_simulation_tests`. It uses the +repository's canonical `CWorkload.h`, the C external-workload interface, and an +executor that keeps futures on their simulator thread. Its unit and safety tests +can be run with `ctest --test-dir build -R '^rust_simulation_' --output-on-failure`. +Running workloads requires a compatible `fdbserver`; see the crate's README. + +## Compatibility + +Select exactly one client API feature: `fdb-5_1`, `fdb-5_2`, `fdb-6_0`, `fdb-6_1`, +`fdb-6_2`, `fdb-6_3`, `fdb-7_0`, `fdb-7_1`, `fdb-7_3`, or `fdb-7_4`. The in-tree +build selects API 740, the highest API exposed by the imported Rust client. +Historical C headers and option snapshots remain available for standalone Cargo +builds using `embedded-fdb-include`. + +A newer C client can serve API 740, but this import does not expose API 800 or +native CDC wrappers. Directory snapshot operations are not supported by the Rust +binding tester. Shared scripted tests select each tester's newest supported API: +740 for Rust and 800 for the current bindings. + +Versionstamped mutations require runtime API 520 or later. With runtime API 510, +these mutations panic before reaching the C client; other supported API 510 +operations remain available. Tuple versionstamp helpers use four-byte offsets. +High-level versionstamped packing requires exactly one incomplete stamp; use +ordinary packing for completed stamps. + +The simulator adapter targets the current C workload ABI. When built against +current C client headers, independently loaded Rust workloads can share an API +selection only after verifying both its runtime and header versions. Historical +client headers cannot verify another library's selection and reject adoption. +Simulator workload contexts, metrics sinks, and phase database handles enforce +their native lifetime and thread boundaries. This does not transfer ownership of +the native network thread. + +The in-tree build initially targets Linux and macOS. Native client distribution +packaging and crate publishing are not configured by this import. Imported crate +versions remain unchanged and are independent of the FoundationDB server version; +this source import does not publish crates or transfer their ownership. + +## License + +The imported workspace remains dual licensed under [Apache 2.0](LICENSE-APACHE) +or [MIT](LICENSE-MIT), at your option. Existing author and copyright notices are +preserved. FoundationDB's other components retain their existing licenses. diff --git a/bindings/rust/UPSTREAM.md b/bindings/rust/UPSTREAM.md new file mode 100644 index 00000000000..50aa5a01bd9 --- /dev/null +++ b/bindings/rust/UPSTREAM.md @@ -0,0 +1,52 @@ +# Import provenance + +- Source: https://github.com/foundationdb-rs/foundationdb-rs +- Revision: `f0eda232ae2e99d3e9377229715a905f90cac4db` +- Source commit date: 2026-09-25 +- Destination: `bindings/rust` +- License: MIT OR Apache-2.0; see `LICENSE-MIT` and `LICENSE-APACHE`. + +The initial source snapshot preserved the upstream workspace. The in-tree +adaptation retains the six client/tester crates and minimal C-ABI simulator +support, with their tests, historical client API snapshots, attribution, and +licenses. Full upstream history remains available at the revision above. + +Application recipes and their simulations, client accounting/budget/reporting and +custom retry-policy APIs, profiling and Timekeeper utilities, simulator tracing +integration, the legacy C++ workload bridge, and upstream Nix/Docker tooling are +outside this binding import. They remain available in the original source +snapshot. The core client still provides native transactions, retries, tuples, +subspaces, directories, and mapped results. + +CMake and shared tests use the repository's canonical C headers, `fdb.options`, +and C workload interface. Historical client headers/options are preserved for +standalone Cargo builds; do not rewrite them when updating current interfaces. +Repository governance, hosted publishing, and upstream scheduled campaigns are +not imported. Existing crate versions do not imply a new published release. + +The adaptation corrects these retained API and safety contracts: + +- Raw C future and simulation context/string constructors require `unsafe`. + Future wrappers take unique ownership of a valid pointer of the declared type. +- Integer options take `i64`, matching the C API's signed 64-bit payloads. +- Native retry handling discovers wrapped FoundationDB errors through their + source chain and retains commit uncertainty across later attempts. +- Mapped-result selectors preserve server-provided range boundaries; futures + recheck readiness after installing the current task's waker. +- Shared allocator synchronization protects callers using one transaction. + Directory metadata with a newer minor version remains readable, while writes + require compatible versions. +- High-level versionstamped tuple/subspace packing requires exactly one incomplete + stamp. Versionstamped mutations require runtime API 520 or later; other supported + API 510 operations remain available. +- Bindingtester database directory operations use native transaction retries and + publish results after commit. `on_error_with_transaction` preserves the original + transaction even when error handling fails. +- Simulation wrappers enforce native context/thread lifetimes. Metrics borrow + their callback sink, process switching requires `unsafe`, and custom metric + formats must consume exactly one double. +- Simulator futures stay on their owner thread. Workload release cancels suspended + phases before teardown; phase database handles remain owned by the native caller. +- Current C headers expose the selected runtime/header pair so compatible loaded + Rust libraries can adopt it safely. Historical headers reject unverified + adoption. Neither path transfers ownership of the native network. diff --git a/bindings/rust/foundationdb-bindingtester/Cargo.toml b/bindings/rust/foundationdb-bindingtester/Cargo.toml new file mode 100644 index 00000000000..ee7b318d949 --- /dev/null +++ b/bindings/rust/foundationdb-bindingtester/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bindingtester" +version = "0.9.0" +authors = [ + "Benjamin Fry ", + "Vincent Rouillé ", + "Pierre Zemb " +] +edition = "2024" +rust-version = "1.85.1" +publish = false + +description = """ +Bindings to the C api for FoundationDB +""" + +repository = "https://github.com/apple/foundationdb" + +readme = "README.md" +keywords = ["foundationdb", "kv"] +categories = ["database"] + +license = "MIT/Apache-2.0" + +[dependencies] +env_logger = { version = "0.11.10", default-features = false, features = ["auto-color", "regex"] } +foundationdb = { path = "../foundationdb", features = ["uuid", "num-bigint", "fdb-7_4", "embedded-fdb-include"], default-features = false } +foundationdb-sys = { version = "0.11.0", path = "../foundationdb-sys", features = ["embedded-fdb-include", "fdb-7_4"], default-features = false } +futures = "0.3.32" +log = "0.4.33" +num-bigint = "0.5.1" +async-trait = "0.1.89" diff --git a/bindings/rust/foundationdb-bindingtester/README.md b/bindings/rust/foundationdb-bindingtester/README.md new file mode 100644 index 00000000000..909cfdd7c16 --- /dev/null +++ b/bindings/rust/foundationdb-bindingtester/README.md @@ -0,0 +1,55 @@ +# Rust FoundationDB bindingtester + +This executable implements the shared FoundationDB bindingtester protocol. +The Rust tester is registered as `rust` in `bindings/bindingtester/known_testers.py`. + +Configure FoundationDB with `BUILD_RUST_BINDING=ON` and +`BUILD_PYTHON_BINDING=ON`, then build the Rust tester and Python reference binding: + +```sh +bindings/rust/scripts/setup_bindingtester.sh /path/to/build +``` + +With a running FoundationDB cluster, run the fixed regression seeds followed by +the shared scripted suite and one iteration of the API, concurrent API, directory, +and directory allocator tests: + +```sh +bindings/rust/scripts/run_bindingtester.sh /path/to/build 1 --cluster-file /path/to/fdb.cluster +``` + +The script uses Python and Rust artifacts from the same build and compares +API/directory results with the Python binding. Fixed regression seeds retain +the API version that reproduced each failure; +the shared runner uses API 740 for scripted cases and samples supported API +versions for randomized cases. It copies the +shared harness and Python tester into the build tree so the Python tester imports +the generated binding there. It does not install a Python package or clone a +second FoundationDB checkout. Increase the second argument to repeat randomized tests; retain the logged seeds to reproduce +failures. + +Both packaged binding-test launchers include Rust automatically when its tester +executable is present. `BINDINGTESTS=rust` selects only Rust with either launcher; +`rust_bindingtester_tests` runs the same suites through CTest when +`RUN_RUST_INTEGRATION_TESTS=ON`. + +For an individual case after the script has staged the shared test harness: + +```sh +export FDB_RUST_BINDINGTESTER=/path/to/build/bindings/rust/bin/bindingtester +export PYTHONPATH=/path/to/build/bindings/python +export LD_LIBRARY_PATH=/path/to/build/lib +python3 /path/to/build/bindings/bindingtester/bindingtester.py rust --compare python \ + --api-version 740 --test-name api --num-ops 1000 --seed 3534790651 \ + --cluster-file /path/to/fdb.cluster +``` + +On macOS, use `DYLD_LIBRARY_PATH` instead of `LD_LIBRARY_PATH`. `FDB_RUST_BINDINGTESTER` +can also select a tester built directly with Cargo. + +The registered tester supports runtime API versions 610 through 740 and the +shared tuple types, including arbitrary-width integers and versionstamps. +Directory snapshot operations are disabled because the imported directory layer +does not implement them. Scripted tests use the newest API shared by the selected +testers, preserving API 800 coverage for bindings that support it. Importing the +binding does not establish full feature parity. diff --git a/bindings/rust/foundationdb-bindingtester/src/main.rs b/bindings/rust/foundationdb-bindingtester/src/main.rs new file mode 100644 index 00000000000..79cb33a4536 --- /dev/null +++ b/bindings/rust/foundationdb-bindingtester/src/main.rs @@ -0,0 +1,2812 @@ +#[macro_use] +extern crate log; + +use foundationdb as fdb; +use foundationdb_sys as fdb_sys; + +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::io::Write; +use std::mem::transmute; +use std::pin::Pin; +use std::sync::Arc; +use std::thread; + +use fdb::options::{ConflictRangeType, DatabaseOption, TransactionOption}; +use fdb::tuple::{Bytes, Element, Subspace, TuplePack, pack, pack_into, unpack}; +use fdb::*; +use futures::future; +use futures::prelude::*; +use num_bigint::BigInt; + +static WAITED_FOR_EMPTY: Element = Element::Bytes(Bytes(Cow::Borrowed(b"WAITED_FOR_EMPTY"))); +static RESULT_NOT_PRESENT: Element = Element::Bytes(Bytes(Cow::Borrowed(b"RESULT_NOT_PRESENT"))); +static GOT_READ_VERSION: Element = Element::Bytes(Bytes(Cow::Borrowed(b"GOT_READ_VERSION"))); +static GOT_COMMITTED_VERSION: Element = + Element::Bytes(Bytes(Cow::Borrowed(b"GOT_COMMITTED_VERSION"))); + +static ERROR_NONE: Element = Element::Bytes(Bytes(Cow::Borrowed(b"ERROR: NONE"))); +static ERROR_MULTIPLE: Element = Element::Bytes(Bytes(Cow::Borrowed(b"ERROR: MULTIPLE"))); +static OK: Element = Element::Bytes(Bytes(Cow::Borrowed(b"OK"))); +static ERROR_DIRECTORY: Element = Element::Bytes(Bytes(Cow::Borrowed(b"DIRECTORY_ERROR"))); +static ESTIMATE_RANGE_RESPONSE: Element = + Element::Bytes(Bytes(Cow::Borrowed(b"GOT_ESTIMATED_RANGE_SIZE"))); +static GOT_APPROXIMATE_SIZE_RESPONSE: Element = + Element::Bytes(Bytes(Cow::Borrowed(b"GOT_APPROXIMATE_SIZE"))); +static GET_RANGE_SPLIT_POINTS_RESPONSE: Element = + Element::Bytes(Bytes(Cow::Borrowed(b"GOT_RANGE_SPLIT_POINTS"))); + +use crate::fdb::options::{MutationType, StreamingMode}; + +use foundationdb::directory::DirectoryError; +use foundationdb::directory::DirectoryLayer; +use foundationdb::directory::{Directory, DirectoryOutput}; +use foundationdb::tuple::{PackResult, TupleUnpack}; + +use tuple::VersionstampOffset; + +fn mutation_from_str(s: &str) -> MutationType { + match s { + "ADD" => MutationType::Add, + "AND" => MutationType::And, + "BIT_AND" => MutationType::BitAnd, + "OR" => MutationType::Or, + "BIT_OR" => MutationType::BitOr, + "XOR" => MutationType::Xor, + "BIT_XOR" => MutationType::BitXor, + "APPEND_IF_FITS" => MutationType::AppendIfFits, + "MAX" => MutationType::Max, + "MIN" => MutationType::Min, + "SET_VERSIONSTAMPED_KEY" => MutationType::SetVersionstampedKey, + "SET_VERSIONSTAMPED_VALUE" => MutationType::SetVersionstampedValue, + "BYTE_MIN" => MutationType::ByteMin, + "BYTE_MAX" => MutationType::ByteMax, + "COMPARE_AND_CLEAR" => MutationType::CompareAndClear, + _ => unimplemented!("mutation_from_str({})", s), + } +} + +pub fn streaming_from_value(val: i32) -> StreamingMode { + match val { + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_WANT_ALL => StreamingMode::WantAll, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_ITERATOR => StreamingMode::Iterator, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_EXACT => StreamingMode::Exact, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_SMALL => StreamingMode::Small, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_MEDIUM => StreamingMode::Medium, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_LARGE => StreamingMode::Large, + fdb_sys::FDBStreamingMode_FDB_STREAMING_MODE_SERIAL => StreamingMode::Serial, + _ => unimplemented!("streaming_from_value({})", val), + } +} + +struct Instr { + code: InstrCode, + database: bool, + snapshot: bool, + starts_with: bool, + selector: bool, +} + +impl std::fmt::Debug for Instr { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "[{:?}", self.code)?; + if self.database { + write!(fmt, " db")?; + } + if self.snapshot { + write!(fmt, " snapshot")?; + } + if self.starts_with { + write!(fmt, " starts_with")?; + } + if self.selector { + write!(fmt, " selector")?; + } + write!(fmt, "]") + } +} + +#[derive(Debug)] +enum DirectoryStackItem { + Directory(DirectoryLayer), + DirectoryOutput(DirectoryOutput), + Subspace(Subspace), + Null, +} + +impl Instr { + fn pop_database(&mut self) -> bool { + if self.database { + self.database = false; + true + } else { + false + } + } + fn pop_snapshot(&mut self) -> bool { + if self.snapshot { + self.snapshot = false; + true + } else { + false + } + } + fn pop_starts_with(&mut self) -> bool { + if self.starts_with { + self.starts_with = false; + true + } else { + false + } + } + fn pop_selector(&mut self) -> bool { + if self.selector { + self.selector = false; + true + } else { + false + } + } + + fn has_flags(&self) -> bool { + self.database || self.snapshot || self.starts_with || self.selector + } +} + +#[derive(Debug)] +enum InstrCode { + // data operations + Push(Element<'static>), + Dup, + EmptyStack, + Swap, + Pop, + Sub, + Concat, + LogStack, + + // foundationdb operations + NewTransaction, + UseTransaction, + OnError, + Get, + GetKey, + GetRange, + GetReadVersion, + GetVersionstamp, + Set, + SetReadVersion, + Clear, + ClearRange, + AtomicOp, + ReadConflictRange, + WriteConflictRange, + ReadConflictKey, + WriteConflictKey, + DisableWriteConflict, + Commit, + Reset, + Cancel, + GetCommittedVersion, + GetApproximateSize, + WaitFuture, + GetEstimatedRangeSize, + GetRangeSplitPoints, + + TuplePack, + TuplePackWithVersionstamp, + TupleUnpack, + TupleRange, + TupleSort, + EncodeFloat, + EncodeDouble, + DecodeFloat, + DecodeDouble, + + // Thread Operations + StartThread, + WaitEmpty, + + // misc + UnitTests, + + // Directory/Subspace/Layer Creation + DirectoryCreateSubspace, + DirectoryCreateLayer, + DirectoryCreateOrOpen, + DirectoryCreate, + DirectoryOpen, + + // Directory Management + DirectoryChange, + DirectorySetErrorIndex, + + // Directory Operations + DirectoryMove, + DirectoryMoveTo, + DirectoryRemove, + DirectoryRemoveIfExists, + DirectoryList, + DirectoryExists, + + // Subspace operation + DirectoryPackKey, + DirectoryUnpackKey, + DirectoryRange, + DirectoryContains, + DirectoryOpenSubspace, + + // Directory Logging + DirectoryLogSubspace, + DirectoryLogDirectory, + + // Other + DirectoryStripPrefix, +} + +fn has_opt<'a>(cmd: &'a str, opt: &'static str) -> (&'a str, bool) { + if cmd.ends_with(opt) { + (&cmd[0..(cmd.len() - opt.len())], true) + } else { + (cmd, false) + } +} + +impl Instr { + fn from(data: &[u8]) -> Self { + use crate::InstrCode::*; + + let data = Bytes::from(data); + trace!("inst {data:?}"); + let tup: Vec = unpack(&data).unwrap(); + let cmd = tup[0].as_str().unwrap(); + + let (cmd, database) = has_opt(cmd, "_DATABASE"); + let (cmd, snapshot) = has_opt(cmd, "_SNAPSHOT"); + let (cmd, starts_with) = has_opt(cmd, "_STARTS_WITH"); + let (cmd, selector) = has_opt(cmd, "_SELECTOR"); + + let code = match cmd { + "PUSH" => Push(tup[1].clone().into_owned()), + "DUP" => Dup, + "EMPTY_STACK" => EmptyStack, + "SWAP" => Swap, + "POP" => Pop, + "SUB" => Sub, + "CONCAT" => Concat, + "LOG_STACK" => LogStack, + + "NEW_TRANSACTION" => NewTransaction, + "USE_TRANSACTION" => UseTransaction, + "ON_ERROR" => OnError, + "GET" => Get, + "GET_KEY" => GetKey, + "GET_RANGE" => GetRange, + "GET_READ_VERSION" => GetReadVersion, + "GET_VERSIONSTAMP" => GetVersionstamp, + + "SET" => Set, + "SET_READ_VERSION" => SetReadVersion, + "CLEAR" => Clear, + "CLEAR_RANGE" => ClearRange, + "ATOMIC_OP" => AtomicOp, + "READ_CONFLICT_RANGE" => ReadConflictRange, + "WRITE_CONFLICT_RANGE" => WriteConflictRange, + "READ_CONFLICT_KEY" => ReadConflictKey, + "WRITE_CONFLICT_KEY" => WriteConflictKey, + "DISABLE_WRITE_CONFLICT" => DisableWriteConflict, + "COMMIT" => Commit, + "RESET" => Reset, + "CANCEL" => Cancel, + "GET_COMMITTED_VERSION" => GetCommittedVersion, + "GET_APPROXIMATE_SIZE" => GetApproximateSize, + "WAIT_FUTURE" => WaitFuture, + "GET_ESTIMATED_RANGE_SIZE" => GetEstimatedRangeSize, + "GET_RANGE_SPLIT_POINTS" => GetRangeSplitPoints, + + "TUPLE_PACK" => TuplePack, + "TUPLE_PACK_WITH_VERSIONSTAMP" => TuplePackWithVersionstamp, + "TUPLE_UNPACK" => TupleUnpack, + "TUPLE_RANGE" => TupleRange, + "TUPLE_SORT" => TupleSort, + "ENCODE_FLOAT" => EncodeFloat, + "ENCODE_DOUBLE" => EncodeDouble, + "DECODE_FLOAT" => DecodeFloat, + "DECODE_DOUBLE" => DecodeDouble, + + "START_THREAD" => StartThread, + "WAIT_EMPTY" => WaitEmpty, + + "UNIT_TESTS" => UnitTests, + + "DIRECTORY_CREATE_SUBSPACE" => DirectoryCreateSubspace, + "DIRECTORY_CREATE_LAYER" => DirectoryCreateLayer, + "DIRECTORY_CREATE_OR_OPEN" => DirectoryCreateOrOpen, + "DIRECTORY_CREATE" => DirectoryCreate, + "DIRECTORY_OPEN" => DirectoryOpen, + + "DIRECTORY_CHANGE" => DirectoryChange, + "DIRECTORY_SET_ERROR_INDEX" => DirectorySetErrorIndex, + + "DIRECTORY_MOVE" => DirectoryMove, + "DIRECTORY_MOVE_TO" => DirectoryMoveTo, + "DIRECTORY_REMOVE" => DirectoryRemove, + "DIRECTORY_REMOVE_IF_EXISTS" => DirectoryRemoveIfExists, + "DIRECTORY_LIST" => DirectoryList, + "DIRECTORY_EXISTS" => DirectoryExists, + + "DIRECTORY_PACK_KEY" => DirectoryPackKey, + "DIRECTORY_UNPACK_KEY" => DirectoryUnpackKey, + "DIRECTORY_RANGE" => DirectoryRange, + "DIRECTORY_CONTAINS" => DirectoryContains, + "DIRECTORY_OPEN_SUBSPACE" => DirectoryOpenSubspace, + + "DIRECTORY_LOG_SUBSPACE" => DirectoryLogSubspace, + "DIRECTORY_LOG_DIRECTORY" => DirectoryLogDirectory, + + "DIRECTORY_STRIP_PREFIX" => DirectoryStripPrefix, + + name => unimplemented!("inimplemented instr: {}", name), + }; + Instr { + code, + database, + snapshot, + starts_with, + selector, + } + } +} + +struct StackResult { + state: Option<(Bytes<'static>, TransactionState)>, + data: FdbResult>, +} +impl From for StackResult { + fn from(err: FdbError) -> Self { + StackResult { + state: None, + data: Err(err), + } + } +} +impl From> for StackResult { + fn from(data: Element<'static>) -> Self { + StackResult { + state: None, + data: Ok(data), + } + } +} + +impl From>> for StackResult { + fn from(data: FdbResult>) -> Self { + StackResult { state: None, data } + } +} + +type StackFuture = Pin>>; +struct StackItem { + number: usize, + data: Option>, + fut: Option<(usize, StackFuture)>, +} + +impl StackItem { + async fn await_fut(&mut self) -> Option<(Bytes<'static>, TransactionState)> { + let mut ret = None; + if let Some((_id, fut)) = self.fut.take() { + let StackResult { state, mut data } = fut.await; + + if let Some((name, state)) = state { + trace!("{name:?} = {state:?}"); + match state { + TransactionState::TransactionCommitError(e) => { + let err = FdbError::from_code(e.code()); + ret = Some((name, TransactionState::TransactionCommitError(e))); + data = Err(err); + } + state => { + ret = Some((name, state)); + } + } + } + + let data = data.unwrap_or_else(|err| { + trace!("ERROR {err:?}"); + let packed = pack(&( + Bytes::from(b"ERROR".as_ref()), + Bytes::from(format!("{}", err.code()).into_bytes()), + )); + Element::Bytes(packed.into()) + }); + + self.data = Some(data); + } + ret + } +} + +impl Clone for StackItem { + fn clone(&self) -> Self { + if self.fut.is_some() { + panic!("cannot clone future stack item"); + } + Self { + number: self.number, + data: self.data.clone(), + fut: None, + } + } +} + +impl std::fmt::Debug for StackItem { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "[item num={}, data={:?}]", self.number, self.data) + } +} + +fn range(prefix: Bytes) -> (Bytes<'static>, Bytes<'static>) { + let begin = prefix.clone().into_owned(); + let end = strinc(prefix).into_owned(); + + (begin.into(), end.into()) +} + +enum TransactionState { + Transaction(Transaction), + TransactionCancelled(TransactionCancelled), + TransactionCommitted(TransactionCommitted), + TransactionCommitError(TransactionCommitError), + Pending(usize), + Dead, +} +impl std::fmt::Debug for TransactionState { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use TransactionState as S; + + match self { + S::Transaction(..) => "Transaction", + S::TransactionCancelled(..) => "TransactionCancelled", + S::TransactionCommitted(..) => "TransactionCommitted", + S::TransactionCommitError(..) => "TransactionCommitError", + S::Pending(..) => "Pending", + S::Dead => "Dead", + } + .fmt(f) + } +} + +impl TransactionState { + fn reset(&mut self) { + use TransactionState as S; + *self = match std::mem::replace(self, S::Dead) { + S::TransactionCommitted(c) => S::Transaction(c.reset()), + S::TransactionCommitError(c) => S::Transaction(c.reset()), + c => c, + } + } + + fn as_mut(&mut self) -> &mut Transaction { + use TransactionState as S; + + self.reset(); + match *self { + S::Transaction(ref mut tr) => tr, + S::TransactionCancelled(ref mut tr) => unsafe { + // rust binding prevent accessing cancelled transaction + &mut *(tr as *mut TransactionCancelled as *mut Transaction) + }, + _ => panic!("transaction is owned by a future that is still not done"), + } + } + + fn take(&mut self, id: usize) -> Transaction { + use TransactionState as S; + + self.reset(); + match std::mem::replace(self, S::Dead) { + S::Transaction(tr) => { + *self = S::Pending(id); + tr + } + S::TransactionCancelled(tr) => { + *self = S::Pending(id); + unsafe { + // rust binding prevent accessing cancelled transaction + transmute::(tr) + } + } + _ => panic!("transaction is owned by a future that is still not done"), + } + } +} + +async fn directory_operation( + db: &Database, + transaction: &TransactionState, + is_db: bool, + data: D, + operation: F, +) -> Result +where + F: for<'a> Fn(&'a Transaction, &'a D) -> future::LocalBoxFuture<'a, Result>, +{ + if is_db { + // Retry only the directory operation and commit. Stack arguments have already + // been popped, and the caller publishes the result once this succeeds. + db.run(|txn, _| { + let data = &data; + let operation = &operation; + async move { operation(&txn, data).await.map_err(FdbBindingError::from) } + }) + .await + } else { + let TransactionState::Transaction(txn) = transaction else { + panic!("could not find an active transaction"); + }; + operation(txn, &data).await.map_err(FdbBindingError::from) + } +} + +struct StackMachine { + prefix: Bytes<'static>, + + // A global transaction map from byte string to Transactions. This map is shared by all tester + // 'threads'. + transactions: HashMap, TransactionState>, + + // A stack of data items of mixed types and their associated metadata. At a minimum, each item + // should be stored with the 0-based instruction number which resulted in it being put onto the + // stack. Your stack must support push and pop operations. It may be helpful if it supports + // random access, clear and a peek operation. The stack is initialized to be empty. + stack: Vec, + + // A current FDB transaction name (stored as a byte string). The transaction name should be + // initialized to the prefix that instructions are being read from. + cur_transaction: Bytes<'static>, + + // A last seen FDB version, which is a 64-bit integer. + last_version: i64, + + threads: Vec>, + + trx_counter: usize, + + directory_stack: Vec, + directory_index: usize, + error_index: usize, +} + +fn strinc(key: Bytes) -> Bytes { + let mut key = key.into_owned(); + for i in (0..key.len()).rev() { + if key[i] != 0xff { + key[i] += 1; + return Bytes::from(key); + } else { + // stripping key from trailing 0xFF bytes + key.remove(i); + } + } + panic!("failed to strinc"); +} + +impl StackMachine { + fn new(db: &Database, prefix: Bytes<'static>) -> Self { + let cur_transaction = prefix.clone(); + let mut transactions = HashMap::new(); + transactions.insert( + cur_transaction.clone(), + TransactionState::Transaction(db.create_trx().unwrap()), + ); + + Self { + prefix, + transactions, + stack: Vec::new(), + cur_transaction, + last_version: 0, + threads: Vec::new(), + trx_counter: 0, + + directory_stack: vec![DirectoryStackItem::Directory(DirectoryLayer::default())], + directory_index: 0, + error_index: 0, + } + } + + fn next_trx_id(&mut self) -> usize { + self.trx_counter += 1; + self.trx_counter + } + + async fn fetch_instr(&self, trx: &Transaction) -> FdbResult> { + let opt = RangeOption::from(&Subspace::from(&self.prefix)); + debug!("opt = {opt:?}"); + let instrs = Vec::new(); + trx.get_ranges(opt, false) + .try_fold(instrs, |mut instrs, res| { + for kv in res.iter() { + let instr = Instr::from(kv.value()); + instrs.push(instr); + } + future::ok(instrs) + }) + .await + } + + async fn pop(&mut self) -> StackItem { + let mut item = self.stack.pop().expect("stack empty"); + if let Some((name, state)) = item.await_fut().await { + self.transactions.insert(name, state); + } + item + } + + async fn maybe_pop(&mut self) -> Option { + let mut item = self.stack.pop()?; + if let Some((name, state)) = item.await_fut().await { + self.transactions.insert(name, state); + } + Some(item) + } + + async fn pop_i64(&mut self) -> i64 { + let element = self.pop_element().await; + match element { + Element::Int(v) => v, + _ => panic!("i64 was expected, found {element:?}"), + } + } + + async fn pop_i32(&mut self) -> i32 { + i32::try_from(self.pop_i64().await).expect("i64 to fit in i32") + } + + async fn pop_usize(&mut self) -> usize { + usize::try_from(self.pop_i64().await).expect("i64 to fit in usize") + } + + async fn pop_str(&mut self) -> String { + let element = self.pop_element().await; + match element { + Element::String(v) => v.into_owned(), + _ => panic!("string was expected, found {element:?}"), + } + } + + async fn pop_bytes(&mut self) -> Bytes<'static> { + let element = self.pop_element().await; + match element { + Element::Bytes(v) => v, + // checking nested bytes in a Tuple as well + Element::Tuple(elements) => match elements.first() { + Some(Element::Bytes(b)) => b.clone(), + _ => panic!("bytes were expected, found a Tuple with no Bytes in it"), + }, + _ => panic!("bytes were expected, found {element:?}"), + } + } + + async fn pop_optional_bytes(&mut self) -> Option> { + let element = self.pop_element().await; + match element { + Element::Bytes(v) => Some(v.to_vec()), + _ => None, + } + } + + async fn pop_string_tuple(&mut self, count: usize) -> Vec> { + let mut result = vec![]; + + if count == 0 { + result.push(vec![]); + } else { + for _i in 0..count { + let mut sub_result = vec![]; + let vec_size = self.pop_i64().await; + for _j in 0..vec_size { + sub_result.push(self.pop_str().await); + } + result.push(sub_result); + } + } + result + } + + async fn pop_element(&mut self) -> Element<'static> { + let item = self.pop().await; + if let Some(data) = item.data { + return data; + } + panic!("no data"); + } + + async fn pop_selector(&mut self) -> KeySelector<'static> { + let key: Bytes = self.pop_bytes().await; + let or_equal: i32 = i32::try_from(self.pop_i64().await).unwrap(); + let offset: i32 = i32::try_from(self.pop_i64().await).unwrap(); + + KeySelector::new(key.0, or_equal != 0, offset) + } + + fn push(&mut self, number: usize, data: Element<'static>) { + self.stack.push(StackItem { + number, + data: Some(data), + fut: None, + }); + } + + fn push_fut(&mut self, number: usize, id: usize, fut: StackFuture) { + let item = StackItem { + number, + data: None, + fut: Some((id, fut)), + }; + self.stack.push(item); + } + + fn push_res(&mut self, number: usize, res: FdbResult<()>, ok_str: &[u8]) { + match res { + Ok(..) => self.push(number, Element::Bytes(ok_str.to_vec().into())), + Err(err) => self.push_err(number, err), + } + } + + fn push_directory_err(&mut self, code: &InstrCode, number: usize, err: impl std::fmt::Debug) { + debug!("[{number}] DIRECTORY_ERROR during {code:?}: {err:?}"); + self.push(number, Element::Tuple(vec![ERROR_DIRECTORY.clone()])); + + if matches!( + code, + InstrCode::DirectoryCreateSubspace + | InstrCode::DirectoryCreateOrOpen + | InstrCode::DirectoryCreateLayer + | InstrCode::DirectoryCreate + | InstrCode::DirectoryOpen + | InstrCode::DirectoryMove + | InstrCode::DirectoryMoveTo + | InstrCode::DirectoryOpenSubspace + ) { + debug!( + "pushed NULL in the directory_stack at index {} because of the error", + self.directory_stack.len() + ); + self.directory_stack.push(DirectoryStackItem::Null); + } + } + + fn push_err(&mut self, number: usize, err: FdbError) { + trace!("ERROR {err:?}"); + let packed = pack(&Element::Tuple(vec![ + Element::Bytes(Bytes::from(b"ERROR".as_ref())), + Element::Bytes(Bytes::from(format!("{}", err.code()).into_bytes())), + ])); + self.push(number, Element::Bytes(packed.into())); + } + + fn check(&mut self, number: usize, r: FdbResult) -> Result { + match r { + Ok(v) => Ok(v), + Err(err) => { + self.push_err(number, err); + Err(()) + } + } + } + + #[allow(clippy::cognitive_complexity)] + async fn run_step( + &mut self, + db: Arc, + number: usize, + mut instr: Instr, + ) -> Result<(), ()> { + use crate::InstrCode::*; + + let is_db = instr.pop_database(); + + let mut mutation = false; + let mut pending = false; + + let (mut trx, trx_name) = if is_db { + ( + TransactionState::Transaction(self.check(number, db.create_trx())?), + None, + ) + } else { + let mut trx = self + .transactions + .remove(&self.cur_transaction) // some instr requires transaction ownership + .expect("failed to find trx"); + if let TransactionState::Pending(id) = trx { + let stack_item = self.stack.iter_mut().find(|s| match s.fut { + Some((trx_id, ..)) => trx_id == id, + _ => false, + }); + if let Some(stack_item) = stack_item { + if let Some((name, state)) = stack_item.await_fut().await { + assert_eq!(name, self.cur_transaction); + trx = state; + } + } + } + (trx, Some(&self.cur_transaction)) + }; + + match instr.code { + // Pushes the provided item onto the stack. + Push(ref element) => { + debug!("push {element:?}"); + self.push(number, element.clone()) + } + // Duplicates the top item on the stack. The instruction number for + // the duplicate item should be the same as the original. + Dup => { + let top = self.pop().await; + debug!("dup {top:?}"); + self.stack.push(top.clone()); + self.stack.push(top); + } + // Discards all items in the stack. + EmptyStack => { + debug!("empty_stack"); + self.stack.clear() + } + // Pops the top item off of the stack as INDEX. + // Swaps the items in the stack at depth 0 and depth INDEX. + // Does not modify the instruction numbers of the swapped items. + Swap => { + let depth: usize = self.pop_usize().await; + assert!( + depth < self.stack.len(), + "swap index invalid {} >= {}", + depth, + self.stack.len() + ); + let depth_0 = self.stack.len() - 1; + let depth = depth_0 - depth; + debug!("swap {depth_0} {depth}"); + self.stack.swap(depth_0, depth); + } + // Pops and discards the top item on the stack. + Pop => { + debug!("pop"); + self.pop().await; + } + // Pops the top two items off of the stack as A and B and then + // pushes the difference (A-B) onto the stack. + // A and B may be assumed to be integers. + Sub => { + let a = self.pop_element().await; + let b = self.pop_element().await; + debug!("sub {a:?} - {b:?}"); + let c = match (a, b) { + // checking for possible overflow + (Element::Int(a), Element::Int(b)) => match a.checked_sub(b) { + None => Element::BigInt(BigInt::from(a) - BigInt::from(b)), + Some(c) => Element::Int(c), + }, + (Element::Float(a), Element::Float(b)) => Element::Float(a - b), + (Element::Double(a), Element::Double(b)) => Element::Double(a - b), + (Element::BigInt(a), Element::BigInt(b)) => Element::BigInt(a - b), + (Element::Int(a), Element::BigInt(b)) => Element::BigInt(a - b), + (Element::BigInt(a), Element::Int(b)) => Element::BigInt(a - b), + (a, b) => panic!("sub between invalid elements {a:?} {b:?}"), + }; + self.push(number, c); + } + // Pops the top two items off the stack as A and B and then pushes + // the concatenation of A and B onto the stack. A and B can be + // assumed to be of the same type and will be either byte strings or + // unicode strings. + Concat => { + let a = self.pop_element().await; + let b = self.pop_element().await; + debug!("concat {a:?} {b:?}"); + let c = match (a, b) { + (Element::String(a), Element::String(b)) => { + Element::String(format!("{a}{b}").into()) + } + (Element::Bytes(a), Element::Bytes(b)) => { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&a); + bytes.extend_from_slice(&b); + Element::Bytes(bytes.into()) + } + (a, b) => panic!("concat between invalid elements {a:?} {b:?}"), + }; + self.push(number, c); + } + // Pops the top item off the stack as PREFIX. Using a new + // transaction with normal retry logic, inserts a key-value pair + // into the database for each item in the stack of the form: + // + // PREFIX + tuple.pack((stackIndex, instructionNumber)) = tuple.pack((item,)) + // + // where stackIndex is the current index of the item in the stack. + // The oldest item in the stack should have stackIndex 0. + // + // If the byte string created by tuple packing the item exceeds 40000 bytes, + // then the value should be truncated to the first 40000 bytes of the packed + // tuple. + // + // When finished, the stack should be empty. Note that because the stack may be + // large, it may be necessary to commit the transaction every so often (e.g. + // after every 100 sets) to avoid past_version errors. + LogStack => { + let prefix: Bytes = self.pop_bytes().await; + let mut stack_idx = self.stack.len(); + let trx_id = self.next_trx_id(); + let mut t = trx.take(trx_id); + let mut n = 0; + while let Some(stack_item) = self.maybe_pop().await { + stack_idx -= 1; + let mut key = prefix.clone().into_owned(); + stack_idx.pack_into_vec(&mut key); + stack_item.number.pack_into_vec(&mut key); + + let value = pack(&stack_item.data.unwrap()); + t.set(&key, &value[..value.len().min(40000)]); + n += 1; + if n == 100 { + t = t.commit().await.unwrap().reset(); + n = 0; + } + } + t = t.commit().await.unwrap().reset(); + trx = TransactionState::Transaction(t); + } + + // Creates a new transaction and stores it in the global transaction map + // under the currently used transaction name. + NewTransaction => { + let name = self.cur_transaction.clone(); + debug!("create_trx {name:?}"); + + let trx = self.check(number, db.create_trx())?; + trx.set_option(fdb::options::TransactionOption::DebugTransactionIdentifier( + "RUST".to_string(), + )) + .unwrap(); + trx.set_option(fdb::options::TransactionOption::LogTransaction) + .unwrap(); + self.transactions + .insert(name, TransactionState::Transaction(trx)); + } + + // Pop the top item off of the stack as TRANSACTION_NAME. Begin using the + // transaction stored at TRANSACTION_NAME in the transaction map for future + // operations. If no entry exists in the map for the given name, a new + // transaction should be inserted. + UseTransaction => { + let name: Bytes = self.pop_bytes().await; + debug!("use_transaction {name:?}"); + if !is_db { + // Popping the name may have resolved a pending future and restored the + // current transaction already. Keep that newer state if it exists. + self.transactions + .entry(self.cur_transaction.clone()) + .or_insert_with(|| std::mem::replace(&mut trx, TransactionState::Dead)); + } + if !self.transactions.contains_key(&name) { + let trx = self.check(number, db.create_trx())?; + self.transactions + .insert(name.clone(), TransactionState::Transaction(trx)); + } + self.cur_transaction = name; + } + // Pops the top item off of the stack as ERROR_CODE. Passes ERROR_CODE in a + // language-appropriate way to the on_error method of current transaction + // object and blocks on the future. If on_error re-raises the error, bubbles + // the error out as indicated above. May optionally push a future onto the + // stack. + OnError => { + let trx_name = trx_name.cloned(); + let error_code: i32 = self.pop_i32().await; + let error = FdbError::from_code(error_code); + debug!("on_error {error:?}"); + let trx_id = self.next_trx_id(); + let f = trx + .take(trx_id) + .on_error_with_transaction(error) + .map(|(trx, result)| StackResult { + state: trx_name.map(|n| (n, TransactionState::Transaction(trx))), + data: result.map(|()| RESULT_NOT_PRESENT.clone().into_owned()), + }) + .boxed_local(); + self.push_fut(number, trx_id, f); + pending = true; + } + + // Pops the top item off of the stack as KEY and then looks up KEY in the + // database using the get() method. May optionally push a future onto the + // stack. + Get => { + let key: Bytes = self.pop_bytes().await; + debug!("get {key:?}"); + let f = trx + .as_mut() + .get(&key, instr.pop_snapshot()) + .map_ok(|v| match v { + Some(v) => Element::Bytes(v.to_vec().into()), + None => RESULT_NOT_PRESENT.clone().into_owned(), + }) + .map(StackResult::from) + .boxed_local(); + self.push_fut(number, 0, f); + pending = true; + } + + // Pops the top four items off of the stack as KEY, OR_EQUAL, OFFSET, PREFIX + // and then constructs a key selector. This key selector is then resolved + // using the get_key() method to yield RESULT. If RESULT starts with PREFIX, + // then RESULT is pushed onto the stack. Otherwise, if RESULT < PREFIX, PREFIX + // is pushed onto the stack. If RESULT > PREFIX, then strinc(PREFIX) is pushed + // onto the stack. May optionally push a future onto the stack. + GetKey => { + let selector = self.pop_selector().await; + let prefix: Bytes = self.pop_bytes().await; + debug!("get_key {selector:?}, prefix = {prefix:?}"); + + let f = trx + .as_mut() + .get_key(&selector, instr.pop_snapshot()) + .map_ok(|key| { + let key = Bytes::from(key.as_ref()); + let bytes = if key.starts_with(&prefix) { + key + } else if key < prefix { + prefix + } else { + assert!(key > prefix); + strinc(prefix) + }; + Element::Bytes(bytes.to_vec().into()) + }) + .map(StackResult::from) + .boxed_local(); + self.push_fut(number, 0, f); + pending = true; + } + + // Pops the top five items off of the stack as BEGIN_KEY, END_KEY, LIMIT, + // REVERSE and STREAMING_MODE. Performs a range read in a language-appropriate + // way using these parameters. The resulting range of n key-value pairs are + // packed into a tuple as [k1,v1,k2,v2,...,kn,vn], and this single packed value + // is pushed onto the stack. + // + // _STARTS_WITH: + // + // Pops the top four items off of the stack as PREFIX, LIMIT, REVERSE and + // STREAMING_MODE. Performs a prefix range read in a language-appropriate way + // using these parameters. Output is pushed onto the stack as with GET_RANGE. + // + // _SELECTOR: + // + // Pops the top ten items off of the stack as BEGIN_KEY, BEGIN_OR_EQUAL, + // BEGIN_OFFSET, END_KEY, END_OR_EQUAL, END_OFFSET, LIMIT, REVERSE, + // STREAMING_MODE, and PREFIX. Constructs key selectors BEGIN and END from + // the first six parameters, and then performs a range read in a language- + // appropriate way using BEGIN, END, LIMIT, REVERSE and STREAMING_MODE. Output + // is pushed onto the stack as with GET_RANGE, excluding any keys that do not + // begin with PREFIX. + GetRange => { + let selector = instr.pop_selector(); + let starts_with = instr.pop_starts_with(); + let snapshot = instr.pop_snapshot(); + + let (begin, end) = if starts_with { + let begin: Bytes = self.pop_bytes().await; + let end = strinc(begin.clone()); + ( + KeySelector::first_greater_or_equal(begin.0), + KeySelector::first_greater_or_equal(end.0), + ) + } else if selector { + let begin = self.pop_selector().await; + let end = self.pop_selector().await; + (begin, end) + } else { + let begin: Bytes = self.pop_bytes().await; + let end: Bytes = self.pop_bytes().await; + ( + KeySelector::first_greater_or_equal(begin.0), + KeySelector::first_greater_or_equal(end.0), + ) + }; + + let limit: i64 = self.pop_i64().await; + let reverse: i64 = self.pop_i64().await; + let streaming_mode: i32 = self.pop_i32().await; + let mode = streaming_from_value(streaming_mode); + debug!( + "get_range begin={:?}\n, begin={:?}\n, end={:?}\n,end={:?}\n, limit={:?}, rev={:?}, mode={:?}", + begin, + unpack::(begin.key()), + end, + unpack::(end.key()), + limit, + reverse, + mode + ); + + let prefix: Option = if selector { + Some(self.pop_bytes().await) + } else { + None + }; + let opt = RangeOption { + begin, + end, + mode, + limit: if limit > 0 { + Some(limit as usize) + } else { + None + }, + reverse: reverse != 0, + ..RangeOption::default() + }; + + let res = trx + .as_mut() + .get_ranges(opt, snapshot) + .try_fold(Vec::new(), move |mut out, kvs| { + for kv in kvs.iter() { + let key = kv.key(); + let value = kv.value(); + debug!(" - {:?} {:?}", Bytes::from(key), Bytes::from(value)); + if let Some(ref prefix) = prefix { + if !key.starts_with(prefix) { + continue; + } + } + pack_into(&Bytes::from(key), &mut out); + pack_into(&Bytes::from(value), &mut out); + } + future::ok(out) + }) + .map_ok(|data| Element::Bytes(data.into())) + .await; + match res { + Err(err) => self.push_err(number, err), + Ok(element) => self.push(number, element), + } + } + + // Gets the current read version and stores it in the internal stack machine + // state as the last seen version. Pushed the string "GOT_READ_VERSION" onto + // the stack. + GetReadVersion => { + let _snapshot = instr.pop_snapshot(); + let version = trx.as_mut().get_read_version().await; + match version { + Ok(version) => { + self.last_version = version; + self.push(number, GOT_READ_VERSION.clone().into_owned()); + } + Err(err) => self.push_err(number, err), + } + } + + // Calls get_versionstamp and pushes the resulting future onto the stack. + GetVersionstamp => { + let f = trx + .as_mut() + .get_versionstamp() + .map_ok(|v| Element::Bytes(v.to_vec().into())) + .map(StackResult::from) + .boxed_local(); + self.push_fut(number, 0, f); + pending = true; + } + + // Pops the top two items off of the stack as KEY and VALUE. Sets KEY to have + // the value VALUE. A SET_DATABASE call may optionally push a future onto the + // stack. + Set => { + let key: Bytes = self.pop_bytes().await; + let value: Bytes = self.pop_bytes().await; + debug!("set {key:?} {value:?}"); + trx.as_mut().set(&key, &value); + mutation = true; + } + + // Sets the current transaction read version to the internal state machine last + // seen version. + SetReadVersion => { + debug!("set_read_version {:?}", self.last_version); + trx.as_mut().set_read_version(self.last_version); + } + + // Pops the top item off of the stack as KEY and then clears KEY from the + // database. A CLEAR_DATABASE call may optionally push a future onto the stack. + Clear => { + let key: Bytes = self.pop_bytes().await; + debug!("clear {key:?}"); + trx.as_mut().clear(&key); + mutation = true; + } + + // CLEAR_RANGE + // Pops the top two items off of the stack as BEGIN_KEY and END_KEY. Clears the + // range of keys from BEGIN_KEY to END_KEY in the database. A + // CLEAR_RANGE_DATABASE call may optionally push a future onto the stack. + // + // CLEAR_RANGE_STARTS_WITH + // Pops the top item off of the stack as PREFIX and then clears all keys from + // the database that begin with PREFIX. A CLEAR_RANGE_STARTS_WITH_DATABASE call + // may optionally push a future onto the stack. + ClearRange => { + let starts_with = instr.pop_starts_with(); + let (begin, end) = if starts_with { + let prefix = self.pop_bytes().await; + range(prefix) + } else { + let begin: Bytes = self.pop_bytes().await; + let end: Bytes = self.pop_bytes().await; + (begin, end) + }; + debug!("clear_range {begin:?} {end:?}"); + trx.as_mut().clear_range(&begin, &end); + mutation = true; + } + + // Pops the top three items off of the stack as OPTYPE, KEY, and VALUE. + // Performs the atomic operation described by OPTYPE upon KEY with VALUE. An + // ATOMIC_OP_DATABASE call may optionally push a future onto the stack. + AtomicOp => { + let optype: String = self.pop_str().await; + let key: Bytes = self.pop_bytes().await; + let value: Bytes = self.pop_bytes().await; + debug!("atomic_op {key:?} {value:?} {optype:?}"); + + let op = mutation_from_str(&optype); + trx.as_mut().atomic_op(&key, &value, op); + mutation = true; + } + + // Pops the top two items off of the stack as BEGIN_KEY and END_KEY. Adds a + // read conflict range or write conflict range from BEGIN_KEY to END_KEY. + // Pushes the byte string "SET_CONFLICT_RANGE" onto the stack. + ReadConflictRange => { + let begin: Bytes = self.pop_bytes().await; + let end: Bytes = self.pop_bytes().await; + debug!("read_conflict_range {begin:?} {end:?}"); + self.push_res( + number, + trx.as_mut() + .add_conflict_range(&begin, &end, ConflictRangeType::Read), + b"SET_CONFLICT_RANGE", + ); + } + WriteConflictRange => { + let begin: Bytes = self.pop_bytes().await; + let end: Bytes = self.pop_bytes().await; + debug!("write_conflict_range {begin:?} {end:?}"); + self.push_res( + number, + trx.as_mut() + .add_conflict_range(&begin, &end, ConflictRangeType::Write), + b"SET_CONFLICT_RANGE", + ); + } + // Pops the top item off of the stack as KEY. Adds KEY as a read conflict key + // or write conflict key. Pushes the byte string "SET_CONFLICT_KEY" onto the + // stack. + ReadConflictKey => { + let begin: Bytes = self.pop_bytes().await; + let mut end = begin.clone().into_owned(); + end.push(0); + debug!("read_conflict_key {begin:?} {end:?}"); + self.push_res( + number, + trx.as_mut() + .add_conflict_range(&begin, &end, ConflictRangeType::Read), + b"SET_CONFLICT_KEY", + ); + } + WriteConflictKey => { + let begin: Bytes = self.pop_bytes().await; + let mut end = begin.clone().into_owned(); + end.push(0); + debug!("write_conflict_key {begin:?} {end:?}"); + self.push_res( + number, + trx.as_mut() + .add_conflict_range(&begin, &end, ConflictRangeType::Write), + b"SET_CONFLICT_KEY", + ); + } + // Sets the NEXT_WRITE_NO_WRITE_CONFLICT_RANGE transaction option on the + // current transaction. Does not modify the stack. + DisableWriteConflict => { + debug!("disable_write_conflict"); + self.check( + number, + trx.as_mut() + .set_option(TransactionOption::NextWriteNoWriteConflictRange), + )? + } + // Commits the current transaction (with no retry behavior). May optionally + // push a future onto the stack. + Commit => { + debug!("commit"); + let trx_name = trx_name.cloned(); + let trx_id = self.next_trx_id(); + let f = trx + .take(trx_id) + .commit() + .map(|r| match r { + Ok(c) => StackResult { + state: trx_name.map(|n| (n, TransactionState::TransactionCommitted(c))), + data: Ok(RESULT_NOT_PRESENT.clone().into_owned()), + }, + Err(c) => { + let err = FdbError::from_code(c.code()); + StackResult { + state: trx_name + .map(|n| (n, TransactionState::TransactionCommitError(c))), + data: Err(err), + } + } + }) + .boxed_local(); + self.push_fut(number, trx_id, f); + pending = true; + } + // Resets the current transaction. + Reset => { + debug!("reset"); + trx.as_mut().reset(); + } + // Cancels the current transaction. + Cancel => { + debug!("cancel"); + let cancelled = trx.take(0).cancel(); + trx = TransactionState::TransactionCancelled(cancelled); + } + + // Gets the committed version from the current transaction and stores it in the + // internal stack machine state as the last seen version. Pushes the byte + // string "GOT_COMMITTED_VERSION" onto the stack. + GetCommittedVersion => { + debug!("committed_version"); + if let TransactionState::TransactionCommitted(t) = &trx { + let last_version = t + .committed_version() + .expect("failed to get committed version"); + self.last_version = last_version; + self.push(number, GOT_COMMITTED_VERSION.clone().into_owned()); + } else { + warn!("committed_version() called on a non commited transaction"); + self.last_version = -1; + self.push(number, GOT_COMMITTED_VERSION.clone().into_owned()); + } + } + + // Calls get_approximate_size and pushes the byte string "GOT_APPROXIMATE_SIZE" + // onto the stack. Note bindings may issue GET_RANGE calls with different + // limits, so these bindings can obtain different sizes back. + GetApproximateSize => { + debug!("get_approximate_size"); + match trx.as_mut().get_approximate_size().await { + Ok(_) => self.push(number, GOT_APPROXIMATE_SIZE_RESPONSE.clone().into_owned()), + Err(error) => self.push_err(number, error), + } + } + + GetRangeSplitPoints => { + debug!("get_range_split_points"); + let begin: Bytes = self.pop_bytes().await; + let end: Bytes = self.pop_bytes().await; + let chunk_size = self.pop_i64().await; + + match trx + .as_mut() + .get_range_split_points(&begin, &end, chunk_size) + .await + { + Ok(_) => { + self.push(number, GET_RANGE_SPLIT_POINTS_RESPONSE.clone().into_owned()) + } + Err(error) => self.push_err(number, error), + } + } + + // Pops the top item off the stack and pushes it back on. If the top item on + // the stack is a future, this will have the side effect of waiting on the + // result of the future and pushing the result on the stack. Does not change + // the instruction number of the item. + WaitFuture => { + debug!("wait_future"); + let item = self.pop().await; + self.stack.push(item); + } + + // Pops the top two items off of the stack as BEGIN_KEY and END_KEY to + // construct a key range. Then call the `getEstimatedRangeSize` API of + // the language binding. Make sure the API returns without error. Finally + // push the string "GOT_ESTIMATED_RANGE_SIZE" onto the stack. + GetEstimatedRangeSize => { + debug!("get estimated range size"); + let begin = self.pop_bytes().await; + let end = self.pop_bytes().await; + + match trx + .as_mut() + .get_estimated_range_size_bytes(&begin, &end) + .await + { + Ok(estimate) => { + debug!("got an estimate of {estimate} bytes"); + self.push(number, ESTIMATE_RANGE_RESPONSE.clone().into_owned()); + } + Err(error) => { + self.push_err(number, error); + } + }; + } + + // Pops the top item off of the stack as N. Pops the next N items off of the + // stack and packs them as the tuple [item0,item1,...,itemN], and then pushes + // this single packed value onto the stack. + TuplePack => { + let n: usize = self.pop_usize().await; + debug!("tuple_pack {n}"); + let mut buf = Vec::new(); + for _ in 0..n { + let element: Element = self.pop_element().await; + debug!(" - {element:?}"); + buf.push(element); + } + let tuple = Element::Tuple(buf); + self.push(number, Element::Bytes(pack(&tuple).into())); + } + + // Pops the top item off of the stack as a byte string prefix. Pops the next item + // off of the stack as N. Pops the next N items off of the stack and packs them + // as the tuple [item0,item1,...,itemN], with the provided prefix and tries to + // append the position of the first incomplete versionstamp as if the byte + // string were to be used as a key in a SET_VERSIONSTAMP_KEY atomic op. If there + // are no incomplete versionstamp instances, then this pushes the literal byte + // string 'ERROR: NONE' to the stack. If there is more than one, then this pushes + // the literal byte string 'ERROR: MULTIPLE'. If there is exactly one, then it pushes + // the literal byte string 'OK' and then pushes the packed tuple. (Languages that + // do not contain a 'Versionstamp' tuple-type do not have to implement this + // operation.) + TuplePackWithVersionstamp => { + let prefix = self.pop_bytes().await; + let n: usize = self.pop_usize().await; + debug!("tuple_pack_with_versionstamp {prefix:?} {n}"); + let mut buf = Vec::new(); + for _ in 0..n { + let element: Element = self.pop_element().await; + debug!(" - {element:?}"); + buf.push(element); + } + + let tuple = Element::Tuple(buf.clone()); + let i = tuple.count_incomplete_versionstamp(); + let mut vec = prefix.into_owned(); + let offset = buf.pack_into_vec_with_versionstamp(&mut vec); + match offset { + VersionstampOffset::None { size: _ } => { + assert_eq!(i, 0); + self.push(number, ERROR_NONE.clone().into_owned()); + } + VersionstampOffset::OneIncomplete { offset: _ } => { + assert_eq!(i, 1); + let data = Element::Bytes(vec.into()); + self.push(number, OK.clone().into_owned()); + self.push(number, data); + } + VersionstampOffset::MultipleIncomplete => { + assert!(i > 1); + self.push(number, ERROR_MULTIPLE.clone().into_owned()); + } + } + } + + // Pops the top item off of the stack as PACKED, and then unpacks PACKED into a + // tuple. For each element of the tuple, packs it as a new tuple and pushes it + // onto the stack. + TupleUnpack => { + let data = self.pop_bytes().await; + debug!("tuple_unpack {data:?}"); + let data: Vec = unpack(&data).unwrap(); + for element in data { + debug!(" - {element:?}"); + self.push(number, Element::Bytes(pack(&(element,)).into())); + } + } + // Pops the top item off of the stack as N. Pops the next N items off of the + // stack, and passes these items as a tuple (or array, or language-appropriate + // structure) to the tuple range method. Pushes the begin and end elements of + // the returned range onto the stack. + TupleRange => { + let n: usize = self.pop_usize().await; + debug!("tuple_range {n:?}"); + let mut tup = Vec::new(); + for _ in 0..n { + let element = self.pop_element().await; + tup.push(element); + } + + let subspace = Subspace::from(tup); + let (start, end) = subspace.range(); + self.push(number, Element::Bytes(start.into())); + self.push(number, Element::Bytes(end.into())); + } + + // Pops the top item off of the stack as N. Pops the next N items off of the + // stack as packed tuples (i.e., byte strings), unpacks them, sorts the tuples, + // repacks them into byte strings, and then pushes these packed tuples onto + // the stack so that the final top of the stack now has the greatest + // element. If the binding has some kind of tuple comparison function, it should + // use that to sort. Otherwise, it should sort them lexicographically by + // their byte representation. The choice of function should not affect final sort order. + TupleSort => { + let n: usize = self.pop_usize().await; + debug!("tuple_sort {n:?}"); + let mut tup = Vec::::new(); + for _ in 0..n { + let packed = self.pop_bytes().await; + let element: Element = unpack(&packed).unwrap(); + debug!("- {element:?}"); + tup.push(element.into_owned()); + } + tup.sort(); + debug!("tuple_sorted"); + for element in tup { + debug!("- {element:?}"); + self.push(number, Element::Bytes(pack(&element).into())); + } + } + + // Pops the top item off of the stack. This will be a byte-string of length 4 + // containing the IEEE 754 encoding of a float in big-endian order. + // This is then converted into a float and pushed onto the stack. + EncodeFloat => { + let bytes = self.pop_bytes().await; + debug!("encode_float {bytes:?}"); + let mut arr = [0; 4]; + arr.copy_from_slice(&bytes); + let f = f32::from_bits(u32::from_be_bytes(arr)); + self.push(number, Element::Float(f)); + } + // Pops the top item off of the stack. This will be a byte-string of length 8 + // containing the IEEE 754 encoding of a double in big-endian order. + // This is then converted into a double and pushed onto the stack. + EncodeDouble => { + let bytes = self.pop_bytes().await; + debug!("encode_double {bytes:?}"); + let mut arr = [0; 8]; + arr.copy_from_slice(&bytes); + let f = f64::from_bits(u64::from_be_bytes(arr)); + self.push(number, Element::Double(f)); + } + // Pops the top item off of the stack. This will be a single-precision float. + // This is converted into a (4 byte) byte-string of its IEEE 754 representation + // in big-endian order, and pushed onto the stack. + DecodeFloat => { + let f: f32 = match self.pop_element().await { + Element::Float(v) => v, + element => panic!("float was expected, found {element:?}"), + }; + debug!("decode_float {f}"); + self.push( + number, + Element::Bytes(f.to_bits().to_be_bytes().to_vec().into()), + ); + } + // Pops the top item off of the stack. This will be a double-precision float. + // This is converted into a (8 byte) byte-string its IEEE 754 representation + // in big-endian order, and pushed onto the stack. + DecodeDouble => { + let f: f64 = match self.pop_element().await { + Element::Double(v) => v, + element => panic!("double was expected, found {element:?}"), + }; + debug!("decode_double {f}"); + self.push( + number, + Element::Bytes(f.to_bits().to_be_bytes().to_vec().into()), + ); + } + + // Pops the top item off of the stack as PREFIX. Creates a new stack machine + // instance operating on the same database as the current stack machine, but + // operating on PREFIX. The new stack machine should have independent internal + // state. The new stack machine should begin executing instructions concurrent + // with the current stack machine through a language-appropriate mechanism. + StartThread => { + let prefix = self.pop_bytes().await; + debug!("start_thread {prefix:?}"); + let db = db.clone(); + self.threads.push( + thread::Builder::new() + .name(format!("{prefix:?}")) + .spawn(move || { + let mut sm = StackMachine::new(&db, prefix.clone()); + futures::executor::block_on(sm.run(db)).unwrap(); + sm.join(); + debug!("thread {prefix:?} exit"); + }) + .unwrap(), + ); + } + + // Pops the top item off of the stack as PREFIX. Blocks execution until the + // range with prefix PREFIX is not present in the database. This should be + // implemented as a polling loop inside of a language- and binding-appropriate + // retryable construct which synthesizes FoundationDB error 1020 when the range + // is not empty. Pushes the string "WAITED_FOR_EMPTY" onto the stack when + // complete. + WaitEmpty => { + let prefix = self.pop_bytes().await; + debug!("wait_empty {prefix:?}"); + let (begin, end) = range(prefix); + + async fn wait_for_empty( + trx: &Transaction, + begin: &[u8], + end: &[u8], + ) -> FdbResult<()> { + let range = trx + .get_range(&RangeOption::from((begin, end)), 1, false) + .await?; + + debug!("wait_empty {:?} range {}", Bytes::from(begin), range.len()); + if !range.is_empty() { + return Err(FdbError::from_code(1020)); + } + Ok(()) + } + let r = db + .transact_boxed_local( + (begin, end), + |trx, (begin, end)| wait_for_empty(trx, begin, end).boxed_local(), + TransactOption::default(), + ) + .await; + self.check(number, r)?; + self.push(number, WAITED_FOR_EMPTY.clone().into_owned()); + } + + UnitTests => { + async fn unit_test(db: &fdb::Database, tr: &mut fdb::Transaction) -> FdbResult<()> { + db.set_option(DatabaseOption::LocationCacheSize(100_001))?; + db.set_option(DatabaseOption::MaxWatches(100_001))?; + db.set_option(DatabaseOption::DatacenterId("dc_id".to_string()))?; + db.set_option(DatabaseOption::MachineId("machine_id".to_string()))?; + db.set_option(DatabaseOption::TransactionTimeout(100_000))?; + db.set_option(DatabaseOption::TransactionTimeout(0))?; + db.set_option(DatabaseOption::TransactionTimeout(0))?; + db.set_option(DatabaseOption::TransactionMaxRetryDelay(100))?; + db.set_option(DatabaseOption::TransactionRetryLimit(10))?; + db.set_option(DatabaseOption::TransactionRetryLimit(-1))?; + db.set_option(DatabaseOption::SnapshotRywEnable)?; + db.set_option(DatabaseOption::SnapshotRywDisable)?; + + tr.set_option(TransactionOption::PrioritySystemImmediate)?; + tr.set_option(TransactionOption::PriorityBatch)?; + tr.set_option(TransactionOption::CausalReadRisky)?; + tr.set_option(TransactionOption::CausalWriteRisky)?; + tr.set_option(TransactionOption::ReadYourWritesDisable)?; + tr.set_option(TransactionOption::ReadSystemKeys)?; + tr.set_option(TransactionOption::AccessSystemKeys)?; + tr.set_option(TransactionOption::Timeout(60 * 1000))?; + tr.set_option(TransactionOption::RetryLimit(50))?; + tr.set_option(TransactionOption::MaxRetryDelay(100))?; + tr.set_option(TransactionOption::UsedDuringCommitProtectionDisable)?; + tr.set_option(TransactionOption::DebugTransactionIdentifier( + "my_transaction".to_string(), + ))?; + tr.set_option(TransactionOption::LogTransaction)?; + tr.set_option(TransactionOption::ReadLockAware)?; + tr.set_option(TransactionOption::LockAware)?; + + tr.get(b"\xff", false).await?; + + Ok(()) + } + let r = unit_test(&db, trx.as_mut()).await; + self.check(number, r)?; + + // TODO + // test_cancellation(db) + // test_retry_limits(db) + // test_db_retry_limits(db) + // test_timeouts(db) + // test_db_timeouts(db) + // test_combinations(db) + // test_locality(db) + // test_predicates() + } + + // Pop 1 tuple off the stack as [path]. Pop 1 additional item as [raw_prefix]. + // Create a subspace with path as the prefix tuple and the specified + // raw_prefix. Append it to the directory list. + DirectoryCreateSubspace => { + let tuple_prefix = self.pop_string_tuple(1).await; + let raw_prefix = self.pop_bytes().await; + let subspace = Subspace::from_bytes(raw_prefix.into_owned()) + .subspace(tuple_prefix.first().unwrap()); + debug!( + "pushing a new subspace {:?} at index {}", + subspace, + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::Subspace(subspace)); + } + + // Pop 3 items off the stack as [index1, index2, allow_manual_prefixes]. Let + // node_subspace be the object in the directory list at index1 and + // content_subspace be the object in the directory list at index2. Create a new + // directory layer with the specified node_subspace and content_subspace. If + // allow_manual_prefixes is 1, then enable manual prefixes on the directory + // layer. Append the resulting directory layer to the directory list. + // + // If either of the two specified subspaces are null, then do not create a + // directory layer and instead push null onto the directory list. + DirectoryCreateLayer => { + let index_node_subspace = self.pop_usize().await; + let index_content_subspace = self.pop_usize().await; + let allow_manual_prefixes = self.pop_i64().await == 1; + + let node_subspace = self.directory_stack.get(index_node_subspace); + let content_subspace = self.directory_stack.get(index_content_subspace); + + match (node_subspace, content_subspace) { + (Some(node_subspace), Some(content_subspace)) => { + let node_subspace = match node_subspace { + DirectoryStackItem::Subspace(s) => s.to_owned(), + _ => panic!("expecting subspace"), + }; + + let content_subspace = match content_subspace { + DirectoryStackItem::Subspace(s) => s.to_owned(), + _ => panic!("expecting subspace"), + }; + + debug!("pushed a directory at index {}", self.directory_stack.len()); + + self.directory_stack.push(DirectoryStackItem::Directory( + DirectoryLayer::new( + node_subspace, + content_subspace, + allow_manual_prefixes, + ), + )); + } + (node_subspace, content_subspace) => { + error!( + "pushing null on the directory list: {}, {}", + node_subspace.is_some(), + content_subspace.is_some() + ); + self.directory_stack.push(DirectoryStackItem::Null); + } + } + } + + // Pop 1 tuple off the stack as [path]. Pop 2 additional items as + // [layer, prefix]. create a directory with the specified path, layer, + // and prefix. If either of layer or prefix is null, use the default value for + // that parameter (layer='', prefix=null). + DirectoryCreate => { + let path = self.pop_string_tuple(1).await; + let layer = self.pop_optional_bytes().await; + let prefix = self.pop_optional_bytes().await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + debug!( + "creating path {:?} with layer {:?} and prefix {:?} using directory at index {}", + path.first().unwrap(), + layer, + prefix, + self.directory_index, + ); + + match directory_operation( + &db, + &trx, + is_db, + (directory, path, prefix, layer), + |txn, (directory, path, prefix, layer)| { + directory + .create( + txn, + path.first().unwrap(), + prefix.as_deref(), + layer.as_deref(), + ) + .boxed_local() + }, + ) + .await + { + Ok(directory_subspace) => { + debug!( + "pushing DirectoryOutput at index {}", + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::DirectoryOutput(directory_subspace)); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Pop 1 tuple off the stack as [path]. Pop 1 additional item as [layer]. Open + // a directory with the specified path and layer. If layer is null, use the + // default value (layer=''). + DirectoryOpen => { + let path = self.pop_string_tuple(1).await; + let bytes_layer = self.pop_bytes().await; + + let layer = if bytes_layer.is_empty() { + None + } else { + Some(bytes_layer.to_vec()) + }; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + debug!( + "opening path {:?} with layer {:?} with index {}", + path.first().unwrap(), + layer, + self.directory_index + ); + + match directory_operation( + &db, + &trx, + is_db, + (directory, path, layer), + |txn, (directory, path, layer)| { + directory + .open(txn, path.first().unwrap(), layer.as_deref()) + .boxed_local() + }, + ) + .await + { + Ok(directory_subspace) => { + debug!( + "pushing newly opened DirectoryOutput at index {}", + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::DirectoryOutput(directory_subspace)); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 tuple off the stack as [path]. Pop 1 additional item as [layer]. Open + // a directory with the specified path and layer. If layer is null, use the + // default value (layer=''). + DirectoryCreateOrOpen => { + let path = self.pop_string_tuple(1).await; + let bytes_layer = self.pop_bytes().await; + + let layer = if bytes_layer.is_empty() { + None + } else { + Some(bytes_layer.to_vec()) + }; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + debug!( + "create_or_open path {:?} with layer {:?} with index {}", + path.first().unwrap(), + layer, + self.directory_index + ); + match directory_operation( + &db, + &trx, + is_db, + (directory, path, layer), + |txn, (directory, path, layer)| { + directory + .create_or_open(txn, path.first().unwrap(), None, layer.as_deref()) + .boxed_local() + }, + ) + .await + { + Ok(directory_subspace) => { + debug!( + "pushing created_or_opened {:?} at index {}", + directory_subspace, + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::DirectoryOutput(directory_subspace)); + } + Err(e) => self.push_directory_err(&instr.code, number, e), + }; + } + + // Pop the top item off the stack as [index]. Set the current directory list + // index to index. In the event that the directory at this new index is null + // (as the result of a previous error), set the directory list index to the + // error index. + DirectoryChange => { + self.directory_index = self.pop_usize().await; + debug!("setting directory_index to {}", self.directory_index); + match self.directory_stack.get(self.directory_index) { + None => { + self.directory_index = self.error_index; + debug!( + "setting directory_index to error index {}: no directory found", + self.directory_index + ); + } + Some(d) => { + if let DirectoryStackItem::Null = d { + self.directory_index = self.error_index; + debug!( + "setting directory_index to error index {}: because it is Null", + self.directory_index + ); + } + } + } + } + + // Pop the top item off the stack as [error_index]. Set the current error index + // to error_index. + DirectorySetErrorIndex => { + self.error_index = self.pop_usize().await; + } + + // Use the current directory for this operation. + // + // Pop 2 tuples off the stack as [old_path, new_path]. Call move with the + // specified old_path and new_path. Append the result onto the directory list. + DirectoryMove => { + let paths = self.pop_string_tuple(2).await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + debug!( + "moving {:?} to {:?} using directory at index {}", + paths.first().unwrap(), + paths.get(1).unwrap(), + self.directory_index + ); + + match directory_operation( + &db, + &trx, + is_db, + (directory, paths), + |txn, (directory, paths)| { + directory + .move_to(txn, paths.first().unwrap(), paths.get(1).unwrap()) + .boxed_local() + }, + ) + .await + { + Ok(s) => { + debug!( + "pushing moved directory {:?} at index {}", + s, + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::DirectoryOutput(s)); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 tuple off the stack as [new_absolute_path]. Call moveTo with the + // specified new_absolute_path. Append the result onto the directory list. + DirectoryMoveTo => { + let paths = self.pop_string_tuple(1).await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + debug!("moving directory {:?} to {:?}", self.directory_index, paths); + + match directory_operation( + &db, + &trx, + is_db, + (directory, paths), + |txn, (directory, paths)| { + directory + .move_directory(txn, paths.first().expect("popped tuple has no item")) + .boxed_local() + }, + ) + .await + { + Ok(s) => { + debug!( + "pushing moved directory {:?} at index {}", + s, + self.directory_stack.len() + ); + self.directory_stack + .push(DirectoryStackItem::DirectoryOutput(s)); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [count] (either 0 or 1). If count is 1, pop 1 + // tuple off the stack as [path]. Call remove_if_exits, passing it path if one + // was popped. + DirectoryRemove => { + let count = self.pop_usize().await; + let paths = self.pop_string_tuple(count).await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + let paths = paths.first().expect("could not retrieve a path"); + debug!( + "removing path {:?} using directory at index {}", + paths, self.directory_index + ); + match directory_operation( + &db, + &trx, + is_db, + (directory, paths), + |txn, (directory, paths)| { + async move { + if directory.remove(txn, paths).await? { + Ok(()) + } else { + Err(DirectoryError::DirectoryDoesNotExists) + } + } + .boxed_local() + }, + ) + .await + { + Ok(()) => {} + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + } + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [count] (either 0 or 1). If count is 1, pop 1 + // tuple off the stack as [path]. Call remove_if_exits, passing it path if one + // was popped. + DirectoryRemoveIfExists => { + let count = self.pop_usize().await; + let paths = self.pop_string_tuple(count).await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + let paths = paths.first().expect("could not retrieve a path"); + match directory_operation( + &db, + &trx, + is_db, + (directory, paths), + |txn, (directory, paths)| directory.remove_if_exists(txn, paths).boxed_local(), + ) + .await + { + Ok(_) => {} + Err(err) => self.push_directory_err(&instr.code, number, err), + }; + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [count] (either 0 or 1). If count is 1, pop 1 + // tuple off the stack as [path]. Call list, passing it path if one was popped. + // Pack the resulting list of directories using the tuple layer and push the + // packed string onto the stack. + DirectoryList => { + let count = self.pop_usize().await; + let paths = match count { + 1 => { + let paths = self.pop_string_tuple(count).await; + paths.first().expect("could not retrieve a path").clone() + } + 0 => vec![], + _ => panic!(), + }; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + match directory_operation( + &db, + &trx, + is_db, + (directory, &paths), + |txn, (directory, paths)| directory.list(txn, paths).boxed_local(), + ) + .await + { + Ok(children) => { + let mut elements: Vec = vec![]; + debug!( + "found {} items under path {:?} with directory at index {}:", + children.len(), + paths, + self.directory_index + ); + for child in children { + debug!("\t - {}", child); + let element = Element::String(Cow::from(child)); + elements.push(element); + } + let tuple = Element::Tuple(elements); + self.push(number, Element::Bytes(Bytes::from(tuple.pack_to_vec()))); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [count] (either 0 or 1). If count is 1, pop 1 + // tuple off the stack as [path]. Call exists, passing it path if one + // was popped. Push 1 onto the stack if the path exists and 0 if it does not. + DirectoryExists => { + let count = self.pop_usize().await; + + let paths = self.pop_string_tuple(count).await; + + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + let paths = paths.first().expect("could not retrieve a path"); + match directory_operation( + &db, + &trx, + is_db, + (directory, paths), + |txn, (directory, paths)| directory.exists(txn, paths).boxed_local(), + ) + .await + { + Ok(exists) => { + self.push(number, Element::Int(i64::from(exists))); + } + Err(e) => { + self.push_directory_err(&instr.code, number, e); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 tuple off the stack as [key_tuple]. Pack key_tuple and push the result + // onto the stack. + DirectoryPackKey => { + let n: usize = self.pop_usize().await; + debug!("DirectoryPackKey {n}"); + let mut buf = Vec::with_capacity(n); + for _ in 0..n { + let element: Element = self.pop_element().await; + debug!(" - {element:?}"); + buf.push(element); + } + + let tuple = Element::Tuple(buf); + + match self.pack_with_current_subspace(&tuple) { + None => self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from("cannot pack with current item")), + ), + Some(bytes) => { + self.push(number, Element::Bytes(bytes.into())); + } + } + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [key]. Unpack key and push the resulting tuple + // onto the stack one item at a time. + DirectoryUnpackKey => { + let data = self.pop_bytes().await; + let data = data.to_vec(); + debug!("directory_unpack {data:?}"); + match self.unpack_with_current_subspace(&data) { + None => self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from( + "Cannot unpack with Directory Partition", + )), + ), + Some(packed) => { + let data: Vec = packed.unwrap(); + for element in data { + debug!(" - {element:?}"); + self.push(number, Element::Tuple(vec![element.into_owned()])); + } + } + } + } + + // Use the current directory for this operation. + // + // Pop 1 tuple off the stack as [tuple]. Create a range using tuple and push + // range.begin and range.end onto the stack. + DirectoryRange => { + let n: usize = self.pop_usize().await; + let mut buf = Vec::with_capacity(n); + for _ in 0..n { + let element: Element = self.pop_element().await; + debug!(" - {element:?}"); + buf.push(element); + } + + match self.get_current_directory_item() { + Some(DirectoryStackItem::DirectoryOutput( + DirectoryOutput::DirectoryPartition(_d), + )) => { + self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from( + "operation not allowed on directoryPartition", + )), + ); + } + _ => { + let tuple = Element::Tuple(buf); + match self.subspace_with_current_item(&tuple) { + None => { + self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from( + "operation not allowed on directoryPartition", + )), + ); + } + Some(subspace) => { + let (begin_range, end_range) = subspace.range(); + self.push(number, Element::Bytes(begin_range.into())); + self.push(number, Element::Bytes(end_range.into())); + } + } + } + } + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [key]. Check if the current directory contains + // the specified key. Push 1 if it does and 0 if it doesn't. + DirectoryContains => { + let raw_prefix = self.pop_bytes().await; + let maybe_contains = match self.get_current_directory_item() { + None => None, + Some(DirectoryStackItem::Subspace(s)) => Some(s.is_start_of(&raw_prefix)), + Some(DirectoryStackItem::DirectoryOutput( + DirectoryOutput::DirectorySubspace(s), + )) => Some(s.is_start_of(&raw_prefix)), + _ => None, + }; + match maybe_contains { + None => self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from("cannot perform contains")), + ), + Some(bool) => self.push(number, Element::Int(bool as i64)), + } + } + + // Use the current directory for this operation. + // + // Pop 1 tuple off the stack as [tuple]. Open the subspace of the current + // directory specified by tuple and push it onto the directory list. + DirectoryOpenSubspace => { + let n: usize = self.pop_usize().await; + debug!("DirectoryRange {n}"); + let mut buf = Vec::with_capacity(n); + for _ in 0..n { + let element: Element = self.pop_element().await; + debug!(" - {element:?}"); + buf.push(element); + } + + let tuple = Element::Tuple(buf); + match self.subspace_with_current_item(&tuple) { + None => self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from("Cannot subspace with current item")), + ), + Some(s) => self.directory_stack.push(DirectoryStackItem::Subspace(s)), + } + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [prefix]. Let key equal + // prefix + tuple.pack([dir_index]). Set key to be the result of calling + // directory.key() in the current transaction. + DirectoryLogSubspace => { + debug!( + "logging subspace {}/{}", + self.directory_index, + self.directory_stack.len() + ); + let raw_prefix = self.pop_bytes().await; + let txn = match trx { + TransactionState::Transaction(ref t) => t, + _ => { + panic!("could not find an active transaction"); + } + }; + let key = Subspace::from_bytes(raw_prefix.into_owned()).pack(&self.directory_index); + + match self.directory_stack.get(self.directory_index) { + None => panic!("nothing in the stack"), + Some(DirectoryStackItem::Null) => panic!("Directory is NULL"), + Some(DirectoryStackItem::Directory(_)) => { + panic!("trying to get a subspace, got a Directory") + } + Some(DirectoryStackItem::DirectoryOutput( + DirectoryOutput::DirectorySubspace(d), + )) => { + txn.set(&key, d.bytes()); + debug!( + "logging subspace [{}] {:?}={:?}", + self.directory_index, + unpack::>(&key).unwrap(), + d.bytes(), + ); + } + Some(DirectoryStackItem::DirectoryOutput( + DirectoryOutput::DirectoryPartition(_), + )) => { + self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from( + "cannot get key for the root of a directory partition", + )), + ); + } + Some(DirectoryStackItem::Subspace(s)) => { + txn.set(&key, s.bytes()); + debug!( + "logging subspace [{}] {:?}={:?}", + self.directory_index, + unpack::>(&key).unwrap(), + s.bytes(), + ); + } + }; + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [raw_prefix]. Create a subspace log_subspace + // with path (dir_index) and the specified raw_prefix. Set: + // + // tr[log_subspace[u'path']] = the tuple packed path of the directory. + // + // tr[log_subspace[u'layer']] = the tuple packed layer of the directory. + // + // tr[log_subspace[u'exists']] = the packed tuple containing a 1 if the + // directory exists and 0 if it doesn't. + // + // tr[log_subspace[u'children']] the tuple packed list of children of the + // directory. + // + // Where log_subspace[u] is the subspace packed tuple containing only the + // single specified unicode string . + DirectoryLogDirectory => { + debug!("logging directory {}", self.directory_index); + let directory = self + .get_current_directory() + .expect("could not find a directory"); + + let txn = match trx { + TransactionState::Transaction(ref t) => t, + _ => { + panic!("could not find an active transaction"); + } + }; + + let raw_prefix = self.pop_bytes().await; + let subspace = + Subspace::from_bytes(raw_prefix.into_owned()).subspace(&self.directory_index); + + let key_path = subspace.pack(&"path"); + let value_path = pack(&self.get_path_for_current_directory().unwrap()); + + let key_layer = subspace.pack(&"layer"); + let value_layer = pack(&self.get_layer_for_current_directory().unwrap()); + + let exists = directory + .exists(txn, &[]) + .await + .expect("could not list directory"); + + let key_exists = subspace.pack(&"exists"); + let value_exists = pack(&Element::Tuple(vec![Element::Int(exists.into())])); + + let children = if exists { + directory.list(txn, &[]).await.unwrap() + } else { + vec![] + }; + + let tuple_children = Element::Tuple( + children + .iter() + .map(|s| Element::String(Cow::Owned(s.clone()))) + .collect(), + ); + + let key_children = subspace.pack(&"children"); + let value_children = pack(&tuple_children); + + txn.set(&key_path, &value_path); + debug!( + "[{}] {:?}={:?}", + self.directory_index, + unpack::>(&key_path).unwrap(), + unpack::>(&value_path).unwrap(), + ); + txn.set(&key_layer, &value_layer); + debug!( + "[{}] {:?}={:?}", + self.directory_index, + unpack::>(&key_layer).unwrap(), + unpack::>(&value_layer).unwrap(), + ); + txn.set(&key_exists, &value_exists); + debug!( + "[{}] {:?}={:?}", + self.directory_index, + unpack::>(&key_exists).unwrap(), + unpack::>(&value_exists).unwrap(), + ); + txn.set(&key_children, &value_children); + debug!( + "[{}] {:?}={:?}", + self.directory_index, + unpack::>(&key_children).unwrap(), + unpack::>(&value_children).unwrap(), + ); + } + + // Use the current directory for this operation. + // + // Pop 1 item off the stack as [byte_array]. Call .key() on the current + // subspace and store the result as [prefix]. Throw an error if the popped + // array does not start with prefix. Otherwise, remove the prefix from the + // popped array and push the result onto the stack. + DirectoryStripPrefix => match self.pop_optional_bytes().await { + None => { + self.push_directory_err( + &instr.code, + number, + DirectoryError::Other(String::from("bad input on bytes")), + ); + } + Some(raw_prefix) => match self.get_bytes_for_current_directory() { + None => self.push_directory_err( + &instr.code, + number, + DirectoryError::Version(String::from( + "cannot get bytes for DirectoryPartition", + )), + ), + Some(ssb) => { + if !raw_prefix.to_vec().starts_with(&ssb) { + self.push_directory_err( + &instr.code, + number, + DirectoryError::Version(String::from( + "String does not start with raw prefix", + )), + ); + } else { + self.push( + number, + Element::Bytes(Bytes::from(raw_prefix[ssb.len()..].to_owned())), + ); + } + } + }, + }, + } + + if is_db && pending { + let item = self.pop().await; + self.stack.push(item); + } + + if is_db && mutation { + let r = trx.take(0).commit().await.map_err(|e| e.into()); + self.check(number, r)?; + self.push(number, RESULT_NOT_PRESENT.clone().into_owned()); + } else if !self.transactions.contains_key(&self.cur_transaction) { + self.transactions.insert(self.cur_transaction.clone(), trx); + } + + if instr.has_flags() { + panic!("flag not handled for instr: {instr:?}"); + } + + Ok(()) + } + + async fn run(&mut self, db: Arc) -> FdbResult<()> { + info!("Fetching instructions..."); + let instrs = self.fetch_instr(&db.create_trx()?).await?; + info!("{} instructions found", instrs.len()); + + for (i, instr) in instrs.into_iter().enumerate() { + trace!("{}/{}, {:?}", i, self.stack.len(), instr); + let _ = self.run_step(db.clone(), i, instr).await; + } + + Ok(()) + } + + fn join(&mut self) { + for handle in self.threads.drain(0..) { + handle.join().expect("joined thread to not panic"); + } + } + + fn get_current_directory_item(&mut self) -> Option<&DirectoryStackItem> { + self.directory_stack.get(self.directory_index) + } + + fn get_current_directory(&mut self) -> Option> { + match self.get_current_directory_item() { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::Directory(d) => Some(Box::new(d.clone())), + DirectoryStackItem::DirectoryOutput(d) => Some(Box::new((*d).clone())), + _ => None, + }, + } + } + + fn pack_with_current_subspace(&self, v: &T) -> Option> { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::DirectoryOutput(DirectoryOutput::DirectorySubspace(d)) => { + Some(d.pack(v)) + } + DirectoryStackItem::Subspace(d) => Some(d.pack(v)), + _ => None, + }, + } + } + + // It can return a None if it is a DirectoryPartition + fn unpack_with_current_subspace<'de, T: TupleUnpack<'de>>( + &self, + key: &'de [u8], + ) -> Option> { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::DirectoryOutput(d) => match d { + DirectoryOutput::DirectorySubspace(directory_subspace) => { + Some(directory_subspace.unpack(key)) + } + DirectoryOutput::DirectoryPartition(_) => None, + }, + DirectoryStackItem::Subspace(d) => Some(d.unpack(key)), + _ => None, + }, + } + } + + // It can return a None if it is a DirectoryPartition + fn subspace_with_current_item(&self, t: &T) -> Option { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::DirectoryOutput(d) => match d { + DirectoryOutput::DirectorySubspace(s) => Some(s.subspace(t)), + DirectoryOutput::DirectoryPartition(_) => None, + }, + DirectoryStackItem::Subspace(d) => Some(d.subspace(t)), + _ => None, + }, + } + } + + fn get_path_for_current_directory(&self) -> Option<&[String]> { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::Directory(d) => Some(d.get_path()), + DirectoryStackItem::DirectoryOutput(d) => Some(d.get_path()), + _ => None, + }, + } + } + + fn get_layer_for_current_directory(&self) -> Option<&[u8]> { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::DirectoryOutput(d) => Some(d.get_layer()), + DirectoryStackItem::Directory(_) => Some(&[]), + _ => None, + }, + } + } + + fn get_bytes_for_current_directory(&self) -> Option> { + match self.directory_stack.get(self.directory_index) { + None => None, + Some(directory_or_subspace) => match directory_or_subspace { + DirectoryStackItem::DirectoryOutput(d) => match d { + DirectoryOutput::DirectorySubspace(s) => Some(Vec::from(s.bytes())), + DirectoryOutput::DirectoryPartition(_) => None, + }, + DirectoryStackItem::Subspace(subspace) => Some(Vec::from(subspace.bytes())), + _ => None, + }, + } + } +} + +fn main() { + let now = std::time::Instant::now(); + env_logger::Builder::from_default_env() + .format(move |buf, record| { + let current_thread = thread::current(); + let thread_name = current_thread.name().unwrap_or("?"); + writeln!( + buf, + "{} {} {} - {}", + (std::time::Instant::now() - now).as_millis(), + thread_name, + record.level(), + record.args() + ) + }) + .format_timestamp_millis() + .target(env_logger::Target::Stdout) + .init(); + + let args = std::env::args().collect::>(); + let prefix = &args[1]; + + let cluster_path = if args.len() > 3 { + Some(args[3].as_str()) + } else { + None + }; + + let api_version = args[2].parse::().expect("failed to parse api version"); + + info!("Starting rust bindingtester with api_version {api_version}"); + api::FdbApiBuilder::default() + .set_runtime_version(api_version) + .build() + .expect("failed to initialize FoundationDB API") + .boot() + .expect("failed to start the FoundationDB network"); + + let db = Arc::new( + futures::executor::block_on(fdb::Database::new_compat(cluster_path)) + .expect("failed to get database"), + ); + + let mut sm = StackMachine::new(&db, Bytes::from(prefix.to_owned().into_bytes())); + futures::executor::block_on(sm.run(db)).unwrap(); + sm.join(); + + info!("Closing..."); + + info!("Done."); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore = "requires a running FoundationDB cluster"] + fn on_error_restores_transaction_before_next_instruction() { + futures::executor::block_on(async { + let db = Arc::new(Database::new_compat(None).await.expect("cannot open fdb")); + let mut sm = StackMachine::new(&db, Bytes::from(b"on-error-reuse".as_slice())); + let key = b"on-error-reuse".as_slice(); + let instructions = [ + pack(&("NEW_TRANSACTION",)), + pack(&("PUSH", 1020)), + pack(&("ON_ERROR",)), + pack(&("GET_READ_VERSION",)), + pack(&("PUSH", 2000)), + pack(&("ON_ERROR",)), + pack(&("WAIT_FUTURE",)), + pack(&("PUSH", key)), + pack(&("GET",)), + pack(&("WAIT_FUTURE",)), + pack(&("RESET",)), + pack(&("GET_READ_VERSION",)), + ]; + + for (number, instruction) in instructions.iter().enumerate() { + sm.run_step(db.clone(), number, Instr::from(instruction)) + .await + .expect("instruction failed"); + } + + let error = Element::Bytes(pack(&(b"ERROR".as_slice(), b"2000".as_slice())).into()); + let cancelled = Element::Bytes(pack(&(b"ERROR".as_slice(), b"1025".as_slice())).into()); + assert_eq!(sm.stack.len(), 5); + assert_eq!(sm.stack[0].data.as_ref(), Some(&RESULT_NOT_PRESENT)); + assert_eq!(sm.stack[1].data.as_ref(), Some(&GOT_READ_VERSION)); + assert_eq!(sm.stack[2].data.as_ref(), Some(&error)); + // A failed on_error cancels the native transaction at API >= 610. + // A replacement transaction or implicit reset would hide this state. + assert_eq!(sm.stack[3].data.as_ref(), Some(&cancelled)); + assert_eq!(sm.stack[4].data.as_ref(), Some(&GOT_READ_VERSION)); + assert!(sm.last_version > 0); + }); + } +} diff --git a/bindings/rust/foundationdb-gen/CHANGELOG.md b/bindings/rust/foundationdb-gen/CHANGELOG.md new file mode 100644 index 00000000000..4bb4b01fad1 --- /dev/null +++ b/bindings/rust/foundationdb-gen/CHANGELOG.md @@ -0,0 +1,162 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.11.0] - 2026-06-24 + +### 🚀 Features + +- Feat: migrate workspace to Rust 2024 edition (#255) + +### ⚙️ Miscellaneous + +- Chore: align nix toolchain with CI stable (Rust 1.96) and fix 1.96 clippy lints + +[0.11.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.10.0..0.11.0 + + +## [0.10.0] - 2025-10-24 + +### ⚠️ BREAKING CHANGES + +- Feat: add compile errors for missing version features + +### 🚀 Features + +- Add support 7.4 + +### ⚙️ Miscellaneous + +- Bump MSRV to 1.82 +- Clippy --fix +- Bump msrv to 1.85 + +[0.10.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.2..0.10.0 + + +## [0.9.2] - 2025-01-06 + +### 🆙 Bump + +- Bump xml-rs from 0.8.22 to 0.8.24 + +[0.9.2]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.1..0.9.2 + +## [0.9.1] - 2024-10-08 + +### 🆙 Bump + +- Bump MSRV to 1.71.1 +- Bump all dependencies and setup dependabot + +### ⚙️ Other changes + +- Generate CHANGELOG per crate + +[0.9.1]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.0..0.9.1 + +## [0.9.0] - 2024-03-15 + +### 🚀 added + +- Add support for 730 +- Bump fdb crate to 0.9.0 and sim to 0.2.0 + +### 🆙 Bump + +- Chore: bump rust to 1.57 +- Bump msrv +- Bump crates version +- Bump msrv for bindgen and env_logger +- Bump dependencies + +### ⚙️ Other changes + +- Build on docs.rs with the required features +- Prepare release +- Derive `Default` for `FdbOptionTy`. +- Cargo fmt +- Removing default fdb version + +## [0.7.0] - 2022-07-11 + +### 🚀 added + +- Add support for api 630 +- Bump crate version, maintainers and documentation +- Bump rust edition to 2021 +- Add support for api 700 +- Initial commit to support fdb 7.1 + +### 🐛 Bug Fixes + +- Fix integer cast possible undefined behavior +- Fix missing import +- Fix build for 620 +- Fix options.rs generation with bindings 610/620 +- Fix build with API 620 +- Fix Cargo.toml repository links +- Fix #170: protect boot from undefined behavior +- Cargo clippy fixes +- Include fdb.options for 630 + +### 🚜 Refactor + +- Remove Inflector dependency +- Remove failure dependency +- Remove clikengo link in Cargo.toml + +### 🆙 Bump + +- Bump version to 0.4.1 +- Bump version to 5.0.1 +- Bump deps + +### ⚙️ Other changes + +- Core: auto-generate all code at build time +- Cleanup the fdb version features from primary crate (#99) +- Rust edition 2018 +- Async/await: complete rewrite of foundationdb +- Renamed foundationdb_sys import from fdb to fdb_sys for clarity +- Cleanup dependencies and rename Error as FdbError +- Embed 6.2.10 fdb_c.h and fdb.options +- Embed all supported version includes +- Default to locally available foundationdb include +- Move include inside crate folders +- Foundationdb-gen/sys README +- Cargo.toml badges & docs.rs metadata +- Use `#[non_exhaustive]` on RangeOption and generated enums +- Update fdb 620 includes to 6.2.20 +- Update fdb 610 includes to 6.1.13 +- Default to foundation 620 api +- Removing reference to master branch +- Prepare for 0.7.0 + +## [0.3.0] - 2018-11-16 + +### 🚀 added + +- Add `Transaction::atomic_op`, #15 +- Add `Transaction::get_range`, #14 + +### 🐛 Bug Fixes + +- Fix stable compilation error + +### ⚙️ Other changes + +- Generate options types from fdb.options +- Use consts from sys crate +- Allow the association of other errors to FdbError +- Prepare 0.3 release + +[unreleased]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.9.0..HEAD +[0.9.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.7.0..v0.9.0 +[0.7.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.3.0..v0.7.0 +[0.3.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2..v0.3.0 + + diff --git a/bindings/rust/foundationdb-gen/Cargo.toml b/bindings/rust/foundationdb-gen/Cargo.toml new file mode 100644 index 00000000000..f9380fe7eac --- /dev/null +++ b/bindings/rust/foundationdb-gen/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "foundationdb-gen" +version = "0.11.0" +authors = [ + "Jihyun Yu ", + "Vincent Rouillé ", + "Pierre Zemb " +] +edition = "2024" +rust-version = "1.85.1" + +description = """ +Binding generation helper for FoundationDB. +""" + +documentation = "https://docs.rs/foundationdb" +repository = "https://github.com/apple/foundationdb" +license = "MIT/Apache-2.0" + +readme = "README.md" +keywords = ["foundationdb", "kv"] +categories = ["database"] + +[package.metadata.docs.rs] +features = ["embedded-fdb-include", "fdb-7_4"] + +[features] +default = [] +# Use the locally embedded foundationdb fdb.options file +embedded-fdb-include = [] +fdb-5_1 = [] +fdb-5_2 = [] +fdb-6_0 = [] +fdb-6_1 = [] +fdb-6_2 = [] +fdb-6_3 = [] +fdb-7_0 = [] +fdb-7_1 = [] +fdb-7_3 = [] +fdb-7_4 = [] + +[dependencies] +xml-rs = "1.0.0" diff --git a/bindings/rust/foundationdb-gen/README.md b/bindings/rust/foundationdb-gen/README.md new file mode 100644 index 00000000000..019bac6d8ee --- /dev/null +++ b/bindings/rust/foundationdb-gen/README.md @@ -0,0 +1,19 @@ +# FoundationDB generator + +This is a helper for generating the FoundationDB bindings used by the [foundationdb](https://crates.io/crates/foundationdb) crate. + +## License + +Licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/bindings/rust/foundationdb-gen/build.rs b/bindings/rust/foundationdb-gen/build.rs new file mode 100644 index 00000000000..eb649c34c98 --- /dev/null +++ b/bindings/rust/foundationdb-gen/build.rs @@ -0,0 +1,69 @@ +use std::{env, fs, path::PathBuf}; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "linux"))] +const OPTIONS_FILE: &str = "/usr/include/foundationdb/fdb.options"; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "macos"))] +const OPTIONS_FILE: &str = "/usr/local/include/foundationdb/fdb.options"; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "windows"))] +const OPTIONS_FILE: &str = "C:/Program Files/foundationdb/include/foundationdb/fdb.options"; + +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-5_1"))] +const OPTIONS_FILE: &str = "include/510/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-5_2"))] +const OPTIONS_FILE: &str = "include/520/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_0"))] +const OPTIONS_FILE: &str = "include/600/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_1"))] +const OPTIONS_FILE: &str = "include/610/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_2"))] +const OPTIONS_FILE: &str = "include/620/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_3"))] +const OPTIONS_FILE: &str = "include/630/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_0"))] +const OPTIONS_FILE: &str = "include/700/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_1"))] +const OPTIONS_FILE: &str = "include/710/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_3"))] +const OPTIONS_FILE: &str = "include/730/fdb.options"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_4"))] +const OPTIONS_FILE: &str = "include/740/fdb.options"; + +// Compile error when no version feature is specified +#[cfg(not(any( + feature = "fdb-5_1", + feature = "fdb-5_2", + feature = "fdb-6_0", + feature = "fdb-6_1", + feature = "fdb-6_2", + feature = "fdb-6_3", + feature = "fdb-7_0", + feature = "fdb-7_1", + feature = "fdb-7_3", + feature = "fdb-7_4", +)))] +compile_error!( + "foundationdb-gen requires a version feature to be specified.\n\ + \n\ + Available version features: fdb-5_1, fdb-5_2, fdb-6_0, fdb-6_1, fdb-6_2, fdb-6_3, fdb-7_0, fdb-7_1, fdb-7_3, fdb-7_4\n\ + \n\ + Examples:\n\ + - With embedded include: features = [\"embedded-fdb-include\", \"fdb-7_4\"]\n\ + - With system install: features = [\"fdb-7_4\"]" +); + +fn main() { + println!("cargo:rerun-if-env-changed=FDB_OPTIONS_FILE"); + println!("cargo:rerun-if-env-changed=FDB_INCLUDE_DIR"); + let options_file = env::var_os("FDB_OPTIONS_FILE") + .map(PathBuf::from) + .or_else(|| { + env::var_os("FDB_INCLUDE_DIR").map(|path| PathBuf::from(path).join("fdb.options")) + }) + .unwrap_or_else(|| PathBuf::from(OPTIONS_FILE)); + println!("cargo:rerun-if-changed={}", options_file.display()); + let out_path = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is undefined!")); + fs::copy(&options_file, out_path.join("fdb.options")) + .unwrap_or_else(|err| panic!("couldn't read {}: {err}", options_file.display())); +} diff --git a/bindings/rust/foundationdb-gen/include/510/fdb.options b/bindings/rust/foundationdb-gen/include/510/fdb.options new file mode 100644 index 00000000000..9f283337ac2 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/510/fdb.options @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/510/version.txt b/bindings/rust/foundationdb-gen/include/510/version.txt new file mode 100644 index 00000000000..693ad745af3 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/510/version.txt @@ -0,0 +1 @@ +5.1.7 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/520/fdb.options b/bindings/rust/foundationdb-gen/include/520/fdb.options new file mode 100644 index 00000000000..467e0a1adcb --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/520/fdb.options @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/520/version.txt b/bindings/rust/foundationdb-gen/include/520/version.txt new file mode 100644 index 00000000000..e60200b54f4 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/520/version.txt @@ -0,0 +1 @@ +5.2.5 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/600/fdb.options b/bindings/rust/foundationdb-gen/include/600/fdb.options new file mode 100644 index 00000000000..89b07c125a1 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/600/fdb.options @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/600/version.txt b/bindings/rust/foundationdb-gen/include/600/version.txt new file mode 100644 index 00000000000..e23d7a6e234 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/600/version.txt @@ -0,0 +1 @@ +6.0.18 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/610/fdb.options b/bindings/rust/foundationdb-gen/include/610/fdb.options new file mode 100644 index 00000000000..cba06df111b --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/610/fdb.options @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/610/version.txt b/bindings/rust/foundationdb-gen/include/610/version.txt new file mode 100644 index 00000000000..4b5ef77d17c --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/610/version.txt @@ -0,0 +1 @@ +6.1.13 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/620/fdb.options b/bindings/rust/foundationdb-gen/include/620/fdb.options new file mode 100644 index 00000000000..2308132734f --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/620/fdb.options @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/620/version.txt b/bindings/rust/foundationdb-gen/include/620/version.txt new file mode 100644 index 00000000000..1a87478a3f4 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/620/version.txt @@ -0,0 +1 @@ +6.2.20 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/630/fdb.options b/bindings/rust/foundationdb-gen/include/630/fdb.options new file mode 100644 index 00000000000..837a2f27c86 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/630/fdb.options @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/630/version.txt b/bindings/rust/foundationdb-gen/include/630/version.txt new file mode 100644 index 00000000000..2712a2c0a05 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/630/version.txt @@ -0,0 +1 @@ +6.3.12 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/700/fdb.options b/bindings/rust/foundationdb-gen/include/700/fdb.options new file mode 100644 index 00000000000..7eb035bb96e --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/700/fdb.options @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/700/version.txt b/bindings/rust/foundationdb-gen/include/700/version.txt new file mode 100644 index 00000000000..4122521804f --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/700/version.txt @@ -0,0 +1 @@ +7.0.0 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/710/fdb.options b/bindings/rust/foundationdb-gen/include/710/fdb.options new file mode 100644 index 00000000000..9f2f9e52f93 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/710/fdb.options @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/710/version.txt b/bindings/rust/foundationdb-gen/include/710/version.txt new file mode 100644 index 00000000000..a52e7a4eb7e --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/710/version.txt @@ -0,0 +1 @@ +7.1.5 \ No newline at end of file diff --git a/bindings/rust/foundationdb-gen/include/730/fdb.options b/bindings/rust/foundationdb-gen/include/730/fdb.options new file mode 100644 index 00000000000..787b1d27cf2 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/730/fdb.options @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/730/version.txt b/bindings/rust/foundationdb-gen/include/730/version.txt new file mode 100644 index 00000000000..aec35f3a894 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/730/version.txt @@ -0,0 +1 @@ +7.3.27 diff --git a/bindings/rust/foundationdb-gen/include/740/fdb.options b/bindings/rust/foundationdb-gen/include/740/fdb.options new file mode 100644 index 00000000000..72462ce327e --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/740/fdb.options @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/rust/foundationdb-gen/include/740/version.txt b/bindings/rust/foundationdb-gen/include/740/version.txt new file mode 100644 index 00000000000..815da58b7a9 --- /dev/null +++ b/bindings/rust/foundationdb-gen/include/740/version.txt @@ -0,0 +1 @@ +7.4.1 diff --git a/bindings/rust/foundationdb-gen/src/bin/foundationdb-options-gen.rs b/bindings/rust/foundationdb-gen/src/bin/foundationdb-options-gen.rs new file mode 100644 index 00000000000..381cf63164d --- /dev/null +++ b/bindings/rust/foundationdb-gen/src/bin/foundationdb-options-gen.rs @@ -0,0 +1,7 @@ +extern crate foundationdb_gen; + +fn main() { + let mut code = String::new(); + foundationdb_gen::emit(&mut code).expect("couldn't generate options.rs code!"); + println!("{code}"); +} diff --git a/bindings/rust/foundationdb-gen/src/lib.rs b/bindings/rust/foundationdb-gen/src/lib.rs new file mode 100644 index 00000000000..442e7faa185 --- /dev/null +++ b/bindings/rust/foundationdb-gen/src/lib.rs @@ -0,0 +1,377 @@ +extern crate xml; + +use std::fmt; +use xml::attribute::OwnedAttribute; +use xml::reader::{EventReader, XmlEvent}; + +const TAB1: &str = " "; +const TAB2: &str = " "; +const TAB3: &str = " "; +const TAB4: &str = " "; + +#[derive(Debug)] +struct FdbScope { + name: String, + options: Vec, +} +impl FdbScope { + fn gen_ty(&self, w: &mut W) -> fmt::Result { + let with_ty = self.with_ty(); + + if with_ty { + writeln!(w, "#[derive(Clone, Debug)]")?; + } else { + writeln!(w, "#[derive(Clone, Copy, Debug)]")?; + } + writeln!(w, "#[non_exhaustive]")?; + writeln!(w, "pub enum {name} {{", name = self.name)?; + + let with_ty = self.with_ty(); + for option in self.options.iter() { + option.gen_ty(w, with_ty)?; + } + writeln!(w, "}}") + } + + fn gen_impl(&self, w: &mut W) -> fmt::Result { + writeln!(w, "impl {name} {{", name = self.name)?; + self.gen_code(w)?; + self.gen_apply(w)?; + writeln!(w, "}}") + } + + fn gen_code(&self, w: &mut W) -> fmt::Result { + writeln!( + w, + "{t}pub fn code(&self) -> fdb_sys::FDB{name} {{", + t = TAB1, + name = self.name, + )?; + writeln!(w, "{TAB2}match *self {{")?; + + let enum_prefix = self.c_enum_prefix(); + let with_ty = self.with_ty(); + + for option in self.options.iter() { + writeln!( + w, + "{t}{scope}::{name}{param} => fdb_sys::{enum_prefix}{code},", + t = TAB3, + scope = self.name, + name = option.name, + param = if let (true, Some(..)) = (with_ty, option.get_ty()) { + "(..)" + } else { + "" + }, + enum_prefix = enum_prefix, + code = option.c_name, + )?; + } + + writeln!(w, "{TAB2}}}")?; + writeln!(w, "{TAB1}}}") + } + + fn gen_apply(&self, w: &mut W) -> fmt::Result { + let fn_name = match self.apply_fn_name() { + Some(name) => name, + _ => return Ok(()), + }; + + let first_arg = match self.apply_arg_name() { + Some(name) => format!(", target: *mut fdb_sys::{name}"), + None => String::new(), + }; + + writeln!( + w, + "{TAB1}pub unsafe fn apply(&self{first_arg}) -> FdbResult<()> {{" + )?; + writeln!(w, "{TAB2}let code = self.code();")?; + writeln!(w, "{TAB2}let err = unsafe {{ match *self {{")?; + + let args = if first_arg.is_empty() { + "code" + } else { + "target, code" + }; + + for option in self.options.iter() { + write!(w, "{}{}::{}", TAB3, self.name, option.name)?; + match option.param_type { + FdbOptionTy::Empty => { + writeln!(w, " => fdb_sys::{fn_name}({args}, std::ptr::null(), 0),")?; + } + FdbOptionTy::Int => { + writeln!(w, "(v) => {{")?; + writeln!(w, "{TAB4}let data: [u8;8] = v.to_ne_bytes();",)?; + writeln!( + w, + "{TAB4}fdb_sys::{fn_name}({args}, data.as_ptr() as *const u8, 8)" + )?; + writeln!(w, "{TAB3}}}")?; + } + FdbOptionTy::Bytes => { + writeln!(w, "(ref v) => {{")?; + writeln!( + w, + "{TAB4}fdb_sys::{fn_name}({args}, v.as_ptr() as *const u8, \ + i32::try_from(v.len()).expect(\"len to fit in i32\"))\n" + )?; + writeln!(w, "{TAB3}}}")?; + } + FdbOptionTy::Str => { + writeln!(w, "(ref v) => {{")?; + writeln!( + w, + "{TAB4}fdb_sys::{fn_name}({args}, v.as_ptr() as *const u8, \ + i32::try_from(v.len()).expect(\"len to fit in i32\"))\n" + )?; + writeln!(w, "{TAB3}}}")?; + } + } + } + + writeln!(w, "{TAB2}}} }};")?; + writeln!( + w, + "{TAB2}if err != 0 {{ Err(FdbError::from_code(err)) }} else {{ Ok(()) }}", + )?; + writeln!(w, "{TAB1}}}") + } + + fn with_ty(&self) -> bool { + self.apply_fn_name().is_some() + } + + fn c_enum_prefix(&self) -> &'static str { + match self.name.as_str() { + "NetworkOption" => "FDBNetworkOption_FDB_NET_OPTION_", + "ClusterOption" => "FDBClusterOption_FDB_CLUSTER_OPTION_", + "DatabaseOption" => "FDBDatabaseOption_FDB_DB_OPTION_", + "TransactionOption" => "FDBTransactionOption_FDB_TR_OPTION_", + "StreamingMode" => "FDBStreamingMode_FDB_STREAMING_MODE_", + "MutationType" => "FDBMutationType_FDB_MUTATION_TYPE_", + "ConflictRangeType" => "FDBConflictRangeType_FDB_CONFLICT_RANGE_TYPE_", + "ErrorPredicate" => "FDBErrorPredicate_FDB_ERROR_PREDICATE_", + ty => panic!("unknown Scope name: `{ty}`"), + } + } + + fn apply_arg_name(&self) -> Option<&'static str> { + let s = match self.name.as_str() { + "ClusterOption" => "FDBCluster", + "DatabaseOption" => "FDBDatabase", + "TransactionOption" => "FDBTransaction", + _ => return None, + }; + Some(s) + } + + fn apply_fn_name(&self) -> Option<&'static str> { + let s = match self.name.as_str() { + "NetworkOption" => "fdb_network_set_option", + "ClusterOption" => "fdb_cluster_set_option", + "DatabaseOption" => "fdb_database_set_option", + "TransactionOption" => "fdb_transaction_set_option", + _ => return None, + }; + Some(s) + } +} + +#[derive(Clone, Copy, Debug, Default)] +enum FdbOptionTy { + #[default] + Empty, + Int, + Str, + Bytes, +} + +#[derive(Default, Debug)] +struct FdbOption { + name: String, + c_name: String, + code: i32, + param_type: FdbOptionTy, + param_description: String, + description: String, + hidden: bool, + default_for: Option, + persistent: bool, + sensitive: bool, +} + +impl FdbOption { + fn gen_ty(&self, w: &mut W, with_ty: bool) -> fmt::Result { + if !self.param_description.is_empty() { + writeln!(w, "{t}/// {desc}", t = TAB1, desc = self.param_description)?; + writeln!(w, "{TAB1}///")?; + } + if !self.description.is_empty() { + writeln!(w, "{t}/// {desc}", t = TAB1, desc = self.description)?; + } + + if let (true, Some(ty)) = (with_ty, self.get_ty()) { + writeln!(w, "{t}{name}({ty}),", t = TAB1, name = self.name, ty = ty)?; + } else { + writeln!(w, "{t}{name},", t = TAB1, name = self.name)?; + } + Ok(()) + } + + fn get_ty(&self) -> Option<&'static str> { + match self.param_type { + FdbOptionTy::Int => Some("i64"), + FdbOptionTy::Str => Some("String"), + FdbOptionTy::Bytes => Some("Vec"), + FdbOptionTy::Empty => None, + } + } +} + +fn to_rs_enum_name(v: &str) -> String { + let mut is_start_of_word = true; + v.chars() + .filter_map(|c| { + if c == '_' { + is_start_of_word = true; + None + } else if is_start_of_word { + is_start_of_word = false; + Some(c.to_ascii_uppercase()) + } else { + Some(c) + } + }) + .collect() +} + +impl From> for FdbOption { + fn from(attrs: Vec) -> Self { + let mut opt = Self::default(); + for attr in attrs { + let v = attr.value; + match attr.name.local_name.as_str() { + "name" => { + opt.name = to_rs_enum_name(v.as_str()); + opt.c_name = v.to_uppercase(); + } + "code" => { + opt.code = v.parse().expect("code to be a i32"); + } + "paramType" => { + opt.param_type = match v.as_str() { + "Int" => FdbOptionTy::Int, + "String" => FdbOptionTy::Str, + "Bytes" => FdbOptionTy::Bytes, + "" => FdbOptionTy::Empty, + ty => panic!("unexpected param_type: {ty}"), + }; + } + "paramDescription" => { + opt.param_description = v; + } + "description" => { + opt.description = v; + } + "hidden" => match v.as_str() { + "true" => opt.hidden = true, + "false" => opt.hidden = false, + _ => panic!("unexpected boolean value in 'hidden': {v}"), + }, + "defaultFor" => { + opt.default_for = Some(v.parse().expect("defaultFor to be a i32")); + } + "persistent" => match v.as_str() { + "true" => opt.persistent = true, + "false" => opt.persistent = false, + _ => panic!("unexpected boolean value in 'persistent': {v}"), + }, + "sensitive" => match v.as_str() { + "true" => opt.sensitive = true, + "false" => opt.sensitive = false, + _ => panic!("unexpected boolean value in 'sensitive': {v}"), + }, + attr => { + panic!("unexpected option attribute: {attr}"); + } + } + } + opt + } +} + +fn on_scope(parser: &mut I) -> Vec +where + I: Iterator>, +{ + let mut options = Vec::new(); + for e in parser { + let e = e.unwrap(); + match e { + XmlEvent::StartElement { + name, attributes, .. + } => { + assert_eq!(name.local_name, "Option", "unexpected token"); + + let option = FdbOption::from(attributes.clone()); + if !option.hidden { + options.push(option); + } + } + XmlEvent::EndElement { name, .. } if name.local_name == "Scope" => { + return options; + } + _ => {} + } + } + + panic!("unexpected end of token"); +} + +const OPTIONS_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fdb.options")); + +pub fn emit(w: &mut impl fmt::Write) -> fmt::Result { + let mut reader = OPTIONS_DATA; + let parser = EventReader::new(&mut reader); + let mut iter = parser.into_iter(); + let mut scopes = Vec::new(); + + while let Some(e) = iter.next() { + match e.unwrap() { + XmlEvent::StartElement { + name, attributes, .. + } => { + if name.local_name == "Scope" { + let scope_name = attributes + .into_iter() + .find(|attr| attr.name.local_name == "name") + .unwrap(); + + let options = on_scope(&mut iter); + scopes.push(FdbScope { + name: scope_name.value, + options, + }); + } + } + XmlEvent::EndElement { .. } => { + // + } + _ => {} + } + } + + writeln!(w, "use std::convert::TryFrom;")?; + writeln!(w, "use crate::{{FdbError, FdbResult}};")?; + writeln!(w, "use foundationdb_sys as fdb_sys;")?; + for scope in scopes.iter() { + scope.gen_ty(w)?; + scope.gen_impl(w)?; + } + + Ok(()) +} diff --git a/bindings/rust/foundationdb-macros/CHANGELOG.md b/bindings/rust/foundationdb-macros/CHANGELOG.md new file mode 100644 index 00000000000..b27d78d9f5e --- /dev/null +++ b/bindings/rust/foundationdb-macros/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.4.0] - 2026-06-24 + +### 🚀 Features + +- Feat: migrate workspace to Rust 2024 edition (#255) + +### 🧪 Testing + +- Fix and run version mapping tests + +[0.4.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.3.3..0.4.0 + + +## [0.3.3] - 2025-10-24 + +### 🚀 Features + +- Add support 7.4 + +### ⚙️ Miscellaneous + +- Bump MSRV to 1.82 +- Bump msrv to 1.85 + +### 📦 Other Changes + +- Update workload bindings + +[0.3.3]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.3.2..0.3.3 + + +## [0.3.2] - 2025-01-06 + +### 🆙 Bump + +- Bump proc-macro2 from 1.0.89 to 1.0.92 +- Bump syn from 2.0.85 to 2.0.87 +- Bump proc-macro2 from 1.0.87 to 1.0.89 +- Bump syn from 2.0.82 to 2.0.85 +- Bump syn from 2.0.79 to 2.0.82 +- Bump proc-macro2 from 1.0.86 to 1.0.87 + +[0.3.2]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.3.1..0.3.2 + +## [0.3.1] - 2024-10-08 + +### 🆙 Bump + +- Bump MSRV to 1.71.1 +- Bump all dependencies and setup dependabot + +[0.3.1]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.3.0..0.3.1 + diff --git a/bindings/rust/foundationdb-macros/Cargo.toml b/bindings/rust/foundationdb-macros/Cargo.toml new file mode 100644 index 00000000000..9a7f87c929a --- /dev/null +++ b/bindings/rust/foundationdb-macros/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundationdb-macros" +version = "0.4.0" +authors = [ + "Pierre Zemb " +] +edition = "2024" +rust-version = "1.85.1" + +description = """ +Macro definitions used to maintain the FoundationDB's crate +""" + +documentation = "https://docs.rs/foundationdb" +repository = "https://github.com/apple/foundationdb" +license = "MIT/Apache-2.0" + +readme = "README.md" +keywords = ["foundationdb", "proc-macro"] + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "3.0.2", features = ["full"] } +proc-macro2 = "1.0.106" +try_map = "0.3.1" +quote = "1.0.46" diff --git a/bindings/rust/foundationdb-macros/README.md b/bindings/rust/foundationdb-macros/README.md new file mode 100644 index 00000000000..c900519d165 --- /dev/null +++ b/bindings/rust/foundationdb-macros/README.md @@ -0,0 +1,3 @@ +# FoundationDB-macros + +This crate is holding a few macros used to maintain the crate. \ No newline at end of file diff --git a/bindings/rust/foundationdb-macros/src/lib.rs b/bindings/rust/foundationdb-macros/src/lib.rs new file mode 100644 index 00000000000..bf9a8feb247 --- /dev/null +++ b/bindings/rust/foundationdb-macros/src/lib.rs @@ -0,0 +1,245 @@ +//! Macro definitions used to maintain the crate +use proc_macro::TokenStream; +use quote::quote; +use std::collections::HashMap; +use syn::__private::TokenStream2; +use syn::parse::Parser; +use syn::{Item, LitInt}; +use try_map::FallibleMapExt; + +/// Allow to compute the range of supported api versions for a functionality. +/// +/// This macro came out from the frustration of bumping fdb's version, where +/// we are spending most of our time searching for things like: +/// `#[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2", feature = "fdb-6_0"))]` +/// and adding the new version manually. +/// +/// Thanks to the macro, we can now specify a `minimum` and an optional `max` version, and +/// generate the right list of any. Not specifying a `max` allow easy bump to a new version. +/// +/// `#[cfg_api_versions(min = 510, max = 600)]` will be translated to: +/// `#[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2", feature = "fdb-6_0"))]` +#[proc_macro_attribute] +pub fn cfg_api_versions(args: TokenStream, input: TokenStream) -> TokenStream { + let args = proc_macro2::TokenStream::from(args); + let input = proc_macro2::TokenStream::from(input); + cfg_api_versions_impl(args, input).into() +} + +fn cfg_api_versions_impl( + args: proc_macro2::TokenStream, + input: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let mut min: Option = None; + let mut max: Option = None; + let version_parser = syn::meta::parser(|meta| { + if meta.path.is_ident("min") { + min = Some(meta.value()?.parse()?); + Ok(()) + } else if meta.path.is_ident("max") { + max = Some(meta.value()?.parse()?); + Ok(()) + } else { + Err(meta.error("unsupported cfg_api_versions property")) + } + }); + + Parser::parse2(version_parser, args).expect("Unable to parse attribute cfg_api_versions"); + + let input: Item = syn::parse2(input).expect("Unable to parse input"); + + let minimum_version = min + .expect("min property must be provided") + .base10_parse::() + .expect("Unable to parse min version"); + let maximum_version = max + .try_map(|x| x.base10_parse::()) + .expect("Unable to parse max version"); + generate_feature_range(&input, minimum_version, maximum_version) +} + +fn generate_feature_range( + input: &Item, + minimum_version: i32, + maximum_version: Option, +) -> proc_macro2::TokenStream { + let allowed_fdb_versions: Vec = + get_supported_feature_range(minimum_version, maximum_version) + .iter() + .map(|fdb_version| quote!(feature = #fdb_version)) + .collect(); + + quote!( + #[cfg(any(#(#allowed_fdb_versions),*))] + #input + ) +} + +/// Given a range of version, this function will generate the appropriate macro text. +fn get_supported_feature_range(minimum_version: i32, maximum_version: Option) -> Vec { + let mut values: Vec = get_version_mapping() + .iter() + .filter(|(_, version)| match maximum_version { + None => minimum_version <= **version, + Some(maximum) => minimum_version <= **version && version <= &&maximum, + }) + .map(|(feature, _)| feature.to_owned()) + .collect(); + values.sort(); + + values +} + +// TODO: Should we import something like lazy_static? +fn get_version_mapping() -> HashMap { + let mut version_mapping = HashMap::with_capacity(9); + version_mapping.insert("fdb-7_4".into(), 740); + version_mapping.insert("fdb-7_3".into(), 730); + version_mapping.insert("fdb-7_1".into(), 710); + version_mapping.insert("fdb-7_0".into(), 700); + version_mapping.insert("fdb-6_3".into(), 630); + version_mapping.insert("fdb-6_2".into(), 620); + version_mapping.insert("fdb-6_1".into(), 610); + version_mapping.insert("fdb-6_0".into(), 600); + version_mapping.insert("fdb-5_2".into(), 520); + version_mapping.insert("fdb-5_1".into(), 510); + version_mapping.insert("fdb-5_0".into(), 500); + version_mapping +} + +#[cfg(test)] +mod tests { + use crate::cfg_api_versions_impl; + use crate::get_supported_feature_range; + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn test_create_supported_list() { + let v = get_supported_feature_range(700, None); + assert_eq!(v.len(), 4); + assert!(v.contains(&String::from("fdb-7_0"))); + assert!(v.contains(&String::from("fdb-7_1"))); + assert!(v.contains(&String::from("fdb-7_3"))); + assert!(v.contains(&String::from("fdb-7_4"))); + + let v = get_supported_feature_range(600, Some(700)); + assert_eq!(v.len(), 5); + assert!(v.contains(&String::from("fdb-7_0"))); + assert!(v.contains(&String::from("fdb-6_3"))); + assert!(v.contains(&String::from("fdb-6_2"))); + assert!(v.contains(&String::from("fdb-6_1"))); + assert!(v.contains(&String::from("fdb-6_0"))); + + let v = get_supported_feature_range(500, Some(610)); + assert_eq!(v.len(), 5); + assert!(v.contains(&String::from("fdb-6_1"))); + assert!(v.contains(&String::from("fdb-6_0"))); + assert!(v.contains(&String::from("fdb-5_2"))); + assert!(v.contains(&String::from("fdb-5_1"))); + assert!(v.contains(&String::from("fdb-5_0"))); + + let v = get_supported_feature_range(500, None); + assert_eq!(v.len(), 11); + assert!(v.contains(&String::from("fdb-7_4"))); + assert!(v.contains(&String::from("fdb-7_3"))); + assert!(v.contains(&String::from("fdb-7_1"))); + assert!(v.contains(&String::from("fdb-7_0"))); + assert!(v.contains(&String::from("fdb-6_3"))); + assert!(v.contains(&String::from("fdb-6_2"))); + assert!(v.contains(&String::from("fdb-6_1"))); + assert!(v.contains(&String::from("fdb-6_0"))); + assert!(v.contains(&String::from("fdb-5_2"))); + assert!(v.contains(&String::from("fdb-5_1"))); + assert!(v.contains(&String::from("fdb-5_0"))); + } + + fn test_cfg_versions(expected_versions: TokenStream, attrs: TokenStream) { + let input = quote! { + fn ma_fonction() {} + }; + + let expected = quote! { + #[cfg(any(#expected_versions))] + fn ma_fonction() {} + }; + + let result = cfg_api_versions_impl(attrs, input); + assert_eq!(result.to_string(), expected.to_string()) + } + + #[test] + fn test_min_700_no_max_version() { + let data = quote!( + feature = "fdb-7_0", + feature = "fdb-7_1", + feature = "fdb-7_3", + feature = "fdb-7_4" + ); + + let attrs = quote!(min = 700); + + test_cfg_versions(data, attrs) + } + + #[test] + fn test_min_600_max_700() { + let expected_versions = quote!( + feature = "fdb-6_0", + feature = "fdb-6_1", + feature = "fdb-6_2", + feature = "fdb-6_3", + feature = "fdb-7_0" + ); + + let attrs = quote!(min = 600, max = 700); + + test_cfg_versions(expected_versions, attrs) + } + + #[test] + fn test_min_500_max_610() { + let expected_versions = quote!( + feature = "fdb-5_0", + feature = "fdb-5_1", + feature = "fdb-5_2", + feature = "fdb-6_0", + feature = "fdb-6_1" + ); + + let attrs = quote!(min = 500, max = 610); + + test_cfg_versions(expected_versions, attrs) + } + + #[test] + fn test_min_500_no_max() { + let expected_versions = quote!( + feature = "fdb-5_0", + feature = "fdb-5_1", + feature = "fdb-5_2", + feature = "fdb-6_0", + feature = "fdb-6_1", + feature = "fdb-6_2", + feature = "fdb-6_3", + feature = "fdb-7_0", + feature = "fdb-7_1", + feature = "fdb-7_3", + feature = "fdb-7_4" + ); + + let attrs = quote!(min = 500); + + test_cfg_versions(expected_versions, attrs) + } + + #[test] + #[should_panic] + fn test_no_min_version() { + let expected_versions = quote!(feature = "fdb-5_0",); + + let attrs = quote!(max = 500); + + test_cfg_versions(expected_versions, attrs) + } +} diff --git a/bindings/rust/foundationdb-simulation/CHANGELOG.md b/bindings/rust/foundationdb-simulation/CHANGELOG.md new file mode 100644 index 00000000000..c9e786008e2 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/CHANGELOG.md @@ -0,0 +1,88 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.0] - 2026-06-24 + +### 🚀 Features + +- Feat(simulation): auto-capitalize trace detail names +- Feat(simulation): WorkloadContext::delay takes Duration, exercised in noop +- Feat: migrate workspace to Rust 2024 edition (#255) + +### 🐛 Bug Fixes + +- Fix(simulation): static storage for C-ABI vtables, target fdbserver 7.4.6 + +### 📦 Other Changes + +- Rewrite fdb_rt to split wakeup and polling phase + +[0.3.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2.3..0.3.0 + + +## [0.2.3] - 2025-10-24 + +### 🚀 Features + +- Add support 7.4 +- Add 7.4 simulation in CI + +### 🐛 Bug Fixes + +- Remove unwrap on checks +- Recover simulation trace public api breaking change +- Fix(foundationdb-simulation): add docs.rs metadata to fix build + +### 🚜 Refactor + +- Remove unnecessary simulation headers + +### ⚙️ Miscellaneous + +- Clippy fixes + +### 📦 Other Changes + +- Update CI and bindingtester to 7.4 +- Some spaces and tabs doc formalization +- Update workload bindings +- Update README +- Async RustWorkload +- Tidy up foundationdb-simulation API: + +[0.2.3]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2.2..0.2.3 + + +## [0.2.2] - 2025-01-06 + +### 🚀 added + +- Add unit for now() + +### 🆙 Bump + +- Bump cc from 1.1.30 to 1.2.1 +- Bump cc from 1.1.24 to 1.1.30 + +[0.2.2]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2.1..0.2.2 + +## [0.2.1] - 2024-10-08 + +### 🐛 Bug Fixes + +- Fix foundationdb-simulation compilation with fdb-7_1 + +### 🆙 Bump + +- Bump all dependencies and setup dependabot + +### ⚙️ Other changes + +- Use C++14 spec + +[0.2.1]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2.0..0.2.1 + diff --git a/bindings/rust/foundationdb-simulation/Cargo.toml b/bindings/rust/foundationdb-simulation/Cargo.toml new file mode 100644 index 00000000000..5162bef698e --- /dev/null +++ b/bindings/rust/foundationdb-simulation/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "foundationdb-simulation" +version = "0.3.0" +publish = false +authors = [ + "Eloi DEMOLIS ", + "Pierre Zemb " +] +edition = "2024" +description = """ +Embed Rust code within FoundationDB's simulation +""" +license = "MIT/Apache-2.0" +repository = "https://github.com/apple/foundationdb" +readme = "README.md" +keywords = ["foundationdb", "simulation"] + +[dependencies] +foundationdb = { version = "0.11.0", path = "../foundationdb", default-features = false } +foundationdb-sys = { version = "0.11.0", path = "../foundationdb-sys", default-features = false } + +[build-dependencies] +bindgen = "0.73.2" + +[dev-dependencies] +futures-util = "0.3.32" +loom = "0.7.2" + +[features] +default = ["embedded-fdb-include"] +fdb-7_4 = ["foundationdb/fdb-7_4", "foundationdb-sys/fdb-7_4"] +embedded-fdb-include = [ + "foundationdb/embedded-fdb-include", + "foundationdb-sys/embedded-fdb-include", +] + +[[example]] +name = "atomic" +path = "examples/atomic/lib.rs" +crate-type = ["cdylib"] + +[[example]] +name = "noop" +path = "examples/noop/lib.rs" +crate-type = ["cdylib"] + +[[example]] +name = "shared" +path = "examples/shared/lib.rs" +crate-type = ["cdylib"] diff --git a/bindings/rust/foundationdb-simulation/README.md b/bindings/rust/foundationdb-simulation/README.md new file mode 100644 index 00000000000..2cb1c0e7d96 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/README.md @@ -0,0 +1,131 @@ +# foundationdb-simulation + +Optional support for Rust workloads loaded by FoundationDB's `ExternalWorkload` +simulator. This crate uses the current `bindings/c/foundationdb/CWorkload.h` +interface and exports its C workload factory. Workload configurations must set +`useCAPI = true`. + +The build uses `CWorkload.h` from `FDB_INCLUDE_DIR` when set, or the canonical +header in this source tree otherwise. Use headers and the C client library from +the same FoundationDB build as the simulator. `FDB_CLIENT_LIB_PATH` selects the +client library directory for Cargo builds. The `fdb-7_4` feature selects the +client API used by the Rust binding; it does not select a historical simulator +ABI. + +## Workloads + +Implement `RustWorkload` to provide asynchronous `setup`, `start`, and `check` +phases, plus synchronous metrics and timeout callbacks. Implement +`SingleRustWorkload` to construct one workload type and register it with +`register_workload!`: + +```rust,ignore +use foundationdb_simulation::{ + Metrics, RustWorkload, SimDatabase, SingleRustWorkload, WorkloadContext, + register_workload, +}; + +struct MyWorkload { + context: WorkloadContext, +} + +impl SingleRustWorkload for MyWorkload { + fn new(_name: String, context: WorkloadContext) -> Self { + Self { context } + } +} + +impl RustWorkload for MyWorkload { + async fn setup(&mut self, _db: SimDatabase) {} + async fn start(&mut self, _db: SimDatabase) { + self.context + .delay(std::time::Duration::from_secs(1)) + .await + .expect("simulated delay"); + } + async fn check(&mut self, _db: SimDatabase) {} + fn get_metrics(&self, _out: Metrics<'_>) {} + fn get_check_timeout(&self) -> f64 { + 30.0 + } +} + +register_workload!(MyWorkload); +``` + +For a library containing several workload types, implement `RustWorkloadFactory` +and use `register_factory!` instead. Register exactly one factory per shared +library. Workload crates must set `crate-type = ["cdylib"]`. + +The examples cover native operations without an application runtime: + +- `noop`: factory dispatch, workload options, tracing, metrics, and simulated delay. +- `atomic`: atomic mutations and a check against committed counter values. +- `shared`: sharing a Rust future backed by the C client. + +From `bindings/rust`, build and run the atomic example with the matching +FoundationDB headers and client library available: + +```bash +cargo build --locked --release -p foundationdb-simulation --example atomic \ + --features fdb-7_4 +fdbserver -r simulation \ + -f foundationdb-simulation/examples/atomic/test_file.toml \ + -b on --trace-format json +``` + +The example configuration loads `target/release/examples/libatomic.so`. Adjust +`libraryPath` if using a different Cargo target directory. + +## Simulator context + +`WorkloadContext` exposes the simulator's clock (`now`), deterministic random +numbers (`rnd` and `shared_random_number`), delay futures, client IDs, workload +options, and tracing. Use these instead of machine clocks, ambient randomness, +or external timers so a simulation seed reproduces the workload. + +Read every custom option supplied in the TOML configuration; unread options make +the native workload configuration invalid. `get_option` returns `None` for a +missing option or a value that cannot be parsed as `T`. + +Context clones do not extend the native workload's lifetime. Their operations +panic before native access when called from another thread or after the +registered workload is released. Raw context and string entry points are unsafe; +factories construct an opaque `WrappedWorkload` through `RustWorkload::wrap()`. + +`context.trace(Severity::Error, ...)` fails the simulation. Traces capitalize the +first character of event names and detail keys, and add `RustWorkload="1"`; +error events also add `RustFailure="1"`. `Metric::val` values are summed across +clients, while `Metric::avg` values are averaged. The `Metrics<'_>` sink is +borrowed only for the metrics callback and must not escape it. + +## Scheduling and ownership + +The simulator drives phases cooperatively on its own thread. Await FoundationDB +futures or `context.delay`; do not busy-wait or use timers from another async +runtime. The executor drains queued Rust futures after native future callbacks. +A wake from another thread is safe but needs a later owner-thread drain to make +progress. See [executor details](docs/fdb_rt.md). + +Native workload release cancels a suspended phase before destroying the workload +or invalidating its context. Final metrics collection or a new phase also cancels +an abandoned phase. Late wakes cannot restart it. Timeout queries during `check` +use the value sampled immediately before that phase; a timeout query before +`check` cancels abandoned setup/start work before reading the timeout. Native +callbacks must not free a workload reentrantly while its phase is being polled. + +`SimDatabase` represents a borrowed native database using a Rust `Arc`. All +strong and weak database references, transactions, and database futures must be +dropped by the end of their phase, including cancellation. The phase guard frees +only the Rust allocation, preserving the native caller's database reference. +Escaping database references terminate the process before they can outlive that +native handle. + +Registration selects the client API through the shared C client. Libraries may +share a selection only when their runtime and header versions match; incompatible +or unverifiable selections fail before workload creation. The simulator retains +ownership of the network and its database handles. + +When built under `cargo llvm-cov`, completed metrics callbacks flush their LLVM +profiles because `fdbserver` can exit without running process-exit handlers. A +failure before metrics collection does not guarantee a profile flush. diff --git a/bindings/rust/foundationdb-simulation/build.rs b/bindings/rust/foundationdb-simulation/build.rs new file mode 100644 index 00000000000..bfe686426e2 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/build.rs @@ -0,0 +1,24 @@ +use std::{env, path::PathBuf}; + +fn main() { + println!("cargo:rustc-check-cfg=cfg(coverage)"); + println!("cargo:rustc-check-cfg=cfg(fdb_simulation_loom)"); + println!("cargo:rerun-if-env-changed=FDB_INCLUDE_DIR"); + + let include_dir = env::var_os("FDB_INCLUDE_DIR") + .map(PathBuf::from) + .unwrap_or_else(|| { + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../../c/foundationdb") + }); + let header = include_dir.join("CWorkload.h"); + println!("cargo:rerun-if-changed={}", header.display()); + let bindings = bindgen::Builder::default() + .header(header.to_str().expect("UTF-8 workload header path")) + .generate() + .expect("generate workload bindings"); + + let out_path = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("write workload bindings"); +} diff --git a/bindings/rust/foundationdb-simulation/docs/fdb_rt.md b/bindings/rust/foundationdb-simulation/docs/fdb_rt.md new file mode 100644 index 00000000000..f5e8a0b126b --- /dev/null +++ b/bindings/rust/foundationdb-simulation/docs/fdb_rt.md @@ -0,0 +1,91 @@ +The simulator executor runs Rust futures on the thread that creates them. The futures +may contain `Rc`, thread-affine simulator state, and other values that are not `Send`. +Their wakers, however, are ordinary Rust `Waker`s and may safely be cloned, woken, or +dropped on any thread. + +## Ownership and scheduling + +Each thread has its own executor registry. Only this registry owns the pinned futures; +neither a waker nor the shared notification queue contains a future or a pointer to one. +Task IDs are never reused within an executor. + +A waker contains a task ID, an atomic queued flag, and a weak reference to its owner's +notification queue. The standard library's `Wake` implementation constructs the `Waker` +from this `Send + Sync` state without a custom unsafe `RawWaker` implementation. + +Waking adds a notification to the owner's queue. Duplicate wakes before the next poll +are coalesced. The queue holds the notification state alive until it is consumed, so +even a consuming `Waker::wake()` schedules a poll before releasing the final waker. +Waking never polls the future directly. + +Before polling, the owner acquires the queued flag while clearing it. A coalesced +wake does not take the queue lock, so this acquire makes its producer's writes +visible to the poll even when no second notification was enqueued. + +`enqueue` inserts a future into the current thread's registry and queues its first poll. +The workload owner records its task ID before calling `poll_pending_tasks`. The drain removes each future from the registry while +polling it and restores it only if it returns `Pending`. No queue lock or registry borrow +spans user polling or destruction. A thread-local guard defers nested drains, including +synchronous FoundationDB callbacks, until the current poll has returned. Tasks spawned +during a poll also wait for that poll to finish. + +The drain continues until the queue is empty. A wake during a poll schedules another poll +after the current one, including when the wake comes from another thread. As with other +executors, a future must not wake itself indefinitely without making progress. + +## FoundationDB callbacks + +The workload registration macros install `poll_pending_tasks` in +`foundationdb::future::CUSTOM_EXECUTOR_HOOK`. A pending `FdbFuture` registers the task's +waker in its callback state. When the C future completes, its callback first wakes that +state and then invokes the executor hook: + +```text +FDB future callback on the simulator thread + -> wake task -> enqueue notification + -> CUSTOM_EXECUTOR_HOOK -> poll_pending_tasks + -> remove future from owner registry + -> poll future + -> Ready: destroy it on its owner + -> Pending: restore it to owner registry +``` + +The simulator's normal callbacks run on the owner thread and therefore resume work +immediately. A wake from another thread is safe but does not create a simulator event +or move execution to that thread: the owner must next call `poll_pending_tasks` to make +progress. Calling the hook on a foreign thread drains only that thread's own executor. +This executor is not a general-purpose cross-thread event loop. + +## Completion and cancellation + +The native workload owns its pending phase task. Releasing the workload synchronously +removes and drops that task before dropping the user workload and invalidating the native +context. Task destruction defers nested executor drains. The phase owns its workload while +suspended and returns it before releasing its promise, including on cancellation. A timeout +query during `check` uses the value sampled immediately before that phase began, avoiding +aliasing its exclusive workload borrow; the cached value is cleared when that phase ends. +A timeout query before `check` cancels an abandoned setup/start before reading its timeout. +A final metrics callback or new phase also cancels any task left behind by an abandoned +native waiter. That cancellation retains only a weak workload reference: if releasing the +old promise frees the native workload reentrantly, its destructor still runs with a live +context and the incoming callback does not access the released workload. Resolving +or releasing that promise can synchronously call native teardown, so no phase borrow or +extra workload owner survives the callback. Native teardown during an actual user poll is +an ABI violation and fails closed; Rust cannot synchronously destroy an executing future. + +A phase database is borrowed from the native caller. A guard retains its last Rust `Arc` +until the user future is destroyed, then frees the Rust allocation without calling the +native database destructor. Strong or weak database references that escape completion or +cancellation terminate the process before the native pointer can be used after its lifetime. + +A completed future is destroyed on its owner even if external wakers still exist. Later +wakes for its task ID are harmless. If a pending future loses its final waker without +being woken, the waker's destructor queues cancellation. The owner drops that future on +its next drain. This preserves owner-thread destruction when the last waker is dropped +by another thread and avoids permanently retaining an unreachable pending task. + +If the owner does not drain again, cancellation remains deferred until its executor is +destroyed at thread exit. Exiting the owner thread drops all remaining futures locally; +outstanding wakers contain only weak queue references and remain safe to use afterward. +Cancellation callbacks that reenter the executor during this teardown return immediately. +The executor has no independent timer or background thread to drive foreign wakes. diff --git a/bindings/rust/foundationdb-simulation/examples/atomic/lib.rs b/bindings/rust/foundationdb-simulation/examples/atomic/lib.rs new file mode 100644 index 00000000000..786a59d95d8 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/atomic/lib.rs @@ -0,0 +1,110 @@ +use foundationdb::{ + FdbBindingError, + options::{MutationType, TransactionOption}, +}; +use foundationdb_simulation::{ + Metric, Metrics, RustWorkload, Severity, SimDatabase, SingleRustWorkload, WorkloadContext, + details, register_workload, +}; + +const COUNT_KEY: &[u8] = b"rust/atomic/count"; + +pub struct AtomicWorkload { + context: WorkloadContext, + client_id: i32, + expected_count: usize, + success_count: usize, + error_count: usize, + maybe_committed_count: usize, + elapsed: f64, +} + +impl SingleRustWorkload for AtomicWorkload { + fn new(_name: String, context: WorkloadContext) -> Self { + Self { + client_id: context.client_id(), + expected_count: context.get_option("count").expect("Could not get count"), + context, + success_count: 0, + error_count: 0, + maybe_committed_count: 0, + elapsed: 0.0, + } + } +} + +impl RustWorkload for AtomicWorkload { + async fn setup(&mut self, db: SimDatabase) { + if self.client_id == 0 { + db.run(|trx, _| async move { + trx.clear(COUNT_KEY); + Ok::<_, FdbBindingError>(()) + }) + .await + .expect("could not clear the counter"); + } + } + + async fn start(&mut self, db: SimDatabase) { + let started_at = self.context.now(); + if self.client_id == 0 { + for _ in 0..self.expected_count { + let trx = db.create_trx().expect("Could not create transaction"); + trx.set_option(TransactionOption::AutomaticIdempotency) + .expect("could not set automatic idempotency"); + trx.atomic_op(COUNT_KEY, &1_i64.to_le_bytes(), MutationType::Add); + match trx.commit().await { + Ok(_) => self.success_count += 1, + Err(error) if error.is_maybe_committed() => self.maybe_committed_count += 1, + Err(_) => self.error_count += 1, + } + } + } + self.elapsed = self.context.now() - started_at; + } + + async fn check(&mut self, db: SimDatabase) { + if self.client_id == 0 { + let count = db + .run(|trx, _| async move { + let bytes = trx.get(COUNT_KEY, true).await?; + Ok::<_, FdbBindingError>(bytes.map_or(0, |value| { + i64::from_le_bytes(value[..8].try_into().unwrap()) as usize + })) + }) + .await + .expect("could not read the counter"); + let severity = if self.success_count == count { + Severity::Info + } else { + Severity::Error + }; + self.context.trace( + severity, + "AtomicCountCheck", + details![ + "Client" => self.client_id, + "Expected" => self.success_count, + "Found" => count, + "MaybeCommitted" => self.maybe_committed_count + ], + ); + } + } + + fn get_metrics(&self, mut out: Metrics<'_>) { + out.extend([ + Metric::val("expected_count", self.expected_count as f64), + Metric::val("success_count", self.success_count as f64), + Metric::val("error_count", self.error_count as f64), + Metric::val("maybe_committed_count", self.maybe_committed_count as f64), + Metric::val("elapsed_simulated_seconds", self.elapsed), + ]); + } + + fn get_check_timeout(&self) -> f64 { + 5000.0 + } +} + +register_workload!(AtomicWorkload); diff --git a/bindings/rust/foundationdb-simulation/examples/atomic/test_file.toml b/bindings/rust/foundationdb-simulation/examples/atomic/test_file.toml new file mode 100644 index 00000000000..01fcffb2406 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/atomic/test_file.toml @@ -0,0 +1,37 @@ +[[test]] +testTitle = 'AtomicWorkload' + + [[test.workload]] + testName = 'External' + useCAPI = true + libraryName = 'atomic' + workloadName = 'AtomicWorkload' + libraryPath = './target/release/examples' + count = 50 + + [[test.workload]] + # Introduce network partitions + testName = 'RandomClogging' + testDuration = 30.0 + # Unclog them in reversed order + swizzle = 1 + + [[test.workload]] + # Reboot processes + testName = 'Attrition' + machinesToKill = 10 + machinesToLeave = 3 + reboot = true + testDuration = 30.0 + + [[test.workload]] + # Introduce specific network partitions errors between proxies and tLogs + testName = 'Rollback' + testDuration = 30 + + [[test.workload]] + # Change configuration of the database + testName = 'ChangeConfig' + maxDelayBeforeChange = 30.0 + # Move consensus-based processes around + coordinators = 'auto' diff --git a/bindings/rust/foundationdb-simulation/examples/noop/lib.rs b/bindings/rust/foundationdb-simulation/examples/noop/lib.rs new file mode 100644 index 00000000000..9e801b7326c --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/noop/lib.rs @@ -0,0 +1,95 @@ +use std::time::Duration; + +use foundationdb_simulation::{ + Metric, Metrics, RustWorkload, RustWorkloadFactory, Severity, SimDatabase, WorkloadContext, + WrappedWorkload, register_factory, +}; + +struct NoopWorkload { + name: String, + client_id: i32, + context: WorkloadContext, +} + +impl RustWorkload for NoopWorkload { + async fn setup(&mut self, _db: SimDatabase) { + println!("rust_setup({}_{})", self.name, self.client_id); + self.context.trace( + Severity::Debug, + "Test", + &[("Layer", "Rust"), ("Stage", "Setup")], + ); + } + async fn start(&mut self, _db: SimDatabase) { + println!("rust_start({}_{})", self.name, self.client_id); + self.context.trace( + Severity::Debug, + "Test", + &[("Layer", "Rust"), ("Stage", "Start")], + ); + let before = self.context.now(); + self.context + .delay(Duration::from_secs(1)) + .await + .expect("delay future should resolve"); + let after = self.context.now(); + // The difference is simulated time, not machine time: the simulator decides + // when the clock advances, so these readings are deterministic. + println!( + "clock({}_{}): before={before:?} after={after:?} difference={:?}", + self.name, + self.client_id, + after - before + ); + } + async fn check(&mut self, _db: SimDatabase) { + println!("rust_check({}_{})", self.name, self.client_id); + self.context.trace( + Severity::Debug, + "Test", + &[("Layer", "Rust"), ("Stage", "Check")], + ); + } + fn get_metrics(&self, mut out: Metrics<'_>) { + println!("rust_get_metrics({}_{})", self.name, self.client_id); + out.reserve(8); + out.push(Metric::val("test", 42)); + } + fn get_check_timeout(&self) -> f64 { + println!("rust_get_check_timeout({}_{})", self.name, self.client_id); + 3000. + } +} +impl NoopWorkload { + fn new(name: String, client_id: i32, context: WorkloadContext) -> Self { + Self { + name, + client_id, + context, + } + } +} +impl Drop for NoopWorkload { + fn drop(&mut self) { + println!("rust_free({}_{})", self.name, self.client_id); + } +} + +struct NoopFactory; +impl RustWorkloadFactory for NoopFactory { + fn create(name: String, context: WorkloadContext) -> WrappedWorkload { + let client_id = context.client_id(); + let client_count = context.client_count(); + println!("RustWorkloadFactory::create({name})[{client_id}/{client_count}]"); + println!( + "my_c_option: {:?}", + context.get_option::("my_c_option") + ); + match name.as_str() { + "NoopWorkload" => NoopWorkload::new(name, client_id, context).wrap(), + _ => panic!("Unknown workload name: {name}"), + } + } +} + +register_factory!(NoopFactory); diff --git a/bindings/rust/foundationdb-simulation/examples/noop/test_file.toml b/bindings/rust/foundationdb-simulation/examples/noop/test_file.toml new file mode 100644 index 00000000000..f3c06d25d2e --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/noop/test_file.toml @@ -0,0 +1,10 @@ +[[test]] +testTitle = 'NOOP_Test' + + [[test.workload]] + useCAPI = true + testName = 'External' + workloadName = 'NoopWorkload' + libraryPath = './target/release/examples' + libraryName = 'noop' + my_c_option = 'my_value' diff --git a/bindings/rust/foundationdb-simulation/examples/shared/lib.rs b/bindings/rust/foundationdb-simulation/examples/shared/lib.rs new file mode 100644 index 00000000000..047bcf60173 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/shared/lib.rs @@ -0,0 +1,44 @@ +use foundationdb_simulation::{ + Metrics, RustWorkload, SimDatabase, SingleRustWorkload, WorkloadContext, register_workload, +}; +use futures_util::FutureExt; + +pub struct SharedWorkload { + client_id: i32, + context: WorkloadContext, +} + +impl SingleRustWorkload for SharedWorkload { + fn new(_name: String, context: WorkloadContext) -> Self { + Self { + client_id: context.client_id(), + context, + } + } +} + +impl RustWorkload for SharedWorkload { + async fn setup(&mut self, _db: SimDatabase) { + println!("rust_setup({}) at {:?}", self.client_id, self.context.now()); + } + async fn start(&mut self, db: SimDatabase) { + println!("rust_start({}) at {:?}", self.client_id, self.context.now()); + let trx = db.create_trx().expect("Could not create transaction"); + let future = trx.get_read_version(); + let shared = future.shared(); + let version = shared.await; + println!("read_version({}) = {version:?}", self.client_id); + } + async fn check(&mut self, _db: SimDatabase) { + println!("rust_check({}) at {:?}", self.client_id, self.context.now()); + } + fn get_metrics(&self, mut _out: Metrics<'_>) { + println!("rust_get_metrics({})", self.client_id); + } + fn get_check_timeout(&self) -> f64 { + println!("rust_get_check_timeout({})", self.client_id); + 5000.0 + } +} + +register_workload!(SharedWorkload); diff --git a/bindings/rust/foundationdb-simulation/examples/shared/test_file.toml b/bindings/rust/foundationdb-simulation/examples/shared/test_file.toml new file mode 100644 index 00000000000..02bc0c07e88 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/examples/shared/test_file.toml @@ -0,0 +1,9 @@ +[[test]] +testTitle = 'Test_Test' + + [[test.workload]] + useCAPI = true + testName = 'External' + workloadName = 'SharedWorkload' + libraryPath = './target/release/examples' + libraryName = 'shared' diff --git a/bindings/rust/foundationdb-simulation/src/bindings.rs b/bindings/rust/foundationdb-simulation/src/bindings.rs new file mode 100644 index 00000000000..846a1a02b55 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/src/bindings.rs @@ -0,0 +1,1477 @@ +//! Wrapper module +//! +//! This module defines all C and Rust structures. +//! It also provides bindings and wrappers to map behavior from Rust to C. + +use std::{ + ffi::{self, c_char}, + str::FromStr, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + thread::{self, ThreadId}, + time::Duration, +}; + +use foundationdb as fdb; + +mod raw_bindings { + #![allow(non_camel_case_types)] + #![allow(non_upper_case_globals)] + #![allow(non_snake_case)] + #![allow(dead_code)] + #![allow(missing_docs)] + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +pub use raw_bindings::{ + FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkloadContext, OpaqueWorkload, +}; +use raw_bindings::{ + FDBMetric, FDBSeverity, FDBSeverity_FDBSeverity_Debug, FDBSeverity_FDBSeverity_Error, + FDBSeverity_FDBSeverity_Info, FDBSeverity_FDBSeverity_Warn, FDBSeverity_FDBSeverity_WarnAlways, + FDBStringPair, +}; + +pub use raw_bindings::FDBWorkload_FDBWorkload_VT as FDBWorkload_VT; +pub const FDB_WORKLOAD_API_VERSION: i32 = raw_bindings::FDB_WORKLOAD_API_VERSION as i32; + +// ----------------------------------------------------------------------------- +// String conversions + +#[doc(hidden)] +/// # Safety +/// `c_buf` must point to a readable, NUL-terminated string for this call. +/// +/// ```compile_fail,E0133 +/// foundationdb_simulation::internals::str_from_c(std::ptr::null()); +/// ``` +pub unsafe fn str_from_c(c_buf: *const c_char) -> String { + let c_str = unsafe { ffi::CStr::from_ptr(c_buf) }; + c_str.to_str().unwrap().to_string() +} +#[doc(hidden)] +pub fn str_for_c(buf: T) -> ffi::CString +where + T: Into>, +{ + let mut buf = buf.into(); + if buf.contains(&0) { + let mut escaped = Vec::with_capacity(buf.len()); + for byte in buf { + if byte == 0 { + escaped.extend_from_slice(br"\0"); + } else { + escaped.push(byte); + } + } + buf = escaped; + } + + // SAFETY: Every interior NUL byte was escaped above. + unsafe { ffi::CString::from_vec_unchecked(buf) } +} + +/// Capitalizes the first letter of a string. +/// Used to ensure trace detail names start with a capital letter. +/// Returns `None` if the string is empty. +fn capitalize_first(s: &str) -> Option { + let mut chars = s.chars(); + chars + .next() + .map(|first| first.to_uppercase().collect::() + chars.as_str()) +} + +/// ASCII-uppercases the first byte of a trace event name, matching FDB's convention that +/// event types start with a capital letter. Leaves an empty name unchanged. +fn capitalize_first_byte(mut name: Vec) -> Vec { + if let Some(first) = name.first_mut() { + first.make_ascii_uppercase(); + } + name +} + +/// Macro that can be used to create log "details" more easily. +#[macro_export] +macro_rules! details { + ($($k:expr_2021 => $v:expr_2021),* $(,)?) => { + &[ + $(( + &$k.to_string(), &$v.to_string() + )),* + ] + }; +} + +// ----------------------------------------------------------------------------- +// Rust Types + +/// A simulator context shared by one workload and its cloned handles. +/// +/// Context operations panic outside the creating thread or after the registered +/// workload is released. Cloning the context does not extend the native lifetime. +#[derive(Clone)] +pub struct WorkloadContext(Arc); + +struct ContextState { + raw: FDBWorkloadContext, + owner: ThreadId, + active: AtomicBool, +} + +// SAFETY: Raw context access is private and always checks the owning thread and +// lifetime first. No other thread can call the native vtable or invalidate the +// context, and dropping the state does not dereference its raw pointers. +unsafe impl Send for ContextState {} +// SAFETY: The same owner-thread check prevents concurrent native access. +unsafe impl Sync for ContextState {} + +pub(crate) struct ContextOwner(pub(crate) WorkloadContext); + +impl Drop for ContextOwner { + fn drop(&mut self) { + self.0.assert_owner(); + self.0.0.active.store(false, Ordering::Release); + } +} +/// Wrapper around the C FDBPromise +pub struct Promise(FDBPromise); +/// A metrics sink borrowed for one [`crate::RustWorkload::get_metrics`] callback. +/// +/// The native sink is destroyed after the callback returns, so workloads cannot +/// retain it for later use: +/// +/// ```compile_fail,E0521 +/// use std::cell::RefCell; +/// use foundationdb_simulation::{Metrics, RustWorkload, SimDatabase}; +/// +/// struct RetainingWorkload { +/// saved: RefCell>>, +/// } +/// +/// impl RustWorkload for RetainingWorkload { +/// async fn setup(&mut self, _: SimDatabase) {} +/// async fn start(&mut self, _: SimDatabase) {} +/// async fn check(&mut self, _: SimDatabase) {} +/// fn get_metrics(&self, out: Metrics<'_>) { +/// *self.saved.borrow_mut() = Some(out); +/// } +/// fn get_check_timeout(&self) -> f64 { 1.0 } +/// } +/// ``` +pub struct Metrics<'callback>(&'callback mut FDBMetrics); + +/// A single metric entry +#[derive(Clone)] +pub struct Metric<'a> { + /// The name of the metric + pub key: &'a str, + /// The value of the metric + pub val: f64, + /// Indicates if the value represents an average or not + pub avg: bool, + /// C `printf` format for one `double`, defaulting to `%.3g`. + /// + /// Supported formats contain exactly one `a`, `A`, `e`, `E`, `f`, `F`, `g`, + /// or `G` conversion, with optional `-`, `+`, space, `#`, and `0` flags and + /// decimal width and precision no greater than `i32::MAX`. Literal text and + /// `%%` are allowed. Length modifiers, positional arguments, `*`, and other + /// conversions are rejected by [`Metrics::push`] before entering native code. + pub fmt: Option<&'a str>, +} + +/// Indicates the severity of a FoundationDB log entry +#[derive(Clone, Copy)] +#[repr(u32)] +pub enum Severity { + /// debug + Debug = FDBSeverity_FDBSeverity_Debug, + /// info + Info = FDBSeverity_FDBSeverity_Info, + /// warn + Warn = FDBSeverity_FDBSeverity_Warn, + /// warn always + WarnAlways = FDBSeverity_FDBSeverity_WarnAlways, + /// error, this severity automatically breaks execution. `WorkloadContext::trace` also + /// appends a `RustFailure="1"` detail on top of the `RustWorkload="1"` detail added to + /// every event, so trace consumers can tell a Rust workload failure apart from an + /// FDB-internal Sev40 event. + Error = FDBSeverity_FDBSeverity_Error, +} + +// ----------------------------------------------------------------------------- +// Implementations + +macro_rules! with { + ($this:expr_2021=>$method:ident($($args:expr_2021),* $(,)?)) => { + { + let raw = $this; + unsafe { (*raw.vt).$method.unwrap_unchecked()(raw.inner $(, $args)*) } + } + }; +} + +/// Detail key automatically appended to `Severity::Error` trace events. +const RUST_FAILURE_KEY: &str = "RustFailure"; +/// Detail key automatically appended to every trace event, regardless of severity. +const RUST_WORKLOAD_KEY: &str = "RustWorkload"; + +/// Appends a `key="1"` detail to `details_storage` unless a detail with that key (already +/// capitalized) is present. +fn push_marker_if_absent(details_storage: &mut Vec<(ffi::CString, ffi::CString)>, key: &str) { + if !details_storage + .iter() + .any(|(k, _)| k.as_bytes() == key.as_bytes()) + { + details_storage.push((str_for_c(key), str_for_c("1"))); + } +} + +/// Builds the trace detail storage for [`WorkloadContext::trace`]. +/// +/// Applies `capitalize_first` to every caller-supplied key (dropping empty values), then +/// appends a `RustWorkload="1"` detail to every event and, for [`Severity::Error`], a +/// `RustFailure="1"` detail as well, unless the caller already supplied one under that key. +fn prepare_trace_details( + severity: Severity, + details: &[(S2, S3)], +) -> Vec<(ffi::CString, ffi::CString)> +where + S2: AsRef, + S3: AsRef, +{ + let mut details_storage = details + .iter() + .filter_map(|(key, val)| { + let val = val.as_ref(); + if val.is_empty() { + return None; + } + capitalize_first(key.as_ref()).map(|k| (str_for_c(k), str_for_c(val))) + }) + .collect::>(); + push_marker_if_absent(&mut details_storage, RUST_WORKLOAD_KEY); + if matches!(severity, Severity::Error) { + push_marker_if_absent(&mut details_storage, RUST_FAILURE_KEY); + } + details_storage +} + +impl WorkloadContext { + #[doc(hidden)] + /// # Safety + /// Every operation on this context or its clones requires `raw` to identify + /// a live native context with a complete vtable owned by the creating thread. + /// The caller must uphold that lifetime for all handles. Construction, + /// cloning, and dropping do not access the native pointers. Registration + /// hooks invalidate all clones before the native context is destroyed. + /// + /// ```compile_fail,E0133 + /// use foundationdb_simulation::{WorkloadContext, internals::FDBWorkloadContext}; + /// let raw = FDBWorkloadContext { + /// api_version: 1, + /// inner: std::ptr::null_mut(), + /// vt: std::ptr::null_mut(), + /// }; + /// WorkloadContext::new(raw); + /// ``` + pub unsafe fn new(raw: FDBWorkloadContext) -> Self { + Self(Arc::new(ContextState { + raw, + owner: thread::current().id(), + active: AtomicBool::new(true), + })) + } + + fn assert_owner(&self) { + assert_eq!( + self.0.owner, + thread::current().id(), + "simulation context accessed outside its owning thread" + ); + } + + fn raw(&self) -> FDBWorkloadContext { + self.assert_owner(); + assert!( + self.0.active.load(Ordering::Acquire), + "simulation context accessed after workload release" + ); + self.0.raw + } + + /// Get the server FDB_WORKLOAD_API_VERSION + pub fn get_workload_api_version(&self) -> i32 { + self.raw().api_version + } + + /// Add a log entry in the FoundationDB logs. + /// + /// The event `name`'s first byte is uppercased automatically, matching FDB's convention + /// for event types. A `RustWorkload="1"` detail is appended to every event (unless the + /// caller already provided one), so trace consumers can grep all Rust-origin trace lines + /// with a single token. When `severity` is [`Severity::Error`], a `RustFailure="1"` detail + /// is appended too (same rule), so a Rust-detected failure can be told apart from an + /// FDB-internal Sev40 event. + pub fn trace(&self, severity: Severity, name: S, details: &[(S2, S3)]) + where + S: Into>, + S2: AsRef, + S3: AsRef, + { + let name = str_for_c(capitalize_first_byte(name.into())); + let details_storage = prepare_trace_details(severity, details); + let details = details_storage + .iter() + .map(|(key, val)| FDBStringPair { + key: key.as_ptr(), + val: val.as_ptr(), + }) + .collect::>(); + with! { + self.raw() => trace( + severity as FDBSeverity, + name.as_ptr(), + details.as_ptr(), + details.len() as i32, + ) + } + } + /// Get the process id of the workload + pub fn get_process_id(&self) -> u64 { + with! { self.raw() => getProcessID() } + } + /// Switch the simulator's current process using a native process handle. + /// + /// # Safety + /// During simulation, `id` must be an unchanged handle previously obtained + /// from [`Self::get_process_id`] in the same simulator instance, and its + /// native process must remain alive while selected. It is a pointer-valued + /// handle, not an arbitrary numeric process identifier. The caller must + /// ensure intervening operations are valid for that process and restore the + /// previous process before yielding, returning, or unwinding. + /// + /// ```compile_fail,E0133 + /// use foundationdb_simulation::WorkloadContext; + /// fn change_process(context: &WorkloadContext) { + /// context.set_process_id(1); + /// } + /// ``` + pub unsafe fn set_process_id(&self, id: u64) { + with! { self.raw() => setProcessID(id) } + } + /// Get the current simulated time in seconds (starts at zero) + pub fn now(&self) -> f64 { + with! { self.raw() => now() } + } + /// Get a determinist 32-bit random number + pub fn rnd(&self) -> u32 { + with! { self.raw() => rnd() } + } + /// Get the value of a parameter from the simulation config file + /// + /// /!\ getting an option consumes it, following call on that option will return `None` + pub fn get_option(&self, name: &str) -> Option + where + T: FromStr, + { + self.get_option_raw(name) + .and_then(|value| value.parse::().ok()) + } + fn get_option_raw(&self, name: &str) -> Option { + let null = ""; + let name = str_for_c(name); + let default_value = str_for_c(null); + let raw_value = with! { + self.raw() => getOption(name.as_ptr(), default_value.as_ptr()) + }; + // SAFETY: getOption returns an owned, NUL-terminated string. + let value = unsafe { str_from_c(raw_value.inner) }; + with! { raw_value => free() }; + if value == null { None } else { Some(value) } + } + /// Get the client id of the workload + pub fn client_id(&self) -> i32 { + with! { self.raw() => clientId() } + } + /// Get the client id of the workload + pub fn client_count(&self) -> i32 { + with! { self.raw() => clientCount() } + } + /// Get a determinist 64-bit random number + pub fn shared_random_number(&self) -> i64 { + with! { self.raw() => sharedRandomNumber() } + } + /// Return a future that will be ready after a given (simulated) duration + pub fn delay( + &self, + duration: Duration, + ) -> impl std::future::Future> + Send + Sync + 'static + use<> { + let f = with! { self.raw() => delay(duration.as_secs_f64()) }; + // SAFETY: delay returns a new, owned C future with no result value. + unsafe { fdb::future::FdbFuture::new(f as *mut _) } + } +} + +impl Promise { + pub(crate) fn new(raw: FDBPromise) -> Self { + Self(raw) + } + /// Resolve a FoundationDB promise by setting its value to a boolean. + /// You can resolve a Promise only once. + /// + /// note: FoundationDB disregards the value sent, so sending `true` or `false` is equivalent + pub fn send(self, value: bool) { + with! { self.0 => send(value) }; + } +} +impl Drop for Promise { + fn drop(&mut self) { + with! { self.0 => free() }; + } +} + +// Native metric formatting passes exactly one double to printf. Keep this +// grammar narrow so neither the conversion nor width/precision consumes a +// differently typed or additional variadic argument. +fn valid_metric_format(format: &str) -> bool { + fn decimal(input: &mut &[u8]) -> bool { + let mut value = 0_i32; + while let Some((&digit, rest)) = input.split_first() { + if !digit.is_ascii_digit() { + break; + } + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(i32::from(digit - b'0'))) + else { + return false; + }; + value = next; + *input = rest; + } + true + } + + let mut input = format.as_bytes(); + let mut conversion = false; + while let Some((&byte, rest)) = input.split_first() { + input = rest; + if byte != b'%' { + continue; + } + if input.first() == Some(&b'%') { + input = &input[1..]; + continue; + } + if conversion { + return false; + } + while matches!(input.first(), Some(b'-' | b'+' | b' ' | b'#' | b'0')) { + input = &input[1..]; + } + if !decimal(&mut input) { + return false; + } + if input.first() == Some(&b'.') { + input = &input[1..]; + if !decimal(&mut input) { + return false; + } + } + match input.split_first() { + Some((b'a' | b'A' | b'e' | b'E' | b'f' | b'F' | b'g' | b'G', rest)) => { + conversion = true; + input = rest; + } + _ => return false, + } + } + conversion +} + +impl<'callback> Metrics<'callback> { + pub(crate) fn new(raw: &'callback mut FDBMetrics) -> Self { + Self(raw) + } + /// Call std::vector::reserve on the underlying C++ sink + pub fn reserve(&mut self, n: usize) { + with! { &*self.0 => reserve(n as i32) } + } + /// Push a [Metric] entry in the underlying C++ sink. + /// + /// # Panics + /// Panics before calling the native sink if [`Metric::fmt`] does not follow + /// its supported single-double format grammar. + pub fn push(&mut self, metric: Metric) { + let format = metric.fmt.unwrap_or("%.3g"); + assert!( + valid_metric_format(format), + "invalid metric format: {format:?}" + ); + let key_storage = str_for_c(metric.key); + let fmt_storage = str_for_c(format); + with! { + &*self.0 => push(FDBMetric { + key: key_storage.as_ptr(), + fmt: fmt_storage.as_ptr(), + val: metric.val, + avg: metric.avg, + }) + } + } + /// Push several [Metric] entries in the underlying C++ sink + pub fn extend<'a, T>(&mut self, metrics: T) + where + T: IntoIterator>, + { + let metrics = metrics.into_iter(); + let (min, max) = metrics.size_hint(); + self.reserve(max.unwrap_or(min)); + for metric in metrics { + self.push(metric); + } + } +} + +impl<'a> Metric<'a> { + /// Create a metric value entry + pub fn val(key: &'a str, val: V) -> Self + where + V: TryInto, + { + Self { + key, + val: val.try_into().ok().expect("convertion failed"), + avg: false, + fmt: None, + } + } + /// Create a metric average entry + pub fn avg(key: &'a str, val: V) -> Self + where + V: TryInto, + { + Self { + key, + val: val.try_into().ok().expect("convertion failed"), + avg: true, + fmt: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::{Severity, capitalize_first_byte, prepare_trace_details, str_for_c}; + + use super::WorkloadContext; + use super::raw_bindings::*; + use crate::registration::register_workload_context; + use crate::{Metric, Metrics, RustWorkload, SimDatabase}; + use std::cell::Cell; + use std::panic::{AssertUnwindSafe, catch_unwind}; + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + + struct NativeContext { + calls: Arc, + next_random: Cell, + } + + unsafe extern "C" fn now(raw: *mut OpaqueWorkloadContext) -> f64 { + // SAFETY: The test keeps NativeContext alive until workload release. + let context = unsafe { &*(raw as *const NativeContext) }; + context.calls.fetch_add(1, Ordering::Relaxed); + 12.0 + } + + unsafe extern "C" fn rnd(raw: *mut OpaqueWorkloadContext) -> u32 { + // SAFETY: The test keeps NativeContext alive until workload release. + let context = unsafe { &*(raw as *const NativeContext) }; + context.calls.fetch_add(1, Ordering::Relaxed); + let value = context.next_random.get(); + context.next_random.set(value + 1); + value + } + + // The unused callbacks are still present so the fake has a complete vtable. + unsafe extern "C" fn trace( + _: *mut OpaqueWorkloadContext, + _: FDBSeverity, + _: *const std::ffi::c_char, + _: *const FDBStringPair, + _: i32, + ) { + } + unsafe extern "C" fn get_process_id(_: *mut OpaqueWorkloadContext) -> u64 { + 0 + } + unsafe extern "C" fn set_process_id(_: *mut OpaqueWorkloadContext, _: u64) {} + unsafe extern "C" fn free_string(_: *const std::ffi::c_char) {} + unsafe extern "C" fn get_option( + _: *mut OpaqueWorkloadContext, + _: *const std::ffi::c_char, + default: *const std::ffi::c_char, + ) -> FDBString { + FDBString { + inner: default, + vt: &FDBString_FDBString_VT { + free: Some(free_string), + } as *const _ as *mut _, + } + } + unsafe extern "C" fn client_id(_: *mut OpaqueWorkloadContext) -> i32 { + 0 + } + unsafe extern "C" fn client_count(_: *mut OpaqueWorkloadContext) -> i32 { + 1 + } + unsafe extern "C" fn shared_random_number(_: *mut OpaqueWorkloadContext) -> i64 { + 42 + } + unsafe extern "C" fn delay(_: *mut OpaqueWorkloadContext, _: f64) -> *mut FDBFuture { + unreachable!("test does not schedule delays") + } + + const CONTEXT_VT: FDBWorkloadContext_FDBWorkloadContext_VT = + FDBWorkloadContext_FDBWorkloadContext_VT { + trace: Some(trace), + getProcessID: Some(get_process_id), + setProcessID: Some(set_process_id), + now: Some(now), + rnd: Some(rnd), + getOption: Some(get_option), + clientId: Some(client_id), + clientCount: Some(client_count), + sharedRandomNumber: Some(shared_random_number), + delay: Some(delay), + }; + + fn native_context(calls: Arc) -> (Box, FDBWorkloadContext) { + let mut native = Box::new(NativeContext { + calls, + next_random: Cell::new(42), + }); + let raw = FDBWorkloadContext { + api_version: super::FDB_WORKLOAD_API_VERSION, + inner: &mut *native as *mut NativeContext as *mut _, + vt: &CONTEXT_VT as *const _ as *mut _, + }; + (native, raw) + } + + struct TestWorkload(Option); + + impl RustWorkload for TestWorkload { + async fn setup(&mut self, _: SimDatabase) {} + async fn start(&mut self, _: SimDatabase) {} + async fn check(&mut self, _: SimDatabase) {} + fn get_metrics(&self, mut out: Metrics<'_>) { + out.reserve(3); + out.push(Metric::val("operations", 7)); + out.extend([ + Metric::avg("latency", 2.5), + Metric { + key: "completion", + val: 1.0, + avg: false, + fmt: Some("%.0f"), + }, + ]); + } + fn get_check_timeout(&self) -> f64 { + 7.0 + } + } + + impl Drop for TestWorkload { + fn drop(&mut self) { + if let Some(context) = &self.0 { + assert_eq!(context.now(), 12.0); + } + } + } + + #[derive(Default)] + struct NativeMetrics { + reservations: Vec, + entries: Vec<(String, f64, bool, String)>, + } + + unsafe extern "C" fn reserve_metrics(raw: *mut OpaqueMetrics, n: i32) { + // SAFETY: The metrics callback keeps the native sink alive and exclusive. + let sink = unsafe { &mut *(raw as *mut NativeMetrics) }; + sink.reservations.push(n); + sink.entries.reserve(n as usize); + } + + unsafe extern "C" fn push_metric(raw: *mut OpaqueMetrics, metric: FDBMetric) { + // SAFETY: The callback owns the sink and borrows these strings for this call. + let sink = unsafe { &mut *(raw as *mut NativeMetrics) }; + let key = unsafe { super::str_from_c(metric.key) }; + let format = unsafe { super::str_from_c(metric.fmt) }; + sink.entries.push((key, metric.val, metric.avg, format)); + } + + #[test] + fn metrics_callback_writes_to_borrowed_native_sink() { + let workload = TestWorkload(None).wrap(); + let mut sink = NativeMetrics::default(); + let mut metrics_vt = FDBMetrics_FDBMetrics_VT { + reserve: Some(reserve_metrics), + push: Some(push_metric), + }; + let raw_metrics = FDBMetrics { + inner: &mut sink as *mut NativeMetrics as *mut _, + vt: &mut metrics_vt, + }; + + // SAFETY: The workload, sink, and vtable all remain live for this callback. + unsafe { (*workload.0.vt).getMetrics.unwrap()(workload.0.inner, raw_metrics) }; + + assert_eq!(sink.reservations.first(), Some(&3)); + assert_eq!( + sink.entries, + vec![ + ("operations".into(), 7.0, false, "%.3g".into()), + ("latency".into(), 2.5, true, "%.3g".into()), + ("completion".into(), 1.0, false, "%.0f".into()), + ] + ); + } + + #[test] + fn metrics_reject_unsafe_formats_before_calling_the_native_sink() { + let mut sink = NativeMetrics::default(); + let mut metrics_vt = FDBMetrics_FDBMetrics_VT { + reserve: Some(reserve_metrics), + push: Some(push_metric), + }; + let mut raw_metrics = FDBMetrics { + inner: &mut sink as *mut NativeMetrics as *mut _, + vt: &mut metrics_vt, + }; + + for format in [ + None, + Some("%a"), + Some("%A"), + Some("%e"), + Some("%E"), + Some("%f"), + Some("%F"), + Some("%g"), + Some("%G"), + Some("%.0f"), + Some("load=%-+#010.3f%%"), + Some("% .f"), + Some("%% latency %.6g ms %%"), + ] { + super::Metrics::new(&mut raw_metrics).push(Metric { + key: "value", + val: 1.25, + avg: false, + fmt: format, + }); + assert_eq!( + sink.entries.last(), + Some(&("value".into(), 1.25, false, format.unwrap_or("%.3g").into(),)), + ); + } + + let forwarded = sink.entries.len(); + for format in [ + "", + "value %%", + "%n", + "%s", + "%d", + "%c", + "%p", + "%Lf", + "%lf", + "%llf", + "%hf", + "%zf", + "%jf", + "%tf", + "%1$f", + "%1$.*2$f", + "%*f", + "%.*f", + "%*.*f", + "%f %g", + "%f %n", + "%f%", + "%", + "%.", + "%.2", + "%.2.3f", + "%2147483648f", + "%.2147483648f", + "%99999999999999999999999g", + "%g\0%n", + ] { + // Catch on the Rust side: a panic must never cross the C callback. + let rejected = catch_unwind(AssertUnwindSafe(|| { + super::Metrics::new(&mut raw_metrics).push(Metric { + key: "rejected", + val: 1.25, + avg: false, + fmt: Some(format), + }); + })); + assert!(rejected.is_err(), "accepted {format:?}"); + assert_eq!(sink.entries.len(), forwarded, "forwarded {format:?}"); + } + } + + #[cfg(not(fdb_simulation_loom))] + mod lifecycle { + use super::*; + use std::{ + cell::RefCell, + future::{pending, poll_fn}, + rc::Rc, + task::{Poll, Waker}, + }; + + // This unit-test executable supplies the C destroy symbol. The workload + // callbacks still construct the real Database wrapper, so an accidental + // Database::drop is observable without dereferencing a fake C++ object. + #[derive(Default)] + struct NativeDatabase { + releases: Cell, + } + + #[unsafe(no_mangle)] + unsafe extern "C" fn fdb_database_destroy(raw: *mut foundationdb_sys::FDBDatabase) { + let database = unsafe { &*(raw as *const NativeDatabase) }; + database.releases.set(database.releases.get() + 1); + } + + struct NativePromise { + events: Rc>>, + on_send: Option, + on_free: Option, + } + + unsafe extern "C" fn send(raw: *mut OpaquePromise, _: bool) { + let promise = unsafe { &mut *(raw as *mut NativePromise) }; + promise.events.borrow_mut().push("promise send"); + if let Some(workload) = promise.on_send.take() { + unsafe { (*workload.vt).free.unwrap()(workload.inner) }; + } + } + + unsafe extern "C" fn free(raw: *mut OpaquePromise) { + let promise = unsafe { Box::from_raw(raw as *mut NativePromise) }; + promise.events.borrow_mut().push("promise free"); + if let Some(workload) = promise.on_free { + unsafe { (*workload.vt).free.unwrap()(workload.inner) }; + } + } + + const PROMISE_VT: FDBPromise_FDBPromise_VT = FDBPromise_FDBPromise_VT { + send: Some(send), + free: Some(free), + }; + + fn promise( + events: &Rc>>, + on_send: Option, + on_free: Option, + ) -> FDBPromise { + FDBPromise { + inner: Box::into_raw(Box::new(NativePromise { + events: events.clone(), + on_send, + on_free, + })) as *mut _, + vt: &PROMISE_VT as *const _ as *mut _, + } + } + + #[derive(Clone, Copy)] + enum Wait { + Ready, + RetainWaker, + NoWaker, + } + + struct PhaseDrop { + context: WorkloadContext, + events: Rc>>, + } + + impl Drop for PhaseDrop { + fn drop(&mut self) { + assert_eq!(self.context.now(), 12.0); + self.events.borrow_mut().push("phase drop"); + // Destruction may synchronously invoke the executor hook. + crate::fdb_rt::poll_pending_tasks(); + } + } + + struct LifecycleWorkload { + context: WorkloadContext, + events: Rc>>, + waker: Rc>>, + wait: Wait, + completed: usize, + } + + impl LifecycleWorkload { + async fn phase(&mut self, db: SimDatabase) { + let _drop = PhaseDrop { + context: self.context.clone(), + events: self.events.clone(), + }; + self.events.borrow_mut().push("phase poll"); + match self.wait { + Wait::Ready => {} + Wait::RetainWaker => { + poll_fn(|cx| { + *self.waker.borrow_mut() = Some(cx.waker().clone()); + Poll::<()>::Pending + }) + .await; + } + Wait::NoWaker => pending::<()>().await, + } + self.completed += 1; + drop(db); + } + } + + impl RustWorkload for LifecycleWorkload { + async fn setup(&mut self, db: SimDatabase) { + self.phase(db).await; + } + async fn start(&mut self, db: SimDatabase) { + self.phase(db).await; + } + async fn check(&mut self, db: SimDatabase) { + self.phase(db).await; + } + fn get_metrics(&self, _: Metrics<'_>) { + assert_eq!(self.context.now(), 12.0); + self.events.borrow_mut().push("metrics"); + } + fn get_check_timeout(&self) -> f64 { + 7.0 + } + } + + impl Drop for LifecycleWorkload { + fn drop(&mut self) { + assert_eq!(self.context.now(), 12.0); + self.events.borrow_mut().push("workload drop"); + } + } + + fn workload( + raw: FDBWorkloadContext, + events: &Rc>>, + waker: &Rc>>, + wait: Wait, + ) -> FDBWorkload { + // SAFETY: Each test owns the native context until the registered + // workload's unique same-thread free, including reentrant callbacks. + unsafe { + register_workload_context(raw, |context| { + LifecycleWorkload { + context, + events: events.clone(), + waker: waker.clone(), + wait, + completed: 0, + } + .wrap() + }) + } + } + + fn release_native_database(database: &mut NativeDatabase) { + // Exercise the same linked C symbol as Database::drop. Only this + // native owner release is permitted for the phase's borrowed handle. + unsafe { + foundationdb_sys::fdb_database_destroy(database as *mut NativeDatabase as *mut _); + } + assert_eq!(database.releases.get(), 1); + } + + #[test] + fn native_free_cancels_a_suspended_phase_before_workload_and_context() { + let (native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let waker = Rc::new(RefCell::new(None)); + let workload = workload(context, &events, &waker, Wait::RetainWaker); + let mut database = NativeDatabase::default(); + unsafe { + (*workload.vt).check.unwrap()( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, None, None), + ); + assert_eq!(*events.borrow(), ["phase poll"]); + // The native timeout expression may start check before asking + // for its timeout; the pending check still owns a mutable W. + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + assert_eq!(*events.borrow(), ["phase poll"]); + (*workload.vt).free.unwrap()(workload.inner); + } + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "workload drop"] + ); + assert_eq!(database.releases.get(), 0); + drop(native); + waker.borrow_mut().take().unwrap().wake(); + crate::fdb_rt::poll_pending_tasks(); + assert_eq!( + events.borrow().len(), + 4, + "late wake cannot poll freed state" + ); + release_native_database(&mut database); + } + + #[test] + fn metrics_after_timeout_cancel_the_phase_before_accessing_the_workload() { + for free_on_cancel in [false, true] { + let (_native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let waker = Rc::new(RefCell::new(None)); + let workload = workload(context, &events, &waker, Wait::RetainWaker); + let mut database = NativeDatabase::default(); + let mut sink = NativeMetrics::default(); + let mut metrics_vt = FDBMetrics_FDBMetrics_VT { + reserve: Some(reserve_metrics), + push: Some(push_metric), + }; + let raw_metrics = FDBMetrics { + inner: &mut sink as *mut NativeMetrics as *mut _, + vt: &mut metrics_vt, + }; + unsafe { + (*workload.vt).check.unwrap()( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, None, free_on_cancel.then_some(workload)), + ); + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + // The tester requests metrics after its check waiter times + // out, although the Rust phase still retains its waker. + (*workload.vt).getMetrics.unwrap()(workload.inner, raw_metrics); + } + if free_on_cancel { + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "workload drop"] + ); + } else { + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "metrics"] + ); + unsafe { (*workload.vt).free.unwrap()(workload.inner) }; + } + let count = events.borrow().len(); + waker.borrow_mut().take().unwrap().wake(); + crate::fdb_rt::poll_pending_tasks(); + assert_eq!(events.borrow().len(), count); + assert_eq!(database.releases.get(), 0); + release_native_database(&mut database); + } + } + + #[test] + fn checking_cancels_an_abandoned_start_in_either_timeout_argument_order() { + for timeout_first in [false, true] { + let (_native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let waker = Rc::new(RefCell::new(None)); + let workload = workload(context, &events, &waker, Wait::RetainWaker); + let mut start_database = NativeDatabase::default(); + let mut check_database = NativeDatabase::default(); + unsafe { + (*workload.vt).start.unwrap()( + workload.inner, + &mut start_database as *mut NativeDatabase as *mut _, + promise(&events, None, None), + ); + } + let old_waker = waker.borrow_mut().take().unwrap(); + unsafe { + if timeout_first { + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free"] + ); + } + (*workload.vt).check.unwrap()( + workload.inner, + &mut check_database as *mut NativeDatabase as *mut _, + promise(&events, None, None), + ); + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + } + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "phase poll"] + ); + old_waker.wake(); + crate::fdb_rt::poll_pending_tasks(); + assert_eq!(events.borrow().len(), 4); + unsafe { (*workload.vt).free.unwrap()(workload.inner) }; + assert_eq!( + *events.borrow(), + [ + "phase poll", + "phase drop", + "promise free", + "phase poll", + "phase drop", + "promise free", + "workload drop" + ] + ); + drop(waker.borrow_mut().take()); + release_native_database(&mut start_database); + release_native_database(&mut check_database); + } + } + + #[test] + fn timeout_query_can_cancel_an_abandoned_phase_and_free_its_native_owner() { + for setup in [false, true] { + let (native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let waker = Rc::new(RefCell::new(None)); + let workload = workload(context, &events, &waker, Wait::RetainWaker); + let mut database = NativeDatabase::default(); + unsafe { + let phase = if setup { + (*workload.vt).setup.unwrap() + } else { + (*workload.vt).start.unwrap() + }; + phase( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, None, Some(workload)), + ); + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 0.0); + } + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "workload drop"] + ); + assert_eq!(database.releases.get(), 0); + drop(native); + waker.borrow_mut().take().unwrap().wake(); + crate::fdb_rt::poll_pending_tasks(); + assert_eq!(events.borrow().len(), 4); + release_native_database(&mut database); + } + } + + #[test] + fn no_waker_cancellation_preserves_the_callers_database_reference() { + let (_native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let workload = workload( + context, + &events, + &Rc::new(RefCell::new(None)), + Wait::NoWaker, + ); + let mut database = NativeDatabase::default(); + unsafe { + (*workload.vt).check.unwrap()( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, None, Some(workload)), + ); + } + assert_eq!( + *events.borrow(), + ["phase poll", "phase drop", "promise free", "workload drop"] + ); + assert_eq!(database.releases.get(), 0); + release_native_database(&mut database); + } + + #[test] + fn completion_releases_phase_state_before_reentrant_native_free() { + let (_native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let workload = workload(context, &events, &Rc::new(RefCell::new(None)), Wait::Ready); + let mut database = NativeDatabase::default(); + unsafe { + (*workload.vt).setup.unwrap()( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, Some(workload), None), + ); + } + assert_eq!( + *events.borrow(), + [ + "phase poll", + "phase drop", + "promise send", + "workload drop", + "promise free" + ] + ); + assert_eq!(database.releases.get(), 0); + release_native_database(&mut database); + } + + #[test] + fn timeout_is_not_queried_before_setup_initializes_the_workload() { + struct SetupDependentTimeout(Option); + + impl RustWorkload for SetupDependentTimeout { + async fn setup(&mut self, _: SimDatabase) { + self.0 = Some(7.0); + } + async fn start(&mut self, _: SimDatabase) {} + async fn check(&mut self, _: SimDatabase) {} + fn get_metrics(&self, _: Metrics<'_>) {} + fn get_check_timeout(&self) -> f64 { + self.0.expect("setup initializes the check timeout") + } + } + + let (_native, context) = native_context(Arc::new(AtomicUsize::new(0))); + let events = Rc::new(RefCell::new(Vec::new())); + let mut database = NativeDatabase::default(); + unsafe { + let workload = + register_workload_context(context, |_| SetupDependentTimeout(None).wrap()); + (*workload.vt).setup.unwrap()( + workload.inner, + &mut database as *mut NativeDatabase as *mut _, + promise(&events, None, None), + ); + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + (*workload.vt).free.unwrap()(workload.inner); + } + assert_eq!(database.releases.get(), 0); + release_native_database(&mut database); + } + } + + #[test] + fn context_rejects_access_after_registered_workload_release() { + let calls = Arc::new(AtomicUsize::new(0)); + let (native, raw) = native_context(calls.clone()); + let mut escaped = None; + // SAFETY: Native context stays alive through the same-thread free call. + let workload = unsafe { + register_workload_context(raw, |context| { + escaped = Some(context.clone()); + TestWorkload(Some(context)).wrap() + }) + }; + let context = escaped.unwrap(); + assert_eq!(context.now(), 12.0); + assert_eq!(context.rnd(), 42); + // SAFETY: These are the callbacks on the registered, live allocation. + unsafe { + assert_eq!((*workload.vt).getCheckTimeout.unwrap()(workload.inner), 7.0); + (*workload.vt).free.unwrap()(workload.inner); + } + assert_eq!( + calls.load(Ordering::Relaxed), + 3, + "destructor retains live context" + ); + drop(native); + assert!(catch_unwind(AssertUnwindSafe(|| context.now())).is_err()); + assert!(catch_unwind(AssertUnwindSafe(|| context.rnd())).is_err()); + assert_eq!( + calls.load(Ordering::Relaxed), + 3, + "rejected access never enters native context" + ); + } + + #[test] + fn context_rejects_other_threads_without_consuming_randomness() { + let calls = Arc::new(AtomicUsize::new(0)); + let (_native, raw) = native_context(calls.clone()); + let mut escaped = None; + // SAFETY: Native context stays alive through the same-thread free call. + let workload = unsafe { + register_workload_context(raw, |context| { + escaped = Some(context); + TestWorkload(None).wrap() + }) + }; + let context = escaped.unwrap(); + let other_thread = context.clone(); + std::thread::spawn(move || { + assert!(catch_unwind(AssertUnwindSafe(|| other_thread.now())).is_err()); + assert!(catch_unwind(AssertUnwindSafe(|| other_thread.rnd())).is_err()); + }) + .join() + .unwrap(); + assert_eq!(calls.load(Ordering::Relaxed), 0); + assert_eq!(context.rnd(), 42); + assert_eq!(context.rnd(), 43); + assert_eq!(calls.load(Ordering::Relaxed), 2); + // SAFETY: This is the unique release of the registered allocation. + unsafe { (*workload.vt).free.unwrap()(workload.inner) }; + assert!(catch_unwind(AssertUnwindSafe(|| context.rnd())).is_err()); + assert_eq!(calls.load(Ordering::Relaxed), 2); + } + + #[test] + fn factory_unwind_invalidates_escaped_context() { + let calls = Arc::new(AtomicUsize::new(0)); + let (native, raw) = native_context(calls.clone()); + let mut escaped = None; + assert!( + catch_unwind(AssertUnwindSafe(|| unsafe { + // SAFETY: The native context outlives registration, which unwinds. + register_workload_context(raw, |context| { + escaped = Some(context); + panic!("factory failed"); + }) + })) + .is_err() + ); + drop(native); + assert!(catch_unwind(AssertUnwindSafe(|| escaped.unwrap().rnd())).is_err()); + assert_eq!(calls.load(Ordering::Relaxed), 0); + } + + #[test] + fn str_for_c_escapes_interior_nul() { + assert_eq!(str_for_c("a\0b").to_bytes(), br"a\0b"); + } + + #[test] + fn error_severity_gets_rust_failure_marker() { + let details: &[(&str, &str)] = &[("Reason", "boom")]; + let result = prepare_trace_details(Severity::Error, details); + assert!( + result + .iter() + .any(|(k, v)| k.as_bytes() == b"RustFailure" && v.as_bytes() == b"1") + ); + } + + #[test] + fn non_error_severity_has_no_rust_failure_marker() { + let details: &[(&str, &str)] = &[("Reason", "boom")]; + let result = prepare_trace_details(Severity::Warn, details); + assert!(!result.iter().any(|(k, _)| k.as_bytes() == b"RustFailure")); + } + + #[test] + fn caller_lowercase_rust_failure_key_is_not_duplicated() { + let details: &[(&str, &str)] = &[("rustFailure", "custom")]; + let result = prepare_trace_details(Severity::Error, details); + let matching: Vec<_> = result + .iter() + .filter(|(k, _)| k.as_bytes() == b"RustFailure") + .collect(); + assert_eq!(matching.len(), 1); + assert_eq!(matching[0].1.as_bytes(), b"custom"); + } + + #[test] + fn caller_capitalized_rust_failure_key_is_not_duplicated() { + let details: &[(&str, &str)] = &[("RustFailure", "custom")]; + let result = prepare_trace_details(Severity::Error, details); + let matching: Vec<_> = result + .iter() + .filter(|(k, _)| k.as_bytes() == b"RustFailure") + .collect(); + assert_eq!(matching.len(), 1); + assert_eq!(matching[0].1.as_bytes(), b"custom"); + } + + #[test] + fn every_severity_gets_rust_workload_marker() { + let details: &[(&str, &str)] = &[]; + for severity in [ + Severity::Debug, + Severity::Info, + Severity::Warn, + Severity::WarnAlways, + Severity::Error, + ] { + let result = prepare_trace_details(severity, details); + assert!( + result + .iter() + .any(|(k, v)| k.as_bytes() == b"RustWorkload" && v.as_bytes() == b"1") + ); + } + } + + #[test] + fn error_severity_gets_both_markers() { + let details: &[(&str, &str)] = &[]; + let result = prepare_trace_details(Severity::Error, details); + assert!( + result + .iter() + .any(|(k, v)| k.as_bytes() == b"RustWorkload" && v.as_bytes() == b"1") + ); + assert!( + result + .iter() + .any(|(k, v)| k.as_bytes() == b"RustFailure" && v.as_bytes() == b"1") + ); + } + + #[test] + fn caller_lowercase_rust_workload_key_is_not_duplicated() { + let details: &[(&str, &str)] = &[("rustWorkload", "custom")]; + let result = prepare_trace_details(Severity::Info, details); + let matching: Vec<_> = result + .iter() + .filter(|(k, _)| k.as_bytes() == b"RustWorkload") + .collect(); + assert_eq!(matching.len(), 1); + assert_eq!(matching[0].1.as_bytes(), b"custom"); + } + + #[test] + fn caller_capitalized_rust_workload_key_is_not_duplicated() { + let details: &[(&str, &str)] = &[("RustWorkload", "custom")]; + let result = prepare_trace_details(Severity::Info, details); + let matching: Vec<_> = result + .iter() + .filter(|(k, _)| k.as_bytes() == b"RustWorkload") + .collect(); + assert_eq!(matching.len(), 1); + assert_eq!(matching[0].1.as_bytes(), b"custom"); + } + + #[test] + fn capitalize_first_byte_uppercases_lowercase_first_byte() { + assert_eq!(capitalize_first_byte(b"event".to_vec()), b"Event".to_vec()); + } + + #[test] + fn capitalize_first_byte_leaves_already_capitalized_name_unchanged() { + assert_eq!(capitalize_first_byte(b"Event".to_vec()), b"Event".to_vec()); + } + + #[test] + fn capitalize_first_byte_leaves_empty_name_unchanged() { + assert_eq!(capitalize_first_byte(Vec::new()), Vec::new()); + } +} diff --git a/bindings/rust/foundationdb-simulation/src/fdb_rt.rs b/bindings/rust/foundationdb-simulation/src/fdb_rt.rs new file mode 100644 index 00000000000..f434ed1cc37 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/src/fdb_rt.rs @@ -0,0 +1,456 @@ +//! Asynchronous runtime module +//! +//! This module defines the `fdb_spawn` method to run to completion asynchronous tasks containing +//! FoundationDB futures + +#![doc = include_str!("../docs/fdb_rt.md")] + +use std::{ + cell::{Cell, RefCell}, + collections::{BTreeMap, VecDeque}, + future::Future, + pin::Pin, + sync::{Arc, Weak, atomic::Ordering}, + task::{Context, Wake, Waker}, +}; + +#[cfg(all(test, fdb_simulation_loom))] +use loom::sync::{Mutex, atomic::AtomicBool}; +#[cfg(not(all(test, fdb_simulation_loom)))] +use std::sync::{Mutex, atomic::AtomicBool}; + +pub(crate) type TaskId = usize; +type Task = Pin>>; +type Queue = Mutex>; + +thread_local! { + static EXECUTOR: RefCell = RefCell::new(Executor::default()); + static POLLING: Cell = const { Cell::new(false) }; + static CURRENT_TASK: Cell> = const { Cell::new(None) }; +} + +#[derive(Default)] +struct Executor { + tasks: BTreeMap, + queue: Arc, + next_id: TaskId, +} + +enum Notification { + Ready(Arc), + Cancel(TaskId), +} + +/// Wakers may cross threads, but must never own or access an executor's futures. +struct TaskWaker { + id: TaskId, + queue: Weak, + queued: AtomicBool, +} + +impl Wake for TaskWaker { + fn wake(self: Arc) { + if self.queued.swap(true, Ordering::AcqRel) { + return; + } + let Some(queue) = self.queue.upgrade() else { + return; + }; + queue + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .push_back(Notification::Ready(self)); + } +} + +impl Drop for TaskWaker { + fn drop(&mut self) { + // A pending future with no remaining wakers cannot make progress. Only + // its owner may destroy it, even when its last waker dies on another thread. + if let Some(queue) = self.queue.upgrade() { + queue + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .push_back(Notification::Cancel(self.id)); + } + } +} + +fn next_notification(queue: &Queue) -> Option { + let notification = queue + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .pop_front(); + if let Some(Notification::Ready(waker)) = ¬ification { + // Coalesced wakes skip the queue lock. Acquire their publication before + // polling; a later wake sees false and queues another notification. + waker.queued.swap(false, Ordering::AcqRel); + } + notification +} + +struct PollingTaskGuard; + +impl Drop for PollingTaskGuard { + fn drop(&mut self) { + CURRENT_TASK.with(|current| current.set(None)); + } +} + +struct PollingGuard(bool); + +impl Drop for PollingGuard { + fn drop(&mut self) { + let _ = POLLING.try_with(|polling| polling.set(self.0)); + } +} + +/// Poll this thread's queued tasks until no notifications remain. +/// +/// Wakes from other threads are delivered here, on the thread that spawned the +/// future. Calling this function on a different thread cannot poll those tasks. +/// Nested calls defer to the outer drain, so a future is never polled reentrantly. +pub fn poll_pending_tasks() { + // Cancelling a future during thread teardown can invoke the callback hook. + // Its executor is already being destroyed and must not be entered again. + let Ok(queue) = EXECUTOR.try_with(|executor| executor.borrow().queue.clone()) else { + return; + }; + let Ok(false) = POLLING.try_with(|polling| polling.replace(true)) else { + return; + }; + let _guard = PollingGuard(false); + loop { + let Some(notification) = next_notification(&queue) else { + return; + }; + // Neither the queue lock nor the registry borrow may span user polling + // or destruction: either can synchronously wake or spawn another task. + match notification { + Notification::Ready(notification) => { + let id = notification.id; + let future = EXECUTOR.with_borrow_mut(|executor| executor.tasks.remove(&id)); + if let Some(mut future) = future { + CURRENT_TASK.with(|current| current.set(Some(id))); + let _task_guard = PollingTaskGuard; + let waker = Waker::from(notification); + let mut cx = Context::from_waker(&waker); + if future.as_mut().poll(&mut cx).is_pending() { + EXECUTOR.with_borrow_mut(|executor| { + executor.tasks.insert(id, future); + }); + } + } + } + Notification::Cancel(id) => { + let future = EXECUTOR.with_borrow_mut(|executor| executor.tasks.remove(&id)); + drop(future); + } + } + } +} + +/// Queue a task without polling it, so its owner can record the cancellation ID. +pub(crate) fn enqueue(future: F) -> TaskId +where + F: Future + 'static, +{ + let notification = EXECUTOR.with_borrow_mut(|executor| { + let id = executor.next_id; + executor.next_id = id.checked_add(1).expect("simulation task IDs exhausted"); + executor.tasks.insert(id, Box::pin(future)); + Arc::new(TaskWaker { + id, + queue: Arc::downgrade(&executor.queue), + queued: AtomicBool::new(false), + }) + }); + let id = notification.id; + notification.wake(); + id +} + +/// Synchronously release a suspended task before its borrowed native state. +pub(crate) fn cancel(id: TaskId) { + CURRENT_TASK.with(|current| { + assert_ne!( + current.get(), + Some(id), + "the native caller cannot free a workload during its phase poll" + ); + }); + let _guard = PollingGuard(POLLING.with(|polling| polling.replace(true))); + let future = EXECUTOR.with_borrow_mut(|executor| executor.tasks.remove(&id)); + drop(future); +} + +#[cfg(all(test, not(fdb_simulation_loom)))] +fn fdb_spawn(future: F) +where + F: Future + 'static, +{ + enqueue(future); + poll_pending_tasks(); +} + +#[cfg(all(test, not(fdb_simulation_loom)))] +mod tests { + use super::*; + use std::{ + future::poll_fn, + rc::Rc, + sync::mpsc, + task::Poll, + thread::{self, ThreadId}, + }; + + struct LocalFuture { + owner: ThreadId, + polls: Rc>, + drops: Arc>>, + wakers: Arc>>, + ready: Arc, + } + + impl Future for LocalFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + assert_eq!(thread::current().id(), self.owner); + self.polls.set(self.polls.get() + 1); + if self.ready.load(Ordering::Acquire) { + Poll::Ready(()) + } else { + self.wakers.lock().unwrap().push(cx.waker().clone()); + Poll::Pending + } + } + } + + impl Drop for LocalFuture { + fn drop(&mut self) { + assert_eq!(thread::current().id(), self.owner); + // FDB cancellation can synchronously invoke this hook, including + // when the owner thread is destroying its executor registry. + poll_pending_tasks(); + self.drops.lock().unwrap().push(thread::current().id()); + } + } + + #[test] + fn foreign_wakes_only_poll_and_drop_the_future_on_its_owner() { + let owner = thread::current().id(); + let polls = Rc::new(Cell::new(0)); + let drops = Arc::new(Mutex::new(Vec::new())); + let wakers = Arc::new(Mutex::new(Vec::new())); + let ready = Arc::new(AtomicBool::new(false)); + fdb_spawn(LocalFuture { + owner, + polls: polls.clone(), + drops: drops.clone(), + wakers: wakers.clone(), + ready: ready.clone(), + }); + let waker = wakers.lock().unwrap().pop().unwrap(); + let other_waker = waker.clone(); + ready.store(true, Ordering::Release); + let first = thread::spawn(move || { + waker.wake_by_ref(); + waker.wake_by_ref(); + waker.wake(); + poll_pending_tasks(); + }); + let second = thread::spawn(move || { + other_waker.wake(); + poll_pending_tasks(); + }); + first.join().unwrap(); + second.join().unwrap(); + assert_eq!(polls.get(), 1); + assert!(drops.lock().unwrap().is_empty()); + + poll_pending_tasks(); + assert_eq!(polls.get(), 2); + assert_eq!(*drops.lock().unwrap(), [owner]); + } + + #[test] + fn dropping_the_last_foreign_waker_cancels_on_the_owner() { + let owner = thread::current().id(); + let polls = Rc::new(Cell::new(0)); + let drops = Arc::new(Mutex::new(Vec::new())); + let wakers = Arc::new(Mutex::new(Vec::new())); + fdb_spawn(LocalFuture { + owner, + polls: polls.clone(), + drops: drops.clone(), + wakers: wakers.clone(), + ready: Arc::new(AtomicBool::new(false)), + }); + let waker = wakers.lock().unwrap().pop().unwrap(); + thread::spawn(move || { + drop(waker); + poll_pending_tasks(); + }) + .join() + .unwrap(); + assert!(drops.lock().unwrap().is_empty()); + + poll_pending_tasks(); + assert_eq!(polls.get(), 1); + assert_eq!(*drops.lock().unwrap(), [owner]); + } + + #[test] + fn duplicate_and_reentrant_wakes_are_polled_after_the_current_poll() { + let polls = Rc::new(Cell::new(0)); + let observed_polls = polls.clone(); + fdb_spawn(poll_fn(move |cx| { + let count = observed_polls.get() + 1; + observed_polls.set(count); + if count == 1 { + cx.waker().wake_by_ref(); + cx.waker().wake_by_ref(); + poll_pending_tasks(); + assert_eq!(observed_polls.get(), 1); + Poll::Pending + } else { + Poll::Ready(()) + } + })); + assert_eq!(polls.get(), 2); + } + + #[test] + fn spawning_during_a_poll_defers_the_child_until_the_parent_returns() { + let events = Rc::new(RefCell::new(Vec::new())); + let observed_events = events.clone(); + fdb_spawn(async move { + observed_events.borrow_mut().push("parent enter"); + let child_events = observed_events.clone(); + fdb_spawn(async move { + child_events.borrow_mut().push("child"); + }); + observed_events.borrow_mut().push("parent exit"); + }); + assert_eq!(*events.borrow(), ["parent enter", "parent exit", "child"]); + } + + #[test] + fn a_pending_future_without_a_waker_is_released() { + let held = Rc::new(()); + let future_held = held.clone(); + fdb_spawn(poll_fn(move |_| { + let _ = &future_held; + Poll::Pending + })); + assert_eq!(Rc::strong_count(&held), 1); + } + + #[test] + fn completed_task_wakers_do_not_retain_or_repoll_its_future() { + let held = Rc::new(()); + let future_held = held.clone(); + let wakers = Arc::new(Mutex::new(Vec::new())); + let captured_wakers = wakers.clone(); + fdb_spawn(poll_fn(move |cx| { + let _ = &future_held; + captured_wakers.lock().unwrap().push(cx.waker().clone()); + Poll::Ready(()) + })); + assert_eq!(Rc::strong_count(&held), 1); + let waker = wakers.lock().unwrap().pop().unwrap(); + thread::spawn(move || waker.wake()).join().unwrap(); + poll_pending_tasks(); + assert_eq!(Rc::strong_count(&held), 1); + } + + #[test] + fn owner_exit_releases_local_futures_while_foreign_wakers_remain_valid() { + let drops = Arc::new(Mutex::new(Vec::new())); + let observed_drops = drops.clone(); + let (sender, receiver) = mpsc::channel(); + let owner = thread::spawn(move || { + let owner = thread::current().id(); + let wakers = Arc::new(Mutex::new(Vec::new())); + fdb_spawn(LocalFuture { + owner, + polls: Rc::new(Cell::new(0)), + drops: observed_drops, + wakers: wakers.clone(), + ready: Arc::new(AtomicBool::new(false)), + }); + sender.send(wakers.lock().unwrap().pop().unwrap()).unwrap(); + owner + }) + .join() + .unwrap(); + let waker = receiver.recv().unwrap(); + assert_eq!(*drops.lock().unwrap(), [owner]); + waker.wake_by_ref(); + drop(waker); + poll_pending_tasks(); + } +} + +#[cfg(all(test, fdb_simulation_loom))] +mod loom_tests { + use super::*; + use std::{future::poll_fn, task::Poll}; + + #[test] + fn coalesced_wake_publishes_before_poll() { + loom::model(|| { + let queue = Arc::new(Queue::default()); + let notification = Arc::new(TaskWaker { + id: 0, + queue: Arc::downgrade(&queue), + queued: AtomicBool::new(false), + }); + let retained_waker = Waker::from(notification.clone()); + retained_waker.wake_by_ref(); + + let ready = Arc::new(AtomicBool::new(false)); + let wake_done = Arc::new(AtomicBool::new(false)); + let producer_ready = ready.clone(); + let producer_done = wake_done.clone(); + let producer_waker = retained_waker.clone(); + let producer = loom::thread::spawn(move || { + producer_ready.store(true, Ordering::Relaxed); + producer_waker.wake_by_ref(); + producer_done.store(true, Ordering::Relaxed); + }); + + // Schedule the second wake while the first is still queued, without + // acquiring its writes through a join, channel, or readiness flag. + while !wake_done.load(Ordering::Relaxed) { + loom::thread::yield_now(); + } + let Some(Notification::Ready(waker)) = next_notification(&queue) else { + panic!("expected the queued wake"); + }; + let waker = Waker::from(waker); + let mut future = std::pin::pin!(poll_fn(|_| { + if ready.load(Ordering::Relaxed) { + Poll::Ready(()) + } else { + Poll::Pending + } + })); + assert!( + future + .as_mut() + .poll(&mut Context::from_waker(&waker)) + .is_ready(), + "the coalesced wake must publish readiness before polling" + ); + producer.join().unwrap(); + + // std Arc/Waker preserve the real Wake implementation. These + // retained references prevent final-drop synchronization from + // supplying the acquire that the queue must provide itself. + drop(retained_waker); + drop(notification); + }); + } +} diff --git a/bindings/rust/foundationdb-simulation/src/lib.rs b/bindings/rust/foundationdb-simulation/src/lib.rs new file mode 100644 index 00000000000..228dea07001 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/src/lib.rs @@ -0,0 +1,494 @@ +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use std::{ + cell::{Cell, RefCell}, + future::Future, + mem::ManuallyDrop, + pin::Pin, + ptr::NonNull, + rc::Rc, + sync::Arc, + task::{Context, Poll}, +}; + +use foundationdb::Database; +use foundationdb_sys::FDBDatabase as FDBDatabaseAlias; + +mod bindings; +mod fdb_rt; +mod registration; + +use bindings::{ + FDB_WORKLOAD_API_VERSION, FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkload_VT, + OpaqueWorkload, Promise, +}; +pub use bindings::{Metric, Metrics, Severity, WorkloadContext}; +use fdb_rt::{TaskId, poll_pending_tasks}; + +// ----------------------------------------------------------------------------- +// User friendly types + +/// Rust representation of a simulated FoundationDB database +pub type SimDatabase = Arc; +/// An owned FoundationDB workload created by [`RustWorkload::wrap`]. +/// +/// The native pointer and callback table are private so safe factories cannot +/// return an invalid workload. +/// +/// ```compile_fail,E0308 +/// use foundationdb_simulation::{WrappedWorkload, internals::FDBWorkload}; +/// let raw = FDBWorkload { +/// api_version: 1, +/// inner: std::ptr::null_mut(), +/// vt: std::ptr::null_mut(), +/// }; +/// let _: WrappedWorkload = raw; +/// ``` +pub struct WrappedWorkload(FDBWorkload); + +impl Drop for WrappedWorkload { + fn drop(&mut self) { + // SAFETY: Only RustWorkload::wrap constructs this owned allocation and + // its matching vtable, and registration transfers rather than copies it. + unsafe { (*self.0.vt).free.unwrap()(self.0.inner) }; + } +} + +/// Equivalent to the C++ abstract class `FDBWorkload` +#[allow(async_fn_in_trait)] +pub trait RustWorkload: Sized + 'static { + /// This method is called by the tester during the setup phase. + /// It should be used to populate the database. + /// + /// # Arguments + /// + /// * `db` - The simulated database. + async fn setup(&mut self, db: SimDatabase); + + /// This method should run the actual test. + /// + /// # Arguments + /// + /// * `db` - The simulated database. + async fn start(&mut self, db: SimDatabase); + + /// This method is called when the tester completes. + /// A workload should run any consistency/correctness tests during this phase. + /// + /// # Arguments + /// + /// * `db` - The simulated database. + async fn check(&mut self, db: SimDatabase); + + /// If a workload collects metrics (like latencies or throughput numbers), these should be reported back here. + /// The multitester (or test orchestrator) will collect all metrics from all test clients and it will aggregate them. + /// + /// # Arguments + /// + /// * `out` - A metric sink borrowed for this callback + fn get_metrics(&self, out: Metrics<'_>); + + /// Set the check timeout in simulated seconds for this workload. + fn get_check_timeout(&self) -> f64; + + /// Wrap the underlying Rust type so it can be passed to the C API + fn wrap(self) -> WrappedWorkload { + let inner = Box::into_raw(Box::new(Rc::new(WorkloadState { + workload: RefCell::new(Some(Box::new(self))), + task: Cell::new(None), + active_check_timeout: Cell::new(None), + releasing_phase: Cell::new(false), + }))); + WrappedWorkload(FDBWorkload { + api_version: FDB_WORKLOAD_API_VERSION, + inner: inner as *mut _, + vt: &WorkloadVtable::::VT as *const _ as *mut _, + }) + } +} + +struct WorkloadVtable(std::marker::PhantomData); + +impl WorkloadVtable { + const VT: FDBWorkload_VT = FDBWorkload_VT { + setup: Some(workload_setup::), + start: Some(workload_start::), + check: Some(workload_check::), + getMetrics: Some(workload_get_metrics::), + getCheckTimeout: Some(workload_get_check_timeout::), + free: Some(workload_drop::), + }; +} + +/// Equivalent to the C++ abstract class `FDBWorkloadFactory` +pub trait RustWorkloadFactory { + /// The runtime FDB_API_VERSION to use + const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION; + /// If the test file contains a key-value pair workloadName the value will be passed to this method (empty string otherwise). + /// This way, a library author can implement many workloads in one library and use the test file to chose which one to run + /// (or run multiple workloads either concurrently or serially). + fn create(name: String, context: WorkloadContext) -> WrappedWorkload; +} + +/// Automatically implements a WorkloadFactory for a single workload +pub trait SingleRustWorkload: RustWorkload { + /// The runtime FDB_API_VERSION to use + const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION; + /// The implicit WorkloadFactory will call this method uppon each instantiation + fn new(name: String, context: WorkloadContext) -> Self; +} + +// ----------------------------------------------------------------------------- +// C to Rust bindings + +struct WorkloadState { + workload: RefCell>>, + task: Cell>, + active_check_timeout: Cell>, + releasing_phase: Cell, +} + +// The phase owns W while suspended, rather than borrowing an allocation that the +// native free callback can destroy. Cancellation returns it before owner teardown. +struct PhaseWorkload { + workload: Option>, + state: Rc>, +} + +impl Drop for PhaseWorkload { + fn drop(&mut self) { + *self.state.workload.borrow_mut() = self.workload.take(); + } +} + +// The native caller owns this database reference. The guard always outlives the +// user future, including cancellation, and releases only the Rust Arc allocation. +struct PhaseDatabase(ManuallyDrop); + +impl Drop for PhaseDatabase { + fn drop(&mut self) { + // SAFETY: This is the guard's only release, and ManuallyDrop prevents a + // borrowed native reference from being destroyed on any error path. + let database = unsafe { ManuallyDrop::take(&mut self.0) }; + if Arc::strong_count(&database) != 1 || Arc::weak_count(&database) != 0 { + eprintln!( + "Reference to Database kept after phase completion or cancellation. All references must be dropped." + ); + std::process::exit(1); + } + let Ok(database) = Arc::try_unwrap(database) else { + unreachable!("the phase owns the only database reference") + }; + let _borrowed = ManuallyDrop::new(database); + } +} + +struct PhaseTask { + future: Option>>>, + database: Option, + state: Option>>, + done: Option, +} + +impl PhaseTask { + fn release_phase(&mut self) { + if let Some(state) = self.state.take() { + state.releasing_phase.set(true); + drop(self.future.take()); + drop(self.database.take()); + state.releasing_phase.set(false); + state.active_check_timeout.set(None); + state.task.set(None); + // Do not retain W across native promise callbacks: resolving or + // releasing a promise may synchronously free the registered workload. + drop(state); + } + } +} + +impl Future for PhaseTask { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let this = self.get_mut(); + if this.future.as_mut().unwrap().as_mut().poll(cx).is_pending() { + return Poll::Pending; + } + this.release_phase(); + this.done.take().unwrap().send(true); + Poll::Ready(()) + } +} + +impl Drop for PhaseTask { + fn drop(&mut self) { + self.release_phase(); + } +} + +#[cfg(coverage)] +fn write_coverage_profile() { + unsafe extern "C" { + fn __llvm_profile_reset_counters(); + fn __llvm_profile_write_file() -> i32; + } + + // fdbserver exits with `_exit`, so LLVM's normal process-exit hook cannot + // persist a profile from this dynamically loaded workload. + let result = unsafe { __llvm_profile_write_file() }; + if result == 0 { + // Every simulator client shares the DSO's counters. Reset only after a + // successful snapshot so later client callbacks add, rather than repeat, + // counts in LLVM_PROFILE_FILE's %m-merged profile. + unsafe { __llvm_profile_reset_counters() }; + } else { + eprintln!("failed to write LLVM coverage profile (status {result})"); + } +} + +enum Phase { + Setup, + Start, + Check, +} + +// A native timeout abandons its waiter without notifying the Rust task. +// Callbacks requiring idle W must end the previous phase's exclusive access. +unsafe fn idle_workload( + raw_workload: *mut OpaqueWorkload, +) -> Option>> { + let state = unsafe { &*(raw_workload as *const Rc>) }; + let weak = Rc::downgrade(state); + let task = state.task.take(); + if let Some(task) = task { + // Releasing the old promise can synchronously free the native owner. + // A strong Rc here would delay W's destructor past context invalidation. + fdb_rt::cancel(task); + } + weak.upgrade() +} + +unsafe fn spawn_phase( + raw_workload: *mut OpaqueWorkload, + raw_database: *mut FDBDatabase, + raw_promise: FDBPromise, + phase: Phase, +) { + // SAFETY: wrap allocates this owner and the ABI invokes its callbacks on the + // same thread, serially, until its unique free callback. + let Some(state) = (unsafe { idle_workload::(raw_workload) }) else { + drop(Promise::new(raw_promise)); + return; + }; + let workload = state + .workload + .borrow_mut() + .take() + .expect("workload phases cannot overlap"); + // Native callers may evaluate check() before getCheckTimeout(). Sampling + // while W is idle avoids aliasing a suspended phase's exclusive borrow. + if matches!(phase, Phase::Check) { + state + .active_check_timeout + .set(Some(workload.get_check_timeout())); + } + let mut workload = PhaseWorkload { + workload: Some(workload), + state: state.clone(), + }; + let ptr = NonNull::new(raw_database as *mut FDBDatabaseAlias) + .expect("the simulator must supply a database"); + // SAFETY: The native caller supplies a live pointer for this phase. The + // guard suppresses Database::drop and rejects references escaping the phase. + let database = PhaseDatabase(ManuallyDrop::new(Arc::new(unsafe { + Database::new_from_pointer(ptr) + }))); + let borrowed_database = Arc::clone(&database.0); + let future = async move { + let inner = workload.workload.as_mut().unwrap(); + match phase { + Phase::Setup => inner.setup(borrowed_database).await, + Phase::Start => inner.start(borrowed_database).await, + Phase::Check => inner.check(borrowed_database).await, + } + // Keep the owning guard, rather than only its workload field, captured + // until the user future has released every borrow of W. + drop(workload); + }; + let task = PhaseTask { + future: Some(Box::pin(future)), + database: Some(database), + state: Some(state.clone()), + done: Some(Promise::new(raw_promise)), + }; + state.task.set(Some(fdb_rt::enqueue(task))); + drop(state); + poll_pending_tasks(); +} + +unsafe extern "C" fn workload_setup( + raw_workload: *mut OpaqueWorkload, + raw_database: *mut FDBDatabase, + raw_promise: FDBPromise, +) { + unsafe { spawn_phase::(raw_workload, raw_database, raw_promise, Phase::Setup) }; +} + +unsafe extern "C" fn workload_start( + raw_workload: *mut OpaqueWorkload, + raw_database: *mut FDBDatabase, + raw_promise: FDBPromise, +) { + unsafe { spawn_phase::(raw_workload, raw_database, raw_promise, Phase::Start) }; +} + +unsafe extern "C" fn workload_check( + raw_workload: *mut OpaqueWorkload, + raw_database: *mut FDBDatabase, + raw_promise: FDBPromise, +) { + unsafe { spawn_phase::(raw_workload, raw_database, raw_promise, Phase::Check) }; +} +unsafe extern "C" fn workload_get_metrics( + raw_workload: *mut OpaqueWorkload, + mut raw_metrics: FDBMetrics, +) { + unsafe { + let Some(state) = idle_workload::(raw_workload) else { + return; + }; + let workload = state.workload.borrow(); + let workload = workload + .as_ref() + .expect("cancellation restores the workload"); + let out = Metrics::new(&mut raw_metrics); + workload.get_metrics(out); + #[cfg(coverage)] + write_coverage_profile(); + } +} +unsafe extern "C" fn workload_get_check_timeout( + raw_workload: *mut OpaqueWorkload, +) -> f64 { + unsafe { + let state = &*(raw_workload as *const Rc>); + if let Some(timeout) = state.active_check_timeout.get() { + // Check can still hold an exclusive borrow of W when the native + // caller evaluates the timeout after starting that phase. + return timeout; + } + // The opposite argument order can leave an abandoned setup/start + // holding W. Cancel it before reading the workload's current timeout. + let Some(state) = idle_workload::(raw_workload) else { + // Releasing the old promise synchronously freed the native owner. + return 0.0; + }; + let workload = state.workload.borrow(); + workload + .as_ref() + .expect("cancellation restores the workload") + .get_check_timeout() + } +} +unsafe extern "C" fn workload_drop(raw_workload: *mut OpaqueWorkload) { + let owner = unsafe { &*(raw_workload as *const Rc>) }; + assert!( + !owner.releasing_phase.get(), + "the native caller cannot free a workload during its phase destructor" + ); + let state = unsafe { Box::from_raw(raw_workload as *mut Rc>) }; + if let Some(task) = state.task.take() { + fdb_rt::cancel(task); + } + drop(state); +} + +// ----------------------------------------------------------------------------- +// Registration hooks + +#[doc(hidden)] +/// Primitives exposed for the registrations hooks, should not be used otherwise +pub mod internals { + pub use crate::bindings::{FDBWorkload, FDBWorkloadContext, str_from_c}; + pub use crate::fdb_rt::poll_pending_tasks; + pub use crate::registration::register_workload_context; +} + +/// Register a [RustWorkloadFactory]. +/// /!\ Should be called only once. +#[macro_export] +macro_rules! register_factory { + ($name:ident) => { + #[unsafe(no_mangle)] + unsafe extern "C" fn workloadCFactory( + raw_name: *const std::ffi::c_char, + raw_context: $crate::internals::FDBWorkloadContext, + ) -> $crate::internals::FDBWorkload { + use std::sync::atomic::{AtomicBool, Ordering}; + static DONE: AtomicBool = AtomicBool::new(false); + if DONE + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + let version = <$name as $crate::RustWorkloadFactory>::FDB_API_VERSION; + foundationdb::api::FdbApiBuilder::default() + .set_runtime_version(version as i32) + .build() + .expect("the workload API must match the shared C client selection"); + println!("FDB API version selected: {version}"); + foundationdb::future::CUSTOM_EXECUTOR_HOOK + .set($crate::internals::poll_pending_tasks) + .unwrap(); + } + // SAFETY: The simulator supplies a live string and context, invokes + // callbacks on this thread, and releases the workload before context. + unsafe { + let name = $crate::internals::str_from_c(raw_name); + $crate::internals::register_workload_context(raw_context, |context| { + <$name as $crate::RustWorkloadFactory>::create(name, context) + }) + } + } + }; +} + +/// Register a [SingleRustWorkload] and creates an implicit WorkloadFactory. +/// /!\ Should be called only once. +#[macro_export] +macro_rules! register_workload { + ($name:ident) => { + #[unsafe(no_mangle)] + unsafe extern "C" fn workloadCFactory( + raw_name: *const std::ffi::c_char, + raw_context: $crate::internals::FDBWorkloadContext, + ) -> $crate::internals::FDBWorkload { + use std::sync::atomic::{AtomicBool, Ordering}; + static DONE: AtomicBool = AtomicBool::new(false); + if DONE + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + let version = <$name as $crate::SingleRustWorkload>::FDB_API_VERSION; + foundationdb::api::FdbApiBuilder::default() + .set_runtime_version(version as i32) + .build() + .expect("the workload API must match the shared C client selection"); + println!("FDB API version selected: {version}"); + foundationdb::future::CUSTOM_EXECUTOR_HOOK + .set($crate::internals::poll_pending_tasks) + .unwrap(); + } + // SAFETY: The simulator supplies a live string and context, invokes + // callbacks on this thread, and releases the workload before context. + unsafe { + let name = $crate::internals::str_from_c(raw_name); + $crate::internals::register_workload_context(raw_context, |context| { + $crate::RustWorkload::wrap(<$name as $crate::SingleRustWorkload>::new( + name, context, + )) + }) + } + } + }; +} diff --git a/bindings/rust/foundationdb-simulation/src/registration.rs b/bindings/rust/foundationdb-simulation/src/registration.rs new file mode 100644 index 00000000000..4655adf0155 --- /dev/null +++ b/bindings/rust/foundationdb-simulation/src/registration.rs @@ -0,0 +1,95 @@ +//! Owns the lifetime shared by a registered workload and its context handles. + +use crate::bindings::{ + ContextOwner, FDB_WORKLOAD_API_VERSION, FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, + FDBWorkload_VT, FDBWorkloadContext, OpaqueWorkload, +}; +use crate::{WorkloadContext, WrappedWorkload}; + +struct RegisteredWorkload { + // Field order keeps the context live through the user's workload destructor. + workload: WrappedWorkload, + _context: ContextOwner, +} + +/// Registers a workload and invalidates its context when the simulator releases it. +/// +/// # Safety +/// `raw_context` must be a valid context on its owning simulator thread, with a +/// complete vtable. The simulator must keep it alive until it calls the returned +/// workload's `free` callback. All callbacks must run on that same thread, follow +/// the workload ABI contracts, and `free` must be called at most once. +/// +/// ```compile_fail,E0133 +/// use foundationdb_simulation::internals::{FDBWorkloadContext, register_workload_context}; +/// let raw = FDBWorkloadContext { +/// api_version: 1, +/// inner: std::ptr::null_mut(), +/// vt: std::ptr::null_mut(), +/// }; +/// register_workload_context(raw, |_| unreachable!()); +/// ``` +#[doc(hidden)] +pub unsafe fn register_workload_context( + raw_context: FDBWorkloadContext, + create: impl FnOnce(WorkloadContext) -> WrappedWorkload, +) -> FDBWorkload { + // SAFETY: This function's caller supplies the native lifetime; ContextOwner + // revokes every clone when this lifetime ends, including factory unwinding. + let context = ContextOwner(unsafe { WorkloadContext::new(raw_context) }); + let workload = create(context.0.clone()); + FDBWorkload { + api_version: FDB_WORKLOAD_API_VERSION, + inner: Box::into_raw(Box::new(RegisteredWorkload { + workload, + _context: context, + })) as *mut _, + vt: &VT as *const _ as *mut _, + } +} + +const VT: FDBWorkload_VT = FDBWorkload_VT { + setup: Some(setup), + start: Some(start), + check: Some(check), + getMetrics: Some(get_metrics), + getCheckTimeout: Some(get_check_timeout), + free: Some(free), +}; + +macro_rules! forward { + ($raw:ident, $method:ident $(, $arg:ident)*) => {{ + // SAFETY: The simulator returns the registered allocation to its matching + // callback with arguments satisfying the external-workload ABI. + unsafe { + let registered = &*($raw as *const RegisteredWorkload); + let workload = ®istered.workload.0; + (*workload.vt).$method.unwrap()(workload.inner $(, $arg)*) + } + }}; +} + +unsafe extern "C" fn setup(raw: *mut OpaqueWorkload, db: *mut FDBDatabase, done: FDBPromise) { + forward!(raw, setup, db, done); +} + +unsafe extern "C" fn start(raw: *mut OpaqueWorkload, db: *mut FDBDatabase, done: FDBPromise) { + forward!(raw, start, db, done); +} + +unsafe extern "C" fn check(raw: *mut OpaqueWorkload, db: *mut FDBDatabase, done: FDBPromise) { + forward!(raw, check, db, done); +} + +unsafe extern "C" fn get_metrics(raw: *mut OpaqueWorkload, out: FDBMetrics) { + forward!(raw, getMetrics, out); +} + +unsafe extern "C" fn get_check_timeout(raw: *mut OpaqueWorkload) -> f64 { + forward!(raw, getCheckTimeout) +} + +unsafe extern "C" fn free(raw: *mut OpaqueWorkload) { + // SAFETY: The simulator releases this registered allocation exactly once. + unsafe { drop(Box::from_raw(raw as *mut RegisteredWorkload)) }; +} diff --git a/bindings/rust/foundationdb-sys/CHANGELOG.md b/bindings/rust/foundationdb-sys/CHANGELOG.md new file mode 100644 index 00000000000..949d566fabd --- /dev/null +++ b/bindings/rust/foundationdb-sys/CHANGELOG.md @@ -0,0 +1,198 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.11.0] - 2026-06-24 + +### 🚀 Features + +- Feat: migrate workspace to Rust 2024 edition (#255) + +### 🐛 Bug Fixes + +- Fix(sys): add /usr/local/lib to macOS linker search path + +[0.11.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.10.0..0.11.0 + + +## [0.10.0] - 2025-10-24 + +### ⚠️ BREAKING CHANGES + +- Feat: add compile errors for missing version features + +### 🚀 Features + +- Add support 7.4 + +### ⚙️ Miscellaneous + +- Bump MSRV to 1.82 +- Clippy --fix +- Bump msrv to 1.85 + +[0.10.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.1..0.10.0 + + +## [0.9.1] - 2024-10-08 + +### 🆙 Bump + +- Bump MSRV to 1.71.1 +- Bump all dependencies and setup dependabot + +### ⚙️ Other changes + +- Generate CHANGELOG per crate + +[0.9.1]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.0..0.9.1 + +## [0.9.0] - 2024-03-15 + +### 🚀 added + +- Add support for 7.3 +- Bump fdb crate to 0.9.0 and sim to 0.2.0 + +### 🆙 Bump + +- Bump msrv +- Bump dependencies +- Bump dep + +### ⚙️ Other changes + +- Chore: link against libc + +## [0.8.0] - 2023-06-07 + +### 🆙 Bump + +- Chore: bump rust to 1.57 +- Bump msrv +- Bump deps +- Bump msrv for bindgen and env_logger +- Bump dependencies + +### ⚙️ Other changes + +- Initial support for Nix +- Build on docs.rs with the required features +- Prepare release + +## [0.7.0] - 2022-07-11 + +### 🚀 added + +- Add support for api 630 +- Bump crate version, maintainers and documentation +- Bump rust edition to 2021 +- Add support for api 700 +- Implement Iterator for FdbKeys +- Initial commit to support fdb 7.1 + +### 🐛 Bug Fixes + +- Fix: nighly build by removing layout tests + +### 🚜 Refactor + +- Remove clikengo link in Cargo.toml + +### 🆙 Bump + +- Bump version to 5.0.1 +- Bump deps + +### ⚙️ Other changes + +- Removing reference to master branch +- Prepare for 0.7.0 + +## [0.5.0] - 2020-09-17 + +### 🐛 Bug Fixes + +- Cargo.toml fix docs.rs features +- Cargo.toml fix badges links +- Fix Cargo.toml repository links +- Fix #170: protect boot from undefined behavior +- Cargo clippy fixes +- Fix code coverage ci + +### 🆙 Bump + +- Bump version to 0.4.1 + +### ⚙️ Other changes + +- Core: auto-generate all code at build time +- Move CI to FoundationDB 5.2.5 +- Foundationdb 5.2.5 -> 6.0.15 +- Update bindgen requirement from 0.36 to 0.43 +- Upgrade rand from 0.4 to 0.6 (#104) +- Cleanup the fdb version features from primary crate (#99) +- Update bindgen requirement from 0.43 to 0.44 +- Update bindgen requirement from 0.44 to 0.46 +- Update bindgen requirement from 0.46 to 0.47 +- Rust edition 2018 +- Async/await: complete rewrite of foundationdb +- Update bindgen to latest version +- Foundationdb-sys version is now 0.4.0 +- Update bindgen requirement from 0.47 to 0.48 +- Update bindgen requirement from 0.48 to 0.49 +- Update bindgen requirement from 0.49 to 0.50 +- Update bindgen requirement from 0.50 to 0.51 +- Update Cargo.toml authors +- Update bindgen requirement from 0.51.1 to 0.52.0 +- Embed 6.2.10 fdb_c.h and fdb.options +- Embed all supported version includes +- Default to locally available foundationdb include +- Move include inside crate folders +- Foundationdb-gen/sys README +- Cargo.toml badges & docs.rs metadata +- Update bindgen requirement from 0.52.0 to 0.53.2 +- Update fdb 620 includes to 6.2.20 +- Update fdb 610 includes to 6.1.13 +- Default to foundation 620 api +- Update bindgen to 0.55.1 + +## [0.3.0] - 2018-11-16 + +### 🚀 added + +- Add some READMEs +- Add metadata +- Add docs link to Cargo.toml + +### 🐛 Bug Fixes + +- Fix some urls + +### 🆙 Bump + +- Bump versions + +### ⚙️ Other changes + +- Initial library create for foundationdb rust bindings +- Initial binding generation working +- Linux and macos in std header paths +- Update readme and cargo.toml +- Track bindings.rs in Git +- Update bindings from linux bindgen +- Prepare patch release of sys 0.1.1 +- Win64 support +- Prepare 0.3 release + +[unreleased]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.9.0..HEAD +[0.9.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.8.0..v0.9.0 +[0.8.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.7.0..v0.8.0 +[0.7.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.5.0..v0.7.0 +[0.5.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.3.0..0.5.0 +[0.3.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2..v0.3.0 + + diff --git a/bindings/rust/foundationdb-sys/Cargo.toml b/bindings/rust/foundationdb-sys/Cargo.toml new file mode 100644 index 00000000000..385dd30ec60 --- /dev/null +++ b/bindings/rust/foundationdb-sys/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "foundationdb-sys" +version = "0.11.0" +authors = [ + "Benjamin Fry ", + "Vincent Rouillé ", + "Pierre Zemb " +] +edition = "2024" +rust-version = "1.85.1" + +description = """ +Bindings to the C api for FoundationDB +""" + +documentation = "https://docs.rs/foundationdb-sys" +repository = "https://github.com/apple/foundationdb" + +readme = "README.md" +keywords = ["foundationdb", "kv"] +categories = ["database"] + +license = "MIT/Apache-2.0" + +[package.metadata.docs.rs] +features = ["embedded-fdb-include", "fdb-7_4"] + +[features] +default = [] +# Use the locally embedded foundationdb fdb_c.h file +embedded-fdb-include = [] +fdb-5_1 = [] +fdb-5_2 = [] +fdb-6_0 = [] +fdb-6_1 = [] +fdb-6_2 = [] +fdb-6_3 = [] +fdb-7_0 = [] +fdb-7_1 = [] +fdb-7_3 = [] +fdb-7_4 = [] + +[dependencies] +libc = "0.2" + +[build-dependencies] +bindgen = "0.73.2" diff --git a/bindings/rust/foundationdb-sys/README.md b/bindings/rust/foundationdb-sys/README.md new file mode 100644 index 00000000000..6bcfaa8b907 --- /dev/null +++ b/bindings/rust/foundationdb-sys/README.md @@ -0,0 +1,19 @@ +# C API bindings for FoundationDB + +See [foundationdb](https://crates.io/crates/foundationdb), for a safe abstraction. + +## License + +Licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/bindings/rust/foundationdb-sys/build.rs b/bindings/rust/foundationdb-sys/build.rs new file mode 100644 index 00000000000..d6496dc3cff --- /dev/null +++ b/bindings/rust/foundationdb-sys/build.rs @@ -0,0 +1,157 @@ +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "linux"))] +const INCLUDE_PATH: &str = "/usr/include/foundationdb/"; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "macos"))] +const INCLUDE_PATH: &str = "/usr/local/include/foundationdb/"; + +#[cfg(all(not(feature = "embedded-fdb-include"), target_os = "windows"))] +const INCLUDE_PATH: &str = "C:/Program Files/foundationdb/include/foundationdb"; + +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-5_1"))] +const INCLUDE_PATH: &str = "./include/510"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-5_2"))] +const INCLUDE_PATH: &str = "./include/520"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_0"))] +const INCLUDE_PATH: &str = "./include/600"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_1"))] +const INCLUDE_PATH: &str = "./include/610"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_2"))] +const INCLUDE_PATH: &str = "./include/620"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-6_3"))] +const INCLUDE_PATH: &str = "./include/630"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_0"))] +const INCLUDE_PATH: &str = "./include/700"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_1"))] +const INCLUDE_PATH: &str = "./include/710"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_3"))] +const INCLUDE_PATH: &str = "./include/730"; +#[cfg(all(feature = "embedded-fdb-include", feature = "fdb-7_4"))] +const INCLUDE_PATH: &str = "./include/740"; + +// Compile error when no version feature is specified +#[cfg(not(any( + feature = "fdb-5_1", + feature = "fdb-5_2", + feature = "fdb-6_0", + feature = "fdb-6_1", + feature = "fdb-6_2", + feature = "fdb-6_3", + feature = "fdb-7_0", + feature = "fdb-7_1", + feature = "fdb-7_3", + feature = "fdb-7_4", +)))] +compile_error!( + "foundationdb-sys requires a version feature to be specified.\n\ + \n\ + Available version features: fdb-5_1, fdb-5_2, fdb-6_0, fdb-6_1, fdb-6_2, fdb-6_3, fdb-7_0, fdb-7_1, fdb-7_3, fdb-7_4\n\ + \n\ + Examples:\n\ + - With embedded include: features = [\"embedded-fdb-include\", \"fdb-7_4\"]\n\ + - With system install: features = [\"fdb-7_4\"]" +); + +fn main() { + println!("cargo:rustc-check-cfg=cfg(fdb_has_selected_api_versions)"); + + // Link against fdb_c. + println!("cargo:rustc-link-lib=fdb_c"); + + println!("cargo:rerun-if-env-changed=FDB_CLIENT_LIB_PATH"); + if let Ok(lib_path) = env::var("FDB_CLIENT_LIB_PATH") { + println!("cargo:rustc-link-search=native={lib_path}"); + } + + // Include the link directory for macOS. + // Since Rust 1.91, /usr/local/lib is no longer searched automatically on macOS. + // See: https://github.com/rust-lang/rust/pull/131477 + // See: https://github.com/rust-lang/rust/issues/147010 + #[cfg(target_os = "macos")] + println!("cargo:rustc-link-search=/usr/local/lib"); + + // Include the link directory for the .lib file on windows (which will resolve to + // the shared library, at runtime) + #[cfg(target_os = "windows")] + println!("cargo:rustc-link-search=C:/Program Files/foundationdb/lib/foundationdb"); + + let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is not defined!")); + + // We need to have FDB_API_VERSION set to a constant so that bindgen will + // generate a const value for it. We could try to pass -DFDB_TRICKY_VERSION=510 + // to the driver and then '#define FDB_API_VERSION FDB_TRICKY_VERSION', but + // bindgen isn't smart enough to resolve that from the arguments. Instead, write + // out a src/wrapper.h file with the chosen version instead. + let api_version; + + #[cfg(feature = "fdb-5_1")] + { + api_version = 510; + } + #[cfg(feature = "fdb-5_2")] + { + api_version = 520; + } + #[cfg(feature = "fdb-6_0")] + { + api_version = 600; + } + #[cfg(feature = "fdb-6_1")] + { + api_version = 610; + } + #[cfg(feature = "fdb-6_2")] + { + api_version = 620; + } + #[cfg(feature = "fdb-6_3")] + { + api_version = 630; + } + #[cfg(feature = "fdb-7_0")] + { + api_version = 700; + } + #[cfg(feature = "fdb-7_1")] + { + api_version = 710; + } + #[cfg(feature = "fdb-7_3")] + { + api_version = 730; + } + #[cfg(feature = "fdb-7_4")] + { + api_version = 740; + } + + println!("cargo:rerun-if-env-changed=FDB_INCLUDE_DIR"); + let include_path = env::var_os("FDB_INCLUDE_DIR") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(INCLUDE_PATH)); + + let bindings = bindgen::Builder::default() + .clang_arg(format!("-I{}", include_path.display())) + .clang_arg(format!("-D FDB_API_VERSION={api_version}")) + .header(include_path.join("fdb_c.h").to_string_lossy()) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate_comments(true) + .layout_tests(false) + .generate() + .expect("Unable to generate FoundationDB bindings"); + // Historical headers cannot query another library's API selection. Detect + // the generated declaration rather than assuming support from the runtime API. + if bindings + .to_string() + .contains("pub fn fdb_get_selected_api_versions(") + { + println!("cargo:rustc-cfg=fdb_has_selected_api_versions"); + } + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/bindings/rust/foundationdb-sys/include/510/fdb_c.h b/bindings/rust/foundationdb-sys/include/510/fdb_c.h new file mode 100644 index 00000000000..8571d59e802 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/510/fdb_c.h @@ -0,0 +1,316 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 510) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 510 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() [=====] +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* Pointers to these opaque types represent objects in the FDB API */ + typedef struct future FDBFuture; + typedef struct cluster FDBCluster; + typedef struct database FDBDatabase; + typedef struct transaction FDBTransaction; + + typedef int fdb_error_t; + typedef int fdb_bool_t; + + DLLEXPORT const char* + fdb_get_error( fdb_error_t code ); + + DLLEXPORT fdb_bool_t + fdb_error_predicate( int predicate_test, fdb_error_t code ); + + #define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_network_set_option( FDBNetworkOption option, uint8_t const* value, + int value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), void *hook_parameter); + +#pragma pack(push, 4) + typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; + } FDBKeyValue; +#pragma pack(pop) + + DLLEXPORT void fdb_future_cancel( FDBFuture *f ); + + DLLEXPORT void fdb_future_release_memory( FDBFuture* f ); + + DLLEXPORT void fdb_future_destroy( FDBFuture* f ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready( FDBFuture* f ); + + DLLEXPORT fdb_bool_t fdb_future_is_ready( FDBFuture* f ); + + typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_set_callback( FDBFuture* f, FDBCallback callback, + void* callback_parameter ); + +#if FDB_API_VERSION >= 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_version( FDBFuture* f, int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_key( FDBFuture* f, uint8_t const** out_key, + int* out_key_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_cluster( FDBFuture* f, FDBCluster** out_cluster ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_database( FDBFuture* f, FDBDatabase** out_database ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_value( FDBFuture* f, fdb_bool_t *out_present, + uint8_t const** out_value, + int* out_value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_keyvalue_array( FDBFuture* f, FDBKeyValue const** out_kv, + int* out_count, fdb_bool_t* out_more ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, int* out_count); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster( const char* cluster_file_path ); + + DLLEXPORT void fdb_cluster_destroy( FDBCluster* c ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_cluster_set_option( FDBCluster* c, FDBClusterOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_cluster_create_database( FDBCluster* c, uint8_t const* db_name, + int db_name_length ); + + DLLEXPORT void fdb_database_destroy( FDBDatabase* d ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_set_option( FDBDatabase* d, FDBDatabaseOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_create_transaction( FDBDatabase* d, + FDBTransaction** out_transaction ); + + DLLEXPORT void fdb_transaction_destroy( FDBTransaction* tr); + + DLLEXPORT void fdb_transaction_cancel( FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_set_option( FDBTransaction* tr, FDBTransactionOption option, + uint8_t const* value, int value_length ); +#endif + + DLLEXPORT void + fdb_transaction_set_read_version( FDBTransaction* tr, int64_t version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version( FDBTransaction* tr ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t snapshot ); +#endif + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_key( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t or_equal, + int offset, fdb_bool_t snapshot ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_addresses_for_key(FDBTransaction* tr, uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, 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 ); +#endif + + DLLEXPORT void + fdb_transaction_set( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* value, + int value_length ); + + DLLEXPORT void + fdb_transaction_atomic_op( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* param, + int param_length, FDBMutationType operation_type ); + + DLLEXPORT void + fdb_transaction_clear( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length ); + + DLLEXPORT void fdb_transaction_clear_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch( FDBTransaction *tr, + uint8_t const* key_name, + int key_name_length); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_get_committed_version( FDBTransaction* tr, + int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_on_error( FDBTransaction* tr, fdb_error_t error ); + + DLLEXPORT void fdb_transaction_reset( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_add_conflict_range(FDBTransaction *tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 + #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 + #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 + #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_select_api_version_impl( int runtime_version, int header_version ); + + DLLEXPORT int fdb_get_max_api_version(); + DLLEXPORT const char* fdb_get_client_version(); + + /* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f, + const char** out_description /* = NULL */ ); + + DLLEXPORT fdb_bool_t fdb_future_is_error( FDBFuture* f ); +#else + #define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array( + FDBFuture* f, FDBKeyValue const** out_kv, int* out_count ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length, + fdb_bool_t or_equal, int offset ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network( const char* local_address ); + + DLLEXPORT void fdb_transaction_set_option( + FDBTransaction* tr, FDBTransactionOption option ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length, int limit ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector( + FDBTransaction* tr, 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 ); +#else + #define fdb_transaction_get_range_selector(tr,bkn,bknl,boe,bo,ekn,eknl,eoe,eo,lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/510/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/510/fdb_c_options.g.h new file mode 100644 index 00000000000..e9882f1fe13 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/510/fdb_c_options.g.h @@ -0,0 +1,341 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'logGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the logGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Set the TLS plugin to load. This option, if used, must be set before any other TLS options + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71 +} FDBNetworkOption; + +typedef enum { + // This option is only a placeholder for C compatibility and should not be used + // Parameter: Option takes no parameter + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE=-1 +} FDBClusterOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a fault or partition + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Committing this transaction will bypass the normal load balancing across proxies and go directly to the specifically nominated 'first proxy'. + // Parameter: Option takes no parameter + FDB_TR_OPTION_COMMIT_ON_FIRST_PROXY=40, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CHECK_WRITES_ENABLE=50, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Disables read-ahead caching for range reads. Under normal operation, a transaction will read extra rows from the database into cache if range reads are used to page through a series of data one row at a time (i.e. if a range read with a one row limit is followed by another one row range read starting immediately after the result of the first). + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions should be processed first. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DEBUG_DUMP=400, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Enables tracing for this transaction and logs results to the client trace logs. Client trace logging must be enabled to get log output. + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Like all transaction options, a timeout must be reset after a call to onError. This behavior allows the user to make the timeout dynamic. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to onError will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Like all transaction options, the retry limit must be reset after a call to onError. This behavior allows the user to make the retry limit dynamic. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to onError if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. Like all transaction options, the maximum retry delay must be reset after a call to onError. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Snapshot read operations will see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is resposible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database. The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Note that this implies versionstamped keys may not be used with the Subspace and Directory layers except in those languages. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets ``key`` in the database to the transformed parameter. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database. The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time versionstamped values are not compatible with the Tuple layer. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/510/version.txt b/bindings/rust/foundationdb-sys/include/510/version.txt new file mode 100644 index 00000000000..693ad745af3 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/510/version.txt @@ -0,0 +1 @@ +5.1.7 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/520/fdb_c.h b/bindings/rust/foundationdb-sys/include/520/fdb_c.h new file mode 100644 index 00000000000..1c96c5914f0 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/520/fdb_c.h @@ -0,0 +1,316 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 520) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 520 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() [=====] +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* Pointers to these opaque types represent objects in the FDB API */ + typedef struct future FDBFuture; + typedef struct cluster FDBCluster; + typedef struct database FDBDatabase; + typedef struct transaction FDBTransaction; + + typedef int fdb_error_t; + typedef int fdb_bool_t; + + DLLEXPORT const char* + fdb_get_error( fdb_error_t code ); + + DLLEXPORT fdb_bool_t + fdb_error_predicate( int predicate_test, fdb_error_t code ); + + #define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_network_set_option( FDBNetworkOption option, uint8_t const* value, + int value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), void *hook_parameter); + +#pragma pack(push, 4) + typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; + } FDBKeyValue; +#pragma pack(pop) + + DLLEXPORT void fdb_future_cancel( FDBFuture *f ); + + DLLEXPORT void fdb_future_release_memory( FDBFuture* f ); + + DLLEXPORT void fdb_future_destroy( FDBFuture* f ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready( FDBFuture* f ); + + DLLEXPORT fdb_bool_t fdb_future_is_ready( FDBFuture* f ); + + typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_set_callback( FDBFuture* f, FDBCallback callback, + void* callback_parameter ); + +#if FDB_API_VERSION >= 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_version( FDBFuture* f, int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_key( FDBFuture* f, uint8_t const** out_key, + int* out_key_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_cluster( FDBFuture* f, FDBCluster** out_cluster ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_database( FDBFuture* f, FDBDatabase** out_database ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_value( FDBFuture* f, fdb_bool_t *out_present, + uint8_t const** out_value, + int* out_value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_keyvalue_array( FDBFuture* f, FDBKeyValue const** out_kv, + int* out_count, fdb_bool_t* out_more ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, int* out_count); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster( const char* cluster_file_path ); + + DLLEXPORT void fdb_cluster_destroy( FDBCluster* c ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_cluster_set_option( FDBCluster* c, FDBClusterOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_cluster_create_database( FDBCluster* c, uint8_t const* db_name, + int db_name_length ); + + DLLEXPORT void fdb_database_destroy( FDBDatabase* d ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_set_option( FDBDatabase* d, FDBDatabaseOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_create_transaction( FDBDatabase* d, + FDBTransaction** out_transaction ); + + DLLEXPORT void fdb_transaction_destroy( FDBTransaction* tr); + + DLLEXPORT void fdb_transaction_cancel( FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_set_option( FDBTransaction* tr, FDBTransactionOption option, + uint8_t const* value, int value_length ); +#endif + + DLLEXPORT void + fdb_transaction_set_read_version( FDBTransaction* tr, int64_t version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version( FDBTransaction* tr ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t snapshot ); +#endif + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_key( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t or_equal, + int offset, fdb_bool_t snapshot ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_addresses_for_key(FDBTransaction* tr, uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, 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 ); +#endif + + DLLEXPORT void + fdb_transaction_set( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* value, + int value_length ); + + DLLEXPORT void + fdb_transaction_atomic_op( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* param, + int param_length, FDBMutationType operation_type ); + + DLLEXPORT void + fdb_transaction_clear( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length ); + + DLLEXPORT void fdb_transaction_clear_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch( FDBTransaction *tr, + uint8_t const* key_name, + int key_name_length); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_get_committed_version( FDBTransaction* tr, + int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_on_error( FDBTransaction* tr, fdb_error_t error ); + + DLLEXPORT void fdb_transaction_reset( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_add_conflict_range(FDBTransaction *tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 + #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 + #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 + #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_select_api_version_impl( int runtime_version, int header_version ); + + DLLEXPORT int fdb_get_max_api_version(); + DLLEXPORT const char* fdb_get_client_version(); + + /* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f, + const char** out_description /* = NULL */ ); + + DLLEXPORT fdb_bool_t fdb_future_is_error( FDBFuture* f ); +#else + #define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array( + FDBFuture* f, FDBKeyValue const** out_kv, int* out_count ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length, + fdb_bool_t or_equal, int offset ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network( const char* local_address ); + + DLLEXPORT void fdb_transaction_set_option( + FDBTransaction* tr, FDBTransactionOption option ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length, int limit ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector( + FDBTransaction* tr, 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 ); +#else + #define fdb_transaction_get_range_selector(tr,bkn,bknl,boe,bo,ekn,eknl,eoe,eo,lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/520/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/520/fdb_c_options.g.h new file mode 100644 index 00000000000..e669926a07d --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/520/fdb_c_options.g.h @@ -0,0 +1,344 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'logGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the logGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Set the TLS plugin to load. This option, if used, must be set before any other TLS options + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Set the ca bundle + // Parameter: (Bytes) ca bundle + FDB_NET_OPTION_TLS_CA_BYTES=52, + + // Set the file from which to load the certificate authority bundle + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CA_PATH=53, + + // Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. + // Parameter: (String) key passphrase + FDB_NET_OPTION_TLS_PASSWORD=54, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71 +} FDBNetworkOption; + +typedef enum { + // This option is only a placeholder for C compatibility and should not be used + // Parameter: Option takes no parameter + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE=-1 +} FDBClusterOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a fault or partition + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions should be processed first. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Enables tracing for this transaction and logs results to the client trace logs. Client trace logging must be enabled to get log output. + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Like all transaction options, a timeout must be reset after a call to onError. This behavior allows the user to make the timeout dynamic. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to onError will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Like all transaction options, the retry limit must be reset after a call to onError. This behavior allows the user to make the retry limit dynamic. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to onError if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. Like all transaction options, the maximum retry delay must be reset after a call to onError. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Snapshot read operations will see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is resposible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/520/version.txt b/bindings/rust/foundationdb-sys/include/520/version.txt new file mode 100644 index 00000000000..e60200b54f4 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/520/version.txt @@ -0,0 +1 @@ +5.2.5 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/600/fdb_c.h b/bindings/rust/foundationdb-sys/include/600/fdb_c.h new file mode 100644 index 00000000000..a9e1bd20b79 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/600/fdb_c.h @@ -0,0 +1,316 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 600) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 600 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() [=====] +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* Pointers to these opaque types represent objects in the FDB API */ + typedef struct FDB_future FDBFuture; + typedef struct FDB_cluster FDBCluster; + typedef struct FDB_database FDBDatabase; + typedef struct FDB_transaction FDBTransaction; + + typedef int fdb_error_t; + typedef int fdb_bool_t; + + DLLEXPORT const char* + fdb_get_error( fdb_error_t code ); + + DLLEXPORT fdb_bool_t + fdb_error_predicate( int predicate_test, fdb_error_t code ); + + #define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_network_set_option( FDBNetworkOption option, uint8_t const* value, + int value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), void *hook_parameter); + +#pragma pack(push, 4) + typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; + } FDBKeyValue; +#pragma pack(pop) + + DLLEXPORT void fdb_future_cancel( FDBFuture *f ); + + DLLEXPORT void fdb_future_release_memory( FDBFuture* f ); + + DLLEXPORT void fdb_future_destroy( FDBFuture* f ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready( FDBFuture* f ); + + DLLEXPORT fdb_bool_t fdb_future_is_ready( FDBFuture* f ); + + typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_set_callback( FDBFuture* f, FDBCallback callback, + void* callback_parameter ); + +#if FDB_API_VERSION >= 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_version( FDBFuture* f, int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_key( FDBFuture* f, uint8_t const** out_key, + int* out_key_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_cluster( FDBFuture* f, FDBCluster** out_cluster ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_database( FDBFuture* f, FDBDatabase** out_database ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_value( FDBFuture* f, fdb_bool_t *out_present, + uint8_t const** out_value, + int* out_value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_keyvalue_array( FDBFuture* f, FDBKeyValue const** out_kv, + int* out_count, fdb_bool_t* out_more ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, int* out_count); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster( const char* cluster_file_path ); + + DLLEXPORT void fdb_cluster_destroy( FDBCluster* c ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_cluster_set_option( FDBCluster* c, FDBClusterOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_cluster_create_database( FDBCluster* c, uint8_t const* db_name, + int db_name_length ); + + DLLEXPORT void fdb_database_destroy( FDBDatabase* d ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_set_option( FDBDatabase* d, FDBDatabaseOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_create_transaction( FDBDatabase* d, + FDBTransaction** out_transaction ); + + DLLEXPORT void fdb_transaction_destroy( FDBTransaction* tr); + + DLLEXPORT void fdb_transaction_cancel( FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_set_option( FDBTransaction* tr, FDBTransactionOption option, + uint8_t const* value, int value_length ); +#endif + + DLLEXPORT void + fdb_transaction_set_read_version( FDBTransaction* tr, int64_t version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version( FDBTransaction* tr ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t snapshot ); +#endif + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_key( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t or_equal, + int offset, fdb_bool_t snapshot ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_addresses_for_key(FDBTransaction* tr, uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, 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 ); +#endif + + DLLEXPORT void + fdb_transaction_set( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* value, + int value_length ); + + DLLEXPORT void + fdb_transaction_atomic_op( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* param, + int param_length, FDBMutationType operation_type ); + + DLLEXPORT void + fdb_transaction_clear( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length ); + + DLLEXPORT void fdb_transaction_clear_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch( FDBTransaction *tr, + uint8_t const* key_name, + int key_name_length); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_get_committed_version( FDBTransaction* tr, + int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_on_error( FDBTransaction* tr, fdb_error_t error ); + + DLLEXPORT void fdb_transaction_reset( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_add_conflict_range(FDBTransaction *tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 + #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 + #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 + #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_select_api_version_impl( int runtime_version, int header_version ); + + DLLEXPORT int fdb_get_max_api_version(); + DLLEXPORT const char* fdb_get_client_version(); + + /* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f, + const char** out_description /* = NULL */ ); + + DLLEXPORT fdb_bool_t fdb_future_is_error( FDBFuture* f ); +#else + #define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array( + FDBFuture* f, FDBKeyValue const** out_kv, int* out_count ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length, + fdb_bool_t or_equal, int offset ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network( const char* local_address ); + + DLLEXPORT void fdb_transaction_set_option( + FDBTransaction* tr, FDBTransactionOption option ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length, int limit ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector( + FDBTransaction* tr, 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 ); +#else + #define fdb_transaction_get_range_selector(tr,bkn,bknl,boe,bo,ekn,eknl,eoe,eo,lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/600/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/600/fdb_c_options.g.h new file mode 100644 index 00000000000..d60657535cc --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/600/fdb_c_options.g.h @@ -0,0 +1,344 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the LogGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Deprecated + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Set the ca bundle + // Parameter: (Bytes) ca bundle + FDB_NET_OPTION_TLS_CA_BYTES=52, + + // Set the file from which to load the certificate authority bundle + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CA_PATH=53, + + // Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. + // Parameter: (String) key passphrase + FDB_NET_OPTION_TLS_PASSWORD=54, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71 +} FDBNetworkOption; + +typedef enum { + // This option is only a placeholder for C compatibility and should not be used + // Parameter: Option takes no parameter + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE=-1 +} FDBClusterOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a fault or partition + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions should be processed first. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Enables tracing for this transaction and logs results to the client trace logs. Client trace logging must be enabled to get log output. + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Like all transaction options, a timeout must be reset after a call to onError. This behavior allows the user to make the timeout dynamic. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to onError will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Like all transaction options, the retry limit must be reset after a call to onError. This behavior allows the user to make the retry limit dynamic. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to onError if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. Like all transaction options, the maximum retry delay must be reset after a call to onError. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Snapshot read operations will see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is resposible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/600/version.txt b/bindings/rust/foundationdb-sys/include/600/version.txt new file mode 100644 index 00000000000..e23d7a6e234 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/600/version.txt @@ -0,0 +1 @@ +6.0.18 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/610/fdb_c.h b/bindings/rust/foundationdb-sys/include/610/fdb_c.h new file mode 100644 index 00000000000..5d988cec387 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/610/fdb_c.h @@ -0,0 +1,336 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 610) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 610 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() [=====] +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* Pointers to these opaque types represent objects in the FDB API */ + typedef struct FDB_future FDBFuture; + typedef struct FDB_database FDBDatabase; + typedef struct FDB_transaction FDBTransaction; + + typedef int fdb_error_t; + typedef int fdb_bool_t; + + DLLEXPORT const char* + fdb_get_error( fdb_error_t code ); + + DLLEXPORT fdb_bool_t + fdb_error_predicate( int predicate_test, fdb_error_t code ); + + #define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_network_set_option( FDBNetworkOption option, uint8_t const* value, + int value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), void *hook_parameter); + +#pragma pack(push, 4) + typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; + } FDBKeyValue; +#pragma pack(pop) + + DLLEXPORT void fdb_future_cancel( FDBFuture* f ); + + DLLEXPORT void fdb_future_release_memory( FDBFuture* f ); + + DLLEXPORT void fdb_future_destroy( FDBFuture* f ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready( FDBFuture* f ); + + DLLEXPORT fdb_bool_t fdb_future_is_ready( FDBFuture* f ); + + typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_set_callback( FDBFuture* f, FDBCallback callback, + void* callback_parameter ); + +#if FDB_API_VERSION >= 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_version( FDBFuture* f, int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_key( FDBFuture* f, uint8_t const** out_key, + int* out_key_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_value( FDBFuture* f, fdb_bool_t *out_present, + uint8_t const** out_value, + int* out_value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_keyvalue_array( FDBFuture* f, FDBKeyValue const** out_kv, + int* out_count, fdb_bool_t* out_more ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, int* out_count); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_create_database( const char* cluster_file_path, FDBDatabase** out_database ); + + DLLEXPORT void fdb_database_destroy( FDBDatabase* d ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_set_option( FDBDatabase* d, FDBDatabaseOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_create_transaction( FDBDatabase* d, + FDBTransaction** out_transaction ); + + DLLEXPORT void fdb_transaction_destroy( FDBTransaction* tr); + + DLLEXPORT void fdb_transaction_cancel( FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_set_option( FDBTransaction* tr, FDBTransactionOption option, + uint8_t const* value, int value_length ); +#endif + + DLLEXPORT void + fdb_transaction_set_read_version( FDBTransaction* tr, int64_t version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version( FDBTransaction* tr ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t snapshot ); +#endif + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_key( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t or_equal, + int offset, fdb_bool_t snapshot ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_addresses_for_key(FDBTransaction* tr, uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, 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 ); +#endif + + DLLEXPORT void + fdb_transaction_set( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* value, + int value_length ); + + DLLEXPORT void + fdb_transaction_atomic_op( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* param, + int param_length, FDBMutationType operation_type ); + + DLLEXPORT void + fdb_transaction_clear( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length ); + + DLLEXPORT void fdb_transaction_clear_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch( FDBTransaction *tr, + uint8_t const* key_name, + int key_name_length); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_get_committed_version( FDBTransaction* tr, + int64_t* out_version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_on_error( FDBTransaction* tr, fdb_error_t error ); + + DLLEXPORT void fdb_transaction_reset( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_add_conflict_range(FDBTransaction *tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 + #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 + #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 + #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_select_api_version_impl( int runtime_version, int header_version ); + + DLLEXPORT int fdb_get_max_api_version(); + DLLEXPORT const char* fdb_get_client_version(); + + /* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES + typedef struct FDB_cluster FDBCluster; + + typedef enum { + // This option is only a placeholder for C compatibility and should not be used + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE=-1 + } FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_cluster( FDBFuture* f, FDBCluster** out_cluster ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_database( FDBFuture* f, FDBDatabase** out_database ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster( const char* cluster_file_path ); + + DLLEXPORT void fdb_cluster_destroy( FDBCluster* c ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_cluster_set_option( FDBCluster* c, FDBClusterOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_cluster_create_database( FDBCluster* c, uint8_t const* db_name, + int db_name_length ); +#else + #define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION + #define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION + #define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION + #define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION + #define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION + #define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f, + const char** out_description /* = NULL */ ); + + DLLEXPORT fdb_bool_t fdb_future_is_error( FDBFuture* f ); +#else + #define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array( + FDBFuture* f, FDBKeyValue const** out_kv, int* out_count ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length, + fdb_bool_t or_equal, int offset ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network( const char* local_address ); + + DLLEXPORT void fdb_transaction_set_option( + FDBTransaction* tr, FDBTransactionOption option ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length, int limit ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector( + FDBTransaction* tr, 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 ); +#else + #define fdb_transaction_get_range_selector(tr,bkn,bknl,boe,bo,ekn,eknl,eoe,eo,lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/610/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/610/fdb_c_options.g.h new file mode 100644 index 00000000000..47e43555b36 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/610/fdb_c_options.g.h @@ -0,0 +1,377 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the LogGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Select the format of the log files. xml (the default) and json are supported. + // Parameter: (String) Format of trace files + FDB_NET_OPTION_TRACE_FORMAT=34, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Deprecated + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Set the ca bundle + // Parameter: (Bytes) ca bundle + FDB_NET_OPTION_TLS_CA_BYTES=52, + + // Set the file from which to load the certificate authority bundle + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CA_PATH=53, + + // Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. + // Parameter: (String) key passphrase + FDB_NET_OPTION_TLS_PASSWORD=54, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71 +} FDBNetworkOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22, + + // Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. + // Parameter: (Int) value in milliseconds of timeout + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + // Set a timeout in milliseconds which, when elapsed, will cause a transaction automatically to be cancelled. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) number of times to retry + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a fault or partition + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Deprecated + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. + // Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + // Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled and to get log output. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOG_TRANSACTION=404, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is resposible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + // This option should only be used by tools which change the database configuration. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17, + + // Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/610/version.txt b/bindings/rust/foundationdb-sys/include/610/version.txt new file mode 100644 index 00000000000..4b5ef77d17c --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/610/version.txt @@ -0,0 +1 @@ +6.1.13 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/620/fdb_c.h b/bindings/rust/foundationdb-sys/include/620/fdb_c.h new file mode 100644 index 00000000000..5459952cf72 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/620/fdb_c.h @@ -0,0 +1,350 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 620) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 620 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() [=====] +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* Pointers to these opaque types represent objects in the FDB API */ + typedef struct FDB_future FDBFuture; + typedef struct FDB_database FDBDatabase; + typedef struct FDB_transaction FDBTransaction; + + typedef int fdb_error_t; + typedef int fdb_bool_t; + + DLLEXPORT const char* + fdb_get_error( fdb_error_t code ); + + DLLEXPORT fdb_bool_t + fdb_error_predicate( int predicate_test, fdb_error_t code ); + + #define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_network_set_option( FDBNetworkOption option, uint8_t const* value, + int value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), void *hook_parameter); + +#pragma pack(push, 4) + typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; + } FDBKeyValue; +#pragma pack(pop) + + DLLEXPORT void fdb_future_cancel( FDBFuture* f ); + + DLLEXPORT void fdb_future_release_memory( FDBFuture* f ); + + DLLEXPORT void fdb_future_destroy( FDBFuture* f ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready( FDBFuture* f ); + + DLLEXPORT fdb_bool_t fdb_future_is_ready( FDBFuture* f ); + + typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_set_callback( FDBFuture* f, FDBCallback callback, + void* callback_parameter ); + +#if FDB_API_VERSION >= 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_int64( FDBFuture* f, int64_t* out ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_key( FDBFuture* f, uint8_t const** out_key, + int* out_key_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_value( FDBFuture* f, fdb_bool_t *out_present, + uint8_t const** out_value, + int* out_value_length ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_keyvalue_array( FDBFuture* f, FDBKeyValue const** out_kv, + int* out_count, fdb_bool_t* out_more ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, int* out_count); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_create_database( const char* cluster_file_path, FDBDatabase** out_database ); + + DLLEXPORT void fdb_database_destroy( FDBDatabase* d ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_set_option( FDBDatabase* d, FDBDatabaseOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_database_create_transaction( FDBDatabase* d, + FDBTransaction** out_transaction ); + + DLLEXPORT void fdb_transaction_destroy( FDBTransaction* tr); + + DLLEXPORT void fdb_transaction_cancel( FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_set_option( FDBTransaction* tr, FDBTransactionOption option, + uint8_t const* value, int value_length ); +#endif + + DLLEXPORT void + fdb_transaction_set_read_version( FDBTransaction* tr, int64_t version ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version( FDBTransaction* tr ); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t snapshot ); +#endif + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_key( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, fdb_bool_t or_equal, + int offset, fdb_bool_t snapshot ); +#endif + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_addresses_for_key(FDBTransaction* tr, uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, 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 ); +#endif + + DLLEXPORT void + fdb_transaction_set( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* value, + int value_length ); + + DLLEXPORT void + fdb_transaction_atomic_op( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length, uint8_t const* param, + int param_length, FDBMutationType operation_type ); + + DLLEXPORT void + fdb_transaction_clear( FDBTransaction* tr, uint8_t const* key_name, + int key_name_length ); + + DLLEXPORT void fdb_transaction_clear_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch( FDBTransaction *tr, + uint8_t const* key_name, + int key_name_length); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_get_committed_version( FDBTransaction* tr, + int64_t* out_version ); + + // This function intentionally returns an FDBFuture instead of an integer directly, + // so that calling this API can see the effect of previous mutations on the transaction. + // Specifically, mutations are applied asynchronously by the main thread. In order to + // see them, this call has to be serviced by the main thread too. + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_get_approximate_size(FDBTransaction* tr); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_transaction_on_error( FDBTransaction* tr, fdb_error_t error ); + + DLLEXPORT void fdb_transaction_reset( FDBTransaction* tr ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_transaction_add_conflict_range(FDBTransaction *tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 + #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 + #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 + #define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_select_api_version_impl( int runtime_version, int header_version ); + + DLLEXPORT int fdb_get_max_api_version(); + DLLEXPORT const char* fdb_get_client_version(); + + /* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_version( FDBFuture* f, int64_t* out_version ); +#else + #define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES + typedef struct FDB_cluster FDBCluster; + + typedef enum { + // This option is only a placeholder for C compatibility and should not be used + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE=-1 + } FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_cluster( FDBFuture* f, FDBCluster** out_cluster ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_database( FDBFuture* f, FDBDatabase** out_database ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster( const char* cluster_file_path ); + + DLLEXPORT void fdb_cluster_destroy( FDBCluster* c ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_cluster_set_option( FDBCluster* c, FDBClusterOption option, + uint8_t const* value, int value_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* + fdb_cluster_create_database( FDBCluster* c, uint8_t const* db_name, + int db_name_length ); +#else + #define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION + #define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION + #define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION + #define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION + #define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION + #define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t + fdb_future_get_error( FDBFuture* f, + const char** out_description /* = NULL */ ); + + DLLEXPORT fdb_bool_t fdb_future_is_error( FDBFuture* f ); +#else + #define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array( + FDBFuture* f, FDBKeyValue const** out_kv, int* out_count ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key( + FDBTransaction* tr, uint8_t const* key_name, int key_name_length, + fdb_bool_t or_equal, int offset ); + + DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network( const char* local_address ); + + DLLEXPORT void fdb_transaction_set_option( + FDBTransaction* tr, FDBTransactionOption option ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range( + FDBTransaction* tr, uint8_t const* begin_key_name, + int begin_key_name_length, uint8_t const* end_key_name, + int end_key_name_length, int limit ); + + DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector( + FDBTransaction* tr, 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 ); +#else + #define fdb_transaction_get_range_selector(tr,bkn,bknl,boe,bo,ekn,eknl,eoe,eo,lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/620/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/620/fdb_c_options.g.h new file mode 100644 index 00000000000..359d98ae441 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/620/fdb_c_options.g.h @@ -0,0 +1,421 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the LogGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Select the format of the log files. xml (the default) and json are supported. + // Parameter: (String) Format of trace files + FDB_NET_OPTION_TRACE_FORMAT=34, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Deprecated + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Set the ca bundle + // Parameter: (Bytes) ca bundle + FDB_NET_OPTION_TLS_CA_BYTES=52, + + // Set the file from which to load the certificate authority bundle + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CA_PATH=53, + + // Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. + // Parameter: (String) key passphrase + FDB_NET_OPTION_TLS_PASSWORD=54, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + // Enable client buggify - will make requests randomly fail (intended for client testing) + // Parameter: Option takes no parameter + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + // Disable client buggify + // Parameter: Option takes no parameter + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + // Set the probability of a CLIENT_BUGGIFY section being active for the current execution. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + // Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83 +} FDBNetworkOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + // Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) Maximum length of escaped key and value fields. + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + // Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. + // Parameter: (Int) value in milliseconds of timeout + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) number of times to retry + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + // Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) value in bytes + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. + // Parameter: Option takes no parameter + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + // Addresses returned by get_addresses_for_key include the port when enabled. This will be enabled by default in api version 630, and this option will be deprecated. + // Parameter: Option takes no parameter + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // Addresses returned by get_addresses_for_key include the port when enabled. This will be enabled by default in api version 630, and this option will be deprecated. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Deprecated + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. + // Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + // Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOG_TRANSACTION=404, + + // Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. + // Parameter: (Int) Maximum length of escaped key and value fields. + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. + // Parameter: (Int) value in bytes + FDB_TR_OPTION_SIZE_LIMIT=503, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is responsible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + // This option should only be used by tools which change the database configuration. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17, + + // Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/620/version.txt b/bindings/rust/foundationdb-sys/include/620/version.txt new file mode 100644 index 00000000000..1a87478a3f4 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/620/version.txt @@ -0,0 +1 @@ +6.2.20 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/630/fdb_c.h b/bindings/rust/foundationdb-sys/include/630/fdb_c.h new file mode 100644 index 00000000000..30177d9614d --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/630/fdb_c.h @@ -0,0 +1,374 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 630) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 630 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +// With default settings, gcc will not warn about unprototyped functions being called, so it +// is easy to erroneously call a function which is not available at FDB_API_VERSION and then +// get an error only at runtime. These macros ensure a compile error in such cases, and +// attempt to make the compile error slightly informative. +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() \ + { == == = } +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Pointers to these opaque types represent objects in the FDB API */ +typedef struct FDB_future FDBFuture; +typedef struct FDB_database FDBDatabase; +typedef struct FDB_transaction FDBTransaction; + +typedef int fdb_error_t; +typedef int fdb_bool_t; + +DLLEXPORT const char* fdb_get_error(fdb_error_t code); + +DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code); + +#define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_network_set_option(FDBNetworkOption option, + uint8_t const* value, + int value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), + void* hook_parameter); + +#pragma pack(push, 4) +#if FDB_API_VERSION >= 630 +typedef struct keyvalue { + const uint8_t* key; + int key_length; + const uint8_t* value; + int value_length; +} FDBKeyValue; +#else +typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; +} FDBKeyValue; +#endif +#pragma pack(pop) + +DLLEXPORT void fdb_future_cancel(FDBFuture* f); + +DLLEXPORT void fdb_future_release_memory(FDBFuture* f); + +DLLEXPORT void fdb_future_destroy(FDBFuture* f); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready(FDBFuture* f); + +DLLEXPORT fdb_bool_t fdb_future_is_ready(FDBFuture* f); + +typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_set_callback(FDBFuture* f, + FDBCallback callback, + void* callback_parameter); + +#if FDB_API_VERSION >= 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_int64(FDBFuture* f, int64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_value(FDBFuture* f, + fdb_bool_t* out_present, + uint8_t const** out_value, + int* out_value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database(const char* cluster_file_path, FDBDatabase** out_database); + +DLLEXPORT void fdb_database_destroy(FDBDatabase* d); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, + FDBDatabaseOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, + FDBTransaction** out_transaction); + +DLLEXPORT void fdb_transaction_destroy(FDBTransaction* tr); + +DLLEXPORT void fdb_transaction_cancel(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_set_option(FDBTransaction* tr, + FDBTransactionOption option, + uint8_t const* value, + int value_length); +#endif + +DLLEXPORT void fdb_transaction_set_read_version(FDBTransaction* tr, int64_t version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t snapshot); +#endif + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset, + fdb_bool_t snapshot); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_addresses_for_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + 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); +#endif + +DLLEXPORT void fdb_transaction_set(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* value, + int value_length); + +DLLEXPORT void fdb_transaction_atomic_op(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* param, + int param_length, + FDBMutationType operation_type); + +DLLEXPORT void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length); + +DLLEXPORT void fdb_transaction_clear_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_get_committed_version(FDBTransaction* tr, + int64_t* out_version); + +// This function intentionally returns an FDBFuture instead of an integer directly, +// so that calling this API can see the effect of previous mutations on the transaction. +// Specifically, mutations are applied asynchronously by the main thread. In order to +// see them, this call has to be serviced by the main thread too. +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_on_error(FDBTransaction* tr, fdb_error_t error); + +DLLEXPORT void fdb_transaction_reset(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_add_conflict_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 +#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 +#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 +#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); + +DLLEXPORT int fdb_get_max_api_version(); +DLLEXPORT const char* fdb_get_client_version(); + +/* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_version(FDBFuture* f, int64_t* out_version); +#else +#define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES +typedef struct FDB_cluster FDBCluster; + +typedef enum { + // This option is only a placeholder for C compatibility and should not be used + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE = -1 +} FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cluster(FDBFuture* f, FDBCluster** out_cluster); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_database(FDBFuture* f, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster(const char* cluster_file_path); + +DLLEXPORT void fdb_cluster_destroy(FDBCluster* c); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cluster_set_option(FDBCluster* c, + FDBClusterOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cluster_create_database(FDBCluster* c, + uint8_t const* db_name, + int db_name_length); +#else +#define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION +#define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION +#define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION +#define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION +#define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION +#define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f, const char** out_description /* = NULL */); + +DLLEXPORT fdb_bool_t fdb_future_is_error(FDBFuture* f); +#else +#define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(const char* local_address); + +DLLEXPORT void fdb_transaction_set_option(FDBTransaction* tr, FDBTransactionOption option); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int limit); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector(FDBTransaction* tr, + 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); +#else +#define fdb_transaction_get_range_selector(tr, bkn, bknl, boe, bo, ekn, eknl, eoe, eo, lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/630/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/630/fdb_c_options.g.h new file mode 100644 index 00000000000..93524d8649b --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/630/fdb_c_options.g.h @@ -0,0 +1,457 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + // Deprecated + // Parameter: (String) IP:PORT + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + // Deprecated + // Parameter: (String) path to cluster file + FDB_NET_OPTION_CLUSTER_FILE=20, + + // Enables trace output to a file in a directory of the clients choosing + // Parameter: (String) path to output directory (or NULL for current working directory) + FDB_NET_OPTION_TRACE_ENABLE=30, + + // Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. + // Parameter: (Int) max size of a single trace output file + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. + // Parameter: (Int) max total size of trace files + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + // Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. + // Parameter: (String) value of the LogGroup attribute + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + // Select the format of the log files. xml (the default) and json are supported. + // Parameter: (String) Format of trace files + FDB_NET_OPTION_TRACE_FORMAT=34, + + // Select clock source for trace files. now (the default) or realtime are supported. + // Parameter: (String) Trace clock source + FDB_NET_OPTION_TRACE_CLOCK_SOURCE=35, + + // Once provided, this string will be used to replace the port/PID in the log file names. + // Parameter: (String) The identifier that will be part of all trace file names + FDB_NET_OPTION_TRACE_FILE_IDENTIFIER=36, + + // Set internal tuning or debugging knobs + // Parameter: (String) knob_name=knob_value + FDB_NET_OPTION_KNOB=40, + + // Deprecated + // Parameter: (String) file path or linker-resolved name + FDB_NET_OPTION_TLS_PLUGIN=41, + + // Set the certificate chain + // Parameter: (Bytes) certificates + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + // Set the file from which to load the certificate chain + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CERT_PATH=43, + + // Set the private key corresponding to your own certificate + // Parameter: (Bytes) key + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + // Set the file from which to load the private key corresponding to your own certificate + // Parameter: (String) file path + FDB_NET_OPTION_TLS_KEY_PATH=46, + + // Set the peer certificate field verification criteria + // Parameter: (Bytes) verification pattern + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + // + // Parameter: Option takes no parameter + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + // Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + // Set the probability of an active BUGGIFY section being fired + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + // Set the ca bundle + // Parameter: (Bytes) ca bundle + FDB_NET_OPTION_TLS_CA_BYTES=52, + + // Set the file from which to load the certificate authority bundle + // Parameter: (String) file path + FDB_NET_OPTION_TLS_CA_PATH=53, + + // Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. + // Parameter: (String) key passphrase + FDB_NET_OPTION_TLS_PASSWORD=54, + + // Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + // If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. + // Parameter: Option takes no parameter + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + // Adds an external client library for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to client library + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + // Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. + // Parameter: (String) path to directory containing client libraries + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + // Prevents connections through the local client, allowing only connections through externally loaded client libraries. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + // Spawns multiple worker threads for each version of the client that is loaded. Setting this to a number greater than one implies disable_local_client. + // Parameter: (Int) Number of client threads to be spawned. Each cluster will be serviced by a single client thread. + FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION=65, + + // Disables logging of client statistics, such as sampled transaction activity. + // Parameter: Option takes no parameter + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + // Deprecated + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + // Enables debugging feature to perform run loop profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. + // Parameter: Option takes no parameter + FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING=71, + + // Enable client buggify - will make requests randomly fail (intended for client testing) + // Parameter: Option takes no parameter + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + // Disable client buggify + // Parameter: Option takes no parameter + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + // Set the probability of a CLIENT_BUGGIFY section being active for the current execution. + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + // Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated + // Parameter: (Int) probability expressed as a percentage between 0 and 100 + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83 +} FDBNetworkOption; + +typedef enum { + // Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. + // Parameter: (Int) Max location cache entries + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. + // Parameter: (Int) Max outstanding watches + FDB_DB_OPTION_MAX_WATCHES=20, + + // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_MACHINE_ID=21, + + // Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. + // Parameter: (String) Hexadecimal ID + FDB_DB_OPTION_DATACENTER_ID=22, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + // Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) Maximum length of escaped key and value fields. + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + // Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. + // Parameter: (Int) value in milliseconds of timeout + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) number of times to retry + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + // Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. + // Parameter: (Int) value in bytes + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. + // Parameter: Option takes no parameter + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + // Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. + // Parameter: Option takes no parameter + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505 +} FDBDatabaseOption; + +typedef enum { + // The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + // The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + // Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + // The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. + // Parameter: Option takes no parameter + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + // Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + // + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_RISKY=120, + + // Deprecated + // Parameter: Option takes no parameter + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + // Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + // Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work + // Parameter: Option takes no parameter + FDB_TR_OPTION_PRIORITY_BATCH=201, + + // This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. + // Parameter: Option takes no parameter + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + // Allows this transaction to read and modify system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + // Allows this transaction to read system keys (those that start with the byte 0xFF) + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + // + // Parameter: (String) Optional transaction name + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + // Deprecated + // Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + // Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. + // Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + // Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOG_TRANSACTION=404, + + // Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. + // Parameter: (Int) Maximum length of escaped key and value fields. + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + // Sets an identifier for server tracing of this transaction. When committed, this identifier triggers logging when each part of the transaction authority encounters it, which is helpful in diagnosing slowness in misbehaving clusters. The identifier is randomly generated. When there is also a debug_transaction_identifier, both IDs are logged together. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SERVER_REQUEST_TRACING=406, + + // Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of timeout + FDB_TR_OPTION_TIMEOUT=500, + + // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) number of times to retry + FDB_TR_OPTION_RETRY_LIMIT=501, + + // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. + // Parameter: (Int) value in milliseconds of maximum delay + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + // Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. + // Parameter: (Int) value in bytes + FDB_TR_OPTION_SIZE_LIMIT=503, + + // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + // Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + // The transaction can read and write to locked databases, and is responsible for checking that it took the lock. + // Parameter: Option takes no parameter + FDB_TR_OPTION_LOCK_AWARE=700, + + // By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + // The transaction can read from locked databases. + // Parameter: Option takes no parameter + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + // This option should only be used by tools which change the database configuration. + // Parameter: Option takes no parameter + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711, + + // The transaction can retrieve keys that are conflicting with other transactions. + // Parameter: Option takes no parameter + FDB_TR_OPTION_REPORT_CONFLICTING_KEYS=712, + + // By default, the special key space will only allow users to read from exactly one module (a subspace in the special key space). Use this option to allow reading from zero or more modules. Users who set this option should be prepared for new modules, which may have different behaviors than the modules they're currently reading. For example, a new module might block or return an error. + // Parameter: Option takes no parameter + FDB_TR_OPTION_SPECIAL_KEY_SPACE_RELAXED=713, + + // Adds a tag to the transaction that can be used to apply manual targeted throttling. At most 5 tags can be set on a transaction. + // Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. + FDB_TR_OPTION_TAG=800, + + // Adds a tag to the transaction that can be used to apply manual or automatic targeted throttling. At most 5 tags can be set on a transaction. + // Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. + FDB_TR_OPTION_AUTO_THROTTLE_TAG=801 +} FDBTransactionOption; + +typedef enum { + // Client intends to consume the entire range and would like it all transferred as early as possible. + FDB_STREAMING_MODE_WANT_ALL=-2, + + // The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` + FDB_STREAMING_MODE_ITERATOR=-1, + + // Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. + FDB_STREAMING_MODE_EXACT=0, + + // Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. + FDB_STREAMING_MODE_SMALL=1, + + // Infrequently used. Transfer data in batches sized in between small and large. + FDB_STREAMING_MODE_MEDIUM=2, + + // Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. + FDB_STREAMING_MODE_LARGE=3, + + // Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + // Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. + FDB_MUTATION_TYPE_ADD=2, + + // Deprecated + FDB_MUTATION_TYPE_AND=6, + + // Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_AND=6, + + // Deprecated + FDB_MUTATION_TYPE_OR=7, + + // Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_OR=7, + + // Deprecated + FDB_MUTATION_TYPE_XOR=8, + + // Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. + FDB_MUTATION_TYPE_BIT_XOR=8, + + // Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_MAX=12, + + // Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_MIN=13, + + // Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + // Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MIN=16, + + // Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. + FDB_MUTATION_TYPE_BYTE_MAX=17, + + // Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + // Used to add a read conflict range + FDB_CONFLICT_RANGE_TYPE_READ=0, + + // Used to add a write conflict range + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + // Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + // Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + // Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/630/version.txt b/bindings/rust/foundationdb-sys/include/630/version.txt new file mode 100644 index 00000000000..8bbf9a8d7aa --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/630/version.txt @@ -0,0 +1 @@ +6.3.16 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/700/fdb_c.h b/bindings/rust/foundationdb-sys/include/700/fdb_c.h new file mode 100644 index 00000000000..cdae34fde09 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/700/fdb_c.h @@ -0,0 +1,434 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 700) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 700 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +/* + * With default settings, gcc will not warn about unprototyped functions being + * called, so it is easy to erroneously call a function which is not available + * at FDB_API_VERSION and then get an error only at runtime. These macros + * ensure a compile error in such cases, and attempt to make the compile error + * slightly informative. + */ +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() \ + { == == = } +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Pointers to these opaque types represent objects in the FDB API */ +typedef struct FDB_future FDBFuture; +typedef struct FDB_database FDBDatabase; +typedef struct FDB_transaction FDBTransaction; + +typedef int fdb_error_t; +typedef int fdb_bool_t; + +DLLEXPORT const char* fdb_get_error(fdb_error_t code); + +DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code); + +#define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_network_set_option(FDBNetworkOption option, + uint8_t const* value, + int value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), + void* hook_parameter); + +#pragma pack(push, 4) +typedef struct key { + const uint8_t* key; + int key_length; +} FDBKey; +#if FDB_API_VERSION >= 700 +typedef struct keyvalue { + const uint8_t* key; + int key_length; + const uint8_t* value; + int value_length; +} FDBKeyValue; +#else +typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; +} FDBKeyValue; +#endif +#pragma pack(pop) + +DLLEXPORT void fdb_future_cancel(FDBFuture* f); + +DLLEXPORT void fdb_future_release_memory(FDBFuture* f); + +DLLEXPORT void fdb_future_destroy(FDBFuture* f); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready(FDBFuture* f); + +DLLEXPORT fdb_bool_t fdb_future_is_ready(FDBFuture* f); + +typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_set_callback(FDBFuture* f, + FDBCallback callback, + void* callback_parameter); + +#if FDB_API_VERSION >= 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_int64(FDBFuture* f, int64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_uint64(FDBFuture* f, uint64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_value(FDBFuture* f, + fdb_bool_t* out_present, + uint8_t const** out_value, + int* out_value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); +#endif +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key_array(FDBFuture* f, + FDBKey const** out_key_array, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database(const char* cluster_file_path, FDBDatabase** out_database); + +DLLEXPORT void fdb_database_destroy(FDBDatabase* d); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, + FDBDatabaseOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db, + uint8_t const* address, + int address_length, + fdb_bool_t check, + int duration); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_force_recovery_with_data_loss(FDBDatabase* db, + uint8_t const* dcid, + int dcid_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_snapshot(FDBDatabase* db, + uint8_t const* uid, + int uid_length, + uint8_t const* snap_command, + int snap_command_length); + +DLLEXPORT WARN_UNUSED_RESULT double fdb_database_get_main_thread_busyness(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_server_protocol(FDBDatabase* db, uint64_t expected_version); + +DLLEXPORT void fdb_transaction_destroy(FDBTransaction* tr); + +DLLEXPORT void fdb_transaction_cancel(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_set_option(FDBTransaction* tr, + FDBTransactionOption option, + uint8_t const* value, + int value_length); +#endif + +DLLEXPORT void fdb_transaction_set_read_version(FDBTransaction* tr, int64_t version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t snapshot); +#endif + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset, + fdb_bool_t snapshot); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_addresses_for_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + 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); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_and_flat_map(FDBTransaction* tr, + 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); + +DLLEXPORT void fdb_transaction_set(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* value, + int value_length); + +DLLEXPORT void fdb_transaction_atomic_op(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* param, + int param_length, + FDBMutationType operation_type); + +DLLEXPORT void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length); + +DLLEXPORT void fdb_transaction_clear_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_get_committed_version(FDBTransaction* tr, + int64_t* out_version); + +/* + * This function intentionally returns an FDBFuture instead of an integer + * directly, so that calling this API can see the effect of previous + * mutations on the transaction. Specifically, mutations are applied + * asynchronously by the main thread. In order to see them, this call has to + * be serviced by the main thread too. + */ +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_on_error(FDBTransaction* tr, fdb_error_t error); + +DLLEXPORT void fdb_transaction_reset(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_add_conflict_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points(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 chunk_size); + +#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 +#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 +#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 +#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); + +DLLEXPORT int fdb_get_max_api_version(); +DLLEXPORT const char* fdb_get_client_version(); + +/* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_version(FDBFuture* f, int64_t* out_version); +#else +#define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES +typedef struct FDB_cluster FDBCluster; + +typedef enum { + /* This option is only a placeholder for C compatibility and should not be used */ + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE = -1 +} FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cluster(FDBFuture* f, FDBCluster** out_cluster); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_database(FDBFuture* f, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster(const char* cluster_file_path); + +DLLEXPORT void fdb_cluster_destroy(FDBCluster* c); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cluster_set_option(FDBCluster* c, + FDBClusterOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cluster_create_database(FDBCluster* c, + uint8_t const* db_name, + int db_name_length); +#else +#define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION +#define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION +#define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION +#define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION +#define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION +#define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f, const char** out_description /* = NULL */); + +DLLEXPORT fdb_bool_t fdb_future_is_error(FDBFuture* f); +#else +#define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(const char* local_address); + +DLLEXPORT void fdb_transaction_set_option(FDBTransaction* tr, FDBTransactionOption option); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int limit); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector(FDBTransaction* tr, + 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); +#else +#define fdb_transaction_get_range_selector(tr, bkn, bknl, boe, bo, ekn, eknl, eoe, eo, lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/700/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/700/fdb_c_options.g.h new file mode 100644 index 00000000000..5b8e89e99db --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/700/fdb_c_options.g.h @@ -0,0 +1,485 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + /* Deprecated */ + /* Parameter: (String) IP:PORT */ + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + /* Deprecated */ + /* Parameter: (String) path to cluster file */ + FDB_NET_OPTION_CLUSTER_FILE=20, + + /* Enables trace output to a file in a directory of the clients choosing */ + /* Parameter: (String) path to output directory (or NULL for current working directory) */ + FDB_NET_OPTION_TRACE_ENABLE=30, + + /* Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. */ + /* Parameter: (Int) max size of a single trace output file */ + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + /* Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. */ + /* Parameter: (Int) max total size of trace files */ + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + /* Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. */ + /* Parameter: (String) value of the LogGroup attribute */ + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + /* Select the format of the log files. xml (the default) and json are supported. */ + /* Parameter: (String) Format of trace files */ + FDB_NET_OPTION_TRACE_FORMAT=34, + + /* Select clock source for trace files. now (the default) or realtime are supported. */ + /* Parameter: (String) Trace clock source */ + FDB_NET_OPTION_TRACE_CLOCK_SOURCE=35, + + /* Once provided, this string will be used to replace the port/PID in the log file names. */ + /* Parameter: (String) The identifier that will be part of all trace file names */ + FDB_NET_OPTION_TRACE_FILE_IDENTIFIER=36, + + /* */ + /* Parameter: (String) Append this suffix to partially written log files. When a log file is complete, it is renamed to remove the suffix. No separator is added between the file and the suffix. If you want to add a file extension, you should include the separator - e.g. '.tmp' instead of 'tmp' to add the 'tmp' extension. */ + FDB_NET_OPTION_TRACE_PARTIAL_FILE_SUFFIX=39, + + /* Set internal tuning or debugging knobs */ + /* Parameter: (String) knob_name=knob_value */ + FDB_NET_OPTION_KNOB=40, + + /* Deprecated */ + /* Parameter: (String) file path or linker-resolved name */ + FDB_NET_OPTION_TLS_PLUGIN=41, + + /* Set the certificate chain */ + /* Parameter: (Bytes) certificates */ + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + /* Set the file from which to load the certificate chain */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CERT_PATH=43, + + /* Set the private key corresponding to your own certificate */ + /* Parameter: (Bytes) key */ + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + /* Set the file from which to load the private key corresponding to your own certificate */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_KEY_PATH=46, + + /* Set the peer certificate field verification criteria */ + /* Parameter: (Bytes) verification pattern */ + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + /* Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + /* Set the probability of an active BUGGIFY section being fired */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + /* Set the ca bundle */ + /* Parameter: (Bytes) ca bundle */ + FDB_NET_OPTION_TLS_CA_BYTES=52, + + /* Set the file from which to load the certificate authority bundle */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CA_PATH=53, + + /* Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. */ + /* Parameter: (String) key passphrase */ + FDB_NET_OPTION_TLS_PASSWORD=54, + + /* Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + /* If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + /* Adds an external client library for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + /* Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to directory containing client libraries */ + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + /* Prevents connections through the local client, allowing only connections through externally loaded client libraries. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + /* Spawns multiple worker threads for each version of the client that is loaded. Setting this to a number greater than one implies disable_local_client. */ + /* Parameter: (Int) Number of client threads to be spawned. Each cluster will be serviced by a single client thread. */ + FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION=65, + + /* Disables logging of client statistics, such as sampled transaction activity. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + /* Enables debugging feature to perform run loop profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING=71, + + /* Enable client buggify - will make requests randomly fail (intended for client testing) */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + /* Disable client buggify */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + /* Set the probability of a CLIENT_BUGGIFY section being active for the current execution. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + /* Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83, + + /* Set a tracer to run on the client. Should be set to the same value as the tracer set on the server. */ + /* Parameter: (String) Distributed tracer type. Choose from none, log_file, or network_lossy */ + FDB_NET_OPTION_DISTRIBUTED_CLIENT_TRACER=90 +} FDBNetworkOption; + +typedef enum { + /* Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. */ + /* Parameter: (Int) Max location cache entries */ + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + /* Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. */ + /* Parameter: (Int) Max outstanding watches */ + FDB_DB_OPTION_MAX_WATCHES=20, + + /* Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_MACHINE_ID=21, + + /* Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_DATACENTER_ID=22, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) number of times to retry */ + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + /* Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in bytes */ + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + /* Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the ``bypass_unreadable`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_BYPASS_UNREADABLE=700 +} FDBDatabaseOption; + +typedef enum { + /* The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + /* Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + /* The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + /* Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. It is an error to set this option after performing any reads or writes on the transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_RISKY=120, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + /* Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + /* Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_BATCH=201, + + /* This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + /* Allows this transaction to read and modify system keys (those that start with the byte 0xFF) */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + /* Allows this transaction to read system keys (those that start with the byte 0xFF) */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + /* */ + /* Parameter: (String) Optional transaction name */ + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + /* Deprecated */ + /* Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + /* Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. */ + /* Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + /* Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOG_TRANSACTION=404, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Sets an identifier for server tracing of this transaction. When committed, this identifier triggers logging when each part of the transaction authority encounters it, which is helpful in diagnosing slowness in misbehaving clusters. The identifier is randomly generated. When there is also a debug_transaction_identifier, both IDs are logged together. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SERVER_REQUEST_TRACING=406, + + /* Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_TR_OPTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) number of times to retry */ + FDB_TR_OPTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + /* Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. */ + /* Parameter: (Int) value in bytes */ + FDB_TR_OPTION_SIZE_LIMIT=503, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + /* The transaction can read and write to locked databases, and is responsible for checking that it took the lock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOCK_AWARE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* The transaction can read from locked databases. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + /* This option should only be used by tools which change the database configuration. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711, + + /* The transaction can retrieve keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_REPORT_CONFLICTING_KEYS=712, + + /* By default, the special key space will only allow users to read from exactly one module (a subspace in the special key space). Use this option to allow reading from zero or more modules. Users who set this option should be prepared for new modules, which may have different behaviors than the modules they're currently reading. For example, a new module might block or return an error. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_RELAXED=713, + + /* By default, users are not allowed to write to special keys. Enable this option will implicitly enable all options required to achieve the configuration change. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_ENABLE_WRITES=714, + + /* Adds a tag to the transaction that can be used to apply manual targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_TAG=800, + + /* Adds a tag to the transaction that can be used to apply manual or automatic targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_AUTO_THROTTLE_TAG=801, + + /* Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with any 16 bytes */ + /* Parameter: (Bytes) A byte string of length 16 used to associate the span of this transaction with a parent */ + FDB_TR_OPTION_SPAN_PARENT=900, + + /* Asks storage servers for how many bytes a clear key range contains. Otherwise uses the location cache to roughly estimate this. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_EXPENSIVE_CLEAR_COST_ESTIMATION_ENABLE=1000, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_UNREADABLE=1100 +} FDBTransactionOption; + +typedef enum { + /* Client intends to consume the entire range and would like it all transferred as early as possible. */ + FDB_STREAMING_MODE_WANT_ALL=-2, + + /* The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` */ + FDB_STREAMING_MODE_ITERATOR=-1, + + /* Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. */ + FDB_STREAMING_MODE_EXACT=0, + + /* Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. */ + FDB_STREAMING_MODE_SMALL=1, + + /* Infrequently used. Transfer data in batches sized in between small and large. */ + FDB_STREAMING_MODE_MEDIUM=2, + + /* Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. */ + FDB_STREAMING_MODE_LARGE=3, + + /* Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. */ + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + /* Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. */ + FDB_MUTATION_TYPE_ADD=2, + + /* Deprecated */ + FDB_MUTATION_TYPE_AND=6, + + /* Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_AND=6, + + /* Deprecated */ + FDB_MUTATION_TYPE_OR=7, + + /* Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_OR=7, + + /* Deprecated */ + FDB_MUTATION_TYPE_XOR=8, + + /* Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_XOR=8, + + /* Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. */ + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MAX=12, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MIN=13, + + /* Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + /* Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MIN=16, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MAX=17, + + /* Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. */ + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + /* Used to add a read conflict range */ + FDB_CONFLICT_RANGE_TYPE_READ=0, + + /* Used to add a write conflict range */ + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + /* Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. */ + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + /* Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. */ + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + /* Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. */ + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/700/version.txt b/bindings/rust/foundationdb-sys/include/700/version.txt new file mode 100644 index 00000000000..4122521804f --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/700/version.txt @@ -0,0 +1 @@ +7.0.0 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/710/fdb_c.h b/bindings/rust/foundationdb-sys/include/710/fdb_c.h new file mode 100644 index 00000000000..cbd90226e7b --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/710/fdb_c.h @@ -0,0 +1,576 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (current version is 710) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > 710 +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +/* + * With default settings, gcc will not warn about unprototyped functions being + * called, so it is easy to erroneously call a function which is not available + * at FDB_API_VERSION and then get an error only at runtime. These macros + * ensure a compile error in such cases, and attempt to make the compile error + * slightly informative. + */ +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() \ + { == == = } +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" +#include "fdb_c_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DLLEXPORT const char* fdb_get_error(fdb_error_t code); + +DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code); + +#define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_network_set_option(FDBNetworkOption option, + uint8_t const* value, + int value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), + void* hook_parameter); + +#pragma pack(push, 4) +typedef struct key { + const uint8_t* key; + int key_length; +} FDBKey; +#if FDB_API_VERSION >= 710 +typedef struct keyvalue { + const uint8_t* key; + int key_length; + const uint8_t* value; + int value_length; +} FDBKeyValue; +#else +typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; +} FDBKeyValue; +#endif + +#pragma pack(pop) + +/* Memory layout of KeySelectorRef. */ +typedef struct keyselector { + FDBKey key; + /* orEqual and offset have not be tested in C binding. Just a placeholder. */ + fdb_bool_t orEqual; + int offset; +} FDBKeySelector; + +/* Memory layout of GetRangeReqAndResultRef. */ +typedef struct getrangereqandresult { + FDBKeySelector begin; + FDBKeySelector end; + FDBKeyValue* data; + int m_size, m_capacity; +} FDBGetRangeReqAndResult; + +/* Memory layout of MappedKeyValueRef. + +Total 112 bytes +- key (12 bytes) +:74:8F:8E:5F:AE:7F:00:00 +:4A:00:00:00 +- value (12 bytes) +:70:8F:8E:5F:AE:7F:00:00 +:00:00:00:00 +- begin selector (20 bytes) +:30:8F:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:7F:00:00 +:01:00:00:00 +- end selector (20 bytes) +:EC:8E:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:2B:3C:60 +:01:00:00:00 +- vector (16 bytes) +:74:94:8E:5F:AE:7F:00:00 +:01:00:00:00 +:01:00:00:00 +- buffer (32 bytes) +:00:20:D1:61:00:00:00:00 +:00:00:00:00:00:00:00:00 +:00:00:00:00:00:00:00:00 +:01:00:00:00:AE:7F:00:00 +*/ +typedef struct mappedkeyvalue { + FDBKey key; + FDBKey value; + /* It's complicated to map a std::variant to C. For now we assume the underlying requests are always getRange and + * take the shortcut. */ + FDBGetRangeReqAndResult getRange; + unsigned char buffer[32]; +} FDBMappedKeyValue; + +#pragma pack(push, 4) +typedef struct keyrange { + const uint8_t* begin_key; + int begin_key_length; + const uint8_t* end_key; + int end_key_length; +} FDBKeyRange; +#pragma pack(pop) + +typedef struct readgranulecontext { + /* 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 this to true for testing if you don't want to read the granule files, + just do the request to the blob workers */ + fdb_bool_t debugNoMaterialize; + + /* Number of granules to load in parallel */ + int granuleParallelism; +} FDBReadBlobGranuleContext; + +DLLEXPORT void fdb_future_cancel(FDBFuture* f); + +DLLEXPORT void fdb_future_release_memory(FDBFuture* f); + +DLLEXPORT void fdb_future_destroy(FDBFuture* f); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready(FDBFuture* f); + +DLLEXPORT fdb_bool_t fdb_future_is_ready(FDBFuture* f); + +typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_set_callback(FDBFuture* f, + FDBCallback callback, + void* callback_parameter); + +#if FDB_API_VERSION >= 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_int64(FDBFuture* f, int64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_uint64(FDBFuture* f, uint64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_value(FDBFuture* f, + fdb_bool_t* out_present, + uint8_t const** out_value, + int* out_value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_mappedkeyvalue_array(FDBFuture* f, + FDBMappedKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key_array(FDBFuture* f, + FDBKey const** out_key_array, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyrange_array(FDBFuture* f, + FDBKeyRange const** out_ranges, + int* out_count); + +/* FDBResult is a synchronous computation result, as opposed to a future that is asynchronous. */ +DLLEXPORT void fdb_result_destroy(FDBResult* r); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_result_get_keyvalue_array(FDBResult* r, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +/* TODO: add other return types as we need them */ + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database(const char* cluster_file_path, FDBDatabase** out_database); + +DLLEXPORT void fdb_database_destroy(FDBDatabase* d); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, + FDBDatabaseOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_open_tenant(FDBDatabase* d, + uint8_t const* tenant_name, + int tenant_name_length, + FDBTenant** out_tenant); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db, + uint8_t const* address, + int address_length, + fdb_bool_t check, + int duration); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_force_recovery_with_data_loss(FDBDatabase* db, + uint8_t const* dcid, + int dcid_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_snapshot(FDBDatabase* db, + uint8_t const* uid, + int uid_length, + uint8_t const* snap_command, + int snap_command_length); + +DLLEXPORT WARN_UNUSED_RESULT double fdb_database_get_main_thread_busyness(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_server_protocol(FDBDatabase* db, uint64_t expected_version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_purge_blob_granules(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 purge_version, + fdb_bool_t force); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_wait_purge_granules_complete(FDBDatabase* db, + uint8_t const* purge_key_name, + int purge_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_tenant_create_transaction(FDBTenant* tenant, + FDBTransaction** out_transaction); + +DLLEXPORT void fdb_tenant_destroy(FDBTenant* tenant); + +DLLEXPORT void fdb_transaction_destroy(FDBTransaction* tr); + +DLLEXPORT void fdb_transaction_cancel(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_set_option(FDBTransaction* tr, + FDBTransactionOption option, + uint8_t const* value, + int value_length); +#endif + +DLLEXPORT void fdb_transaction_set_read_version(FDBTransaction* tr, int64_t version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t snapshot); +#endif + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset, + fdb_bool_t snapshot); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_addresses_for_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + 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); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_mapped_range(FDBTransaction* tr, + 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); + +DLLEXPORT void fdb_transaction_set(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* value, + int value_length); + +DLLEXPORT void fdb_transaction_atomic_op(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* param, + int param_length, + FDBMutationType operation_type); + +DLLEXPORT void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length); + +DLLEXPORT void fdb_transaction_clear_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_get_committed_version(FDBTransaction* tr, + int64_t* out_version); + +/* + * This function intentionally returns an FDBFuture instead of an integer + * directly, so that calling this API can see the effect of previous + * mutations on the transaction. Specifically, mutations are applied + * asynchronously by the main thread. In order to see them, this call has to + * be serviced by the main thread too. + */ +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_on_error(FDBTransaction* tr, fdb_error_t error); + +DLLEXPORT void fdb_transaction_reset(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_add_conflict_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points(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 chunk_size); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_blob_granule_ranges(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +/* LatestVersion (-2) for readVersion means get read version from transaction + Separated out as optional because BG reads can support longer-lived reads than normal FDB transactions */ +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_transaction_read_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 beginVersion, + int64_t readVersion, + FDBReadBlobGranuleContext granuleContext); + +#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 +#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 +#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 +#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); + +DLLEXPORT int fdb_get_max_api_version(); +DLLEXPORT const char* fdb_get_client_version(); + +/* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_version(FDBFuture* f, int64_t* out_version); +#else +#define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES +typedef struct FDB_cluster FDBCluster; + +typedef enum { + /* This option is only a placeholder for C compatibility and should not be used */ + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE = -1 +} FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cluster(FDBFuture* f, FDBCluster** out_cluster); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_database(FDBFuture* f, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster(const char* cluster_file_path); + +DLLEXPORT void fdb_cluster_destroy(FDBCluster* c); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cluster_set_option(FDBCluster* c, + FDBClusterOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cluster_create_database(FDBCluster* c, + uint8_t const* db_name, + int db_name_length); +#else +#define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION +#define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION +#define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION +#define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION +#define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION +#define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f, const char** out_description /* = NULL */); + +DLLEXPORT fdb_bool_t fdb_future_is_error(FDBFuture* f); +#else +#define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(const char* local_address); + +DLLEXPORT void fdb_transaction_set_option(FDBTransaction* tr, FDBTransactionOption option); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int limit); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector(FDBTransaction* tr, + 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); +#else +#define fdb_transaction_get_range_selector(tr, bkn, bknl, boe, bo, ekn, eknl, eoe, eo, lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/710/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/710/fdb_c_options.g.h new file mode 100644 index 00000000000..06812ce4fa3 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/710/fdb_c_options.g.h @@ -0,0 +1,533 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + /* Deprecated */ + /* Parameter: (String) IP:PORT */ + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + /* Deprecated */ + /* Parameter: (String) path to cluster file */ + FDB_NET_OPTION_CLUSTER_FILE=20, + + /* Enables trace output to a file in a directory of the clients choosing */ + /* Parameter: (String) path to output directory (or NULL for current working directory) */ + FDB_NET_OPTION_TRACE_ENABLE=30, + + /* Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. */ + /* Parameter: (Int) max size of a single trace output file */ + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + /* Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. */ + /* Parameter: (Int) max total size of trace files */ + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + /* Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. */ + /* Parameter: (String) value of the LogGroup attribute */ + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + /* Select the format of the log files. xml (the default) and json are supported. */ + /* Parameter: (String) Format of trace files */ + FDB_NET_OPTION_TRACE_FORMAT=34, + + /* Select clock source for trace files. now (the default) or realtime are supported. */ + /* Parameter: (String) Trace clock source */ + FDB_NET_OPTION_TRACE_CLOCK_SOURCE=35, + + /* Once provided, this string will be used to replace the port/PID in the log file names. */ + /* Parameter: (String) The identifier that will be part of all trace file names */ + FDB_NET_OPTION_TRACE_FILE_IDENTIFIER=36, + + /* Set file suffix for partially written log files. */ + /* Parameter: (String) Append this suffix to partially written log files. When a log file is complete, it is renamed to remove the suffix. No separator is added between the file and the suffix. If you want to add a file extension, you should include the separator - e.g. '.tmp' instead of 'tmp' to add the 'tmp' extension. */ + FDB_NET_OPTION_TRACE_PARTIAL_FILE_SUFFIX=39, + + /* Set internal tuning or debugging knobs */ + /* Parameter: (String) knob_name=knob_value */ + FDB_NET_OPTION_KNOB=40, + + /* Deprecated */ + /* Parameter: (String) file path or linker-resolved name */ + FDB_NET_OPTION_TLS_PLUGIN=41, + + /* Set the certificate chain */ + /* Parameter: (Bytes) certificates */ + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + /* Set the file from which to load the certificate chain */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CERT_PATH=43, + + /* Set the private key corresponding to your own certificate */ + /* Parameter: (Bytes) key */ + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + /* Set the file from which to load the private key corresponding to your own certificate */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_KEY_PATH=46, + + /* Set the peer certificate field verification criteria */ + /* Parameter: (Bytes) verification pattern */ + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + /* Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + /* Set the probability of an active BUGGIFY section being fired */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + /* Set the ca bundle */ + /* Parameter: (Bytes) ca bundle */ + FDB_NET_OPTION_TLS_CA_BYTES=52, + + /* Set the file from which to load the certificate authority bundle */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CA_PATH=53, + + /* Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. */ + /* Parameter: (String) key passphrase */ + FDB_NET_OPTION_TLS_PASSWORD=54, + + /* Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + /* If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + /* Adds an external client library for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + /* Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to directory containing client libraries */ + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + /* Prevents connections through the local client, allowing only connections through externally loaded client libraries. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + /* Spawns multiple worker threads for each version of the client that is loaded. Setting this to a number greater than one implies disable_local_client. */ + /* Parameter: (Int) Number of client threads to be spawned. Each cluster will be serviced by a single client thread. */ + FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION=65, + + /* Disables logging of client statistics, such as sampled transaction activity. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + /* Enables debugging feature to perform run loop profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING=71, + + /* Enable client buggify - will make requests randomly fail (intended for client testing) */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + /* Disable client buggify */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + /* Set the probability of a CLIENT_BUGGIFY section being active for the current execution. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + /* Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83, + + /* Set a tracer to run on the client. Should be set to the same value as the tracer set on the server. */ + /* Parameter: (String) Distributed tracer type. Choose from none, log_file, or network_lossy */ + FDB_NET_OPTION_DISTRIBUTED_CLIENT_TRACER=90, + + /* This option is set automatically to communicate the list of supported clients to the active client. */ + /* Parameter: (String) [release version],[source version],[protocol version];... This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_SUPPORTED_CLIENT_VERSIONS=1000, + + /* This option is set automatically on all clients loaded externally using the multi-version API. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT=1001, + + /* This option tells a child on a multiversion client what transport ID to use. */ + /* Parameter: (Int) Transport ID for the child connection This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT_TRANSPORT_ID=1002 +} FDBNetworkOption; + +typedef enum { + /* Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. */ + /* Parameter: (Int) Max location cache entries */ + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + /* Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. */ + /* Parameter: (Int) Max outstanding watches */ + FDB_DB_OPTION_MAX_WATCHES=20, + + /* Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_MACHINE_ID=21, + + /* Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_DATACENTER_ID=22, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) number of times to retry */ + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + /* Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in bytes */ + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + /* Deprecated. Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the ``bypass_unreadable`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_BYPASS_UNREADABLE=700, + + /* Use configuration database. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_USE_CONFIG_DATABASE=800, + + /* An integer between 0 and 100 (default is 0) expressing the probability that a client will verify it can't read stale data whenever it detects a recovery. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TEST_CAUSAL_READ_RISKY=900 +} FDBDatabaseOption; + +typedef enum { + /* The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + /* Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + /* The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + /* Committing this transaction will bypass the normal load balancing across commit proxies and go directly to the specifically nominated 'first commit proxy'. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_COMMIT_ON_FIRST_PROXY=40, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_CHECK_WRITES_ENABLE=50, + + /* Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. It is an error to set this option after performing any reads or writes on the transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_RISKY=120, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + /* Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + /* Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_BATCH=201, + + /* This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + /* Allows this transaction to read and modify system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + /* Allows this transaction to read system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + /* Allows this transaction to access the raw key-space when tenant mode is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_RAW_ACCESS=303, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_DEBUG_DUMP=400, + + /* */ + /* Parameter: (String) Optional transaction name */ + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + /* Deprecated */ + /* Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + /* Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. */ + /* Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + /* Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOG_TRANSACTION=404, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Sets an identifier for server tracing of this transaction. When committed, this identifier triggers logging when each part of the transaction authority encounters it, which is helpful in diagnosing slowness in misbehaving clusters. The identifier is randomly generated. When there is also a debug_transaction_identifier, both IDs are logged together. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SERVER_REQUEST_TRACING=406, + + /* Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_TR_OPTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) number of times to retry */ + FDB_TR_OPTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + /* Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. */ + /* Parameter: (Int) value in bytes */ + FDB_TR_OPTION_SIZE_LIMIT=503, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + /* The transaction can read and write to locked databases, and is responsible for checking that it took the lock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOCK_AWARE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* The transaction can read from locked databases. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + /* No other transactions will be applied before this transaction within the same commit version. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_FIRST_IN_BATCH=710, + + /* This option should only be used by tools which change the database configuration. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711, + + /* The transaction can retrieve keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_REPORT_CONFLICTING_KEYS=712, + + /* By default, the special key space will only allow users to read from exactly one module (a subspace in the special key space). Use this option to allow reading from zero or more modules. Users who set this option should be prepared for new modules, which may have different behaviors than the modules they're currently reading. For example, a new module might block or return an error. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_RELAXED=713, + + /* By default, users are not allowed to write to special keys. Enable this option will implicitly enable all options required to achieve the configuration change. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_ENABLE_WRITES=714, + + /* Adds a tag to the transaction that can be used to apply manual targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_TAG=800, + + /* Adds a tag to the transaction that can be used to apply manual or automatic targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_AUTO_THROTTLE_TAG=801, + + /* Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with any 16 bytes */ + /* Parameter: (Bytes) A byte string of length 16 used to associate the span of this transaction with a parent */ + FDB_TR_OPTION_SPAN_PARENT=900, + + /* Asks storage servers for how many bytes a clear key range contains. Otherwise uses the location cache to roughly estimate this. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_EXPENSIVE_CLEAR_COST_ESTIMATION_ENABLE=1000, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_UNREADABLE=1100, + + /* Allows this transaction to use cached GRV from the database context. Defaults to off. Upon first usage, starts a background updater to periodically update the cache to avoid stale read versions. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_GRV_CACHE=1101, + + /* Specifically instruct this transaction to NOT use cached GRV. Primarily used for the read version cache's background updater to avoid attempting to read a cached entry in specific situations. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_SKIP_GRV_CACHE=1102 +} FDBTransactionOption; + +typedef enum { + /* Client intends to consume the entire range and would like it all transferred as early as possible. */ + FDB_STREAMING_MODE_WANT_ALL=-2, + + /* The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` */ + FDB_STREAMING_MODE_ITERATOR=-1, + + /* Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. */ + FDB_STREAMING_MODE_EXACT=0, + + /* Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. */ + FDB_STREAMING_MODE_SMALL=1, + + /* Infrequently used. Transfer data in batches sized in between small and large. */ + FDB_STREAMING_MODE_MEDIUM=2, + + /* Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. */ + FDB_STREAMING_MODE_LARGE=3, + + /* Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. */ + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + /* Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. */ + FDB_MUTATION_TYPE_ADD=2, + + /* Deprecated */ + FDB_MUTATION_TYPE_AND=6, + + /* Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_AND=6, + + /* Deprecated */ + FDB_MUTATION_TYPE_OR=7, + + /* Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_OR=7, + + /* Deprecated */ + FDB_MUTATION_TYPE_XOR=8, + + /* Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_XOR=8, + + /* Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. */ + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MAX=12, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MIN=13, + + /* Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + /* Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MIN=16, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MAX=17, + + /* Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. */ + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + /* Used to add a read conflict range */ + FDB_CONFLICT_RANGE_TYPE_READ=0, + + /* Used to add a write conflict range */ + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + /* Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. */ + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + /* Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. */ + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + /* Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. */ + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/710/fdb_c_types.h b/bindings/rust/foundationdb-sys/include/710/fdb_c_types.h new file mode 100644 index 00000000000..779e227ad5e --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/710/fdb_c_types.h @@ -0,0 +1,47 @@ +/* + * fdb_c_types.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_TYPES_H +#define FDB_C_TYPES_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Pointers to these opaque types represent objects in the FDB API */ +typedef struct FDB_future FDBFuture; +typedef struct FDB_result FDBResult; +typedef struct FDB_cluster FDBCluster; +typedef struct FDB_database FDBDatabase; +typedef struct FDB_tenant FDBTenant; +typedef struct FDB_transaction FDBTransaction; + +typedef int fdb_error_t; +typedef int fdb_bool_t; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/710/version.txt b/bindings/rust/foundationdb-sys/include/710/version.txt new file mode 100644 index 00000000000..a52e7a4eb7e --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/710/version.txt @@ -0,0 +1 @@ +7.1.5 \ No newline at end of file diff --git a/bindings/rust/foundationdb-sys/include/730/fdb_c.h b/bindings/rust/foundationdb-sys/include/730/fdb_c.h new file mode 100644 index 00000000000..f13bb29a958 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/fdb_c.h @@ -0,0 +1,813 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#include "fdb_c_apiversion.g.h" +#if (defined FDB_USE_LATEST_API_VERSION) +#define FDB_API_VERSION FDB_LATEST_API_VERSION +#elif (defined FDB_USE_LATEST_BINDINGS_API_VERSION) +#define FDB_API_VERSION FDB_LATEST_BINDINGS_API_VERSION +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (the latest version is defined as FDB_LATEST_API_VERSION) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > FDB_LATEST_API_VERSION +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +/* + * With default settings, gcc will not warn about unprototyped functions being + * called, so it is easy to erroneously call a function which is not available + * at FDB_API_VERSION and then get an error only at runtime. These macros + * ensure a compile error in such cases, and attempt to make the compile error + * slightly informative. + */ +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() \ + { == == = } +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" +#include "fdb_c_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DLLEXPORT const char* fdb_get_error(fdb_error_t code); + +DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code); + +#define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + +/* + * A variant of fdb_select_api_version that caps the header API version by the maximum API version + * supported by the client library. It is intended mainly for use in combination with the shim + * layer, which loads the client library dynamically. + */ +#define /* fdb_error_t */ fdb_select_api_version_capped(v) \ + fdb_select_api_version_impl( \ + v, FDB_API_VERSION < fdb_get_max_api_version() ? FDB_API_VERSION : fdb_get_max_api_version()) + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_network_set_option(FDBNetworkOption option, + uint8_t const* value, + int value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(void); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(void); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(void); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), + void* hook_parameter); + +#pragma pack(push, 4) +typedef struct key { + const uint8_t* key; + int key_length; +} FDBKey; +#if FDB_API_VERSION >= 630 +typedef struct keyvalue { + const uint8_t* key; + int key_length; + const uint8_t* value; + int value_length; +} FDBKeyValue; +#else +typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; +} FDBKeyValue; +#endif + +#pragma pack(pop) + +/* Memory layout of KeySelectorRef. */ +typedef struct keyselector { + FDBKey key; + /* orEqual and offset have not be tested in C binding. Just a placeholder. */ + fdb_bool_t orEqual; + int offset; +} FDBKeySelector; + +/* Memory layout of GetRangeReqAndResultRef. */ +typedef struct getrangereqandresult { + FDBKeySelector begin; + FDBKeySelector end; + FDBKeyValue* data; + int m_size, m_capacity; +} FDBGetRangeReqAndResult; + +/* Memory layout of MappedKeyValueRef. + +Total 112 bytes +- key (12 bytes) +:74:8F:8E:5F:AE:7F:00:00 +:4A:00:00:00 +- value (12 bytes) +:70:8F:8E:5F:AE:7F:00:00 +:00:00:00:00 +- begin selector (20 bytes) +:30:8F:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:7F:00:00 +:01:00:00:00 +- end selector (20 bytes) +:EC:8E:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:2B:3C:60 +:01:00:00:00 +- vector (16 bytes) +:74:94:8E:5F:AE:7F:00:00 +:01:00:00:00 +:01:00:00:00 +- buffer (32 bytes) +:00:20:D1:61:00:00:00:00 +:00:00:00:00:00:00:00:00 +:00:00:00:00:00:00:00:00 +:01:00:00:00:AE:7F:00:00 +*/ +typedef struct mappedkeyvalue { + FDBKey key; + FDBKey value; + /* It's complicated to map a std::variant to C. For now we assume the underlying requests are always getRange and + * take the shortcut. */ + FDBGetRangeReqAndResult getRange; + unsigned char buffer[32]; +} FDBMappedKeyValue; + +#pragma pack(push, 4) +typedef struct keyrange { + const uint8_t* begin_key; + int begin_key_length; + const uint8_t* end_key; + int end_key_length; +} FDBKeyRange; + +typedef struct granulesummary { + FDBKeyRange key_range; + int64_t snapshot_version; + int64_t snapshot_size; + int64_t delta_version; + int64_t delta_size; +} FDBGranuleSummary; +#pragma pack(pop) + +typedef struct readgranulecontext { + /* 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 this to true for testing if you don't want to read the granule files, + just do the request to the blob workers */ + fdb_bool_t debugNoMaterialize; + + /* Number of granules to load in parallel */ + int granuleParallelism; +} FDBReadBlobGranuleContext; + +typedef enum { FDB_BG_MUTATION_TYPE_SET_VALUE = 0, FDB_BG_MUTATION_TYPE_CLEAR_RANGE = 1 } FDBBGMutationType; + +#pragma pack(push, 4) + +typedef struct bgtenantprefix { + fdb_bool_t present; + FDBKey prefix; +} FDBBGTenantPrefix; + +/* encryption structs correspond to similar ones in BlobGranuleCommon.h */ +typedef struct bgencryptionkey { + int64_t domain_id; + uint64_t base_key_id; + uint32_t base_kcv; + uint64_t random_salt; + FDBKey base_key; +} FDBBGEncryptionKey; + +typedef struct bgencryptionctx { + fdb_bool_t present; + FDBBGEncryptionKey textKey; + uint32_t textKCV; + FDBBGEncryptionKey headerKey; + uint32_t headerKCV; + FDBKey iv; +} FDBBGEncryptionCtx; + +typedef struct bgfilepointer { + const uint8_t* filename_ptr; + int filename_length; + int64_t file_offset; + int64_t file_length; + int64_t full_file_length; + int64_t file_version; + FDBBGEncryptionCtx encryption_ctx; +} FDBBGFilePointer; + +typedef struct bgmutation { + /* FDBBGMutationType */ uint8_t type; + int64_t version; + const uint8_t* param1_ptr; + int param1_length; + const uint8_t* param2_ptr; + int param2_length; +} FDBBGMutation; + +typedef struct bgfiledescription { + FDBKeyRange key_range; + fdb_bool_t snapshot_present; + FDBBGFilePointer snapshot_file_pointer; + int delta_file_count; + FDBBGFilePointer* delta_files; + int memory_mutation_count; + FDBBGMutation* memory_mutations; + FDBBGTenantPrefix tenant_prefix; +} FDBBGFileDescription; +#pragma pack(pop) + +DLLEXPORT void fdb_future_cancel(FDBFuture* f); + +DLLEXPORT void fdb_future_release_memory(FDBFuture* f); + +DLLEXPORT void fdb_future_destroy(FDBFuture* f); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready(FDBFuture* f); + +DLLEXPORT fdb_bool_t fdb_future_is_ready(FDBFuture* f); + +typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_set_callback(FDBFuture* f, + FDBCallback callback, + void* callback_parameter); + +#if FDB_API_VERSION >= 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_bool(FDBFuture* f, fdb_bool_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_int64(FDBFuture* f, int64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_uint64(FDBFuture* f, uint64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_double(FDBFuture* f, double* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_value(FDBFuture* f, + fdb_bool_t* out_present, + uint8_t const** out_value, + int* out_value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_mappedkeyvalue_array(FDBFuture* f, + FDBMappedKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key_array(FDBFuture* f, + FDBKey const** out_key_array, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyrange_array(FDBFuture* f, + FDBKeyRange const** out_ranges, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_granule_summary_array(FDBFuture* f, + FDBGranuleSummary const** out_summaries, + int* out_count); + +/* all for using future result from read_blob_granules_description */ +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_readbg_get_descriptions(FDBFuture* f, + FDBBGFileDescription** out, + int* desc_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_readbg_parse_snapshot_file(const uint8_t* file_data, + int file_len, + FDBBGTenantPrefix const* tenant_prefix, + FDBBGEncryptionCtx const* encryption_ctx); + +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_readbg_parse_delta_file(const uint8_t* file_data, + int file_len, + FDBBGTenantPrefix const* tenant_prefix, + FDBBGEncryptionCtx const* encryption_ctx); + +/* FDBResult is a synchronous computation result, as opposed to a future that is asynchronous. */ +DLLEXPORT void fdb_result_destroy(FDBResult* r); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_result_get_keyvalue_array(FDBResult* r, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_result_get_bg_mutations_array(FDBResult* r, + FDBBGMutation const** out_mutations, + int* out_count); + +/* TODO: add other return types as we need them */ + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database(const char* cluster_file_path, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database_from_connection_string(const char* connection_string, + FDBDatabase** out_database); + +DLLEXPORT void fdb_database_destroy(FDBDatabase* d); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, + FDBDatabaseOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_open_tenant(FDBDatabase* d, + uint8_t const* tenant_name, + int tenant_name_length, + FDBTenant** out_tenant); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db, + uint8_t const* address, + int address_length, + fdb_bool_t check, + int duration); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_force_recovery_with_data_loss(FDBDatabase* db, + uint8_t const* dcid, + int dcid_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_snapshot(FDBDatabase* db, + uint8_t const* uid, + int uid_length, + uint8_t const* snap_command, + int snap_command_length); + +DLLEXPORT WARN_UNUSED_RESULT double fdb_database_get_main_thread_busyness(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_server_protocol(FDBDatabase* db, uint64_t expected_version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_purge_blob_granules(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 purge_version, + fdb_bool_t force); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_wait_purge_granules_complete(FDBDatabase* db, + uint8_t const* purge_key_name, + int purge_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_client_status(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_tenant_create_transaction(FDBTenant* tenant, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_purge_blob_granules(FDBTenant* 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 purge_version, + fdb_bool_t force); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_wait_purge_granules_complete(FDBTenant* db, + uint8_t const* purge_key_name, + int purge_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_get_id(FDBTenant* tenant); + +DLLEXPORT void fdb_tenant_destroy(FDBTenant* tenant); + +DLLEXPORT void fdb_transaction_destroy(FDBTransaction* tr); + +DLLEXPORT void fdb_transaction_cancel(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_set_option(FDBTransaction* tr, + FDBTransactionOption option, + uint8_t const* value, + int value_length); +#endif + +DLLEXPORT void fdb_transaction_set_read_version(FDBTransaction* tr, int64_t version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t snapshot); +#endif + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset, + fdb_bool_t snapshot); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_addresses_for_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + 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); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_mapped_range(FDBTransaction* tr, + 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); + +DLLEXPORT void fdb_transaction_set(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* value, + int value_length); + +DLLEXPORT void fdb_transaction_atomic_op(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* param, + int param_length, + FDBMutationType operation_type); + +DLLEXPORT void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length); + +DLLEXPORT void fdb_transaction_clear_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_get_committed_version(FDBTransaction* tr, + int64_t* out_version); + +/* + * These functions intentionally return an FDBFuture instead of a numeric value + * directly, so that calling the API can see the effect of previous + * mutations on the transaction. Specifically, mutations are applied + * asynchronously by the main thread. In order to see them, this call has to + * be serviced by the main thread too. + */ +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_tag_throttled_duration(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_total_cost(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_on_error(FDBTransaction* tr, fdb_error_t error); + +DLLEXPORT void fdb_transaction_reset(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_add_conflict_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points(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 chunk_size); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_blob_granule_ranges(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int rangeLimit); + +/* LatestVersion (-2) for readVersion means get read version from transaction + Separated out as optional because BG reads can support longer-lived reads than normal FDB transactions */ +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_transaction_read_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 beginVersion, + int64_t readVersion, + FDBReadBlobGranuleContext granuleContext); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_read_blob_granules_description(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 begin_version, + int64_t read_version, + int64_t* read_version_out); + +#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 +#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 +#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 +#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); + +DLLEXPORT int fdb_get_max_api_version(void); +DLLEXPORT const char* fdb_get_client_version(void); + +/* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_version(FDBFuture* f, int64_t* out_version); +#else +#define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES +typedef struct FDB_cluster FDBCluster; + +typedef enum { + /* This option is only a placeholder for C compatibility and should not be used */ + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE = -1 +} FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cluster(FDBFuture* f, FDBCluster** out_cluster); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_database(FDBFuture* f, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster(const char* cluster_file_path); + +DLLEXPORT void fdb_cluster_destroy(FDBCluster* c); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cluster_set_option(FDBCluster* c, + FDBClusterOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cluster_create_database(FDBCluster* c, + uint8_t const* db_name, + int db_name_length); +#else +#define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION +#define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION +#define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION +#define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION +#define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION +#define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f, const char** out_description /* = NULL */); + +DLLEXPORT fdb_bool_t fdb_future_is_error(FDBFuture* f); +#else +#define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(const char* local_address); + +DLLEXPORT void fdb_transaction_set_option(FDBTransaction* tr, FDBTransactionOption option); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int limit); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector(FDBTransaction* tr, + 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); +#else +#define fdb_transaction_get_range_selector(tr, bkn, bknl, boe, bo, ekn, eknl, eoe, eo, lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/730/fdb_c_apiversion.g.h b/bindings/rust/foundationdb-sys/include/730/fdb_c_apiversion.g.h new file mode 100644 index 00000000000..b8785014dfc --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/fdb_c_apiversion.g.h @@ -0,0 +1,41 @@ +#ifndef FDB_C_APIVERSION_G_H +#define FDB_C_APIVERSION_G_H +#pragma once + +/* + * fdb_c_apiversion.g.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2023 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +/* The latest FDB C API version */ +#define FDB_LATEST_API_VERSION 730 + +/* The latest FDB API version supported by bindings. It may lag behind the latest C API version */ +#define FDB_LATEST_BINDINGS_API_VERSION 730 + +/* API version introducing client_tmp_dir option */ +#define FDB_API_VERSION_CLIENT_TMP_DIR 720 + +/* API version introducing disable_client_bypass option */ +#define FDB_API_VERSION_DISABLE_CLIENT_BYPASS 720 + +/* API version with multitenancy API released */ +#define FDB_API_VERSION_TENANT_API_RELEASED 720 + +#endif diff --git a/bindings/rust/foundationdb-sys/include/730/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/730/fdb_c_options.g.h new file mode 100644 index 00000000000..93ea031a1aa --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/fdb_c_options.g.h @@ -0,0 +1,613 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + /* Deprecated */ + /* Parameter: (String) IP:PORT */ + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + /* Deprecated */ + /* Parameter: (String) path to cluster file */ + FDB_NET_OPTION_CLUSTER_FILE=20, + + /* Enables trace output to a file in a directory of the clients choosing */ + /* Parameter: (String) path to output directory (or NULL for current working directory) */ + FDB_NET_OPTION_TRACE_ENABLE=30, + + /* Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. */ + /* Parameter: (Int) max size of a single trace output file */ + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + /* Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. */ + /* Parameter: (Int) max total size of trace files */ + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + /* Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. */ + /* Parameter: (String) value of the LogGroup attribute */ + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + /* Select the format of the log files. xml (the default) and json are supported. */ + /* Parameter: (String) Format of trace files */ + FDB_NET_OPTION_TRACE_FORMAT=34, + + /* Select clock source for trace files. now (the default) or realtime are supported. */ + /* Parameter: (String) Trace clock source */ + FDB_NET_OPTION_TRACE_CLOCK_SOURCE=35, + + /* Once provided, this string will be used to replace the port/PID in the log file names. */ + /* Parameter: (String) The identifier that will be part of all trace file names */ + FDB_NET_OPTION_TRACE_FILE_IDENTIFIER=36, + + /* Use the same base trace file name for all client threads as it did before version 7.2. The current default behavior is to use distinct trace file names for client threads by including their version and thread index. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_TRACE_SHARE_AMONG_CLIENT_THREADS=37, + + /* Initialize trace files on network setup, determine the local IP later. Otherwise tracing is initialized when opening the first database. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_TRACE_INITIALIZE_ON_SETUP=38, + + /* Set file suffix for partially written log files. */ + /* Parameter: (String) Append this suffix to partially written log files. When a log file is complete, it is renamed to remove the suffix. No separator is added between the file and the suffix. If you want to add a file extension, you should include the separator - e.g. '.tmp' instead of 'tmp' to add the 'tmp' extension. */ + FDB_NET_OPTION_TRACE_PARTIAL_FILE_SUFFIX=39, + + /* Set internal tuning or debugging knobs */ + /* Parameter: (String) knob_name=knob_value */ + FDB_NET_OPTION_KNOB=40, + + /* Deprecated */ + /* Parameter: (String) file path or linker-resolved name */ + FDB_NET_OPTION_TLS_PLUGIN=41, + + /* Set the certificate chain */ + /* Parameter: (Bytes) certificates */ + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + /* Set the file from which to load the certificate chain */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CERT_PATH=43, + + /* Set the private key corresponding to your own certificate */ + /* Parameter: (Bytes) key */ + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + /* Set the file from which to load the private key corresponding to your own certificate */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_KEY_PATH=46, + + /* Set the peer certificate field verification criteria */ + /* Parameter: (Bytes) verification pattern */ + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + /* Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + /* Set the probability of an active BUGGIFY section being fired */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + /* Set the ca bundle */ + /* Parameter: (Bytes) ca bundle */ + FDB_NET_OPTION_TLS_CA_BYTES=52, + + /* Set the file from which to load the certificate authority bundle */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CA_PATH=53, + + /* Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. */ + /* Parameter: (String) key passphrase */ + FDB_NET_OPTION_TLS_PASSWORD=54, + + /* Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + /* If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + /* Adds an external client library for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + /* Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to directory containing client libraries */ + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + /* Prevents connections through the local client, allowing only connections through externally loaded client libraries. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + /* Spawns multiple worker threads for each version of the client that is loaded. Setting this to a number greater than one implies disable_local_client. */ + /* Parameter: (Int) Number of client threads to be spawned. Each cluster will be serviced by a single client thread. */ + FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION=65, + + /* Adds an external client library to be used with a future version protocol. This option can be used testing purposes only! */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_FUTURE_VERSION_CLIENT_LIBRARY=66, + + /* Retain temporary external client library copies that are created for enabling multi-threading. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_RETAIN_CLIENT_LIBRARY_COPIES=67, + + /* Ignore the failure to initialize some of the external clients */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_IGNORE_EXTERNAL_CLIENT_FAILURES=68, + + /* Fail with an error if there is no client matching the server version the client is connecting to */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_FAIL_INCOMPATIBLE_CLIENT=69, + + /* Disables logging of client statistics, such as sampled transaction activity. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + /* Enables debugging feature to perform run loop profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING=71, + + /* Prevents the multi-version client API from being disabled, even if no external clients are configured. This option is required to use GRV caching. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_BYPASS=72, + + /* Enable client buggify - will make requests randomly fail (intended for client testing) */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + /* Disable client buggify */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + /* Set the probability of a CLIENT_BUGGIFY section being active for the current execution. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + /* Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83, + + /* Set a tracer to run on the client. Should be set to the same value as the tracer set on the server. */ + /* Parameter: (String) Distributed tracer type. Choose from none, log_file, or network_lossy */ + FDB_NET_OPTION_DISTRIBUTED_CLIENT_TRACER=90, + + /* Sets the directory for storing temporary files created by FDB client, such as temporary copies of client libraries. Defaults to /tmp */ + /* Parameter: (String) Client directory for temporary files. */ + FDB_NET_OPTION_CLIENT_TMP_DIR=91, + + /* This option is set automatically to communicate the list of supported clients to the active client. */ + /* Parameter: (String) [release version],[source version],[protocol version];... This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_SUPPORTED_CLIENT_VERSIONS=1000, + + /* This option is set automatically on all clients loaded externally using the multi-version API. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT=1001, + + /* This option tells a child on a multiversion client what transport ID to use. */ + /* Parameter: (Int) Transport ID for the child connection This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT_TRANSPORT_ID=1002 +} FDBNetworkOption; + +typedef enum { + /* Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. */ + /* Parameter: (Int) Max location cache entries */ + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + /* Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. */ + /* Parameter: (Int) Max outstanding watches */ + FDB_DB_OPTION_MAX_WATCHES=20, + + /* Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_MACHINE_ID=21, + + /* Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_DATACENTER_ID=22, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) number of times to retry */ + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + /* Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in bytes */ + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + /* Deprecated. Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505, + + /* Set a random idempotency id for all transactions. See the transaction option description for more information. This feature is in development and not ready for general use. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_AUTOMATIC_IDEMPOTENCY=506, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the ``bypass_unreadable`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_BYPASS_UNREADABLE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* Enables conflicting key reporting on all transactions, allowing them to retrieve the keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_REPORT_CONFLICTING_KEYS=702, + + /* Use configuration database. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_USE_CONFIG_DATABASE=800, + + /* Enables verification of causal read risky by checking whether clients are able to read stale data when they detect a recovery, and logging an error if so. */ + /* Parameter: (Int) integer between 0 and 100 expressing the probability a client will verify it can't read stale data */ + FDB_DB_OPTION_TEST_CAUSAL_READ_RISKY=900 +} FDBDatabaseOption; + +typedef enum { + /* The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + /* Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + /* The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + /* Committing this transaction will bypass the normal load balancing across commit proxies and go directly to the specifically nominated 'first commit proxy'. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_COMMIT_ON_FIRST_PROXY=40, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_CHECK_WRITES_ENABLE=50, + + /* Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. It is an error to set this option after performing any reads or writes on the transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + /* Storage server should cache disk blocks needed for subsequent read requests in this transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SERVER_SIDE_CACHE_ENABLE=507, + + /* Storage server should not cache disk blocks needed for subsequent read requests in this transaction. This can be used to avoid cache pollution for reads not expected to be repeated. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SERVER_SIDE_CACHE_DISABLE=508, + + /* Use normal read priority for subsequent read requests in this transaction. This is the default read priority. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_NORMAL=509, + + /* Use low read priority for subsequent read requests in this transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_LOW=510, + + /* Use high read priority for subsequent read requests in this transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_HIGH=511, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_RISKY=120, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + /* Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + /* Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_BATCH=201, + + /* This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + /* Allows this transaction to read and modify system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + /* Allows this transaction to read system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + /* Allows this transaction to access the raw key-space when tenant mode is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_RAW_ACCESS=303, + + /* Allows this transaction to bypass storage quota enforcement. Should only be used for transactions that directly or indirectly decrease the size of the tenant group's data. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_STORAGE_QUOTA=304, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_DEBUG_DUMP=400, + + /* */ + /* Parameter: (String) Optional transaction name */ + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + /* Deprecated */ + /* Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + /* Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. */ + /* Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + /* Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOG_TRANSACTION=404, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Sets an identifier for server tracing of this transaction. When committed, this identifier triggers logging when each part of the transaction authority encounters it, which is helpful in diagnosing slowness in misbehaving clusters. The identifier is randomly generated. When there is also a debug_transaction_identifier, both IDs are logged together. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SERVER_REQUEST_TRACING=406, + + /* Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_TR_OPTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) number of times to retry */ + FDB_TR_OPTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + /* Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. */ + /* Parameter: (Int) value in bytes */ + FDB_TR_OPTION_SIZE_LIMIT=503, + + /* Associate this transaction with this ID for the purpose of checking whether or not this transaction has already committed. Must be at least 16 bytes and less than 256 bytes. This feature is in development and not ready for general use. Unless the automatic_idempotency option is set after this option, the client will not automatically attempt to remove this id from the cluster after a successful commit. */ + /* Parameter: (String) Unique ID This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_IDEMPOTENCY_ID=504, + + /* Automatically assign a random 16 byte idempotency id for this transaction. Prevents commits from failing with ``commit_unknown_result``. WARNING: If you are also using the multiversion client or transaction timeouts, if either cluster_version_changed or transaction_timed_out was thrown during a commit, then that commit may have already succeeded or may succeed in the future. This feature is in development and not ready for general use. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_AUTOMATIC_IDEMPOTENCY=505, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + /* The transaction can read and write to locked databases, and is responsible for checking that it took the lock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOCK_AWARE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* The transaction can read from locked databases. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + /* No other transactions will be applied before this transaction within the same commit version. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_FIRST_IN_BATCH=710, + + /* This option should only be used by tools which change the database configuration. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711, + + /* The transaction can retrieve keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_REPORT_CONFLICTING_KEYS=712, + + /* By default, the special key space will only allow users to read from exactly one module (a subspace in the special key space). Use this option to allow reading from zero or more modules. Users who set this option should be prepared for new modules, which may have different behaviors than the modules they're currently reading. For example, a new module might block or return an error. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_RELAXED=713, + + /* By default, users are not allowed to write to special keys. Enable this option will implicitly enable all options required to achieve the configuration change. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_ENABLE_WRITES=714, + + /* Adds a tag to the transaction that can be used to apply manual targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_TAG=800, + + /* Adds a tag to the transaction that can be used to apply manual or automatic targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_AUTO_THROTTLE_TAG=801, + + /* Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with any 16 bytes */ + /* Parameter: (Bytes) A byte string of length 16 used to associate the span of this transaction with a parent */ + FDB_TR_OPTION_SPAN_PARENT=900, + + /* Asks storage servers for how many bytes a clear key range contains. Otherwise uses the location cache to roughly estimate this. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_EXPENSIVE_CLEAR_COST_ESTIMATION_ENABLE=1000, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_UNREADABLE=1100, + + /* Allows this transaction to use cached GRV from the database context. Defaults to off. Upon first usage, starts a background updater to periodically update the cache to avoid stale read versions. The disable_client_bypass option must also be set. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_GRV_CACHE=1101, + + /* Specifically instruct this transaction to NOT use cached GRV. Primarily used for the read version cache's background updater to avoid attempting to read a cached entry in specific situations. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_SKIP_GRV_CACHE=1102, + + /* Attach given authorization token to the transaction such that subsequent tenant-aware requests are authorized */ + /* Parameter: (String) A JSON Web Token authorized to access data belonging to one or more tenants, indicated by 'tenants' claim of the token's payload. */ + FDB_TR_OPTION_AUTHORIZATION_TOKEN=2000 +} FDBTransactionOption; + +typedef enum { + /* Client intends to consume the entire range and would like it all transferred as early as possible. */ + FDB_STREAMING_MODE_WANT_ALL=-2, + + /* The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` */ + FDB_STREAMING_MODE_ITERATOR=-1, + + /* Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. */ + FDB_STREAMING_MODE_EXACT=0, + + /* Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. */ + FDB_STREAMING_MODE_SMALL=1, + + /* Infrequently used. Transfer data in batches sized in between small and large. */ + FDB_STREAMING_MODE_MEDIUM=2, + + /* Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. */ + FDB_STREAMING_MODE_LARGE=3, + + /* Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. */ + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + /* Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. */ + FDB_MUTATION_TYPE_ADD=2, + + /* Deprecated */ + FDB_MUTATION_TYPE_AND=6, + + /* Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_AND=6, + + /* Deprecated */ + FDB_MUTATION_TYPE_OR=7, + + /* Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_OR=7, + + /* Deprecated */ + FDB_MUTATION_TYPE_XOR=8, + + /* Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_XOR=8, + + /* Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. */ + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MAX=12, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MIN=13, + + /* Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + /* Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MIN=16, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MAX=17, + + /* Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. */ + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + /* Used to add a read conflict range */ + FDB_CONFLICT_RANGE_TYPE_READ=0, + + /* Used to add a write conflict range */ + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + /* Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. */ + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + /* Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. */ + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + /* Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. */ + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/730/fdb_c_shim.h b/bindings/rust/foundationdb-sys/include/730/fdb_c_shim.h new file mode 100644 index 00000000000..44dfdc7348b --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/fdb_c_shim.h @@ -0,0 +1,47 @@ +/* + * fdb_shim_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_SHIM_C_H +#define FDB_SHIM_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Specify the path of the local libfdb_c.so library to be dynamically loaded by the shim layer + * + * This enables running the same application code with different client library versions, + * e.g. using the latest development build for testing new features, but still using the latest + * stable release in production deployments. + * + * The given path overrides the environment variable FDB_LOCAL_CLIENT_LIBRARY_PATH + */ +DLLEXPORT void fdb_shim_set_local_client_library_path(const char* filePath); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/730/fdb_c_types.h b/bindings/rust/foundationdb-sys/include/730/fdb_c_types.h new file mode 100644 index 00000000000..779e227ad5e --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/fdb_c_types.h @@ -0,0 +1,47 @@ +/* + * fdb_c_types.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_TYPES_H +#define FDB_C_TYPES_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Pointers to these opaque types represent objects in the FDB API */ +typedef struct FDB_future FDBFuture; +typedef struct FDB_result FDBResult; +typedef struct FDB_cluster FDBCluster; +typedef struct FDB_database FDBDatabase; +typedef struct FDB_tenant FDBTenant; +typedef struct FDB_transaction FDBTransaction; + +typedef int fdb_error_t; +typedef int fdb_bool_t; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/730/version.txt b/bindings/rust/foundationdb-sys/include/730/version.txt new file mode 100644 index 00000000000..aec35f3a894 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/730/version.txt @@ -0,0 +1 @@ +7.3.27 diff --git a/bindings/rust/foundationdb-sys/include/740/fdb_c.h b/bindings/rust/foundationdb-sys/include/740/fdb_c.h new file mode 100644 index 00000000000..c6f5a100395 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/740/fdb_c.h @@ -0,0 +1,805 @@ +/* + * fdb_c.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_H +#define FDB_C_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#include "fdb_c_apiversion.g.h" +#if (defined FDB_USE_LATEST_API_VERSION) +#define FDB_API_VERSION FDB_LATEST_API_VERSION +#elif (defined FDB_USE_LATEST_BINDINGS_API_VERSION) +#define FDB_API_VERSION FDB_LATEST_BINDINGS_API_VERSION +#endif + +#if !defined(FDB_API_VERSION) +#error You must #define FDB_API_VERSION prior to including fdb_c.h (the latest version is defined as FDB_LATEST_API_VERSION) +#elif FDB_API_VERSION < 13 +#error API version no longer supported (upgrade to 13) +#elif FDB_API_VERSION > FDB_LATEST_API_VERSION +#error Requested API version requires a newer version of this header +#endif + +#if FDB_API_VERSION >= 23 && !defined(WARN_UNUSED_RESULT) +#ifdef __GNUG__ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#else +#define WARN_UNUSED_RESULT +#endif + +/* + * With default settings, gcc will not warn about unprototyped functions being + * called, so it is easy to erroneously call a function which is not available + * at FDB_API_VERSION and then get an error only at runtime. These macros + * ensure a compile error in such cases, and attempt to make the compile error + * slightly informative. + */ +#define This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION() { == == = } +#define FDB_REMOVED_FUNCTION This_FoundationDB_API_function_is_removed_at_this_FDB_API_VERSION(0) + +#include + +#include "fdb_c_options.g.h" +#include "fdb_c_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DLLEXPORT const char* fdb_get_error(fdb_error_t code); + +DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code); + +#define /* fdb_error_t */ fdb_select_api_version(v) fdb_select_api_version_impl(v, FDB_API_VERSION) + +/* + * A variant of fdb_select_api_version that caps the header API version by the maximum API version + * supported by the client library. It is intended mainly for use in combination with the shim + * layer, which loads the client library dynamically. + */ +#define /* fdb_error_t */ fdb_select_api_version_capped(v) \ + fdb_select_api_version_impl( \ + v, FDB_API_VERSION < fdb_get_max_api_version() ? FDB_API_VERSION : fdb_get_max_api_version()) + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_network_set_option(FDBNetworkOption option, + uint8_t const* value, + int value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(void); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_run_network(void); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_stop_network(void); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_add_network_thread_completion_hook(void (*hook)(void*), + void* hook_parameter); + +#pragma pack(push, 4) +typedef struct key { + const uint8_t* key; + int key_length; +} FDBKey; +#if FDB_API_VERSION >= 630 +typedef struct keyvalue { + const uint8_t* key; + int key_length; + const uint8_t* value; + int value_length; +} FDBKeyValue; +#else +typedef struct keyvalue { + const void* key; + int key_length; + const void* value; + int value_length; +} FDBKeyValue; +#endif + +#pragma pack(pop) + +/* Memory layout of KeySelectorRef. */ +typedef struct keyselector { + FDBKey key; + /* orEqual and offset have not be tested in C binding. Just a placeholder. */ + fdb_bool_t orEqual; + int offset; +} FDBKeySelector; + +/* Memory layout of GetRangeReqAndResultRef. */ +typedef struct getrangereqandresult { + FDBKeySelector begin; + FDBKeySelector end; + FDBKeyValue* data; + int m_size, m_capacity; +} FDBGetRangeReqAndResult; + +/* Memory layout of MappedKeyValueRef. + +Total 112 bytes +- key (12 bytes) +:74:8F:8E:5F:AE:7F:00:00 +:4A:00:00:00 +- value (12 bytes) +:70:8F:8E:5F:AE:7F:00:00 +:00:00:00:00 +- begin selector (20 bytes) +:30:8F:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:7F:00:00 +:01:00:00:00 +- end selector (20 bytes) +:EC:8E:8E:5F:AE:7F:00:00 +:2D:00:00:00 +:00:2B:3C:60 +:01:00:00:00 +- vector (16 bytes) +:74:94:8E:5F:AE:7F:00:00 +:01:00:00:00 +:01:00:00:00 +- buffer (32 bytes) +:00:20:D1:61:00:00:00:00 +:00:00:00:00:00:00:00:00 +:00:00:00:00:00:00:00:00 +:01:00:00:00:AE:7F:00:00 +*/ +typedef struct mappedkeyvalue { + FDBKey key; + FDBKey value; + /* It's complicated to map a std::variant to C. For now we assume the underlying requests are always getRange and + * take the shortcut. */ + FDBGetRangeReqAndResult getRange; + unsigned char buffer[32]; +} FDBMappedKeyValue; + +#pragma pack(push, 4) +typedef struct keyrange { + const uint8_t* begin_key; + int begin_key_length; + const uint8_t* end_key; + int end_key_length; +} FDBKeyRange; + +typedef struct granulesummary { + FDBKeyRange key_range; + int64_t snapshot_version; + int64_t snapshot_size; + int64_t delta_version; + int64_t delta_size; +} FDBGranuleSummary; +#pragma pack(pop) + +typedef struct readgranulecontext { + /* 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 this to true for testing if you don't want to read the granule files, + just do the request to the blob workers */ + fdb_bool_t debugNoMaterialize; + + /* Number of granules to load in parallel */ + int granuleParallelism; +} FDBReadBlobGranuleContext; + +typedef enum { FDB_BG_MUTATION_TYPE_SET_VALUE = 0, FDB_BG_MUTATION_TYPE_CLEAR_RANGE = 1 } FDBBGMutationType; + +#pragma pack(push, 4) + +typedef struct bgtenantprefix { + fdb_bool_t present; + FDBKey prefix; +} FDBBGTenantPrefix; + +/* encryption structs correspond to similar ones in BlobGranuleCommon.h */ +typedef struct bgencryptionkey { + int64_t domain_id; + uint64_t base_key_id; + uint32_t base_kcv; + uint64_t random_salt; + FDBKey base_key; +} FDBBGEncryptionKey; + +typedef struct bgencryptionctx { + fdb_bool_t present; + FDBBGEncryptionKey textKey; + uint32_t textKCV; + FDBBGEncryptionKey headerKey; + uint32_t headerKCV; + FDBKey iv; +} FDBBGEncryptionCtx; + +typedef struct bgfilepointer { + const uint8_t* filename_ptr; + int filename_length; + int64_t file_offset; + int64_t file_length; + int64_t full_file_length; + int64_t file_version; + FDBBGEncryptionCtx encryption_ctx; +} FDBBGFilePointer; + +typedef struct bgmutation { + /* FDBBGMutationType */ uint8_t type; + int64_t version; + const uint8_t* param1_ptr; + int param1_length; + const uint8_t* param2_ptr; + int param2_length; +} FDBBGMutation; + +typedef struct bgfiledescription { + FDBKeyRange key_range; + fdb_bool_t snapshot_present; + FDBBGFilePointer snapshot_file_pointer; + int delta_file_count; + FDBBGFilePointer* delta_files; + int memory_mutation_count; + FDBBGMutation* memory_mutations; + FDBBGTenantPrefix tenant_prefix; +} FDBBGFileDescription; +#pragma pack(pop) + +DLLEXPORT void fdb_future_cancel(FDBFuture* f); + +DLLEXPORT void fdb_future_release_memory(FDBFuture* f); + +DLLEXPORT void fdb_future_destroy(FDBFuture* f); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_block_until_ready(FDBFuture* f); + +DLLEXPORT fdb_bool_t fdb_future_is_ready(FDBFuture* f); + +typedef void (*FDBCallback)(FDBFuture* future, void* callback_parameter); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_set_callback(FDBFuture* f, + FDBCallback callback, + void* callback_parameter); + +#if FDB_API_VERSION >= 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_bool(FDBFuture* f, fdb_bool_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_int64(FDBFuture* f, int64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_uint64(FDBFuture* f, uint64_t* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_double(FDBFuture* f, double* out); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_value(FDBFuture* f, + fdb_bool_t* out_present, + uint8_t const** out_value, + int* out_value_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); +#endif + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_mappedkeyvalue_array(FDBFuture* f, + FDBMappedKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_key_array(FDBFuture* f, + FDBKey const** out_key_array, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_string_array(FDBFuture* f, + const char*** out_strings, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyrange_array(FDBFuture* f, + FDBKeyRange const** out_ranges, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_granule_summary_array(FDBFuture* f, + FDBGranuleSummary const** out_summaries, + int* out_count); + +/* all for using future result from read_blob_granules_description */ +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_readbg_get_descriptions(FDBFuture* f, + FDBBGFileDescription** out, + int* desc_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_readbg_parse_snapshot_file(const uint8_t* file_data, + int file_len, + FDBBGTenantPrefix const* tenant_prefix, + FDBBGEncryptionCtx const* encryption_ctx); + +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_readbg_parse_delta_file(const uint8_t* file_data, + int file_len, + FDBBGTenantPrefix const* tenant_prefix, + FDBBGEncryptionCtx const* encryption_ctx); + +/* FDBResult is a synchronous computation result, as opposed to a future that is asynchronous. */ +DLLEXPORT void fdb_result_destroy(FDBResult* r); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_result_get_keyvalue_array(FDBResult* r, + FDBKeyValue const** out_kv, + int* out_count, + fdb_bool_t* out_more); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_result_get_bg_mutations_array(FDBResult* r, + FDBBGMutation const** out_mutations, + int* out_count); + +/* TODO: add other return types as we need them */ + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database(const char* cluster_file_path, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_create_database_from_connection_string(const char* connection_string, + FDBDatabase** out_database); + +DLLEXPORT void fdb_database_destroy(FDBDatabase* d); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, + FDBDatabaseOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_open_tenant(FDBDatabase* d, + uint8_t const* tenant_name, + int tenant_name_length, + FDBTenant** out_tenant); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db, + uint8_t const* address, + int address_length, + fdb_bool_t check, + int duration); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_force_recovery_with_data_loss(FDBDatabase* db, + uint8_t const* dcid, + int dcid_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_snapshot(FDBDatabase* db, + uint8_t const* uid, + int uid_length, + uint8_t const* snap_command, + int snap_command_length); + +DLLEXPORT WARN_UNUSED_RESULT double fdb_database_get_main_thread_busyness(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_server_protocol(FDBDatabase* db, uint64_t expected_version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_purge_blob_granules(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 purge_version, + fdb_bool_t force); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_wait_purge_granules_complete(FDBDatabase* db, + uint8_t const* purge_key_name, + int purge_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_get_client_status(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_tenant_create_transaction(FDBTenant* tenant, + FDBTransaction** out_transaction); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_purge_blob_granules(FDBTenant* 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 purge_version, + fdb_bool_t force); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_wait_purge_granules_complete(FDBTenant* db, + uint8_t const* purge_key_name, + int purge_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_tenant_get_id(FDBTenant* tenant); + +DLLEXPORT void fdb_tenant_destroy(FDBTenant* tenant); + +DLLEXPORT void fdb_transaction_destroy(FDBTransaction* tr); + +DLLEXPORT void fdb_transaction_cancel(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_set_option(FDBTransaction* tr, + FDBTransactionOption option, + uint8_t const* value, + int value_length); +#endif + +DLLEXPORT void fdb_transaction_set_read_version(FDBTransaction* tr, int64_t version); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_read_version(FDBTransaction* tr); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t snapshot); +#endif + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset, + fdb_bool_t snapshot); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_addresses_for_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +#if FDB_API_VERSION >= 14 +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + 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); +#endif + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_mapped_range(FDBTransaction* tr, + 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); + +DLLEXPORT void fdb_transaction_set(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* value, + int value_length); + +DLLEXPORT void fdb_transaction_atomic_op(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + uint8_t const* param, + int param_length, + FDBMutationType operation_type); + +DLLEXPORT void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length); + +DLLEXPORT void fdb_transaction_clear_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_watch(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_commit(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_get_committed_version(FDBTransaction* tr, + int64_t* out_version); + +/* + * These functions intentionally return an FDBFuture instead of a numeric value + * directly, so that calling the API can see the effect of previous + * mutations on the transaction. Specifically, mutations are applied + * asynchronously by the main thread. In order to see them, this call has to + * be serviced by the main thread too. + */ +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_tag_throttled_duration(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_total_cost(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_on_error(FDBTransaction* tr, fdb_error_t error); + +DLLEXPORT void fdb_transaction_reset(FDBTransaction* tr); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_transaction_add_conflict_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + FDBConflictRangeType type); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points(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 chunk_size); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_blob_granule_ranges(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int rangeLimit); + +/* LatestVersion (-2) for readVersion means get read version from transaction + Separated out as optional because BG reads can support longer-lived reads than normal FDB transactions */ +DLLEXPORT WARN_UNUSED_RESULT FDBResult* fdb_transaction_read_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 beginVersion, + int64_t readVersion, + FDBReadBlobGranuleContext granuleContext); + +DLLEXPORT WARN_UNUSED_RESULT 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); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_read_blob_granules_description(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 begin_version, + int64_t read_version, + int64_t* read_version_out); + +#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 +#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 +#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 +#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1 + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version); + +DLLEXPORT int fdb_get_max_api_version(void); +DLLEXPORT const char* fdb_get_client_version(void); + +/* LEGACY API VERSIONS */ + +#if FDB_API_VERSION < 620 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_version(FDBFuture* f, int64_t* out_version); +#else +#define fdb_future_get_version(f, ov) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 610 || defined FDB_INCLUDE_LEGACY_TYPES +typedef struct FDB_cluster FDBCluster; + +typedef enum { + /* This option is only a placeholder for C compatibility and should not be used */ + FDB_CLUSTER_OPTION_DUMMY_DO_NOT_USE = -1 +} FDBClusterOption; +#endif + +#if FDB_API_VERSION < 610 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cluster(FDBFuture* f, FDBCluster** out_cluster); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_database(FDBFuture* f, FDBDatabase** out_database); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_create_cluster(const char* cluster_file_path); + +DLLEXPORT void fdb_cluster_destroy(FDBCluster* c); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cluster_set_option(FDBCluster* c, + FDBClusterOption option, + uint8_t const* value, + int value_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cluster_create_database(FDBCluster* c, + uint8_t const* db_name, + int db_name_length); +#else +#define fdb_future_get_cluster(f, oc) FDB_REMOVED_FUNCTION +#define fdb_future_get_database(f, od) FDB_REMOVED_FUNCTION +#define fdb_create_cluster(cfp) FDB_REMOVED_FUNCTION +#define fdb_cluster_destroy(c) FDB_REMOVED_FUNCTION +#define fdb_cluster_set_option(c, o, v, vl) FDB_REMOVED_FUNCTION +#define fdb_cluster_create_database(c, dn, dnl) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 23 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_error(FDBFuture* f, const char** out_description /* = NULL */); + +DLLEXPORT fdb_bool_t fdb_future_is_error(FDBFuture* f); +#else +#define fdb_future_is_error(x) FDB_REMOVED_FUNCTION +#endif + +#if FDB_API_VERSION < 14 +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyvalue_array(FDBFuture* f, + FDBKeyValue const** out_kv, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, + uint8_t const* key_name, + int key_name_length, + fdb_bool_t or_equal, + int offset); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_setup_network(const char* local_address); + +DLLEXPORT void fdb_transaction_set_option(FDBTransaction* tr, FDBTransactionOption option); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int limit); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_selector(FDBTransaction* tr, + 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); +#else +#define fdb_transaction_get_range_selector(tr, bkn, bknl, boe, bo, ekn, eknl, eoe, eo, lim) FDB_REMOVED_FUNCTION +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/740/fdb_c_apiversion.g.h b/bindings/rust/foundationdb-sys/include/740/fdb_c_apiversion.g.h new file mode 100644 index 00000000000..c9a25d96e76 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/740/fdb_c_apiversion.g.h @@ -0,0 +1,41 @@ +#ifndef FDB_C_APIVERSION_G_H +#define FDB_C_APIVERSION_G_H +#pragma once + +/* + * fdb_c_apiversion.g.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +/* The latest FDB C API version */ +#define FDB_LATEST_API_VERSION 740 + +/* The latest FDB API version supported by bindings. It may lag behind the latest C API version */ +#define FDB_LATEST_BINDINGS_API_VERSION 740 + +/* API version introducing client_tmp_dir option */ +#define FDB_API_VERSION_CLIENT_TMP_DIR 720 + +/* API version introducing disable_client_bypass option */ +#define FDB_API_VERSION_DISABLE_CLIENT_BYPASS 720 + +/* API version with multitenancy API released */ +#define FDB_API_VERSION_TENANT_API_RELEASED 720 + +#endif diff --git a/bindings/rust/foundationdb-sys/include/740/fdb_c_options.g.h b/bindings/rust/foundationdb-sys/include/740/fdb_c_options.g.h new file mode 100644 index 00000000000..e695d099dd1 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/740/fdb_c_options.g.h @@ -0,0 +1,625 @@ +#ifndef FDB_C_OPTIONS_G_H +#define FDB_C_OPTIONS_G_H +#pragma once + +/* + * FoundationDB C API + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +typedef enum { + /* Deprecated */ + /* Parameter: (String) IP:PORT */ + FDB_NET_OPTION_LOCAL_ADDRESS=10, + + /* Deprecated */ + /* Parameter: (String) path to cluster file */ + FDB_NET_OPTION_CLUSTER_FILE=20, + + /* Enables trace output to a file in a directory of the clients choosing */ + /* Parameter: (String) path to output directory (or NULL for current working directory) */ + FDB_NET_OPTION_TRACE_ENABLE=30, + + /* Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. */ + /* Parameter: (Int) max size of a single trace output file */ + FDB_NET_OPTION_TRACE_ROLL_SIZE=31, + + /* Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. */ + /* Parameter: (Int) max total size of trace files */ + FDB_NET_OPTION_TRACE_MAX_LOGS_SIZE=32, + + /* Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. */ + /* Parameter: (String) value of the LogGroup attribute */ + FDB_NET_OPTION_TRACE_LOG_GROUP=33, + + /* Select the format of the log files. xml (the default) and json are supported. */ + /* Parameter: (String) Format of trace files */ + FDB_NET_OPTION_TRACE_FORMAT=34, + + /* Select clock source for trace files. now (the default) or realtime are supported. */ + /* Parameter: (String) Trace clock source */ + FDB_NET_OPTION_TRACE_CLOCK_SOURCE=35, + + /* Once provided, this string will be used to replace the port/PID in the log file names. */ + /* Parameter: (String) The identifier that will be part of all trace file names */ + FDB_NET_OPTION_TRACE_FILE_IDENTIFIER=36, + + /* Use the same base trace file name for all client threads as it did before version 7.2. The current default behavior is to use distinct trace file names for client threads by including their version and thread index. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_TRACE_SHARE_AMONG_CLIENT_THREADS=37, + + /* Initialize trace files on network setup, determine the local IP later. Otherwise tracing is initialized when opening the first database. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_TRACE_INITIALIZE_ON_SETUP=38, + + /* Set file suffix for partially written log files. */ + /* Parameter: (String) Append this suffix to partially written log files. When a log file is complete, it is renamed to remove the suffix. No separator is added between the file and the suffix. If you want to add a file extension, you should include the separator - e.g. '.tmp' instead of 'tmp' to add the 'tmp' extension. */ + FDB_NET_OPTION_TRACE_PARTIAL_FILE_SUFFIX=39, + + /* Set internal tuning or debugging knobs */ + /* Parameter: (String) knob_name=knob_value */ + FDB_NET_OPTION_KNOB=40, + + /* Deprecated */ + /* Parameter: (String) file path or linker-resolved name */ + FDB_NET_OPTION_TLS_PLUGIN=41, + + /* Set the certificate chain */ + /* Parameter: (Bytes) certificates */ + FDB_NET_OPTION_TLS_CERT_BYTES=42, + + /* Set the file from which to load the certificate chain */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CERT_PATH=43, + + /* Set the private key corresponding to your own certificate */ + /* Parameter: (Bytes) key */ + FDB_NET_OPTION_TLS_KEY_BYTES=45, + + /* Set the file from which to load the private key corresponding to your own certificate */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_KEY_PATH=46, + + /* Set the peer certificate field verification criteria */ + /* Parameter: (Bytes) verification pattern */ + FDB_NET_OPTION_TLS_VERIFY_PEERS=47, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_ENABLE=48, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_BUGGIFY_DISABLE=49, + + /* Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=50, + + /* Set the probability of an active BUGGIFY section being fired */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_BUGGIFY_SECTION_FIRED_PROBABILITY=51, + + /* Set the ca bundle */ + /* Parameter: (Bytes) ca bundle */ + FDB_NET_OPTION_TLS_CA_BYTES=52, + + /* Set the file from which to load the certificate authority bundle */ + /* Parameter: (String) file path */ + FDB_NET_OPTION_TLS_CA_PATH=53, + + /* Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used. */ + /* Parameter: (String) key passphrase */ + FDB_NET_OPTION_TLS_PASSWORD=54, + + /* Prevent client from connecting to a non-TLS endpoint by throwing network connection failed error. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_TLS_DISABLE_PLAINTEXT_CONNECTION=55, + + /* Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_MULTI_VERSION_CLIENT_API=60, + + /* If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS=61, + + /* Adds an external client library for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY=62, + + /* Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network. */ + /* Parameter: (String) path to directory containing client libraries */ + FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY=63, + + /* Prevents connections through the local client, allowing only connections through externally loaded client libraries. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_LOCAL_CLIENT=64, + + /* Spawns multiple worker threads for each version of the client that is loaded. Setting this to a number greater than one implies disable_local_client. */ + /* Parameter: (Int) Number of client threads to be spawned. Each cluster will be serviced by a single client thread. */ + FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION=65, + + /* Adds an external client library to be used with a future version protocol. This option can be used testing purposes only! */ + /* Parameter: (String) path to client library */ + FDB_NET_OPTION_FUTURE_VERSION_CLIENT_LIBRARY=66, + + /* Retain temporary external client library copies that are created for enabling multi-threading. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_RETAIN_CLIENT_LIBRARY_COPIES=67, + + /* Ignore the failure to initialize some of the external clients */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_IGNORE_EXTERNAL_CLIENT_FAILURES=68, + + /* Fail with an error if there is no client matching the server version the client is connecting to */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_FAIL_INCOMPATIBLE_CLIENT=69, + + /* Disables logging of client statistics, such as sampled transaction activity. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING=70, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING=71, + + /* Enables debugging feature to perform run loop profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING=71, + + /* Prevents the multi-version client API from being disabled, even if no external clients are configured. This option is required to use GRV caching. */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_DISABLE_CLIENT_BYPASS=72, + + /* Enable client buggify - will make requests randomly fail (intended for client testing) */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_ENABLE=80, + + /* Disable client buggify */ + /* Parameter: Option takes no parameter */ + FDB_NET_OPTION_CLIENT_BUGGIFY_DISABLE=81, + + /* Set the probability of a CLIENT_BUGGIFY section being active for the current execution. */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_ACTIVATED_PROBABILITY=82, + + /* Set the probability of an active CLIENT_BUGGIFY section being fired. A section will only fire if it was activated */ + /* Parameter: (Int) probability expressed as a percentage between 0 and 100 */ + FDB_NET_OPTION_CLIENT_BUGGIFY_SECTION_FIRED_PROBABILITY=83, + + /* Set a tracer to run on the client. Should be set to the same value as the tracer set on the server. */ + /* Parameter: (String) Distributed tracer type. Choose from none, log_file, or network_lossy */ + FDB_NET_OPTION_DISTRIBUTED_CLIENT_TRACER=90, + + /* Sets the directory for storing temporary files created by FDB client, such as temporary copies of client libraries. Defaults to /tmp */ + /* Parameter: (String) Client directory for temporary files. */ + FDB_NET_OPTION_CLIENT_TMP_DIR=91, + + /* This option is set automatically to communicate the list of supported clients to the active client. */ + /* Parameter: (String) [release version],[source version],[protocol version];... This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_SUPPORTED_CLIENT_VERSIONS=1000, + + /* This option is set automatically on all clients loaded externally using the multi-version API. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT=1001, + + /* This option tells a child on a multiversion client what transport ID to use. */ + /* Parameter: (Int) Transport ID for the child connection This is a hidden parameter and should not be used directly by applications.*/ + FDB_NET_OPTION_EXTERNAL_CLIENT_TRANSPORT_ID=1002 +} FDBNetworkOption; + +typedef enum { + /* Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000. */ + /* Parameter: (Int) Max location cache entries */ + FDB_DB_OPTION_LOCATION_CACHE_SIZE=10, + + /* Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. */ + /* Parameter: (Int) Max outstanding watches */ + FDB_DB_OPTION_MAX_WATCHES=20, + + /* Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. */ + /* Parameter: (String) Hexadecimal ID */ + FDB_DB_OPTION_MACHINE_ID=21, + + /* Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing. */ + /* Parameter: (String) A string identifier for the datacenter */ + FDB_DB_OPTION_DATACENTER_ID=22, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_ENABLE=26, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_SNAPSHOT_RYW_DISABLE=27, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_DB_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_DB_OPTION_TRANSACTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) number of times to retry */ + FDB_DB_OPTION_TRANSACTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_DB_OPTION_TRANSACTION_MAX_RETRY_DELAY=502, + + /* Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: (Int) value in bytes */ + FDB_DB_OPTION_TRANSACTION_SIZE_LIMIT=503, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY=504, + + /* Deprecated. Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS=505, + + /* Set a random idempotency id for all transactions. See the transaction option description for more information. This feature is in development and not ready for general use. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_AUTOMATIC_IDEMPOTENCY=506, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the ``bypass_unreadable`` option of each transaction created by this database. See the transaction option description for more information. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_BYPASS_UNREADABLE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* Enables conflicting key reporting on all transactions, allowing them to retrieve the keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_TRANSACTION_REPORT_CONFLICTING_KEYS=702, + + /* Use configuration database. */ + /* Parameter: Option takes no parameter */ + FDB_DB_OPTION_USE_CONFIG_DATABASE=800, + + /* Enables verification of causal read risky by checking whether clients are able to read stale data when they detect a recovery, and logging an error if so. */ + /* Parameter: (Int) integer between 0 and 100 expressing the probability a client will verify it can't read stale data */ + FDB_DB_OPTION_TEST_CAUSAL_READ_RISKY=900 +} FDBDatabaseOption; + +typedef enum { + /* The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_WRITE_RISKY=10, + + /* The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a simultaneous fault and misbehaving clock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_RISKY=20, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_CAUSAL_READ_DISABLE=21, + + /* Addresses returned by get_addresses_for_key include the port when enabled. As of api version 630, this option is enabled by default and setting this has no effect. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS=23, + + /* The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE=30, + + /* Committing this transaction will bypass the normal load balancing across commit proxies and go directly to the specifically nominated 'first commit proxy'. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_COMMIT_ON_FIRST_PROXY=40, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_CHECK_WRITES_ENABLE=50, + + /* Reads performed by a transaction will not see any prior mutations that occurred in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction. It is an error to set this option after performing any reads or writes on the transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE=51, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_AHEAD_DISABLE=52, + + /* Storage server should cache disk blocks needed for subsequent read requests in this transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SERVER_SIDE_CACHE_ENABLE=507, + + /* Storage server should not cache disk blocks needed for subsequent read requests in this transaction. This can be used to avoid cache pollution for reads not expected to be repeated. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SERVER_SIDE_CACHE_DISABLE=508, + + /* Use normal read priority for subsequent read requests in this transaction. This is the default read priority. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_NORMAL=509, + + /* Use low read priority for subsequent read requests in this transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_LOW=510, + + /* Use high read priority for subsequent read requests in this transaction. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_PRIORITY_HIGH=511, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DATACENTER=110, + + /* */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_RISKY=120, + + /* Deprecated */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_DURABILITY_DEV_NULL_IS_WEB_SCALE=130, + + /* Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE=200, + + /* Specifies that this transaction should be treated as low priority and that default priority transactions will be processed first. Batch priority transactions will also be throttled at load levels smaller than for other types of transactions and may be fully cut off in the event of machine failures. Useful for doing batch work simultaneously with latency-sensitive work */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_PRIORITY_BATCH=201, + + /* This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_INITIALIZE_NEW_DATABASE=300, + + /* Allows this transaction to read and modify system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_ACCESS_SYSTEM_KEYS=301, + + /* Allows this transaction to read system keys (those that start with the byte 0xFF). Implies raw_access. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_SYSTEM_KEYS=302, + + /* Allows this transaction to access the raw key-space when tenant mode is on. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_RAW_ACCESS=303, + + /* Allows this transaction to bypass storage quota enforcement. Should only be used for transactions that directly or indirectly decrease the size of the tenant group's data. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_STORAGE_QUOTA=304, + + /* */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_DEBUG_DUMP=400, + + /* */ + /* Parameter: (String) Optional transaction name */ + FDB_TR_OPTION_DEBUG_RETRY_LOGGING=401, + + /* Deprecated */ + /* Parameter: (String) String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_ENABLE=402, + + /* Sets a client provided identifier for the transaction that will be used in scenarios like tracing or profiling. Client trace logging or transaction profiling must be separately enabled. */ + /* Parameter: (String) String identifier to be used when tracing or profiling this transaction. The identifier must not exceed 100 characters. */ + FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER=403, + + /* Enables tracing for this transaction and logs results to the client trace logs. The DEBUG_TRANSACTION_IDENTIFIER option must be set before using this option, and client trace logging must be enabled to get log output. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOG_TRANSACTION=404, + + /* Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option, after which the field will be truncated. A negative value disables truncation. */ + /* Parameter: (Int) Maximum length of escaped key and value fields. */ + FDB_TR_OPTION_TRANSACTION_LOGGING_MAX_FIELD_LENGTH=405, + + /* Sets an identifier for server tracing of this transaction. When committed, this identifier triggers logging when each part of the transaction authority encounters it, which is helpful in diagnosing slowness in misbehaving clusters. The identifier is randomly generated. When there is also a debug_transaction_identifier, both IDs are logged together. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SERVER_REQUEST_TRACING=406, + + /* Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of timeout */ + FDB_TR_OPTION_TIMEOUT=500, + + /* Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) number of times to retry */ + FDB_TR_OPTION_RETRY_LIMIT=501, + + /* Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. */ + /* Parameter: (Int) value in milliseconds of maximum delay */ + FDB_TR_OPTION_MAX_RETRY_DELAY=502, + + /* Set the transaction size limit in bytes. The size is calculated by combining the sizes of all keys and values written or mutated, all key ranges cleared, and all read and write conflict ranges. (In other words, it includes the total size of all data included in the request to the cluster to commit the transaction.) Large transactions can cause performance problems on FoundationDB clusters, so setting this limit to a smaller value than the default can help prevent the client from accidentally degrading the cluster's performance. This value must be at least 32 and cannot be set to higher than 10,000,000, the default transaction size limit. */ + /* Parameter: (Int) value in bytes */ + FDB_TR_OPTION_SIZE_LIMIT=503, + + /* Associate this transaction with this ID for the purpose of checking whether or not this transaction has already committed. Must be at least 16 bytes and less than 256 bytes. This feature is in development and not ready for general use. Unless the automatic_idempotency option is set after this option, the client will not automatically attempt to remove this id from the cluster after a successful commit. */ + /* Parameter: (String) Unique ID This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_IDEMPOTENCY_ID=504, + + /* Automatically assign a random 16 byte idempotency id for this transaction. Prevents commits from failing with ``commit_unknown_result``. WARNING: If you are also using the multiversion client or transaction timeouts, if either cluster_version_changed or transaction_timed_out was thrown during a commit, then that commit may have already succeeded or may succeed in the future. This feature is in development and not ready for general use. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_AUTOMATIC_IDEMPOTENCY=505, + + /* Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_ENABLE=600, + + /* Snapshot read operations will not see the results of writes done in the same transaction. This was the default behavior prior to API version 300. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SNAPSHOT_RYW_DISABLE=601, + + /* The transaction can read and write to locked databases, and is responsible for checking that it took the lock. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_LOCK_AWARE=700, + + /* By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USED_DURING_COMMIT_PROTECTION_DISABLE=701, + + /* The transaction can read from locked databases. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_READ_LOCK_AWARE=702, + + /* No other transactions will be applied before this transaction within the same commit version. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_FIRST_IN_BATCH=710, + + /* This option should only be used by tools which change the database configuration. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_PROVISIONAL_PROXIES=711, + + /* The transaction can retrieve keys that are conflicting with other transactions. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_REPORT_CONFLICTING_KEYS=712, + + /* By default, the special key space will only allow users to read from exactly one module (a subspace in the special key space). Use this option to allow reading from zero or more modules. Users who set this option should be prepared for new modules, which may have different behaviors than the modules they're currently reading. For example, a new module might block or return an error. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_RELAXED=713, + + /* By default, users are not allowed to write to special keys. Enable this option will implicitly enable all options required to achieve the configuration change. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_SPECIAL_KEY_SPACE_ENABLE_WRITES=714, + + /* Adds a tag to the transaction that can be used to apply manual targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_TAG=800, + + /* Adds a tag to the transaction that can be used to apply manual or automatic targeted throttling. At most 5 tags can be set on a transaction. */ + /* Parameter: (String) String identifier used to associated this transaction with a throttling group. Must not exceed 16 characters. */ + FDB_TR_OPTION_AUTO_THROTTLE_TAG=801, + + /* Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with a 33 bytes serialized binary format which consists of: 8 bytes protocol version, e.g. ``0x0FDB00B073000000LL`` in little-endian format, 16 bytes trace id, 8 bytes span id, 1 byte set to 1 if sampling is enabled */ + /* Parameter: (Bytes) A serialized binary byte string of length 33 used to associate the span of this transaction with a parent */ + FDB_TR_OPTION_SPAN_PARENT=900, + + /* Asks storage servers for how many bytes a clear key range contains. Otherwise uses the location cache to roughly estimate this. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_EXPENSIVE_CLEAR_COST_ESTIMATION_ENABLE=1000, + + /* Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_BYPASS_UNREADABLE=1100, + + /* Allows this transaction to use cached GRV from the database context. Defaults to off. Upon first usage, starts a background updater to periodically update the cache to avoid stale read versions. The disable_client_bypass option must also be set. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_USE_GRV_CACHE=1101, + + /* Specifically instruct this transaction to NOT use cached GRV. Primarily used for the read version cache's background updater to avoid attempting to read a cached entry in specific situations. */ + /* Parameter: Option takes no parameter This is a hidden parameter and should not be used directly by applications.*/ + FDB_TR_OPTION_SKIP_GRV_CACHE=1102, + + /* Attach given authorization token to the transaction such that subsequent tenant-aware requests are authorized */ + /* Parameter: (String) A JSON Web Token authorized to access data belonging to one or more tenants, indicated by 'tenants' claim of the token's payload. */ + FDB_TR_OPTION_AUTHORIZATION_TOKEN=2000, + + /* Enables replica consistency check, which compares the results returned by storage server replicas (as many as specified by consistency_check_required_replicas option) for a given read request, in client-side load balancer. */ + /* Parameter: Option takes no parameter */ + FDB_TR_OPTION_ENABLE_REPLICA_CONSISTENCY_CHECK=4000, + + /* Specifies the number of storage server replica results that the load balancer needs to compare when enable_replica_consistency_check option is set. */ + /* Parameter: (Int) Number of storage replicas over which the load balancer consistency check is done. */ + FDB_TR_OPTION_CONSISTENCY_CHECK_REQUIRED_REPLICAS=4001 +} FDBTransactionOption; + +typedef enum { + /* Client intends to consume the entire range and would like it all transferred as early as possible. */ + FDB_STREAMING_MODE_WANT_ALL=-2, + + /* The default. The client doesn't know how much of the range it is likely to used and wants different performance concerns to be balanced. Only a small portion of data is transferred to the client initially (in order to minimize costs if the client doesn't read the entire range), and as the caller iterates over more items in the range larger batches will be transferred in order to minimize latency. After enough iterations, the iterator mode will eventually reach the same byte limit as ``WANT_ALL`` */ + FDB_STREAMING_MODE_ITERATOR=-1, + + /* Infrequently used. The client has passed a specific row limit and wants that many rows delivered in a single batch. Because of iterator operation in client drivers make request batches transparent to the user, consider ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this mode is used. */ + FDB_STREAMING_MODE_EXACT=0, + + /* Infrequently used. Transfer data in batches small enough to not be much more expensive than reading individual rows, to minimize cost if iteration stops early. */ + FDB_STREAMING_MODE_SMALL=1, + + /* Infrequently used. Transfer data in batches sized in between small and large. */ + FDB_STREAMING_MODE_MEDIUM=2, + + /* Infrequently used. Transfer data in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the client stops iteration early, some disk and network bandwidth may be wasted. The batch size may still be too small to allow a single client to get high throughput from the database, so if that is what you need consider the SERIAL StreamingMode. */ + FDB_STREAMING_MODE_LARGE=3, + + /* Transfer data in batches large enough that an individual client can get reasonable read bandwidth from the database. If the client stops iteration early, considerable disk and network bandwidth may be wasted. */ + FDB_STREAMING_MODE_SERIAL=4 +} FDBStreamingMode; + +typedef enum { + /* Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. */ + FDB_MUTATION_TYPE_ADD=2, + + /* Deprecated */ + FDB_MUTATION_TYPE_AND=6, + + /* Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_AND=6, + + /* Deprecated */ + FDB_MUTATION_TYPE_OR=7, + + /* Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_OR=7, + + /* Deprecated */ + FDB_MUTATION_TYPE_XOR=8, + + /* Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. */ + FDB_MUTATION_TYPE_BIT_XOR=8, + + /* Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. */ + FDB_MUTATION_TYPE_APPEND_IF_FITS=9, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MAX=12, + + /* Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_MIN=13, + + /* Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY=14, + + /* Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. */ + FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE=15, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MIN=16, + + /* Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. */ + FDB_MUTATION_TYPE_BYTE_MAX=17, + + /* Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. */ + FDB_MUTATION_TYPE_COMPARE_AND_CLEAR=20 +} FDBMutationType; + +typedef enum { + /* Used to add a read conflict range */ + FDB_CONFLICT_RANGE_TYPE_READ=0, + + /* Used to add a write conflict range */ + FDB_CONFLICT_RANGE_TYPE_WRITE=1 +} FDBConflictRangeType; + +typedef enum { + /* Returns ``true`` if the error indicates the operations in the transactions should be retried because of transient error. */ + FDB_ERROR_PREDICATE_RETRYABLE=50000, + + /* Returns ``true`` if the error indicates the transaction may have succeeded, though not in a way the system can verify. */ + FDB_ERROR_PREDICATE_MAYBE_COMMITTED=50001, + + /* Returns ``true`` if the error indicates the transaction has not committed, though in a way that can be retried. */ + FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED=50002 +} FDBErrorPredicate; + +#endif diff --git a/bindings/rust/foundationdb-sys/include/740/fdb_c_types.h b/bindings/rust/foundationdb-sys/include/740/fdb_c_types.h new file mode 100644 index 00000000000..779e227ad5e --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/740/fdb_c_types.h @@ -0,0 +1,47 @@ +/* + * fdb_c_types.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDB_C_TYPES_H +#define FDB_C_TYPES_H +#pragma once + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Pointers to these opaque types represent objects in the FDB API */ +typedef struct FDB_future FDBFuture; +typedef struct FDB_result FDBResult; +typedef struct FDB_cluster FDBCluster; +typedef struct FDB_database FDBDatabase; +typedef struct FDB_tenant FDBTenant; +typedef struct FDB_transaction FDBTransaction; + +typedef int fdb_error_t; +typedef int fdb_bool_t; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bindings/rust/foundationdb-sys/include/740/version.txt b/bindings/rust/foundationdb-sys/include/740/version.txt new file mode 100644 index 00000000000..815da58b7a9 --- /dev/null +++ b/bindings/rust/foundationdb-sys/include/740/version.txt @@ -0,0 +1 @@ +7.4.1 diff --git a/bindings/rust/foundationdb-sys/src/lib.rs b/bindings/rust/foundationdb-sys/src/lib.rs new file mode 100644 index 00000000000..234bd9b541c --- /dev/null +++ b/bindings/rust/foundationdb-sys/src/lib.rs @@ -0,0 +1,273 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(clippy::unreadable_literal)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +/// Returns the selected runtime and header API versions when the build headers +/// expose the query. The returned pair is `(0, 0)` before successful selection. +/// +/// Historical headers return `None`; code built with current headers requires a +/// C library that also exports `fdb_get_selected_api_versions`. +pub fn selected_api_versions() -> Option<(i32, i32)> { + #[cfg(fdb_has_selected_api_versions)] + { + let mut runtime_version = 0; + let mut header_version = 0; + // SAFETY: Both outputs are live, distinct integers. The C query is + // thread-safe and does not change API selection or the network lifecycle. + unsafe { + fdb_get_selected_api_versions(&mut runtime_version, &mut header_version); + } + Some((runtime_version, header_version)) + } + #[cfg(not(fdb_has_selected_api_versions))] + { + None + } +} + +/// Defines the FDB_API_VERSION constant and generates the `if_cfg_api_versions` macro +/// from a list of (name, number) versions. +/// +/// `if_cfg_api_versions` has one branch for all possible version combinations: +/// - (min = ...) +/// - (max = ...) +/// - (min = ..., max = ...) with min < max +/// +/// For macro expansion reasons, we need to generate all the branches in a single expansion, +/// so this macro uses accumulators to gather all branch data before expanding it in one go. +/// In short, this is a combinatorial generator. Given `[0, 1, 2, 3]` it generates branches for: +/// - (min = 0) +/// - (min = 1) +/// - (min = 2) +/// - (min = 3) +/// - (max = 0) +/// - (max = 1) +/// - (max = 2) +/// - (max = 3) +/// - (min = 0, max = 1) +/// - (min = 0, max = 2) +/// - (min = 0, max = 3) +/// - (min = 1, max = 2) +/// - (min = 1, max = 3) +/// - (min = 2, max = 3) +macro_rules! versions_expand { + ($(($version_name:tt, $version:tt)),+ $(,)?) => { + // Entry point: start with a list of (version_name, version_number) + // define the FDB_API_VERSION constant + $( + #[cfg(feature = $version_name)] + pub const FDB_API_VERSION: u32 = $version; + )* + // Call the "@pre" phase with: + // - visited_versions: [] + // - remaining_versions: versions + // - acc2: [] (will store min branches) + // - acc3: [] (will store max branches) + versions_expand!(@pre [][$(($version_name, $version))+][][]); + }; + (@pre [$($max:tt)*][$min:tt $($other:tt)*][$(($($acc2:tt)*))*][$(($($acc3:tt)*))*]) => { + // "@pre" phase generates (min = ...) and (max = ...) branches + // While remaining_versions is not empty: + // - move one version from remaining to visited + // - add one "min" branch to acc2 + // - add one "max" branch to acc3 + versions_expand!(@pre [$($max)* $min][$($other)*][$(($($acc2)*))* ($min $($other)*)][$(($($acc3)*))* ($min $($max)*)]); + }; + (@pre [$min:tt $($version:tt)*][] $acc2:tt $acc3:tt) => { + // remaining_versions is empty: end of "@pre" phase + // Call the "@mid" phase with: + // - min_range: [versions[0]] + // - max_range: versions[1..] + // - acc1: [] (will store min-max combination branches) + // - acc2: min_branches + // - acc3: max_branches + versions_expand!(@mid [$min][$($version)*][] $acc2 $acc3); + }; + (@mid [$min:tt $($version:tt)*][$max:tt $($other:tt)*][$(($($acc1:tt)*))*] $acc2:tt $acc3:tt) => { + // "@mid" phase generates all (min = ..., max = ...) branches + // This step generates all pairs for the current min + // While the max_range is not empty: + // - let max be the smallest version in max_range + // - add the "min max" branch to acc1 + // - move max to the min_range + versions_expand!(@mid [$min $($version)* $max][$($other)*][$(($($acc1)*))* ($min $max $($version)*)] $acc2 $acc3); + }; + (@mid [$drop:tt $min:tt $($version:tt)*][] $acc1:tt $acc2:tt $acc3:tt) => { + // max_range is empty: end of inner "@mid" loop + // - drop current min + // - set the second smallest version in min_range as new min + // - set all remaining versions as the max_range + versions_expand!(@mid [$min][$($version)*] $acc1 $acc2 $acc3); + }; + (@mid [$drop:tt][] $acc1:tt $acc2:tt $acc3:tt) => { + // min_range is empty: end of "@mid" phase + // Call the "@end" phase with: + // - the $ sign so it can use it as a symbol in the macro + // - acc1: min_max_branches + // - acc2: min_branches + // - acc3: max_branches + versions_expand!(@end ($) $acc1 $acc2 $acc3); + }; + (@end ($d:tt) + [$((($min_name1:tt, $min1:tt) ($max_name1:tt, $max1:tt) $(($version_name1:tt, $version1:tt))*))*] + [$((($min_name2:tt, $min2:tt) $(($version_name2:tt, $version2:tt))*))*] + [$((($max_name3:tt, $max3:tt) $(($version_name3:tt, $version3:tt))*))*] + ) => { + // "@end" phase generates the macro in one expansion + // For each branch type, generates the appropriate cfg attributes + + /// Similar to `cfg_api_versions` proc-macro, but as a regular macro. + /// Unlike proc-macros which are unstable on expressions and non-inlined modules, + /// `if_cfg_api_versions` can be used on these places. + /// + /// Any feature after the "min" and "max" arguments will be joined in the "any" predicate. + /// + /// ### Constraints: + /// - `if/else` always requires block syntax (i.e., `{}` around branches). + /// - Avoid using multiple items in a single macro call. + /// - Best suited for guarding expressions, single items, or blocks. + /// + /// ## Behavior + /// ### Single statement + /// Guard the statment without additional curly braces + /// ```rs + /// if_cfg_api_versions!(max = 520 => prinln!("foo")); + /// // expands to: + /// #[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2"))] + /// println!("foo"); + /// ``` + /// + /// ### Muliple statements + /// Guard a block wrapping the statments + /// ```rs + /// if_cfg_api_versions!(max = 520 => + /// prinln!("foo"); + /// prinln!("bar"); + /// ); + /// // expands to: + /// #[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2"))] + /// { + /// println!("foo"); + /// println!("bar"); + /// } + /// ``` + /// + /// ### Single Item (e.g., `mod`, `fn`, etc.) + /// Guard the item without additional culry braces + /// ```rs + /// if_cfg_api_versions!(max = 520 => pub mod foo); + /// // expands to: + /// #[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2"))] + /// pub mod foo; + /// ``` + /// + /// ### Multiple Items: **NOT RECOMMANDED** + /// Only guard the first item (does not add curly braces since it would be invalid syntax) + /// ```rs + /// if_cfg_api_versions!(max = 520 => + /// pub mod foo; + /// pub mod bar; + /// ); + /// // expands to: + /// #[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2"))] + /// pub mod foo; + /// pub mod bar; // not guarded + /// ``` + /// + /// ### If/Else + /// Both branches must be wrapped in curly braces, guard the first branch and the second with the opposite condition + /// ```rs + /// let result = if_cfg_api_versions!(max = 520 => { + /// println!("5.x"); + /// true + /// } else { + /// println!("6.0+"); + /// false + /// }); + /// // expands to: + /// let result = { + /// #[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2"))] + /// { + /// println!("5.x"); + /// true + /// } + /// #[cfg(not(any(feature = "fdb-5_1", feature = "fdb-5_2")))] + /// { + /// println!("6.0+"); + /// false + /// } + /// }; + /// ``` + #[macro_export] + macro_rules! if_cfg_api_versions { + // multiple statements + ($d($k:tt = $v:tt),+ => $a:stmt; $d($b:stmt)+) => { + if_cfg_api_versions!($d($k=$v),+ => { $a; $d($b)+ }) + }; + // min-max branches + $( + (min=$min1, max=$max1 $d(,feature = $feature:literal)* => {$d($then:tt)*} else {$d($else:tt)*}) => {{ + #[cfg(any(feature=$min_name1 $(,feature=$version_name1)* ,feature=$max_name1 $d(,feature=$feature)*))] + { $d($then)* } + #[cfg(not(any(feature=$min_name1 $(,feature=$version_name1)* ,feature=$max_name1 $d(,feature=$feature)*)))] + { $d($else)* } + }}; + (min=$min1, max=$max1 $d(,feature = $feature:literal)* => $d($then:tt)*) => { + #[cfg(any(feature=$min_name1 $(,feature=$version_name1)* ,feature=$max_name1 $d(,feature=$feature)*))] + $d($then)* + }; + )* + // min branches + $( + (min=$min2 $d(,feature = $feature:literal)* => {$d($then:tt)*} else {$d($else:tt)*}) => {{ + #[cfg(any(feature=$min_name2 $(,feature=$version_name2)* $d(,feature=$feature)*))] + { $d($then)* } + #[cfg(not(any(feature=$min_name2 $(,feature=$version_name2)* $d(,feature=$feature)*)))] + { $d($else)* } + }}; + (min=$min2 $d(,feature = $feature:literal)* => $d($then:tt)*) => { + #[cfg(any(feature=$min_name2 $(,feature=$version_name2)* $d(,feature=$feature)*))] + $d($then)* + }; + )* + // max branches + $( + (max=$max3 $d(,feature = $feature:literal)* => {$d($then:tt)*} else {$d($else:tt)*}) => {{ + #[cfg(any($(feature=$version_name3,)* feature=$max_name3 $d(,feature=$feature)*))] + { $d($then)* } + #[cfg(not(any($(feature=$version_name3,)* feature=$max_name3 $d(,feature=$feature)*)))] + { $d($else)* } + }}; + (max=$max3 $d(,feature = $feature:literal)* => $d($then:tt)*) => { + #[cfg(any($(feature=$version_name3,)* feature=$max_name3 $d(,feature=$feature)*))] + $d($then)* + }; + )* + // unsupported versions + (min=$min:tt, max=$max:tt $d(,feature = $feature:literal)* => $d($t:tt)*) => { + compile_error!(concat!("Use of unsupported version, or min >= max. Supported: ", $($min2, " " ,)*)); + }; + (min=$min:tt $d(,feature = $feature:literal)* => $d($t:tt)*) => { + compile_error!(concat!("Use of unsupported version. Supported: ", $($min2, " " ,)*)); + }; + (max=$max:tt $d(,feature = $feature:literal)* => $d($t:tt)*) => { + compile_error!(concat!("Use of unsupported version. Supported: ", $($min2, " " ,)*)); + }; + } + }; +} + +versions_expand![ + ("fdb-5_1", 510), + ("fdb-5_2", 520), + ("fdb-6_0", 600), + ("fdb-6_1", 610), + ("fdb-6_2", 620), + ("fdb-6_3", 630), + ("fdb-7_0", 700), + ("fdb-7_1", 710), + ("fdb-7_3", 730), + ("fdb-7_4", 740), +]; diff --git a/bindings/rust/foundationdb-tuple/CHANGELOG.md b/bindings/rust/foundationdb-tuple/CHANGELOG.md new file mode 100644 index 00000000000..c7f45eddce7 --- /dev/null +++ b/bindings/rust/foundationdb-tuple/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.11.0] - 2026-06-24 + +### 🚀 Features + +- Feat: migrate workspace to Rust 2024 edition (#255) + +[0.11.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.10.0..0.11.0 + + +## [0.10.0] - 2025-10-24 + +### 🚀 Features + +- Implement `std::fmt::Display` on `Subspace` + +### 🐛 Bug Fixes + +- Fix: resolve clippy warnings and add doc alias + +### ⚙️ Miscellaneous + +- Clippy fixes +- Bump MSRV to 1.82 +- Clippy --fix +- Bump msrv to 1.85 + +[0.10.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.1..0.10.0 + + +## [0.9.1] - 2025-01-06 + +### ⚙️ Other changes + +- Update release +- Split the fdb-tuple module into its own crate + + diff --git a/bindings/rust/foundationdb-tuple/Cargo.toml b/bindings/rust/foundationdb-tuple/Cargo.toml new file mode 100644 index 00000000000..52069042a8c --- /dev/null +++ b/bindings/rust/foundationdb-tuple/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "foundationdb-tuple" +version = "0.11.0" +authors = [ + "Benjamin Fry ", + "Vincent Rouillé ", + "Pierre Zemb ", +] +edition = "2024" +rust-version = "1.85.1" + +description = """ +Foundationdb tuple pack/unpack implementation in rust +""" + +documentation = "https://docs.rs/foundationdb" +repository = "https://github.com/apple/foundationdb" +license = "MIT/Apache-2.0" + +readme = "README.md" +keywords = ["foundationdb", "kv", "tuple"] +categories = ["database"] + +[package.metadata.docs.rs] +features = ["uuid", "num-bigint"] + +[features] +default = ["uuid"] + +[dependencies] +memchr = "2.8.2" +uuid = { version = "1.23.3", optional = true } +num-bigint = { version = "0.5.1", optional = true } diff --git a/bindings/rust/foundationdb-tuple/README.md b/bindings/rust/foundationdb-tuple/README.md new file mode 100644 index 00000000000..29269bf2b74 --- /dev/null +++ b/bindings/rust/foundationdb-tuple/README.md @@ -0,0 +1,3 @@ +# FoundationDB Tuple + +A rust implementation of the [tuple encoding](https://github.com/apple/foundationdb/blob/main/design/tuple.md) used by FoundationDB. \ No newline at end of file diff --git a/bindings/rust/foundationdb-tuple/src/element.rs b/bindings/rust/foundationdb-tuple/src/element.rs new file mode 100644 index 00000000000..71749cf162c --- /dev/null +++ b/bindings/rust/foundationdb-tuple/src/element.rs @@ -0,0 +1,230 @@ +use super::pack::{f32_to_u32_be_bytes, f64_to_u64_be_bytes}; +use super::{Bytes, Versionstamp}; +use std::{borrow::Cow, cmp}; + +#[cfg(feature = "num-bigint")] +use num_bigint::Sign; +#[cfg(feature = "num-bigint")] +use std::convert::TryFrom; + +#[derive(Clone, Debug)] +pub enum Element<'a> { + Nil, + Bytes(Bytes<'a>), + String(Cow<'a, str>), + Tuple(Vec>), + Int(i64), + #[cfg(feature = "num-bigint")] + BigInt(num_bigint::BigInt), + Float(f32), + Double(f64), + Bool(bool), + #[cfg(feature = "uuid")] + Uuid(uuid::Uuid), + Versionstamp(Versionstamp), +} + +struct CmpElement<'a, 'b>(&'a Element<'b>); + +impl PartialEq for CmpElement<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} +impl Eq for CmpElement<'_, '_> {} + +impl PartialOrd for CmpElement<'_, '_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CmpElement<'_, '_> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.0 + .code() + .cmp(&other.0.code()) + .then_with(|| match (&self.0, &other.0) { + (Element::Bytes(a), Element::Bytes(b)) => a.cmp(b), + (Element::String(a), Element::String(b)) => a.cmp(b), + (Element::Tuple(a), Element::Tuple(b)) => { + let a_values = a.iter().map(CmpElement); + let b_values = b.iter().map(CmpElement); + a_values.cmp(b_values) + } + (Element::Int(a), Element::Int(b)) => a.cmp(b), + #[cfg(feature = "num-bigint")] + (Element::BigInt(a), Element::BigInt(b)) => a.cmp(b), + #[cfg(feature = "num-bigint")] + (Element::BigInt(a), Element::Int(b)) => match i64::try_from(a) { + Ok(a) => a.cmp(b), + Err(_) => a.sign().cmp(&Sign::NoSign), + }, + #[cfg(feature = "num-bigint")] + (Element::Int(a), Element::BigInt(b)) => match i64::try_from(b) { + Ok(b) => a.cmp(&b), + Err(_) => Sign::NoSign.cmp(&b.sign()), + }, + (Element::Float(a), Element::Float(b)) => { + f32_to_u32_be_bytes(*a).cmp(&f32_to_u32_be_bytes(*b)) + } + (Element::Double(a), Element::Double(b)) => { + f64_to_u64_be_bytes(*a).cmp(&f64_to_u64_be_bytes(*b)) + } + #[cfg(feature = "uuid")] + (Element::Uuid(a), Element::Uuid(b)) => a.cmp(b), + (Element::Versionstamp(a), Element::Versionstamp(b)) => a.cmp(b), + _ => cmp::Ordering::Equal, + }) + } +} + +impl PartialEq for Element<'_> { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} +impl Eq for Element<'_> {} + +impl PartialOrd for Element<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for Element<'_> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.cmp_at_root(other) + } +} + +impl<'a> Element<'a> { + fn code(&self) -> u8 { + match self { + Element::Nil => super::NIL, + Element::Bytes(_) => super::BYTES, + Element::String(_) => super::STRING, + Element::Tuple(_) => super::NESTED, + Element::Int(_) => super::INTZERO, + #[cfg(feature = "num-bigint")] + Element::BigInt(_) => super::INTZERO, + Element::Float(_) => super::FLOAT, + Element::Double(_) => super::DOUBLE, + Element::Bool(v) => { + if *v { + super::TRUE + } else { + super::FALSE + } + } + #[cfg(feature = "uuid")] + Element::Uuid(_) => super::UUID, + Element::Versionstamp(_) => super::VERSIONSTAMP, + } + } + + #[inline] + fn cmp_values(&self) -> &[Self] { + match self { + Element::Tuple(v) => v.as_slice(), + v => std::slice::from_ref(v), + } + } + + fn cmp_at_root(&self, b: &Element<'_>) -> cmp::Ordering { + let a_values = self.cmp_values().iter().map(CmpElement); + let b_values = b.cmp_values().iter().map(CmpElement); + a_values.cmp(b_values) + } + + pub fn into_owned(self) -> Element<'static> { + match self { + Element::Nil => Element::Nil, + Element::Bytes(v) => Element::Bytes(v.into_owned().into()), + Element::String(v) => Element::String(Cow::Owned(v.into_owned())), + Element::Tuple(v) => Element::Tuple(v.into_iter().map(|e| e.into_owned()).collect()), + Element::Int(v) => Element::Int(v), + #[cfg(feature = "num-bigint")] + Element::BigInt(v) => Element::BigInt(v), + Element::Float(v) => Element::Float(v), + Element::Double(v) => Element::Double(v), + Element::Bool(v) => Element::Bool(v), + #[cfg(feature = "uuid")] + Element::Uuid(v) => Element::Uuid(v), + Element::Versionstamp(v) => Element::Versionstamp(v), + } + } + + pub fn as_bytes(&self) -> Option<&Bytes<'_>> { + match self { + Element::Bytes(v) => Some(v), + _ => None, + } + } + + pub fn as_str(&self) -> Option<&str> { + match self { + Element::String(v) => Some(v), + _ => None, + } + } + + pub fn as_tuple(&self) -> Option<&[Element<'a>]> { + match self { + Element::Tuple(v) => Some(v.as_slice()), + _ => None, + } + } + + pub fn as_i64(&self) -> Option { + match self { + Element::Int(v) => Some(*v), + #[cfg(feature = "num-bigint")] + Element::BigInt(v) => i64::try_from(v).ok(), + _ => None, + } + } + + #[cfg(feature = "num-bigint")] + pub fn as_bigint(&self) -> Option<&num_bigint::BigInt> { + match self { + Element::BigInt(v) => Some(v), + _ => None, + } + } + + pub fn as_f32(&self) -> Option { + match self { + Element::Float(v) => Some(*v), + _ => None, + } + } + + pub fn as_f64(&self) -> Option { + match self { + Element::Double(v) => Some(*v), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match *self { + Element::Bool(v) => Some(v), + _ => None, + } + } + + #[cfg(feature = "uuid")] + pub fn as_uuid(&self) -> Option<&uuid::Uuid> { + match self { + Element::Uuid(v) => Some(v), + _ => None, + } + } + + pub fn as_versionstamp(&self) -> Option<&Versionstamp> { + match self { + Element::Versionstamp(v) => Some(v), + _ => None, + } + } +} diff --git a/bindings/rust/foundationdb-tuple/src/lib.rs b/bindings/rust/foundationdb-tuple/src/lib.rs new file mode 100644 index 00000000000..033c580a69f --- /dev/null +++ b/bindings/rust/foundationdb-tuple/src/lib.rs @@ -0,0 +1,732 @@ +//! Implementation of the official tuple layer typecodes +//! +//! The official specification can be found [here](https://github.com/apple/foundationdb/blob/master/design/tuple.md). + +mod element; +mod pack; +mod subspace; +mod versionstamp; + +use std::borrow::Cow; +use std::fmt::{self, Display}; +use std::io; +use std::ops::Deref; +use std::result; + +#[cfg(feature = "uuid")] +pub use uuid::Uuid; + +pub use element::Element; +pub use pack::{TuplePack, TupleUnpack, VersionstampOffset}; +pub use subspace::Subspace; +pub use versionstamp::Versionstamp; + +const NIL: u8 = 0x00; +const BYTES: u8 = 0x01; +const STRING: u8 = 0x02; +const NESTED: u8 = 0x05; +const NEGINTSTART: u8 = 0x0b; +const INTZERO: u8 = 0x14; +const POSINTEND: u8 = 0x1d; +const FLOAT: u8 = 0x20; +const DOUBLE: u8 = 0x21; +const FALSE: u8 = 0x26; +const TRUE: u8 = 0x27; +#[cfg(feature = "uuid")] +const UUID: u8 = 0x30; +// Not a single official binding is implementing 80 Bit versionstamp... +// const VERSIONSTAMP_88: u8 = 0x32; +const VERSIONSTAMP: u8 = 0x33; + +const ESCAPE: u8 = 0xff; + +/// Tracks the depth of a Tuple decoding chain +#[derive(Copy, Clone)] +pub struct TupleDepth(usize); + +impl TupleDepth { + fn new() -> Self { + TupleDepth(0) + } + + /// Increment the depth by one, this be called when calling into `Tuple::{encode, decode}` of tuple-like datastructures + pub fn increment(self) -> Self { + TupleDepth(self.0 + 1) + } + + /// Returns the current depth in any recursive tuple processing, 0 representing there having been no recursion + pub fn depth(self) -> usize { + self.0 + } +} + +/// A packing/unpacking error +#[derive(Debug)] +pub enum PackError { + Message(Box), + IoError(io::Error), + TrailingBytes, + MissingBytes, + BadStringFormat, + BadCode { + found: u8, + expected: Option, + }, + BadPrefix, + #[cfg(feature = "uuid")] + BadUuid, + UnsupportedIntLength, +} + +impl From for PackError { + fn from(err: io::Error) -> Self { + PackError::IoError(err) + } +} + +impl Display for PackError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PackError::Message(s) => s.fmt(f), + PackError::IoError(err) => err.fmt(f), + PackError::TrailingBytes => write!(f, "trailing bytes"), + PackError::MissingBytes => write!(f, "missing bytes"), + PackError::BadStringFormat => write!(f, "not an utf8 string"), + PackError::BadCode { found, .. } => write!(f, "bad code, found {found}"), + PackError::BadPrefix => write!(f, "bad prefix"), + #[cfg(feature = "uuid")] + PackError::BadUuid => write!(f, "bad uuid"), + PackError::UnsupportedIntLength => write!(f, "integer length was to large"), + } + } +} + +impl std::error::Error for PackError {} + +/// Alias for `Result<..., tuple::Error>` +pub type PackResult = result::Result; + +/// Represent a sequence of bytes (i.e. &[u8]) +/// +/// This sequence can be either owned or borrowed. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Bytes<'a>(pub Cow<'a, [u8]>); + +impl fmt::Debug for Bytes<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for Bytes<'_> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "b\"")?; + for &byte in self.0.iter() { + if byte == b'\\' { + write!(fmt, r"\\")?; + } else if byte.is_ascii_alphanumeric() { + write!(fmt, "{}", byte as char)?; + } else { + write!(fmt, "\\x{byte:02x}")?; + } + } + write!(fmt, "\"") + } +} + +impl Bytes<'_> { + pub fn into_owned(self) -> Vec { + self.0.into_owned() + } +} + +impl Deref for Bytes<'_> { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl AsRef<[u8]> for Bytes<'_> { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl<'a> From<&'a [u8]> for Bytes<'a> { + fn from(bytes: &'a [u8]) -> Self { + Self(Cow::Borrowed(bytes)) + } +} +impl From> for Bytes<'static> { + fn from(vec: Vec) -> Self { + Self(Cow::Owned(vec)) + } +} + +impl<'a> From<&'a str> for Bytes<'a> { + fn from(s: &'a str) -> Self { + s.as_bytes().into() + } +} +impl From for Bytes<'static> { + fn from(vec: String) -> Self { + vec.into_bytes().into() + } +} + +/// Pack value and returns the packed buffer +/// +/// # Panics +/// +/// Panics if the encoded data size doesn't fit in `u32`. +pub fn pack(v: &T) -> Vec { + v.pack_to_vec() +} + +/// Pack value and returns the packed buffer +/// +/// An incomplete versionstamp uses the four-byte offset encoding required by +/// FoundationDB API 520 and later; earlier mutation formats are not supported. +/// +/// # Panics +/// +/// Panics unless there is exactly one incomplete versionstamp, or if the +/// encoded data size doesn't fit in `u32`. +pub fn pack_with_versionstamp(v: &T) -> Vec { + v.pack_to_vec_with_versionstamp() +} + +/// Pack value into the given buffer +/// +/// # Panics +/// +/// Panics if the encoded data size doesn't fit in `u32`. +pub fn pack_into(v: &T, output: &mut Vec) -> VersionstampOffset { + v.pack_into_vec(output) +} + +/// Pack value into the given buffer +/// +/// # Panics +/// +/// Panics unless there is exactly one incomplete versionstamp, or if the +/// encoded data size doesn't fit in `u32`. +pub fn pack_into_with_versionstamp(v: &T, output: &mut Vec) { + let offset = v.pack_into_vec_with_versionstamp(output); + assert!( + matches!(offset, VersionstampOffset::OneIncomplete { .. }), + "pack_into_with_versionstamp requires exactly one incomplete versionstamp" + ); +} + +/// Unpack input +pub fn unpack<'de, T: TupleUnpack<'de>>(input: &'de [u8]) -> PackResult { + T::unpack_root(input) +} + +#[cfg(test)] +mod tests { + use super::*; + + const NIL_VAL: Option<()> = None; + + fn test_serde<'de, T>(val: T, buf: &'de [u8]) + where + T: TuplePack + TupleUnpack<'de> + fmt::Debug + PartialEq, + { + assert_eq!(Bytes::from(pack(&val)), Bytes::from(buf)); + assert_eq!(unpack::<'de, T>(buf).unwrap(), val); + } + + #[test] + fn test_spec() { + test_serde(NIL_VAL, &[NIL]); + test_serde((NIL_VAL,), &[NIL]); + test_serde(((NIL_VAL,),), &[NESTED, NIL, ESCAPE, NIL]); + // assert_eq!(to_bytes(b"foo\x00bar").unwrap(), b"\x01foo\x00\xffbar\x00"); + test_serde("FÔO\x00bar".to_owned(), b"\x02F\xc3\x94O\x00\xffbar\x00"); + test_serde( + (("foo\x00bar".to_owned(), NIL_VAL, ()),), + b"\x05\x02foo\x00\xffbar\x00\x00\xff\x05\x00\x00", + ); + test_serde(-1, b"\x13\xfe"); + test_serde(-5551212, b"\x11\xabK\x93"); + test_serde(-42f32, b"\x20\x3d\xd7\xff\xff"); + } + + #[test] + fn test_simple() { + // bool + test_serde(false, &[FALSE]); + test_serde(true, &[TRUE]); + + // int + test_serde(0i64, &[INTZERO]); + test_serde(1i64, &[0x15, 1]); + test_serde(-1i64, &[0x13, 254]); + test_serde(100i64, &[21, 100]); + + test_serde(10000i32, &[22, 39, 16]); + test_serde(-100i16, &[19, 155]); + test_serde(-10000i64, &[18, 216, 239]); + test_serde(-1000000i64, &[17, 240, 189, 191]); + + // boundary condition + test_serde(255u16, &[0x15, 255]); + test_serde(256i32, &[0x16, 1, 0]); + test_serde(-255i16, &[0x13, 0]); + test_serde(-256i64, &[0x12, 254, 255]); + + // versionstamp + test_serde( + Versionstamp::complete(*b"\xaa\xbb\xcc\xdd\xee\xff\x00\x01\x02\x03", 0), + b"\x33\xaa\xbb\xcc\xdd\xee\xff\x00\x01\x02\x03\x00\x00", + ); + test_serde( + Versionstamp::complete(*b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a", 657), + b"\x33\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x02\x91", + ); + + test_serde(0, b"\x14"); + test_serde(1, b"\x15\x01"); + test_serde(-1, b"\x13\xfe"); + test_serde(255, b"\x15\xff"); + test_serde(-255, b"\x13\x00"); + test_serde(256, b"\x16\x01\x00"); + test_serde(-256, b"\x12\xfe\xff"); + test_serde(65536, b"\x17\x01\x00\x00"); + test_serde(-65536, b"\x11\xfe\xff\xff"); + test_serde(i64::MAX, b"\x1C\x7f\xff\xff\xff\xff\xff\xff\xff"); + test_serde(i64::MAX as u64 + 1, b"\x1C\x80\x00\x00\x00\x00\x00\x00\x00"); + test_serde(u64::MAX, b"\x1C\xff\xff\xff\xff\xff\xff\xff\xff"); + test_serde( + u128::MAX, + b"\x1D\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + u64::MAX as u128 + 1, + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + u64::MAX as i128 + 1, + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + i64::MIN as i128 + 1, + b"\x0C\x80\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + i64::MIN as i128 - 1, + b"\x0C\x7f\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde(-(u64::MAX as i128), b"\x0C\x00\x00\x00\x00\x00\x00\x00\x00"); + test_serde( + -(u64::MAX as i128) - 1, + b"\x0b\xf6\xfe\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + -(u64::MAX as i128) - 2, + b"\x0b\xf6\xfe\xff\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde( + (u64::MAX as i128) * -2, + b"\x0b\xf6\xfe\x00\x00\x00\x00\x00\x00\x00\x01", + ); + test_serde( + (u64::MAX as i128) * 2, + b"\x1d\x09\x01\xff\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde(-4294967295i64, b"\x10\x00\x00\x00\x00"); + test_serde(i64::MIN + 2, b"\x0C\x80\x00\x00\x00\x00\x00\x00\x01"); + test_serde(i64::MIN + 1, b"\x0C\x80\x00\x00\x00\x00\x00\x00\x00"); + test_serde(i64::MIN, b"\x0C\x7f\xff\xff\xff\xff\xff\xff\xff"); + + test_serde(9252427359321063944i128, b"\x1c\x80g9\xa9np\x02\x08"); + assert!(matches!( + unpack::(b"\x1c\x80g9\xa9np\x02\x08").unwrap_err(), + PackError::UnsupportedIntLength + )); + + test_serde( + -9252427359321063944i128, + b"\x0c\x7f\x98\xc6V\x91\x8f\xfd\xf7", + ); + assert!(matches!( + unpack::(b"\x0c\x7f\x98\xc6V\x91\x8f\xfd\xf7").unwrap_err(), + PackError::UnsupportedIntLength + )); + + test_serde(u64::MAX as i128, b"\x1c\xff\xff\xff\xff\xff\xff\xff\xff"); + assert!(matches!( + unpack::(b"\x1c\xff\xff\xff\xff\xff\xff\xff\xff").unwrap_err(), + PackError::UnsupportedIntLength + )); + + test_serde(-(u64::MAX as i128), b"\x0c\x00\x00\x00\x00\x00\x00\x00\x00"); + assert!(matches!( + unpack::(b"\x0c\x00\x00\x00\x00\x00\x00\x00\x00").unwrap_err(), + PackError::UnsupportedIntLength + )); + + test_serde( + (i64::MAX as i128) + 1, + b"\x1c\x80\x00\x00\x00\x00\x00\x00\x00", + ); + assert!(matches!( + unpack::(b"\x1c\x80\x00\x00\x00\x00\x00\x00\x00").unwrap_err(), + PackError::UnsupportedIntLength + )); + + test_serde( + (i64::MIN as i128) - 1, + b"\x0c\x7f\xff\xff\xff\xff\xff\xff\xfe", + ); + assert!(matches!( + unpack::(b"\x0c\x7f\xff\xff\xff\xff\xff\xff\xfe").unwrap_err(), + PackError::UnsupportedIntLength + )); + } + + #[test] + fn test_typecode_band_ordering() { + // Order-preserving keys rely on packed tuples comparing byte-lexicographically + // (Rust's `Vec` ordering). This mirrors a subspace-prefix scheme where the + // prefix is a u32 discriminant, with a negative "header" band strictly before it + // and an f32 band strictly after it. The typecode band, not the value, decides: + // negatives (0x0c..=0x13) < u32 (0x14..=0x18) < f32 (0x20 ...) + let hex = |b: &[u8]| { + b.iter() + .map(|x| format!("{x:02x}")) + .collect::>() + .join(" ") + }; + + // "before" band: signed ints, min and max of the range we might use. + let neg_min = pack(&i64::MIN); // 0c 7f ff ff ff ff ff ff ff (most negative) + let neg_max = pack(&-1i64); // 13 fe (closest to u32 band) + + // middle band: the u32 subspace prefix, min and max discriminant. + let u32_min = pack(&0u32); // 14 (INTZERO) + let u32_max = pack(&u32::MAX); // 18 ff ff ff ff (4-byte positive int) + + // "after" band: f32 markers >= 1.0, min and max of the range we might use. + let f_min = pack(&1.0f32); // 20 bf 80 00 00 (closest to u32 band) + let f_max = pack(&f32::MAX); // 20 ff 7f ff ff + + println!("i64::MIN = {}", hex(&neg_min)); + println!("-1i64 = {}", hex(&neg_max)); + println!("0u32 = {}", hex(&u32_min)); + println!("u32::MAX = {}", hex(&u32_max)); + println!("1.0f32 = {}", hex(&f_min)); + println!("f32::MAX = {}", hex(&f_max)); + + // Within each band numeric order is preserved (nothing weird inside a band). + assert!(neg_min < neg_max); // 0c .. < 13 fe + assert!(u32_min < u32_max); // 14 < 18 .. + assert!(f_min < f_max); // 20 bf .. < 20 ff .. + + // Whole "before" band sorts under the u32 band; tightest pair is -1 vs 0. + assert!(neg_max < u32_min); // 13 fe < 14 + assert!(neg_min < u32_min); // 0c .. < 14 (even the most negative stays below) + + // Whole "after" band sorts over the u32 band; tightest pair is u32::MAX vs 1.0. + assert!(u32_max < f_min); // 18 ff ff ff ff < 20 bf 80 00 00 + assert!(u32_max < f_max); // 18 .. < 20 ff .. (magnitude irrelevant) + } + + #[cfg(feature = "num-bigint")] + #[test] + fn test_bigint() { + use num_bigint::{BigInt, BigUint}; + // int + test_serde(BigInt::from(0i64), &[INTZERO]); + test_serde(BigUint::from(0u64), &[INTZERO]); + test_serde(BigInt::from(1i64), &[0x15, 1]); + test_serde(BigUint::from(1u64), &[0x15, 1]); + test_serde(BigInt::from(-1i64), &[0x13, 254]); + test_serde(BigInt::from(100i64), &[0x15, 100]); + test_serde(BigUint::from(100u64), &[0x15, 100]); + + test_serde(BigInt::from(10000i32), &[0x16, 39, 16]); + test_serde(BigUint::from(10000u32), &[0x16, 39, 16]); + test_serde(BigInt::from(-100i16), &[19, 155]); + test_serde(BigInt::from(-10000i64), &[18, 216, 239]); + test_serde(BigInt::from(-1000000i64), &[17, 240, 189, 191]); + + // boundary condition + test_serde(BigInt::from(255u16), &[0x15, 255]); + test_serde(BigUint::from(255u16), &[0x15, 255]); + test_serde(BigInt::from(256i32), &[0x16, 1, 0]); + test_serde(BigInt::from(-255i16), &[0x13, 0]); + test_serde(BigInt::from(-256i64), &[0x12, 254, 255]); + + test_serde(BigInt::from(0), b"\x14"); + test_serde(BigUint::from(0u64), b"\x14"); + test_serde(BigInt::from(1), b"\x15\x01"); + test_serde(BigUint::from(1u64), b"\x15\x01"); + test_serde(BigInt::from(-1), b"\x13\xfe"); + test_serde(BigInt::from(255), b"\x15\xff"); + test_serde(BigUint::from(255u64), b"\x15\xff"); + test_serde(BigInt::from(-255), b"\x13\x00"); + test_serde(BigInt::from(256), b"\x16\x01\x00"); + test_serde(BigUint::from(256u64), b"\x16\x01\x00"); + test_serde(BigInt::from(-256), b"\x12\xfe\xff"); + test_serde(BigInt::from(65536), b"\x17\x01\x00\x00"); + test_serde(BigUint::from(65536u64), b"\x17\x01\x00\x00"); + test_serde(BigInt::from(-65536), b"\x11\xfe\xff\xff"); + test_serde( + BigInt::from(i64::MAX), + b"\x1C\x7f\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigUint::from(i64::MAX as u64), + b"\x1C\x7f\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigInt::from(i64::MAX as u64 + 1), + b"\x1C\x80\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigUint::from(i64::MAX as u64 + 1), + b"\x1C\x80\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(u64::MAX), + b"\x1C\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigUint::from(u64::MAX), + b"\x1C\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigInt::from(u128::MAX), + b"\x1D\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigUint::from(u128::MAX), + b"\x1D\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigInt::from(u64::MAX as u128 + 1), + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigUint::from(u64::MAX as u128 + 1), + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(u64::MAX as i128 + 1), + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigUint::from(u64::MAX as u128 + 1), + b"\x1D\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(i64::MIN as i128 + 1), + b"\x0C\x80\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(i64::MIN as i128 - 1), + b"\x0C\x7f\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde( + BigInt::from(-(u64::MAX as i128)), + b"\x0C\x00\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(-(u64::MAX as i128) - 1), + b"\x0b\xf6\xfe\xff\xff\xff\xff\xff\xff\xff\xff", + ); + test_serde( + BigInt::from(-(u64::MAX as i128) - 2), + b"\x0b\xf6\xfe\xff\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde( + BigInt::from((u64::MAX as i128) * -2), + b"\x0b\xf6\xfe\x00\x00\x00\x00\x00\x00\x00\x01", + ); + test_serde( + BigInt::from((u64::MAX as i128) * 2), + b"\x1d\x09\x01\xff\xff\xff\xff\xff\xff\xff\xfe", + ); + test_serde(BigInt::from(-4294967295i64), b"\x10\x00\x00\x00\x00"); + test_serde( + BigInt::from(i64::MIN + 2), + b"\x0C\x80\x00\x00\x00\x00\x00\x00\x01", + ); + test_serde( + BigInt::from(i64::MIN + 1), + b"\x0C\x80\x00\x00\x00\x00\x00\x00\x00", + ); + test_serde( + BigInt::from(i64::MIN), + b"\x0C\x7f\xff\xff\xff\xff\xff\xff\xff", + ); + + test_serde( + Element::BigInt(9252427359321063944i128.into()), + b"\x1c\x80g9\xa9np\x02\x08", + ); + test_serde( + Element::BigInt((-9252427359321063944i128).into()), + b"\x0c\x7f\x98\xc6V\x91\x8f\xfd\xf7", + ); + } + + #[cfg(feature = "uuid")] + #[test] + fn test_uuid() { + use uuid::Uuid; + + test_serde( + Element::Uuid( + Uuid::from_slice( + b"\xba\xff\xff\xff\xff\x5e\xba\x11\x00\x00\x00\x00\x5c\xa1\xab\x1e", + ) + .unwrap(), + ), + b"\x30\xba\xff\xff\xff\xff\x5e\xba\x11\x00\x00\x00\x00\x5c\xa1\xab\x1e", + ); + } + + #[test] + fn test_bindingtester() { + test_serde("NEW_TRANSACTION".to_string(), b"\x02NEW_TRANSACTION\x00"); + test_serde( + vec!["NEW_TRANSACTION".to_string()], + b"\x02NEW_TRANSACTION\x00", + ); + test_serde( + vec![ + Element::String(Cow::Borrowed("PUSH")), + Element::Bytes(Bytes::from( + b"\x01tester_output\x00\x01results\x00\x14".as_ref(), + )), + ], + b"\x02PUSH\x00\x01\x01tester_output\x00\xff\x01results\x00\xff\x14\x00", + ); + test_serde( + vec![Element::String(Cow::Borrowed("PUSH")), Element::Nil], + b"\x02PUSH\x00\x00", + ); + test_serde( + vec![ + Element::String(Cow::Borrowed("PUSH")), + Element::Tuple(vec![ + Element::Nil, + Element::Float(3299069000000.0), + Element::Float(-0.000000000000000000000000000000000000011883096), + ]), + ], + b"\x02PUSH\x00\x05\x00\xff \xd4@\x07\xf5 \x7f~\x9a\xc2\x00", + ); + test_serde( + vec![ + Element::String(Cow::Borrowed("PUSH")), + Element::Int(-133525682914243904), + ], + b"\x02PUSH\x00\x0c\xfe%\x9f\x19M\x81J\xbf", + ); + + test_serde( + Element::Tuple(vec![Element::Nil, Element::Nil]), + b"\x00\x00", + ); + test_serde( + Element::Tuple(vec![ + Element::String(Cow::Borrowed("PUSH")), + Element::Tuple(vec![Element::Double(-8.251343508909708e-15)]), + ]), + b"\x02PUSH\x00\x05!B\xfdkl\x9f\xcc\x8eK\x00", + ); + } + + #[test] + fn test_element() { + test_serde(Element::Bool(true), &[TRUE]); + test_serde(Element::Bool(false), &[FALSE]); + test_serde(Element::Int(-1), &[0x13, 254]); + test_serde( + Element::Versionstamp(Versionstamp::complete( + *b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a", + 657, + )), + b"\x33\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x02\x91", + ); + test_serde( + (Element::Versionstamp(Versionstamp::complete( + *b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a", + 657, + )),), + b"\x33\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x02\x91", + ); + test_serde( + (Element::Versionstamp(Versionstamp::complete( + *b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a", + 657, + )),), + b"\x33\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x02\x91", + ); + test_serde( + vec![Element::Bool(true), Element::Bool(false)], + &[TRUE, FALSE], + ); + test_serde( + vec![Element::Tuple(vec![ + Element::Bool(true), + Element::Bool(false), + ])], + &[NESTED, TRUE, FALSE, NIL], + ); + test_serde(Vec::::new(), &[]); + test_serde(Element::Tuple(vec![]), &[]); + } + + #[test] + fn test_verstionstamp() { + assert_eq!( + Bytes::from(pack(&("foo", Versionstamp::incomplete(0)))), + Bytes::from(&b"\x02foo\x00\x33\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00"[..]) + ); + assert_eq!( + Bytes::from(pack_with_versionstamp(&( + "foo", + Versionstamp::incomplete(0) + ))), + Bytes::from( + &b"\x02foo\x00\x33\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x06\x00\x00\x00" + [..] + ) + ); + + let mut prefixed = b"prefix".to_vec(); + pack_into_with_versionstamp(&Versionstamp::incomplete(0), &mut prefixed); + let mut expected = b"prefix".to_vec(); + expected.extend_from_slice(&pack(&Versionstamp::incomplete(0))); + expected.extend_from_slice(&7_u32.to_le_bytes()); + assert_eq!(prefixed, expected); + } + + #[test] + fn versionstamped_packing_requires_one_incomplete_stamp() { + let invalid = [ + vec![Element::String("foo".into())], + vec![Element::Versionstamp(Versionstamp::complete([1; 10], 0))], + vec![ + Element::Versionstamp(Versionstamp::incomplete(0)), + Element::Versionstamp(Versionstamp::incomplete(1)), + ], + ]; + for tuple in invalid { + assert!(std::panic::catch_unwind(|| pack_with_versionstamp(&tuple)).is_err()); + assert!( + std::panic::catch_unwind(|| { + let mut output = b"prefix".to_vec(); + pack_into_with_versionstamp(&tuple, &mut output); + }) + .is_err() + ); + } + } +} diff --git a/bindings/rust/foundationdb-tuple/src/pack.rs b/bindings/rust/foundationdb-tuple/src/pack.rs new file mode 100644 index 00000000000..c693446f745 --- /dev/null +++ b/bindings/rust/foundationdb-tuple/src/pack.rs @@ -0,0 +1,1114 @@ +use super::*; +use memchr::memchr_iter; +use std::convert::TryFrom; +use std::io; +use std::mem; + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum VersionstampOffset { + None { size: u32 }, + OneIncomplete { offset: u32 }, + MultipleIncomplete, +} +impl std::ops::AddAssign for VersionstampOffset { + fn add_assign(&mut self, r: u32) { + if let VersionstampOffset::None { size } = self { + *size += r; + } + } +} +impl std::ops::AddAssign for VersionstampOffset { + fn add_assign(&mut self, rhs: Self) { + match (&mut *self, rhs) { + (VersionstampOffset::None { size }, VersionstampOffset::None { size: r }) => { + *size += r; + } + (VersionstampOffset::None { size }, VersionstampOffset::OneIncomplete { offset }) => { + *self = VersionstampOffset::OneIncomplete { + offset: *size + offset, + }; + } + ( + VersionstampOffset::OneIncomplete { .. }, + VersionstampOffset::OneIncomplete { .. }, + ) + | (VersionstampOffset::None { .. }, VersionstampOffset::MultipleIncomplete) + | (VersionstampOffset::OneIncomplete { .. }, VersionstampOffset::MultipleIncomplete) => { + *self = VersionstampOffset::MultipleIncomplete; + } + _ => {} + } + } +} + +const PACK_ERR_MSG: &str = "pack io error on Vec, data size didn't fit in `u32`?"; + +/// A type that can be packed +pub trait TuplePack { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result; + + fn pack_root(&self, w: &mut W) -> io::Result { + self.pack(w, TupleDepth::new()) + } + + /// Pack value and returns the packed buffer + /// + /// # Panics + /// + /// Panics if the encoded data size doesn't fit in `u32`. + fn pack_to_vec(&self) -> Vec { + let mut vec = Vec::new(); + self.pack_into_vec(&mut vec); + vec + } + + /// Pack value and returns the packed buffer + /// + /// An incomplete versionstamp appends the four-byte offset used by + /// FoundationDB API 520 and later. Earlier versionstamped mutation formats + /// are not supported. + /// + /// # Panics + /// + /// Panics unless there is exactly one incomplete versionstamp, or if the + /// encoded data size doesn't fit in `u32`. + fn pack_to_vec_with_versionstamp(&self) -> Vec { + let mut vec = Vec::new(); + let offset = self.pack_into_vec_with_versionstamp(&mut vec); + assert!( + matches!(offset, VersionstampOffset::OneIncomplete { .. }), + "pack_to_vec_with_versionstamp requires exactly one incomplete versionstamp" + ); + vec + } + + /// Pack value into the given buffer + /// + /// # Panics + /// + /// Panics if the encoded data size doesn't fit in `u32`. + fn pack_into_vec(&self, output: &mut Vec) -> VersionstampOffset { + self.pack_root(output).expect(PACK_ERR_MSG) + } + + /// Pack value into the given buffer, appending a four-byte offset for an + /// incomplete versionstamp. This encoding requires FoundationDB API 520 or + /// later for versionstamped mutations. + /// + /// # Panics + /// + /// Panics if the encoded data size doesn't fit in `u32`. + fn pack_into_vec_with_versionstamp(&self, output: &mut Vec) -> VersionstampOffset { + let mut offset = VersionstampOffset::None { + size: u32::try_from(output.len()).expect(PACK_ERR_MSG), + }; + offset += self.pack_root(output).expect(PACK_ERR_MSG); + if let VersionstampOffset::OneIncomplete { offset } = offset { + output.extend_from_slice(&offset.to_le_bytes()); + } + offset + } +} + +/// A type that can be unpacked +pub trait TupleUnpack<'de>: Sized { + fn unpack(input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)>; + + fn unpack_root(input: &'de [u8]) -> PackResult { + let (input, this) = Self::unpack(input, TupleDepth::new())?; + if !input.is_empty() { + return Err(PackError::TrailingBytes); + } + Ok(this) + } +} + +impl TuplePack for &T +where + T: TuplePack, +{ + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + (*self).pack(w, tuple_depth) + } +} + +#[inline] +fn parse_bytes(input: &[u8], num: usize) -> PackResult<(&[u8], &[u8])> { + if input.len() < num { + Err(PackError::MissingBytes) + } else { + Ok((&input[num..], &input[..num])) + } +} + +#[inline] +fn parse_byte(input: &[u8]) -> PackResult<(&[u8], u8)> { + if input.is_empty() { + Err(PackError::MissingBytes) + } else { + Ok((&input[1..], input[0])) + } +} + +fn parse_code(input: &[u8], expected: u8) -> PackResult<&[u8]> { + let (input, found) = parse_byte(input)?; + if found == expected { + Ok(input) + } else { + Err(PackError::BadCode { + found, + expected: Some(expected), + }) + } +} + +fn write_bytes(w: &mut W, v: &[u8]) -> io::Result { + let mut size = + u32::try_from(v.len()).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + let mut pos = 0; + for idx in memchr_iter(NIL, v) { + let next_idx = idx + 1; + size += 1; + w.write_all(&v[pos..next_idx])?; + w.write_all(&[ESCAPE])?; + pos = next_idx; + } + w.write_all(&v[pos..])?; + w.write_all(&[NIL])?; + size += 2; + Ok(VersionstampOffset::None { size }) +} + +fn parse_slice(input: &[u8]) -> PackResult<(&[u8], Cow<'_, [u8]>)> { + let mut bytes = Vec::new(); + let mut pos = 0; + for idx in memchr_iter(NIL, input) { + let next_idx = idx + 1; + if input.get(next_idx) == Some(&ESCAPE) { + bytes.extend_from_slice(&input[pos..next_idx]); + pos = next_idx + 1; + } else { + let slice = &input[pos..idx]; + return Ok(( + &input[next_idx..], + (if pos == 0 { + Cow::Borrowed(slice) + } else { + bytes.extend_from_slice(slice); + Cow::Owned(bytes) + }), + )); + } + } + Err(PackError::MissingBytes) +} + +fn parse_string(input: &[u8]) -> PackResult<(&[u8], Cow<'_, str>)> { + let (input, slice) = parse_slice(input)?; + Ok(( + input, + match slice { + Cow::Borrowed(slice) => { + Cow::Borrowed(std::str::from_utf8(slice).map_err(|_| PackError::BadStringFormat)?) + } + Cow::Owned(vec) => { + Cow::Owned(String::from_utf8(vec).map_err(|_| PackError::BadStringFormat)?) + } + }, + )) +} + +impl TuplePack for () { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + if tuple_depth.depth() > 0 { + w.write_all(&[NESTED, NIL])?; + Ok(VersionstampOffset::None { size: 2 }) + } else { + Ok(VersionstampOffset::None { size: 0 }) + } + } +} + +impl TupleUnpack<'_> for () { + fn unpack(mut input: &[u8], tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + if tuple_depth.depth() > 0 { + input = parse_code(input, NESTED)?; + input = parse_code(input, NIL)?; + } + Ok((input, ())) + } +} + +macro_rules! tuple_impls { + ($(($($n:tt $name:ident $v:ident)+))+) => { + $( + impl<$($name),+> TuplePack for ($($name,)+) + where + $($name: TuplePack,)+ + { + fn pack(&self, w: &mut W, tuple_depth: TupleDepth) -> io::Result { + let mut offset = VersionstampOffset::None { size: 0 }; + if tuple_depth.depth() > 0 { + w.write_all(&[NESTED])?; + offset += 1; + } + + $( + offset += self.$n.pack(w, tuple_depth.increment())?; + )* + + if tuple_depth.depth() > 0 { + w.write_all(&[NIL])?; + offset += 1; + } + Ok(offset) + } + } + + impl<'de, $($name),+> TupleUnpack<'de> for ($($name,)+) + where + $($name: TupleUnpack<'de>,)+ + { + fn unpack(input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + let input = if tuple_depth.depth() > 0 { parse_code(input, NESTED)? } else { input }; + + $( + let (input, $v) = $name::unpack(input, tuple_depth.increment())?; + )* + + let input = if tuple_depth.depth() > 0 { parse_code(input, NIL)? } else { input }; + + let tuple = ( $($v,)* ); + Ok((input, tuple)) + } + } + )+ + } +} + +tuple_impls! { + (0 T0 t0) + (0 T0 t0 1 T1 t1) + (0 T0 t0 1 T1 t1 2 T2 t2) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6 7 T7 t7) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6 7 T7 t7 8 T8 t8) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6 7 T7 t7 8 T8 t8 9 T9 t9) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6 7 T7 t7 8 T8 t8 9 T9 t9 10 T10 t10) + (0 T0 t0 1 T1 t1 2 T2 t2 3 T3 t3 4 T4 t4 5 T5 t5 6 T6 t6 7 T7 t7 8 T8 t8 9 T9 t9 10 T10 t10 11 T11 t11) +} + +const MAX_SZ: usize = 8; + +macro_rules! sign_bit { + ($type:ident) => { + (1 << (mem::size_of::<$type>() * 8 - 1)) + }; +} + +macro_rules! unpack_ux { + ($ux: ident, $input: expr_2021, $n: expr_2021) => {{ + let (input, bytes) = parse_bytes($input, $n)?; + let mut arr = [0u8; ::std::mem::size_of::<$ux>()]; + (&mut arr[(::std::mem::size_of::<$ux>() - $n)..]).copy_from_slice(bytes); + (input, $ux::from_be_bytes(arr)) + }}; +} + +macro_rules! unpack_px { + ($ix: ident, $ux: ident, $input: expr_2021, $n: expr_2021) => {{ + let (input, bytes) = parse_bytes($input, $n)?; + let mut arr = [0u8; ::std::mem::size_of::<$ux>()]; + (&mut arr[(::std::mem::size_of::<$ux>() - $n)..]).copy_from_slice(bytes); + let x = $ix::from_be_bytes(arr); + if x < 0 { + Err(PackError::UnsupportedIntLength) + } else { + Ok((input, x)) + } + }}; +} +macro_rules! unpack_nx { + ($ix: ident, $ux: ident, $input: expr_2021, $n: expr_2021) => {{ + let (input, bytes) = parse_bytes($input, $n)?; + let mut arr = [0xffu8; ::std::mem::size_of::<$ix>()]; + (&mut arr[(::std::mem::size_of::<$ix>() - $n)..]).copy_from_slice(bytes); + let x = $ix::from_be_bytes(arr).wrapping_add(1); + if x > 0 { + Err(PackError::UnsupportedIntLength) + } else { + Ok((input, x)) + } + }}; +} + +macro_rules! impl_ux { + ($ux: ident) => { + impl_ux!($ux, mem::size_of::<$ux>()); + }; + ($ux: ident, $max_sz:expr_2021) => { + impl TuplePack for $ux { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + const SZ: usize = mem::size_of::<$ux>(); + let u = *self; + let n = SZ - (u.leading_zeros() as usize) / 8; + let mut offset = VersionstampOffset::None { size: n as u32 + 1 }; + if SZ <= MAX_SZ || n <= MAX_SZ { + w.write_all(&[INTZERO + n as u8])?; + } else { + w.write_all(&[POSINTEND, n as u8])?; + offset += 1; + }; + w.write_all(&u.to_be_bytes()[SZ - n..])?; + + Ok(offset) + } + } + + impl<'de> TupleUnpack<'de> for $ux { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + const SZ: usize = mem::size_of::<$ux>(); + let (input, found) = parse_byte(input)?; + if INTZERO <= found && found <= INTZERO + $max_sz as u8 { + let n = (found - INTZERO) as usize; + Ok(unpack_ux!($ux, input, n)) + } else if found == POSINTEND { + let (input, raw_length) = parse_byte(input)?; + let n: usize = usize::from(raw_length); + if n > SZ { + return Err(PackError::UnsupportedIntLength); + } + Ok(unpack_ux!($ux, input, n)) + } else { + Err(PackError::BadCode { + found, + expected: None, + }) + } + } + } + }; +} + +macro_rules! impl_ix { + ($ix: ident, $ux: ident) => { + impl_ix!($ix, $ux, mem::size_of::<$ix>()); + }; + ($ix: ident, $ux: ident, $max_sz:expr_2021) => { + impl TuplePack for $ix { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + const SZ: usize = mem::size_of::<$ix>(); + let i = *self; + let u = self.wrapping_abs() as $ux; + let n = SZ - (u.leading_zeros() as usize) / 8; + let mut offset = VersionstampOffset::None { size: n as u32 + 1 }; + let arr = if i >= 0 { + if SZ <= MAX_SZ || n <= MAX_SZ { + w.write_all(&[INTZERO + n as u8])?; + } else { + w.write_all(&[POSINTEND, n as u8])?; + offset += 1; + } + (u.to_be_bytes()) + } else { + if SZ <= MAX_SZ || n <= MAX_SZ { + w.write_all(&[INTZERO - n as u8])?; + } else { + w.write_all(&[NEGINTSTART, n as u8 ^ 0xff])?; + offset += 1; + } + (i.wrapping_sub(1).to_be_bytes()) + }; + w.write_all(&arr[SZ - n..])?; + + Ok(offset) + } + } + + impl<'de> TupleUnpack<'de> for $ix { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + const SZ: usize = mem::size_of::<$ix>(); + let (input, found) = parse_byte(input)?; + if INTZERO <= found && found <= INTZERO + $max_sz as u8 { + let n = (found - INTZERO) as usize; + unpack_px!($ix, $ux, input, n) + } else if INTZERO - $max_sz as u8 <= found && found < INTZERO { + let n = (INTZERO - found) as usize; + unpack_nx!($ix, $ux, input, n) + } else if found == NEGINTSTART { + let (input, raw_length) = parse_byte(input)?; + let n = usize::from(raw_length ^ 0xff); + if n > SZ { + return Err(PackError::UnsupportedIntLength); + } + unpack_nx!($ix, $ux, input, n) + } else if found == POSINTEND { + let (input, raw_length) = parse_byte(input)?; + let n: usize = usize::from(raw_length); + if n > SZ { + return Err(PackError::UnsupportedIntLength); + } + unpack_px!($ix, $ux, input, n) + } else { + Err(PackError::BadCode { + found, + expected: None, + }) + } + } + } + }; +} + +macro_rules! impl_fx { + ( $fx: ident, $fx_to_ux_be_bytes: ident, $ux_width: tt, $parse_ux: ident, $ux: ident, $code: ident) => { + #[inline] + pub(super) fn $fx_to_ux_be_bytes(f: $fx) -> [u8; $ux_width] { + let u = if f.is_sign_negative() { + f.to_bits() ^ $ux::MAX + } else { + f.to_bits() ^ sign_bit!($ux) + }; + u.to_be_bytes() + } + impl TuplePack for $fx { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + let bytes = $fx_to_ux_be_bytes(*self); + w.write_all(&[$code])?; + w.write_all(&bytes)?; + Ok(VersionstampOffset::None { + size: std::mem::size_of::<$fx>() as u32 + 1, + }) + } + } + + fn $parse_ux(input: &[u8]) -> PackResult<(&[u8], $ux)> { + let (input, bytes) = parse_bytes(input, mem::size_of::<$ux>())?; + let mut arr = [0u8; mem::size_of::<$ux>()]; + arr.copy_from_slice(bytes); + Ok((input, $ux::from_be_bytes(arr))) + } + impl<'de> TupleUnpack<'de> for $fx { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let input = parse_code(input, $code)?; + let (input, u) = $parse_ux(input)?; + Ok(( + input, + $fx::from_bits(if (u & sign_bit!($ux)) == 0 { + u ^ $ux::MAX + } else { + u ^ sign_bit!($ux) + }), + )) + } + } + }; +} + +//impl_ux!(u8); +impl_ux!(u16); +impl_ux!(u32); +impl_ux!(u64); +impl_ux!(u128, MAX_SZ); +impl_ux!(usize); + +//impl_ix!(i8, u8); +impl_ix!(i16, u16); +impl_ix!(i32, u32); +impl_ix!(i64, u64); +impl_ix!(i128, u128, MAX_SZ); +impl_ix!(isize, usize); + +impl_fx!(f32, f32_to_u32_be_bytes, 4, parse_u32, u32, FLOAT); +impl_fx!(f64, f64_to_u64_be_bytes, 8, parse_u64, u64, DOUBLE); + +#[cfg(feature = "num-bigint")] +mod bigint { + use super::*; + use num_bigint::{BigInt, BigUint, Sign}; + use std::convert::TryFrom; + + fn invert(bytes: &mut [u8]) { + // The ones' complement of a binary number is defined as the value + // obtained by inverting all the bits in the binary representation + // of the number (swapping 0s for 1s and vice versa). + for byte in bytes.iter_mut() { + *byte = !*byte; + } + } + + fn inverted(bytes: &[u8]) -> Vec { + // The ones' complement of a binary number is defined as the value + // obtained by inverting all the bits in the binary representation + // of the number (swapping 0s for 1s and vice versa). + bytes.iter().map(|byte| !*byte).collect() + } + + fn bigint_n(n: usize) -> io::Result { + u8::try_from(n).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "BigUint requires more than 255 bytes to be represented", + ) + }) + } + + impl TuplePack for BigInt { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + if self.sign() == Sign::NoSign { + w.write_all(&[INTZERO])?; + return Ok(VersionstampOffset::None { size: 1 }); + } + let (sign, mut bytes) = self.to_bytes_be(); + let n = bytes.len(); + let mut offset = VersionstampOffset::None { size: n as u32 + 1 }; + match sign { + Sign::Minus => { + if n <= MAX_SZ { + w.write_all(&[INTZERO - n as u8])?; + } else { + w.write_all(&[NEGINTSTART, bigint_n(n)? ^ 0xff])?; + offset += 1; + } + invert(&mut bytes); + w.write_all(&bytes)?; + } + Sign::NoSign => unreachable!(), + Sign::Plus => { + if n <= MAX_SZ { + w.write_all(&[INTZERO + n as u8])?; + } else { + w.write_all(&[POSINTEND, bigint_n(n)?])?; + offset += 1; + } + w.write_all(&bytes)?; + } + } + + Ok(offset) + } + } + + impl TupleUnpack<'_> for BigInt { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let (input, found) = parse_byte(input)?; + if INTZERO <= found && found <= INTZERO + MAX_SZ as u8 { + let n = (found - INTZERO) as usize; + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(Sign::Plus, bytes))) + } else if INTZERO - MAX_SZ as u8 <= found && found < INTZERO { + let n = (INTZERO - found) as usize; + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(Sign::Minus, &inverted(bytes)))) + } else if found == NEGINTSTART { + let (input, raw_length) = parse_byte(input)?; + let n = usize::from(raw_length ^ 0xff); + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(Sign::Minus, &inverted(bytes)))) + } else if found == POSINTEND { + let (input, raw_length) = parse_byte(input)?; + let n: usize = usize::from(raw_length); + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(Sign::Plus, bytes))) + } else { + Err(PackError::BadCode { + found, + expected: None, + }) + } + } + } + + impl TuplePack for BigUint { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + let n = self.bits(); + if n == 0 { + w.write_all(&[INTZERO])?; + return Ok(VersionstampOffset::None { size: 1 }); + } + let bytes = self.to_bytes_be(); + let n = bytes.len(); + let mut offset = VersionstampOffset::None { size: n as u32 + 1 }; + if n <= MAX_SZ { + w.write_all(&[INTZERO + n as u8])?; + } else { + w.write_all(&[POSINTEND, bigint_n(n)?])?; + offset += 1; + } + w.write_all(&bytes)?; + + Ok(offset) + } + } + + impl TupleUnpack<'_> for BigUint { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let (input, found) = parse_byte(input)?; + if INTZERO <= found && found <= INTZERO + MAX_SZ as u8 { + let n = (found - INTZERO) as usize; + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(bytes))) + } else if found == POSINTEND { + let (input, raw_length) = parse_byte(input)?; + let n: usize = usize::from(raw_length); + let (input, bytes) = parse_bytes(input, n)?; + Ok((input, Self::from_bytes_be(bytes))) + } else { + Err(PackError::BadCode { + found, + expected: None, + }) + } + } + } +} + +impl TuplePack for bool { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + w.write_all(&[if *self { TRUE } else { FALSE }])?; + Ok(VersionstampOffset::None { size: 1 }) + } +} + +impl TupleUnpack<'_> for bool { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let (input, v) = parse_byte(input)?; + match v { + FALSE => Ok((input, false)), + TRUE => Ok((input, true)), + _ => Err(PackError::Message( + format!("{v} is not a valid bool value").into_boxed_str(), + )), + } + } +} + +impl TuplePack for &[T] +where + T: TuplePack, +{ + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + let mut offset = VersionstampOffset::None { size: 0 }; + + if tuple_depth.depth() > 0 { + w.write_all(&[NESTED])?; + offset += 1; + } + + for v in self.iter() { + offset += v.pack(w, tuple_depth.increment())?; + } + + if tuple_depth.depth() > 0 { + w.write_all(&[NIL])?; + offset += 1; + } + Ok(offset) + } +} + +impl TuplePack for Vec +where + T: TuplePack, +{ + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + self.as_slice().pack(w, tuple_depth) + } +} + +fn is_end_of_tuple(input: &[u8], nested: bool) -> bool { + match input.first() { + None => true, + _ if !nested => false, + Some(&NIL) => Some(&ESCAPE) != input.get(1), + _ => false, + } +} + +impl<'de, T> TupleUnpack<'de> for Vec +where + T: TupleUnpack<'de>, +{ + fn unpack(mut input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + let nested = tuple_depth.depth() > 0; + if nested { + input = parse_code(input, NESTED)?; + } + + let mut vec = Vec::new(); + + while !is_end_of_tuple(input, nested) { + let (rem, v) = T::unpack(input, tuple_depth.increment())?; + input = rem; + vec.push(v); + } + + if nested { + input = parse_code(input, NIL)?; + } + + Ok((input, vec)) + } +} + +impl TuplePack for Bytes<'_> { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + w.write_all(&[BYTES])?; + write_bytes(w, self.as_ref()) + } +} + +impl<'de> TupleUnpack<'de> for Bytes<'de> { + fn unpack(input: &'de [u8], _tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + let input = parse_code(input, BYTES)?; + let (input, v) = parse_slice(input)?; + Ok((input, Bytes(v))) + } +} + +impl TuplePack for &[u8] { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + Bytes::from(*self).pack(w, tuple_depth) + } +} + +impl TuplePack for Vec { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + Bytes::from(self.as_slice()).pack(w, tuple_depth) + } +} + +impl<'de> TupleUnpack<'de> for Vec { + fn unpack(input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + let (input, bytes) = Bytes::unpack(input, tuple_depth)?; + Ok((input, bytes.into_owned())) + } +} + +impl TuplePack for &str { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + w.write_all(&[STRING])?; + write_bytes(w, self.as_bytes()) + } +} + +impl TuplePack for String { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + self.as_str().pack(w, tuple_depth) + } +} + +impl TuplePack for Cow<'_, str> { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + self.as_ref().pack(w, tuple_depth) + } +} + +impl<'de> TupleUnpack<'de> for Cow<'de, str> { + fn unpack(input: &'de [u8], _tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + let input = parse_code(input, STRING)?; + let (input, v) = parse_string(input)?; + Ok((input, v)) + } +} + +impl TupleUnpack<'_> for String { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let input = parse_code(input, STRING)?; + let (input, v) = parse_string(input)?; + Ok((input, v.into_owned())) + } +} + +impl TuplePack for Option +where + T: TuplePack, +{ + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + match self { + None => { + let size = if tuple_depth.depth() > 1 { + // Empty value in nested tuple is encoded with [NIL, ESCAPE] to + // disambiguate itself with end-of-tuple marker. + w.write_all(&[NIL, ESCAPE])?; + 2 + } else { + w.write_all(&[NIL])?; + 1 + }; + Ok(VersionstampOffset::None { size }) + } + Some(v) => v.pack(w, tuple_depth), + } + } +} + +impl<'de, T> TupleUnpack<'de> for Option +where + T: TupleUnpack<'de>, +{ + fn unpack(mut input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + if let Some(&NIL) = input.first() { + input = parse_code(input, NIL)?; + if tuple_depth.depth() > 1 { + input = parse_code(input, ESCAPE)?; + } + Ok((input, None)) + } else { + let (input, v) = T::unpack(input, tuple_depth)?; + Ok((input, Some(v))) + } + } +} + +impl TuplePack for Element<'_> { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> io::Result { + match self { + Element::Nil => Option::<()>::None.pack(w, tuple_depth), + Element::Bool(b) => b.pack(w, tuple_depth), + Element::Int(i) => i.pack(w, tuple_depth), + Element::Float(f) => f.pack(w, tuple_depth), + Element::Double(f) => f.pack(w, tuple_depth), + Element::String(c) => c.pack(w, tuple_depth), + Element::Bytes(b) => b.pack(w, tuple_depth), + Element::Versionstamp(b) => b.pack(w, tuple_depth), + Element::Tuple(v) => v.pack(w, tuple_depth), + #[cfg(feature = "uuid")] + Element::Uuid(v) => v.pack(w, tuple_depth), + #[cfg(feature = "num-bigint")] + Element::BigInt(v) => v.pack(w, tuple_depth), + } + } +} + +impl<'de> TupleUnpack<'de> for Element<'de> { + fn unpack(input: &'de [u8], tuple_depth: TupleDepth) -> PackResult<(&'de [u8], Self)> { + const INTMIN: u8 = INTZERO - 8; + const INTMAX: u8 = INTZERO + 8; + + let first = match input.first() { + None if tuple_depth.depth() == 0 => return Ok((input, Element::Tuple(Vec::new()))), + None => return Err(PackError::MissingBytes), + Some(byte) => byte, + }; + + let (mut input, mut v) = match *first { + NIL => { + let (input, _) = Option::<()>::unpack(input, tuple_depth)?; + (input, Element::Nil) + } + BYTES => { + let (input, v) = Bytes::unpack(input, tuple_depth)?; + (input, Element::Bytes(v)) + } + STRING => { + let (input, v) = Cow::<'de, str>::unpack(input, tuple_depth)?; + (input, Element::String(v)) + } + NESTED => { + let (input, v) = Vec::::unpack(input, tuple_depth)?; + (input, Element::Tuple(v)) + } + INTMIN..=INTMAX => match i64::unpack(input, tuple_depth) { + Ok((input, v)) => (input, Element::Int(v)), + #[cfg(feature = "num-bigint")] + Err(PackError::UnsupportedIntLength) => { + let (input, v) = num_bigint::BigInt::unpack(input, tuple_depth)?; + (input, Element::BigInt(v)) + } + Err(err) => return Err(err), + }, + #[cfg(feature = "num-bigint")] + NEGINTSTART => { + let (input, v) = num_bigint::BigInt::unpack(input, tuple_depth)?; + (input, Element::BigInt(v)) + } + #[cfg(feature = "num-bigint")] + POSINTEND => { + let (input, v) = num_bigint::BigInt::unpack(input, tuple_depth)?; + (input, Element::BigInt(v)) + } + #[cfg(not(feature = "num-bigint"))] + NEGINTSTART => { + let (input, v) = i64::unpack(input, tuple_depth)?; + (input, Element::Int(v)) + } + #[cfg(not(feature = "num-bigint"))] + POSINTEND => { + let (input, v) = i64::unpack(input, tuple_depth)?; + (input, Element::Int(v)) + } + FLOAT => { + let (input, v) = f32::unpack(input, tuple_depth)?; + (input, Element::Float(v)) + } + DOUBLE => { + let (input, v) = f64::unpack(input, tuple_depth)?; + (input, Element::Double(v)) + } + FALSE | TRUE => { + let (input, v) = bool::unpack(input, tuple_depth)?; + (input, Element::Bool(v)) + } + VERSIONSTAMP => { + let (input, v) = Versionstamp::unpack(input, tuple_depth)?; + (input, Element::Versionstamp(v)) + } + #[cfg(feature = "uuid")] + UUID => { + let (input, v) = uuid::Uuid::unpack(input, tuple_depth)?; + (input, Element::Uuid(v)) + } + found => { + return Err(PackError::BadCode { + found, + expected: None, + }); + } + }; + + if tuple_depth.depth() == 0 && !input.is_empty() { + let mut tuple = vec![v]; + while !input.is_empty() { + let (rem, v) = Self::unpack(input, tuple_depth.increment())?; + tuple.push(v); + input = rem; + } + v = Element::Tuple(tuple); + } + + Ok((input, v)) + } +} + +impl TuplePack for Versionstamp { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + w.write_all(&[VERSIONSTAMP])?; + w.write_all(self.as_bytes())?; + if self.is_complete() { + Ok(VersionstampOffset::None { size: 1 + 12 }) + } else { + Ok(VersionstampOffset::OneIncomplete { offset: 1 }) + } + } +} + +impl TupleUnpack<'_> for Versionstamp { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let input = parse_code(input, VERSIONSTAMP)?; + let (input, slice) = parse_bytes(input, 12)?; + let mut bytes = [0xff; 12]; + bytes.copy_from_slice(slice); + Ok((input, Versionstamp::from(bytes))) + } +} + +#[cfg(feature = "uuid")] +mod pack_uuid { + use super::*; + use uuid::Uuid; + + impl TuplePack for Uuid { + fn pack( + &self, + w: &mut W, + _tuple_depth: TupleDepth, + ) -> io::Result { + w.write_all(&[UUID])?; + w.write_all(self.as_bytes())?; + Ok(VersionstampOffset::None { size: 1 + 16 }) + } + } + + impl TupleUnpack<'_> for Uuid { + fn unpack(input: &[u8], _tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let input = parse_code(input, UUID)?; + let (input, slice) = parse_bytes(input, 16)?; + let uuid = Self::from_slice(slice).map_err(|_| PackError::BadUuid)?; + Ok((input, uuid)) + } + } +} diff --git a/bindings/rust/foundationdb-tuple/src/subspace.rs b/bindings/rust/foundationdb-tuple/src/subspace.rs new file mode 100644 index 00000000000..af812a14c2f --- /dev/null +++ b/bindings/rust/foundationdb-tuple/src/subspace.rs @@ -0,0 +1,337 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::*; +use std::fmt::Formatter; +use std::hash::Hash; + +/// Represents a well-defined region of keyspace in a FoundationDB database +/// +/// It provides a convenient way to use FoundationDB tuples to define namespaces for +/// different categories of data. The namespace is specified by a prefix tuple which is prepended +/// to all tuples packed by the subspace. When unpacking a key with the subspace, the prefix tuple +/// will be removed from the result. +/// +/// As a best practice, API clients should use at least one subspace for application data. For +/// general guidance on subspace usage, see the Subspaces section of the [Developer Guide]. +/// +/// [Developer Guide]: https://apple.github.io/foundationdb/developer-guide.html#subspaces +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Subspace { + prefix: Vec, + versionstamp_offset: VersionstampOffset, +} + +impl Display for Subspace { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for &b in &self.prefix { + if (32..127).contains(&b) && b != b'\\' { + write!(f, "{}", b as char)?; + } else if b == b'\\' { + write!(f, "\\\\")?; + } else { + write!(f, "\\x{b:02x}")?; + } + } + Ok(()) + } +} + +impl From for Subspace { + fn from(e: E) -> Self { + let mut prefix = vec![]; + let versionstamp_offset = pack_into(&e, &mut prefix); + Self { + prefix, + versionstamp_offset, + } + } +} + +impl Subspace { + /// `all` returns the Subspace corresponding to all keys in a FoundationDB database. + pub fn all() -> Self { + Self { + prefix: Vec::new(), + versionstamp_offset: VersionstampOffset::None { size: 0 }, + } + } + + /// Returns a new Subspace from the provided bytes. + pub fn from_bytes(prefix: impl Into>) -> Self { + let prefix = prefix.into(); + Self { + versionstamp_offset: VersionstampOffset::None { + size: u32::try_from(prefix.len()).expect("data doesn't fit u32"), + }, + prefix, + } + } + + /// Convert into prefix key bytes + pub fn into_bytes(self) -> Vec { + self.prefix + } + + /// Returns a new Subspace whose prefix extends this Subspace with a given tuple encodable. + pub fn subspace(&self, t: &T) -> Self { + let mut prefix = self.prefix.clone(); + let mut versionstamp_offset = self.versionstamp_offset; + versionstamp_offset += pack_into(t, &mut prefix); + Self { + prefix, + versionstamp_offset, + } + } + + /// `bytes` returns the literal bytes of the prefix of this Subspace. + pub fn bytes(&self) -> &[u8] { + self.prefix.as_slice() + } + + /// Returns the key encoding the specified Tuple with the prefix of this Subspace + /// prepended. + pub fn pack(&self, t: &T) -> Vec { + let mut out = self.prefix.clone(); + pack_into(t, &mut out); + out + } + + /// Returns the key encoding the specified Tuple with the prefix of this Subspace + /// prepended, with a versionstamp. + /// + /// An incomplete versionstamp appends a four-byte offset, suitable for + /// versionstamped mutations with FoundationDB API 520 or later. + /// + /// # Panics + /// + /// Panics unless the subspace and tuple together contain exactly one + /// incomplete versionstamp. + pub fn pack_with_versionstamp(&self, t: &T) -> Vec { + let mut output = self.prefix.clone(); + let mut versionstamp_offset = self.versionstamp_offset; + versionstamp_offset += pack_into(t, &mut output); + match versionstamp_offset { + VersionstampOffset::OneIncomplete { offset } => { + output.extend_from_slice(&offset.to_le_bytes()); + } + VersionstampOffset::None { .. } | VersionstampOffset::MultipleIncomplete => { + panic!( + "Subspace::pack_with_versionstamp requires exactly one incomplete versionstamp" + ); + } + } + output + } + + /// `unpack` returns the Tuple encoded by the given key with the prefix of this Subspace + /// removed. `unpack` will return an error if the key is not in this Subspace or does not + /// encode a well-formed Tuple. + pub fn unpack<'de, T: TupleUnpack<'de>>(&self, key: &'de [u8]) -> PackResult { + if !self.is_start_of(key) { + return Err(PackError::BadPrefix); + } + let key = &key[self.prefix.len()..]; + unpack(key) + } + + /// `is_start_of` returns true if the provided key starts with the prefix of this Subspace, + /// indicating that the Subspace logically contains the key. + pub fn is_start_of(&self, key: &[u8]) -> bool { + key.starts_with(&self.prefix) + } + + /// `range` returns first and last key of given Subspace + pub fn range(&self) -> (Vec, Vec) { + let mut begin = Vec::with_capacity(self.prefix.len() + 1); + begin.extend_from_slice(&self.prefix); + begin.push(0x00); + + let mut end = Vec::with_capacity(self.prefix.len() + 1); + end.extend_from_slice(&self.prefix); + end.push(0xff); + + (begin, end) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn sub() { + let ss0: Subspace = 1.into(); + let ss1 = ss0.subspace(&2); + + let ss2: Subspace = (1, 2).into(); + + assert_eq!(ss1.bytes(), ss2.bytes()); + } + + #[test] + fn pack_unpack() { + let ss0: Subspace = 1.into(); + let tup = (2, 3); + + let packed = ss0.pack(&tup); + let expected = pack(&(1, 2, 3)); + assert_eq!(expected, packed); + + let tup_unpack: (i64, i64) = ss0.unpack(&packed).unwrap(); + assert_eq!(tup, tup_unpack); + + assert!(ss0.unpack::<(i64, i64, i64)>(&packed).is_err()); + } + + #[test] + fn subspace_pack_with_versionstamp() { + let subspace: Subspace = 1.into(); + let tup = (Versionstamp::incomplete(0), 2); + + let packed = subspace.pack_with_versionstamp(&tup); + let expected = pack_with_versionstamp(&(1, Versionstamp::incomplete(0), 2)); + assert_eq!(expected, packed); + } + + #[test] + fn subspace_unpack_with_versionstamp() { + let subspace: Subspace = 1.into(); + let tup = (Versionstamp::complete([1; 10], 0), 2); + let packed = subspace.pack(&tup); + let tup_unpack: (Versionstamp, i64) = subspace.unpack(&packed).unwrap(); + assert_eq!(tup, tup_unpack); + + assert!( + subspace + .unpack::<(i64, Versionstamp, i64)>(&packed) + .is_err() + ); + } + + #[test] + fn unpack_with_subspace_versionstamp() { + let subspace: Subspace = Versionstamp::complete([1; 10], 2).into(); + let tup = (Versionstamp::complete([1; 10], 0), 2); + let packed = subspace.pack(&tup); + let tup_unpack: (Versionstamp, i64) = subspace.unpack(&packed).unwrap(); + assert_eq!(tup, tup_unpack); + assert!( + subspace + .unpack::<(Versionstamp, Versionstamp, i64)>(&packed) + .is_err() + ); + } + + #[test] + fn subspace_can_use_incomplete_versionstamp() { + let subspace: Subspace = Versionstamp::incomplete(0).into(); + let tup = (1, 2); + + let packed = subspace.pack_with_versionstamp(&tup); + let expected = pack_with_versionstamp(&(Versionstamp::incomplete(0), 1, 2)); + assert_eq!(expected, packed); + } + + #[test] + fn child_subspace_can_use_incomplete_versionstamp() { + let subspace: Subspace = 1.into(); + let subspace = subspace.subspace(&Versionstamp::incomplete(0)); + let tup = (1, 2); + let packed = subspace.pack_with_versionstamp(&tup); + let expected = pack_with_versionstamp(&(1, Versionstamp::incomplete(0), 1, 2)); + assert_eq!(expected, packed); + } + + #[test] + fn subspace_versionstamped_packing_requires_an_incomplete_stamp() { + let complete = Versionstamp::complete([1; 10], 0); + let subspace: Subspace = 1.into(); + assert!(std::panic::catch_unwind(|| subspace.pack_with_versionstamp(&2)).is_err()); + assert!(std::panic::catch_unwind(|| subspace.pack_with_versionstamp(&complete)).is_err()); + let subspace: Subspace = complete.into(); + assert!(std::panic::catch_unwind(|| subspace.pack_with_versionstamp(&2)).is_err()); + } + + #[test] + #[should_panic] + fn subspace_cannot_use_multiple_incomplete_versionstamps() { + let subspace: Subspace = Versionstamp::incomplete(0).into(); + let subspace = subspace.subspace(&Versionstamp::incomplete(0)); + subspace.pack_with_versionstamp(&1); + } + + #[test] + #[should_panic] + fn subspace_cannot_pack_multiple_incomplete_versionstamps() { + let subspace: Subspace = Versionstamp::incomplete(0).into(); + subspace.pack_with_versionstamp(&Versionstamp::incomplete(0)); + } + + #[test] + fn is_start_of() { + let ss0: Subspace = 1.into(); + let ss1: Subspace = 2.into(); + let tup = (2, 3); + + assert!(ss0.is_start_of(&ss0.pack(&tup))); + assert!(!ss1.is_start_of(&ss0.pack(&tup))); + assert!(Subspace::from("start").is_start_of(&pack(&"start"))); + assert!(Subspace::from("start").is_start_of(&pack(&"start".to_string()))); + assert!(!Subspace::from("start").is_start_of(&pack(&"starting"))); + assert!(Subspace::from("start").is_start_of(&pack(&("start", "end")))); + assert!(Subspace::from(("start", 42)).is_start_of(&pack(&("start", 42, "end")))); + } + + #[test] + fn range() { + let ss: Subspace = 1.into(); + let tup = (2, 3); + let packed = ss.pack(&tup); + + let (begin, end) = ss.range(); + assert!(packed >= begin && packed <= end); + } + + #[test] + fn equality() { + let sub1 = Subspace::all().subspace(&"test"); + let sub2 = Subspace::all().subspace(&"test"); + let sub3 = Subspace::all().subspace(&"test2"); + assert_eq!(sub1, sub2); + assert_ne!(sub1, sub3); + } + + #[test] + fn hash() { + let sub1 = Subspace::all().subspace(&"test"); + let sub2 = Subspace::all().subspace(&"test2"); + + let map: HashMap = HashMap::from([(sub1, 1), (sub2, 2)]); + + assert_eq!(map.get(&Subspace::all().subspace(&"test")).unwrap(), &1); + assert_eq!(map.get(&Subspace::all().subspace(&"test2")).unwrap(), &2); + } + + #[test] + fn display() { + let sub1 = Subspace::all().subspace(&"test"); + let sub2 = Subspace::all().subspace(&"test2").subspace(&"path1"); + let sub3 = Subspace::all().subspace(&"test3").subspace(&"__커피__"); + let sub4 = Subspace::all().subspace(&"test4\\path1\\path2"); + + assert_eq!(format!("{sub1}"), r#"\x02test\x00"#); + assert_eq!(format!("{sub2}"), r#"\x02test2\x00\x02path1\x00"#); + assert_eq!( + format!("{sub3}"), + r#"\x02test3\x00\x02__\xec\xbb\xa4\xed\x94\xbc__\x00"# + ); + assert_eq!(format!("{sub4}"), r#"\x02test4\\path1\\path2\x00"#); + } +} diff --git a/bindings/rust/foundationdb-tuple/src/versionstamp.rs b/bindings/rust/foundationdb-tuple/src/versionstamp.rs new file mode 100644 index 00000000000..516f5e65b19 --- /dev/null +++ b/bindings/rust/foundationdb-tuple/src/versionstamp.rs @@ -0,0 +1,62 @@ +use super::{Bytes, Element}; +use std::fmt; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Versionstamp { + bytes: [u8; 12], +} + +impl fmt::Debug for Versionstamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Bytes::from(&self.bytes[..]).fmt(f) + } +} + +impl Versionstamp { + pub fn incomplete(user_version: u16) -> Self { + let mut bytes = [0xff; 12]; + bytes[10..].copy_from_slice(&user_version.to_be_bytes()); + Versionstamp { bytes } + } + + pub fn complete(tr_version: [u8; 10], user_version: u16) -> Self { + let mut bytes = [0xff; 12]; + bytes[0..10].copy_from_slice(&tr_version); + bytes[10..].copy_from_slice(&user_version.to_be_bytes()); + Versionstamp { bytes } + } + + pub fn transaction_version(&self) -> &[u8] { + &self.bytes[0..10] + } + + pub fn user_version(&self) -> u16 { + let mut user_version = [0; 2]; + user_version.copy_from_slice(&self.bytes[10..12]); + u16::from_be_bytes(user_version) + } + + pub fn is_complete(&self) -> bool { + self.bytes[0..10] != [0xff; 10] + } + + pub fn as_bytes(&self) -> &[u8; 12] { + &self.bytes + } +} + +impl From<[u8; 12]> for Versionstamp { + fn from(bytes: [u8; 12]) -> Self { + Versionstamp { bytes } + } +} + +impl Element<'_> { + pub fn count_incomplete_versionstamp(&self) -> usize { + match self { + Element::Versionstamp(v) if !v.is_complete() => 1, + Element::Tuple(v) => v.iter().map(Element::count_incomplete_versionstamp).sum(), + _ => 0, + } + } +} diff --git a/bindings/rust/foundationdb/CHANGELOG.md b/bindings/rust/foundationdb/CHANGELOG.md new file mode 100644 index 00000000000..44927f0b078 --- /dev/null +++ b/bindings/rust/foundationdb/CHANGELOG.md @@ -0,0 +1,625 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.11.0] - 2026-06-24 + +### 🚀 Features + +- Feat: first draft of leader election recipe implementation +- Use Deref +- Feat(leader_election): implement O(1) ballot-based Active Disk Paxos algorithm +- Feat(recipes): add ranked register recipe +- Feat: add transaction conflict reporting and unified retry runner +- Feat: migrate workspace to Rust 2024 edition (#255) + +### 🐛 Bug Fixes + +- Fix(directory): use get_ranges_keyvalues for proper pagination +- Fix: replace version-based timeouts with Duration-based approach +- Fix(simulation): simplify leader election invariants to core algorithm properties +- Fix(leader-election): internalize versionstamp to prevent unregistered leadership claims +- Fix(simulation): use microsecond precision for log entry ordering +- Use get_ranges_keyvalues for complete range reads (fixes #72) + +### 🚜 Refactor + +- Refactor(simulation): remove redundant invariants and unused types + +### 📚 Documentation + +- Docs(leader-election): add simulation testing documentation +- Docs(leader-election): improve module and struct documentation + +### ⚙️ Miscellaneous + +- Format code and update invariants +- Clarify infinite-retry behavior and add timeout/retry safeguards (fixes +- Chore: align nix toolchain with CI stable (Rust 1.96) and fix 1.96 clippy lints + +### 📦 Other Changes + +- Rewrite fdb_rt to split wakeup and polling phase +- Review comments + +[0.11.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.10.0..0.11.0 + + +## [0.10.0] - 2025-10-24 + +### 🚀 Features + +- Feat(foundationdb) : Expose Timekeeper API +- Profiling Keyspace Scanner +- Add support 7.4 +- Impl std::error::Error for TransactionCommitError +- Add optional tracing feature +- Add `intrumented_run` +- Add unsafe to new_from_pointer function + +### 🐛 Bug Fixes + +- Fix(foundationdb) : Replace unmaintained ring crate with sha2 in examples +- Add on fields in MetricKey +- Don't log as error all commit errors +- Fix: resolve clippy warnings and add doc alias + +### 📚 Documentation + +- Doc, multi-statements support +- Doc-related clippy fixes + +### 🧪 Testing + +- Add a test on failed closure + +### ⚙️ Miscellaneous + +- Bump rand to 0.9.0 +- Bump MSRV to 1.82 +- Test on Apple silicon +- Clippy --fix +- Fix warnings +- Bump msrv to 1.85 + +### 📦 Other Changes + +- Use cfg_api_versions and if_cfg_api_versions +- Specifically use C ABI for packed structs +- Some spaces and tabs doc formalization +- Rename instrumentation by trace + +[0.10.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.2..0.10.0 + + +## [0.9.2] - 2025-01-06 + +### 🚀 added + +- Add multi_version compatibility + +### 🐛 Bug Fixes + +- Test get_metadata_version on 7.3 + +### 🆙 Bump + +- Bump async-trait from 0.1.83 to 0.1.84 +- Bump tokio from 1.40.0 to 1.41.0 +- Bump uuid from 1.10.0 to 1.11.0 +- Bump futures from 0.3.30 to 0.3.31 + +### ⚙️ Other changes + +- Merge pull request [#211](https://github.com/foundationdb-rs/foundationdb-rs/pull/211) from foundationdb-rs/dependabot/cargo/serde-1.0.217 +- Merge pull request [#190](https://github.com/foundationdb-rs/foundationdb-rs/pull/190) from spiraldb/ji/split-tuple-into-crate +- Merge pull request [#173](https://github.com/foundationdb-rs/foundationdb-rs/pull/173) from foundationdb-rs/dependabot/cargo/serde_json-1.0.132 +- Apply review changes +- Improve libfdb transaction error + +[0.9.2]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.1..0.9.2 + +## [0.9.1] - 2024-10-08 + +### 🚀 added + +- Add examples for using versionstamps +- Add Database.get_main_thread_busyness + +### 🆙 Bump + +- Bump MSRV to 1.71.1 +- Bump all dependencies and setup dependabot + +### ⚙️ Other changes + +- Generate CHANGELOG per crate +- Handle nested FdbBindingErrors +- Expose FdbBindingError::get_fdb_error + +[0.9.1]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.9.0..0.9.1 + +## [0.9.0] - 2024-03-15 + +### 🚀 added + +- Add more useful version of get_mapped_ranges +- Add perform_no_op method +- Introduce db_run +- Introduce FdbBindingError +- Add hello_world using db.run +- Use cargo add in doc +- Remove panic during RetryableTransaction::commit +- Feat(foundationdb): rework RetryableTransaction +- Feat(foundationdb): add raw method to create a Database +- Initial commit for Tenant +- Implement Tenant{Create,SetActive,Delete} +- Implement list_tenant +- Get TenantManagement::get_tenant +- Fix `list_range` and add tenant::run +- Feat(tuple) add function to pack a subspace key with a versionstamp +- Add integ tests for tuple subspaces with versionstamps +- Add support for 7.3 +- Add more feature flag +- Add Database.get_client_status +- Add support for tenant info in 7.3 +- Add MicroQueue examples +- Bump fdb crate to 0.9.0 and sim to 0.2.0 + +### 🐛 Bug Fixes + +- Clippy warnings +- Run tenants tests when feature is used +- Avoid infinite recursion on debug impl +- Fix: Mark Fdb struct wrappers as packed +- Use Debug impl as Display +- Make the db_run non capturing +- Apply cargo fmt recommendation +- Fix std::slice::from_raw_parts behavior on nightly +- Fix(foundationdb): remove flappy tests + +### 🚜 Refactor + +- Remove unused StreamingMode +- Remove unnecessary casts. + +### 🆙 Bump + +- Chore: bump rust to 1.57 +- Bump msrv +- Bump crates version +- Bump msrv for bindgen and env_logger +- Bump dependencies +- Bump dep + +### ⚙️ Other changes + +- Build on docs.rs with the required features +- Reformat table +- Clippy +- Rename examples +- Hide tenant api behind a feature +- Introduce a pass-through transaction option setter +- Cargo {fmt,clippy} +- Prepare release +- Clippy: Simplify non-minimal-cfg. +- Clippy: Suppress "should_implement_trait" for `Database::default()`. +- Cargo fmt +- Removing default fdb version +- Cargo fmt/clippy +- Replace get(0) by first() +- Return errors instead of expect +- Use higher-level get_ranges +- Create MicroQueue struct +- Simplify first_item method and prevent extra allocations +- Use local variable instead of struct ffield for random seed +- Default Unit value for FdbResult + +## [0.7.0] - 2022-07-11 + +### 🚀 added + +- Add micro index example +- Add tested example to explain how to use fdb to store blob data. +- Add missing async keyword on simple-index example +- Add explanation about blob example +- Add a simple blob example +- Add support for api 700 +- Initial commit for get_range_split_points +- Implement Iterator for FdbKeys +- FdbAddress should be #[repr(transparent)] +- Expose FdbKey and other only in 7.0 +- Implement range split point +- Introduce get/set metatadaVersion +- Introduce run method +- Add Hash, Eq and PartialEq to Subspace +- Add example about atomic_op +- Initial commit to support fdb 7.1 +- Initial commit for get_mapped_range +- Allow retrieval of embedded kvs in mapped_range +- Allow KeySelector in mapped_range +- Improve doc about mapped_range + +### 🚜 Refactor + +- Remove clikengo link in Cargo.toml +- Use macro cfg_api_versions when possible + +### 🆙 Bump + +- Bump dependencies +- Bump deps +- Bump rust patch version + +### ⚙️ Other changes + +- Improve blob example display +- Use tokio reactor in examples +- Improve blob example with a manifest system +- Reduce unsafe scope +- Limit FdbKeys and FdbKey to fdb700+ +- Reduce use of `cfg_api_versions` +- Clippy +- Revert "feat(foundationdb): introduce run method" +- Split modules in separate files +- Move test mapped_range into range's tests +- Improve documentation +- Prepare for 0.7.0 + +## [0.6.0] - 2022-04-08 + +### 🚀 added + +- Add support for api 630 +- Introduce platform tier +- Feat(foundationdb,bindingtester): Add directory +- Add warning on create +- Fix compilation with 1.46.0 and add back install on script +- Bump crate version, maintainers and documentation +- Bump rust edition to 2021 + +### 🐛 Bug Fixes + +- Fix doc artifacts of removed methods +- Fix: Bump minimum version to rust 1.46 +- Cargo clippy --fix and fmt +- Run estimate range tests only on 630 +- Fix several panics during some seeds +- Fix clippy warnings +- Typo +- Fix(directory): avoid throwing bad CannotMoveBetweenPartition +- Fix code in documentation +- Fix(directory): avoid initializing the version key on create_or_open +- Fix(directory): Scan old and new path during Directory.Move + +### 🚜 Refactor + +- Accept a slice instead of a Vec as path +- Refactor(directory): Node are always loaded and existing + +### 🆙 Bump + +- Bump version to 5.0.1 +- Bump deps + +### ⚙️ Other changes + +- Cargo fmt +- Make tuple::hca::HighContentionAllocator be Send (#214) +- Removing reference to master branch +- Code-review +- Chore: code-review +- Rollback breaking change on Subspace::from_bytes +- Chore(directory): cleanup tuple looking syntax +- Clippy + +## [0.5.0] - 2020-09-17 + +### 🚀 added + +- Add versionstamp support to tuple +- Add docs and description of algorithm +- Get_addresses_for_key test +- Add NEGINSTART and POSINTEND to tuple +- Add boot_async method +- Add support for pack with versionstamp +- Simplify add_assign logic for VerstionstampOffset +- Add transact limits test and make the code easier to read +- Add tests for boot_async + +### 🐛 Bug Fixes + +- Fixed flaky networking test +- Fix variable does not need to be mutable in bindingtester +- Fix integer cast possible undefined behavior +- Fix missing rename for fdb_api to api +- Fix default feature compilation +- Fix Element::tuple deserialization +- Fix build on linux/osx +- Clippy fixes +- Fix tests +- Fix build after naming changes +- Fix build without uuid feature +- Fix bindingtester scripted seed 674776253 +- Fixed i64 encoding index out of range error +- Fix panic on i64 +- Fix #136: Segfault when pending future dropped +- Fix foundationdb-gen features +- Cargo.toml fix docs.rs features +- Fix copyright link +- Cargo.toml fix badges links +- Fix spurious test_future_discard panic +- Fix DoubleEndedIterator for FdbValuesIter +- Fix Cargo.toml repository links +- Fixes from review +- Fix #170: protect boot from undefined behavior +- Fix #181: boot can still trigger undefined behavior if `f` panic and does not abort. +- Fix some cargo clippy issues +- Fix use after free in Database::new +- Fix doc in KeySelector first_greater_than* +- Fix bigint support +- Fix bigint support on element +- Fix Element order between BigInt and Int +- Fix versionstamp offset and bindingtester +- Fix unpack of ix that doesn't fix in ix +- Fix build for rust < 1.42 +- Fix broken transact() limits +- Fix cargo clippy warning +- Fix missing tokio features for tokio_async test +- Simplify and fix UB in boot API +- Fix even `run` can lead to UB +- Fix api_ub test on API <610 + +### 🚜 Refactor + +- Remove unnecessary cast in unsafe blocks +- Remove is_maybe_committed as it is already accessible +- Remove failure dependency +- Remove useless import +- Bindingtester refactoring +- Remove dbg! + +### 🆙 Bump + +- Bump version to 0.4.1 + +### ⚙️ Other changes + +- Core: auto-generate all code at build time +- Upgrade rand from 0.4 to 0.6 (#104) +- Upgrade uuid from 0.6 to 0.7 (#105) +- Cleanup the fdb version features from primary crate (#99) +- Rust edition 2018 +- Async/await: complete rewrite of foundationdb +- New get_ranges_key_values -> Stream api on Transaction +- Renamed foundationdb_sys import from fdb to fdb_sys for clarity +- Rename fdb_api to api +- Foundationdb api 510, 520, 600 support +- Working threaded bindingtester +- Bindingtester is now complete and serde is replaced by a solution close to the original +- Make RangeOption::next_range public +- Fdb 620 support +- Futures 0.3 is stable now +- Cleanup dependencies and rename Error as FdbError +- Documentation and API improvements +- Transaction documentation and some API simplifications +- Impl Send/Sync for some future results +- Make FdbFuture and FdbFutureHandle private +- Rename tuple::Error and tuple::Result to PackError and PackResult +- Hca implementation and tests +- Foundationdb-sys version is now 0.4.0 +- Returned Future/Stream are now publicly Unpin +- Restore abort future test +- :transact data is now passed as mutable +- Changed back to i64 +- Handle when encoded value overflows +- Hca outline +- Lib updates + tweaks to hca +- Consider each key via fold +- Bahgawd it works +- Working tests +- Tweaks to doc formatting +- Update rand requirement from 0.6.5 to 0.7.0 +- Mark FdbFuture as Send + Sync +- Make comment more accurate +- Update README links and requirements +- Force foundationdb-gen to version 0.4.0+ +- Update Cargo.toml authors +- :transact api is now generic and should be future proof. +- RangeOption can now be constructed from (Vec, Vec) +- RangeOption tests & from RangeInclusive +- Embed 6.2.10 fdb_c.h and fdb.options +- Embed all supported version includes +- Default to locally available foundationdb include +- Test_set_conflict now checks the error message +- Cargo.toml badges & docs.rs metadata +- Impl std::error::Error for FdbError +- Bigint tuple suport enhancements +- Disable tokio test until #170 is resolved +- Renamed BadLength to UnsupportedIntLength +- Pack/unpack optional bigint support +- Limit risks of transmute errors of fdb_sys::FDBKeyValue to FdbKeyValue +- Impl std::error::Error for PackError +- Use `#[non_exhaustive]` on RangeOption and generated enums +- Typo in documentation +- Few api improvements: +- Default to foundation 620 api +- Cargo fmt +- Ignore test_ub test due to panic hook issue in the code coverage CI test +- Update README and CHANGELOG + +## [0.3.0] - 2018-11-16 + +### 🚀 added + +- Add docs link to Cargo.toml +- Add `futures` dependency to example. +- Add futures to docs +- `GetKeyResult` and `GetAddressResult` return value + +### 🐛 Bug Fixes + +- Fix imports in example + +### ⚙️ Other changes + +- Win64 support +- Change `TrxGet::value` to return `Option<&[u8]>` +- Prepare 0.3 release + +## [0.2] - 2018-05-09 + +### 🚀 added + +- Add some READMEs +- Add Readme for the fdb crate +- Add metadata +- Add FdbError and conversion helpers +- Add first working example +- Add `example_get_multi` +- Add comments +- Add set_option() api to Database/Transaction +- Add run and stop +- Add network set_option +- Add init functions +- Add library level documentation and examples +- Add Apple copyright due to most of the docs from FoundationDB docs +- Add docs for options +- Cancel/committed_version/get_address_for_key +- Add `Transaction::watch`, #19 +- Add `Transaction::atomic_op`, #15 +- Add `Transaction::get_range`, #14 +- Add testcases with conflict scenario +- Add `Database::transact`, #29 +- Add docs about retrylimit/timeout, fix watch +- Add floating-point support +- Add `bindingtester`, #39 +- Add nested tuple/single value +- Add `Transaction::add_conflict_range`, #20 +- Add Subspace, #36 +- Add benchmark +- Add support for threading/batching +- Add into FdbError for tuple::Error +- Add some tests to validate recursive tuples +- Add some debug lines +- Add decode/encode for Option +- Add single tuple support back + +### 🐛 Bug Fixes + +- Fix version +- Fix some urls +- Fix doc tests for stable +- Fix typo on comment +- Fix compile error on tests +- Fix crash with uninitialized memory access +- Fix bug on binary decoding +- Fix off-by-one on integer encoding +- Support nested Empty, fix overflow on int +- Fix segfault on watch test, #58 +- Fix errors to updated format +- Publicize TupleDepth::increment, fix typos + +### 🚜 Refactor + +- Remove unsafe from futures +- Remove tokio from the example +- Refactor context and network +- Remove debug printlns +- Remove `Send` from `FdbFuture` +- Remove bad test +- Remove the redundant `new` method +- Impl `From` and remove `RangeOptionBuilder::from_tuple` +- Remove single tuple variant + +### 🆙 Bump + +- Bump versions + +### ⚙️ Other changes + +- Initial library create for foundationdb rust bindings +- Initial binding generation working +- Context initialization +- Generate options types from fdb.options +- Use consts from sys crate +- Move examples code to lib +- Default config path based on OS +- Change `TrxCommit::Item` to `Transaction` +- Delete outdated comment +- Cleanup errors and warnings +- Update readme and cargo.toml +- Use defualt path for all examples +- Update hello example +- Copy the library level docs to the crate Readme +- Copied docs from C api +- Call `fdb_future_destory` on FdbFuture::drop +- Track bindings.rs in Git +- Revise `Tranasction::get_range` +- Move examples into tests, cleanup tests +- Versionstamp, {get,set}_read_version, #20 +- Support rust-style tuple +- Integer encoding +- Support tuple encoding +- A type safe SingleType and conversions +- Switch to named constants +- Check edge conditions +- Switch nested to use TupleValue +- Cleanup subspace +- Rename `covers` to `is_start_of` +- Extract `Single` types to `tuple::single` +- Rename TupleError to tuple::Error +- Rename TupleValue to tuple::Value +- Rename SingleValue to tuple::single::Value +- Rename SingleType to tuple::single::Type +- Split `Single` and `Tuple` traits into `Encode` and `Decode` +- Rename `single` to `item` +- Use `Uuid` from the `uuid` crate +- Dedup Tuple::{Decode,Encode} and Item::{Decode,Encode} +- Make `tuple::item` private and re-export `tuple::item::Value` as `tuple::Item` +- Make `tuple::item::Value` variants more Rusty +- Make `tuple::item::Value` non-exhaustive +- Rename `tuple::Value` to `Tuple` and `tuple::Item` to `Element` +- Impl Encode for &str +- Revert "impl Encode for &str" +- Impl Encode for some borrowed types +- Subspace tests for tuple variants +- Reduce allocations when decoding +- Allow the association of other errors to FdbError +- Reexport Error from the lib +- Dont force conversion of error in transact +- Allow custom error type +- Drop Other from Error +- Initialization of class-scheduling +- Implement simulator +- Cleanup +- Cleanup with new tuple semantics +- All working +- Use transact on all DB interactions +- Don't force borrowing +- Make the `subspace` module private +- Clean up tests with single-element tuples +- Revert "clean up tests with single-element tuples" +- Rename `keyvalues` to `key_values` +- Rename `encode_to_vec` to `to_vec` +- Rename `decode_full` to `try_from` +- Track when inside tuples during encoding +- Drop the single tuple variant +- Rebase to Rust named fns +- Bounds checks and more tests for Option... +- Encode_to and decode_from +- Clean up one last reference to str +- Update changelog for 0.2 +- Document key selector + +[unreleased]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.9.0..HEAD +[0.9.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.7.0..v0.9.0 +[0.7.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.6.0..v0.7.0 +[0.6.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.5.0..v0.6.0 +[0.5.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/v0.3.0..0.5.0 +[0.3.0]: https://github.com/foundationdb-rs}/foundationdb-rs/compare/0.2..v0.3.0 + + diff --git a/bindings/rust/foundationdb/Cargo.toml b/bindings/rust/foundationdb/Cargo.toml new file mode 100644 index 00000000000..68277fae291 --- /dev/null +++ b/bindings/rust/foundationdb/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "foundationdb" +version = "0.11.0" +authors = [ + "Benjamin Fry ", + "Vincent Rouillé ", + "Pierre Zemb " +] +edition = "2024" +rust-version = "1.85.1" + +description = """ +High level client bindings for FoundationDB. +""" + +documentation = "https://docs.rs/foundationdb" +repository = "https://github.com/apple/foundationdb" +license = "MIT/Apache-2.0" + +readme = "README.md" +keywords = ["foundationdb", "kv"] +categories = ["database"] + +[package.metadata.docs.rs] +features = ["embedded-fdb-include", "fdb-7_4", "uuid"] + +[features] +default = ["uuid"] +uuid = ["dep:uuid", "foundationdb-tuple/uuid"] +num-bigint = ["dep:num-bigint", "foundationdb-tuple/num-bigint"] +# Use the locally embedded foundationdb fdb_c.h and fdb.options files +embedded-fdb-include = [ + "foundationdb-sys/embedded-fdb-include", + "foundationdb-gen/embedded-fdb-include", +] +fdb-5_1 = ["foundationdb-sys/fdb-5_1", "foundationdb-gen/fdb-5_1"] +fdb-5_2 = ["foundationdb-sys/fdb-5_2", "foundationdb-gen/fdb-5_2"] +fdb-6_0 = ["foundationdb-sys/fdb-6_0", "foundationdb-gen/fdb-6_0"] +fdb-6_1 = ["foundationdb-sys/fdb-6_1", "foundationdb-gen/fdb-6_1"] +fdb-6_2 = ["foundationdb-sys/fdb-6_2", "foundationdb-gen/fdb-6_2"] +fdb-6_3 = ["foundationdb-sys/fdb-6_3", "foundationdb-gen/fdb-6_3"] +fdb-7_0 = ["foundationdb-sys/fdb-7_0", "foundationdb-gen/fdb-7_0"] +fdb-7_1 = ["foundationdb-sys/fdb-7_1", "foundationdb-gen/fdb-7_1"] +fdb-7_3 = ["foundationdb-sys/fdb-7_3", "foundationdb-gen/fdb-7_3"] +fdb-7_4 = ["foundationdb-sys/fdb-7_4", "foundationdb-gen/fdb-7_4"] +trace = ["tracing"] + +[build-dependencies] +foundationdb-gen = { version = "0.11.0", path = "../foundationdb-gen", default-features = false } + +[dependencies] +foundationdb-sys = { version = "0.11.0", path = "../foundationdb-sys", default-features = false } +foundationdb-macros = { version = "0.4.0", path = "../foundationdb-macros" } +foundationdb-tuple = { version = "0.11.0", path = "../foundationdb-tuple" } +futures = "0.3.32" +libc = "0.2" +rand = { version = "0.10.1", features = ["default"] } +static_assertions = "1.1.0" +uuid = { version = "1.23.3", optional = true } +num-bigint = { version = "0.5.1", optional = true } +async-trait = "0.1.89" +async-recursion = "1.1.1" +tracing = { version = "0.1.44", optional = true } + +[dev-dependencies] +byteorder = "1.5.0" +lazy_static = "1.5.0" +tokio = { version = "1.52.3", features = ["full"] } +uuid = { version = "1.23.3", features = ["v4"] } +futures-util = "0.3.32" +tracing-subscriber = "0.3.23" + +[[example]] +name = "api_version_library" +crate-type = ["cdylib"] +required-features = ["fdb-7_4"] diff --git a/bindings/rust/foundationdb/README.md b/bindings/rust/foundationdb/README.md new file mode 100644 index 00000000000..d1a61675cb4 --- /dev/null +++ b/bindings/rust/foundationdb/README.md @@ -0,0 +1,142 @@ +# FoundationDB Rust Client API + +This is a wrapper library around the FoundationDB (Fdb) C API. It implements futures based interfaces over the Fdb future C implementations. + +## Prerequisites + +* Rust 1.85.1 or more, +* FoundationDB's client installed. + +## Platform support + +The imported client supports Linux and macOS; standalone Windows support is +community maintained. See the [workspace README](../README.md) for the in-tree +build and current validation scope. Upstream platform tiers and scheduled CI do +not imply equivalent coverage in this repository. + +## Getting Started + +### Install FoundationDB + +You first need to install FoundationDB. You can follow the official documentation: + +* [Getting Started on Linux](https://apple.github.io/foundationdb/getting-started-linux.html) +* [Getting started on macOS](https://apple.github.io/foundationdb/getting-started-mac.html) + +### Add dependencies on foundationdb-rs + +```shell +cargo add foundationdb -F embedded-fdb-include,fdb-7_4 +cargo add futures +``` + +This Rust crate is not tied to any Async Runtime. + +### Exposed features + +| Features | Notes | +|------------------------|--------------------------------------------------------------------------------| +| `fdb-5_1` | Support for FoundationDB 5.1.X | +| `fdb-5_2` | Support for FoundationDB 5.2.X | +| `fdb-6_0` | Support for FoundationDB 6.0.X | +| `fdb-6_1` | Support for FoundationDB 6.1.X | +| `fdb-6_2` | Support for FoundationDB 6.2.X | +| `fdb-6_3` | Support for FoundationDB 6.3.X | +| `fdb-7_0` | Support for FoundationDB 7.0.X | +| `fdb-7_1` | Support for FoundationDB 7.1.X | +| `fdb-7_3` | Support for FoundationDB 7.3.X | +| `fdb-7_4` | Support for FoundationDB 7.4.X | +| `embedded-fdb-include` | Use the locally embedded FoundationDB fdb_c.h and fdb.options files to compile | +| `uuid` | Support for the uuid crate for Tuples | +| `num-bigint` | Support for the bigint crate for Tuples | +| `trace` | Enable tracing on transaction related operations | + +Only `uuid` is enabled by default. + +### Hello, World using the crate + +We are going to use the Tokio runtime for this example: + +```rust +use futures::prelude::*; + +#[tokio::main] +async fn main() { + // Optional: creating a Database initializes the client automatically. + foundationdb::boot().expect("failed to initialize FoundationDB"); + + // Have fun with the FDB API + hello_world().await.expect("could not run the hello world"); + + // The network is stopped automatically at process exit. +} + +async fn hello_world() -> foundationdb::FdbResult<()> { + let db = foundationdb::Database::default()?; + + // write a value in a retryable closure + match db + .run(|trx, _maybe_committed| async move { + trx.set(b"hello", b"world"); + Ok::<_, foundationdb::FdbBindingError>(()) + }) + .await + { + Ok(_) => println!("transaction committed"), + Err(_) => eprintln!("cannot commit transaction"), + }; + + // read a value + match db + .run(|trx, _maybe_committed| async move { Ok::<_, foundationdb::FdbBindingError>(trx.get(b"hello", false).await.unwrap()) }) + .await + { + Ok(slice) => assert_eq!(b"world", slice.unwrap().as_ref()), + Err(_) => eprintln!("cannot commit transaction"), + } + + Ok(()) +} +``` + +## Additional notes + +### API examples + +The `examples/` directory demonstrates native operations, key selectors, +versionstamps, and multiple client API versions. + +### Must-read documentations + +* [Developer Guide](https://apple.github.io/foundationdb/developer-guide.html) +* [Data Modeling Guide](https://apple.github.io/foundationdb/data-modeling.html) + +### Initialization + +The Client is initialized on first use: creating a `Database` starts the network automatically, like `fdb.open()` does in the official bindings. You can also initialize it explicitly with the safe and idempotent `foundationdb::boot` function. The network then runs until process exit, where it is stopped and joined automatically. See `foundationdb::api` for more configuration options of the Fdb Client. + +**Warning**: the automatic stop at process exit is meant for tests and short-lived tools. In a production application, prefer handling the network stop yourself: the network thread is the event loop driving every transaction, you may still have on-going operations at exit time, and you usually want a clean teardown. Finish or cancel your work, drop the `Database` handles, then call `foundationdb::api::stop_network()` (`foundationdb::api::disable_stop_on_exit()` additionally turns the exit hook into a no-op). + +### Migration from 0.11 to 0.12 + +The initialization of the foundationdb API is now safe and idempotent, and the network can no longer be stopped by dropping the boot guard (issues #132, #195). Previously you had to write: + +```rust,ignore +// old, up to 0.11 +let network = unsafe { foundationdb::boot() }; +// do stuff +drop(network); +``` + +Now this can be converted to: + +```rust +foundationdb::boot().expect("failed to initialize FoundationDB"); +// do stuff; the network is stopped automatically at process exit +``` + +Calling `boot` is even optional if you create a `Database`. Tests no longer need to be serialized with `--test-threads=1`: every test can boot in any order, in parallel. + +### API stability + +_WARNING_ Until the 1.0 release of this library, the API may be in constant flux. diff --git a/bindings/rust/foundationdb/build.rs b/bindings/rust/foundationdb/build.rs new file mode 100644 index 00000000000..5fb37d7783b --- /dev/null +++ b/bindings/rust/foundationdb/build.rs @@ -0,0 +1,18 @@ +extern crate foundationdb_gen; + +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +fn main() { + let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is undefined!")); + let options_file = out_path.join("options.rs"); + let mut options = String::new(); + foundationdb_gen::emit(&mut options).expect("couldn't emit options.rs code!"); + + File::create(options_file) + .expect("couldn't create options.rs!") + .write_all(options.as_bytes()) + .expect("couldn't write options.rs!"); +} diff --git a/bindings/rust/foundationdb/examples/README.md b/bindings/rust/foundationdb/examples/README.md new file mode 100644 index 00000000000..601807dc57c --- /dev/null +++ b/bindings/rust/foundationdb/examples/README.md @@ -0,0 +1 @@ +See [tests](../tests/) for examples diff --git a/bindings/rust/foundationdb/examples/api_version_library.rs b/bindings/rust/foundationdb/examples/api_version_library.rs new file mode 100644 index 00000000000..4bc728bb817 --- /dev/null +++ b/bindings/rust/foundationdb/examples/api_version_library.rs @@ -0,0 +1,57 @@ +//! Shared-library fixture for the API-selection integration test. + +use foundationdb::{ + Database, + api::FdbApiBuilder, + options::{MutationType, TransactionOption}, + tuple::{Subspace, Versionstamp}, +}; +use foundationdb_sys::FDBDatabase; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; + +#[unsafe(no_mangle)] +pub extern "C" fn test_select_api(version: i32) -> i32 { + match FdbApiBuilder::default() + .set_runtime_version(version) + .build() + { + Ok(_) => 0, + Err(error) => error.code(), + } +} + +/// Queue modern versionstamp mutations using this library's API-selection state. +/// +/// # Safety +/// +/// `database` must point to a live C API database with the network running. The +/// caller must keep it alive and must not access it until this call returns. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn test_versionstamped_mutations(database: *mut FDBDatabase) -> i32 { + // The harness retains ownership. This wrapper is used exclusively for this + // call, and all transactions are destroyed before returning the borrow. + let database = ManuallyDrop::new(unsafe { + Database::new_from_pointer(NonNull::new(database).expect("database pointer")) + }); + let result = futures::executor::block_on(async { + let trx = database.create_trx()?; + trx.set_option(TransactionOption::Timeout(10_000))?; + trx.get_read_version().await?; + + let subspace = Subspace::from("test-api-version-library"); + let key = subspace.pack_with_versionstamp(&("key", Versionstamp::incomplete(0))); + let value = subspace.pack_with_versionstamp(&("value", Versionstamp::incomplete(1))); + trx.atomic_op(&key, b"value", MutationType::SetVersionstampedKey); + trx.atomic_op( + &subspace.pack(&"value"), + &value, + MutationType::SetVersionstampedValue, + ); + Ok::<_, foundationdb::FdbError>(()) + }); + match result { + Ok(()) => 0, + Err(error) => error.code(), + } +} diff --git a/bindings/rust/foundationdb/examples/atomic-op-counter.rs b/bindings/rust/foundationdb/examples/atomic-op-counter.rs new file mode 100644 index 00000000000..c3d250ff7a4 --- /dev/null +++ b/bindings/rust/foundationdb/examples/atomic-op-counter.rs @@ -0,0 +1,88 @@ +use byteorder::ByteOrder; +use foundationdb::tuple::Subspace; +use foundationdb::{Database, FdbBindingError, FdbResult, Transaction, options}; + +#[tokio::main] +async fn main() { + foundationdb::boot().expect("failed to initialize FoundationDB"); + // The network is stopped and joined automatically at process exit, which is + // fine for tests and short-lived tools like this example. In a production + // application, prefer a clean teardown: the network thread is the event loop + // driving every transaction and you may still have on-going operations at + // exit time. Finish or cancel your work, drop the Database handles, then + // call `foundationdb::api::stop_network()` yourself (terminal: any + // FoundationDB use afterwards fails with error 2025). + let db = Database::new_compat(None) + .await + .expect("failed to get database"); + db.set_option(options::DatabaseOption::TransactionTimeout(5000)) + .expect("failed to set transaction timeout"); + db.set_option(options::DatabaseOption::TransactionRetryLimit(3)) + .expect("failed to set transaction retry limit"); + + let counter_key = Subspace::all().subspace(&"stats").pack(&"my_counter"); + + // write initial value + db.run(|trx, _maybe_committed| { + let counter_key = counter_key.clone(); + async move { + increment(&trx, &counter_key, 1); + Ok::<_, FdbBindingError>(()) + } + }) + .await + .expect("could not commit"); + + // read counter + let v1 = db + .run(|trx, _maybe_committed| { + let counter_key = counter_key.clone(); + async move { + let val = read_counter(&trx, &counter_key).await?; + Ok::<_, FdbBindingError>(val) + } + }) + .await + .expect("could not read counter"); + dbg!(v1); + assert!(v1 > 0); + + // decrement + db.run(|trx, _maybe_committed| { + let counter_key = counter_key.clone(); + async move { + increment(&trx, &counter_key, -1); + Ok::<_, FdbBindingError>(()) + } + }) + .await + .expect("could not commit"); + + let v2 = db + .run(|trx, _maybe_committed| { + let counter_key = counter_key.clone(); + async move { + let val = read_counter(&trx, &counter_key).await?; + Ok::<_, FdbBindingError>(val) + } + }) + .await + .expect("could not read counter"); + dbg!(v2); + assert_eq!(v1 - 1, v2); +} + +fn increment(trx: &Transaction, key: &[u8], incr: i64) { + // generate the right buffer for atomic_op + let mut buf = [0u8; 8]; + byteorder::LE::write_i64(&mut buf, incr); + + trx.atomic_op(key, &buf, options::MutationType::Add); +} + +async fn read_counter(trx: &Transaction, key: &[u8]) -> FdbResult { + let raw_counter = trx.get(key, true).await?.expect("no value found"); + + let counter = byteorder::LE::read_i64(raw_counter.as_ref()); + Ok(counter) +} diff --git a/bindings/rust/foundationdb/examples/hello-world.rs b/bindings/rust/foundationdb/examples/hello-world.rs new file mode 100644 index 00000000000..641bc9252c4 --- /dev/null +++ b/bindings/rust/foundationdb/examples/hello-world.rs @@ -0,0 +1,49 @@ +#[tokio::main] +async fn main() { + // The FoundationDB client is initialized on first use, and the network is + // stopped and joined automatically at process exit, which is fine for tests + // and short-lived tools like this example. In a production application, + // prefer a clean teardown: the network thread is the event loop driving + // every transaction and you may still have on-going operations at exit time. + // Finish or cancel your work, drop the Database handles, then call + // `foundationdb::api::stop_network()` yourself (terminal: any FoundationDB + // use afterwards fails with error 2025). + hello_world().await.expect("could not run the hello world"); +} + +async fn hello_world() -> foundationdb::FdbResult<()> { + let db = foundationdb::Database::default()?; + + // By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster + // or if DNS resolution fails. To prevent this, you can set a timeout or a retry limit on the + // database object. + db.set_option(foundationdb::options::DatabaseOption::TransactionTimeout( + 5000, + ))?; // 5 seconds + db.set_option(foundationdb::options::DatabaseOption::TransactionRetryLimit(3))?; + + // write a value in a retryable closure + match db + .run(|trx, _maybe_committed| async move { + trx.set(b"hello", b"world"); + Ok::<_, foundationdb::FdbBindingError>(()) + }) + .await + { + Ok(_) => println!("transaction committed"), + Err(_) => eprintln!("cannot commit transaction"), + }; + + // read a value + match db + .run(|trx, _maybe_committed| async move { + Ok::<_, foundationdb::FdbBindingError>(trx.get(b"hello", false).await.unwrap()) + }) + .await + { + Ok(slice) => assert_eq!(b"world", slice.unwrap().as_ref()), + Err(_) => eprintln!("cannot commit transaction"), + } + + Ok(()) +} diff --git a/bindings/rust/foundationdb/examples/key_selectors.rs b/bindings/rust/foundationdb/examples/key_selectors.rs new file mode 100644 index 00000000000..ce2f9672274 --- /dev/null +++ b/bindings/rust/foundationdb/examples/key_selectors.rs @@ -0,0 +1,432 @@ +use std::collections::HashSet; + +use foundationdb::{Database, FdbError, KeySelector, RangeOption}; +use futures_util::TryStreamExt; + +// Helper function to insert some initial data: "a" through "z" +async fn setup_db(db: &Database) -> Result<(), FdbError> { + let transaction_result: Result<(), FdbError> = db + .run(|trx, _maybe_committed| async move { + trx.clear_range(b"", b"\\xFF"); // Clear all existing keys + let keys_to_insert = vec![ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", + "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + ]; + for key_str in keys_to_insert { + let key_bytes = key_str.as_bytes(); + trx.set(key_bytes, key_bytes); // Using key as value for simplicity + } + Ok(()) + }) + .await + .map_err(|binding_err: foundationdb::FdbBindingError| { + eprintln!("FoundationDB Binding Error during setup_db: {binding_err:?}"); + FdbError::from_code(2004) // client_invalid_operation + }); + + transaction_result +} + +/** + * Helper function to run a range query test using KeySelectors. + * It fetches a range of keys and asserts them against the expected keys. + */ +async fn run_range_query_test<'a>( + db: &'a Database, + options: RangeOption<'a>, + expected_keys: Vec<&str>, +) -> Result<(), FdbError> { + let query_result: Result, FdbError> = db + .run(move |trx, _maybe_committed| { + let options = options.clone(); // Clone options to move into async block + async move { + let mut results: HashSet = HashSet::new(); + let mut elements = trx.get_ranges_keyvalues(options, false); + while let Ok(Some(element)) = elements.try_next().await { + // Collect keys for comparison + results.insert(String::from_utf8_lossy(element.key()).to_string()); + } + Ok(results) + } + }) + .await + .map_err(|binding_err: foundationdb::FdbBindingError| { + eprintln!("FoundationDB Binding Error during run_range_query_test: {binding_err:?}"); + FdbError::from_code(2004) // client_invalid_operation + }); + + let actual_keys_set = query_result?; + + let expected = HashSet::from_iter(expected_keys.iter().map(|s| s.to_string())); + + let difference = actual_keys_set.difference(&expected).collect::>(); + if !difference.is_empty() { + eprintln!("Expected keys: {expected_keys:?}"); + eprintln!("Actual keys: {actual_keys_set:?}"); + eprintln!("Difference: {difference:?}"); + assert!( + difference.is_empty(), + "Expected {} keys, but got {}", + expected_keys.len(), + difference.len() + ); + } + + Ok(()) +} + +async fn key_selector_example(db: &Database) -> Result<(), FdbError> { + setup_db(db).await?; + + // Selectors: first_greater_or_equal(b"a") to first_greater_than(b"h") + // Keys: a, b, c, d, ..., z + // Begin: first_greater_or_equal(b"a") -> resolves to "a" + // End: first_greater_than(b"h") -> resolves to "i" + // Range is ["a", "i") -> "a", "b", "c", "d", "e", "f", "g", "h" + println!( + "Running: FGOE(\"a\") to FGT(\"h\") -> expecting ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']" + ); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"a"), + end: KeySelector::first_greater_than(b"h"), + ..Default::default() + }, + vec!["a", "b", "c", "d", "e", "f", "g", "h"], + ) + .await?; + + // Original example: Get range ["b", "d") which means keys "b", "c" + // Keys: a, b, c, d, ..., z + // Begin: first_greater_or_equal(b"b") -> resolves to "b" + // End: first_greater_than(b"c") -> resolves to "d" + // Range is ["b", "d") -> "b", "c" + println!("Running: FGOE(\"b\") to FGT(\"c\") -> expecting ['b', 'c']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"b"), + end: KeySelector::first_greater_than(b"c"), // resolves to "d" + ..Default::default() + }, + vec!["b", "c"], + ) + .await?; + + // Selectors: first_greater_or_equal (begin) and first_greater_or_equal (end) + // Begin: KeySelector::first_greater_or_equal(b"c") -> "c" + // End: KeySelector::first_greater_or_equal(b"f") -> "f" + // Range: ["c", "f") -> "c", "d", "e" + println!("Running: FGOE(\"c\") to FGOE(\"f\") -> expecting ['c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"c"), + end: KeySelector::first_greater_or_equal(b"f"), + ..Default::default() + }, + vec!["c", "d", "e"], + ) + .await?; + + // Selectors: first_greater_than (begin) and last_less_than (end) + // Begin: KeySelector::first_greater_than(b"b") -> "c" + // End: KeySelector::last_less_than(b"f") -> "e" (key is "e", range is exclusive at end) + // Range: ["c", "e") -> "c", "d" + println!("Running: FGT(\"b\") to LLT(\"f\") -> expecting ['c', 'd']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_than(b"b"), + end: KeySelector::last_less_than(b"f"), + ..Default::default() + }, + vec!["c", "d"], + ) + .await?; + + // Selectors: KeySelector::new for both begin and end, with various offsets + // Begin: KeySelector::new(b"a", true, 2) -> first_greater_or_equal("a") + 2 -> "c" + // End: KeySelector::new(b"g", false, 0) -> first_greater_than("g") + 0 -> "h" + // Range: ["c", "h") -> "c", "d", "e", "f", "g" + println!("Running: new(a,T,2) to new(g,F,0) -> expecting ['c', 'd', 'e', 'f', 'g']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::new(std::borrow::Cow::Borrowed(b"a"), true, 2), + end: KeySelector::new(std::borrow::Cow::Borrowed(b"g"), false, 0), + ..Default::default() + }, + vec!["c", "d", "e", "f", "g"], + ) + .await?; + + // Selectors: KeySelector::new(b"a", true, 0) and KeySelector::new(b"g", false, -2) + // Begin: KeySelector::new(b"a", true, 0) -> FGOE("a") + 0 -> "a" + // End: KeySelector::new(b"g", false, -2) -> FGT("g") - 2 -> "h" - 2 -> "d" + // Range: ["a", "d") -> "a", "b", "c" + println!("Running: new(a,T,0) to new(g,F,-2) -> expecting ['a', 'b', 'c']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::new(std::borrow::Cow::Borrowed(b"a"), true, 0), + end: KeySelector::new(std::borrow::Cow::Borrowed(b"g"), false, -2), + ..Default::default() + }, + vec!["a", "b", "c"], + ) + .await?; + + // Selectors resolve to the same key, resulting in an empty range + // Begin: KeySelector::first_greater_or_equal(b"d") -> "d" + // End: KeySelector::first_greater_than(b"c") -> "d" (equivalent to first_greater_or_equal(b"d")) + // Range: ["d", "d") -> empty + println!("Running: FGOE(\"d\") to FGT(\"c\") -> expecting []"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"d"), + end: KeySelector::first_greater_than(b"c"), + ..Default::default() + }, + vec![], + ) + .await?; + + // Selectors: last_less_or_equal (begin) and first_greater_than (end) + // Begin: KeySelector::last_less_or_equal(b"c") -> "c" + // End: KeySelector::first_greater_than(b"f") -> "g" + // Range: ["c", "g") -> "c", "d", "e", "f" + println!("Running: LLOE(\"c\") to FGT(\"f\") -> expecting ['c', 'd', 'e', 'f']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_or_equal(b"c"), + end: KeySelector::first_greater_than(b"f"), + ..Default::default() + }, + vec!["c", "d", "e", "f"], + ) + .await?; + + // Selectors: last_less_than (begin) and last_less_than (end) + // Begin: KeySelector::last_less_than(b"c") -> "b" + // End: KeySelector::last_less_than(b"f") -> "e" + // Range: ["b", "e") -> "b", "c", "d" + println!("Running: LLT(\"c\") to LLT(\"f\") -> expecting ['b', 'c', 'd']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_than(b"c"), + end: KeySelector::last_less_than(b"f"), + ..Default::default() + }, + vec!["b", "c", "d"], + ) + .await?; + + // Selectors: last_less_than (begin) and last_less_or_equal (end) + // Begin: KeySelector::last_less_than(b"c") -> "b" + // End: KeySelector::last_less_or_equal(b"f") -> "f" + // Range: ["b", "f") -> "b", "c", "d", "e" + println!("Running: LLT(\"c\") to LLOE(\"f\") -> expecting ['b', 'c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_than(b"c"), + end: KeySelector::last_less_or_equal(b"f"), + ..Default::default() + }, + vec!["b", "c", "d", "e"], + ) + .await?; + + // Selectors: last_less_than (begin) and first_greater_than (end) + // Begin: KeySelector::last_less_than(b"c") -> "b" + // End: KeySelector::first_greater_than(b"f") -> "g" + // Range: ["b", "g") -> "b", "c", "d", "e", "f" + println!("Running: LLT(\"c\") to FGT(\"f\") -> expecting ['b', 'c', 'd', 'e', 'f']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_than(b"c"), + end: KeySelector::first_greater_than(b"f"), + ..Default::default() + }, + vec!["b", "c", "d", "e", "f"], + ) + .await?; + + // Selectors: last_less_than (begin) and first_greater_or_equal (end) + // Begin: KeySelector::last_less_than(b"c") -> "b" + // End: KeySelector::first_greater_or_equal(b"f") -> "f" + // Range: ["b", "f") -> "b", "c", "d", "e" + println!("Running: LLT(\"c\") to FGOE(\"f\") -> expecting ['b', 'c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_than(b"c"), + end: KeySelector::first_greater_or_equal(b"f"), + ..Default::default() + }, + vec!["b", "c", "d", "e"], + ) + .await?; + + // Selectors: last_less_or_equal (begin) and last_less_than (end) + // Begin: KeySelector::last_less_or_equal(b"c") -> "c" + // End: KeySelector::last_less_than(b"f") -> "e" + // Range: ["c", "e") -> "c", "d" + println!("Running: LLOE(\"c\") to LLT(\"f\") -> expecting ['c', 'd']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_or_equal(b"c"), + end: KeySelector::last_less_than(b"f"), + ..Default::default() + }, + vec!["c", "d"], + ) + .await?; + + // Selectors: last_less_or_equal (begin) and last_less_or_equal (end) + // Begin: KeySelector::last_less_or_equal(b"c") -> "c" + // End: KeySelector::last_less_or_equal(b"f") -> "f" + // Range: ["c", "f") -> "c", "d", "e" + println!("Running: LLOE(\"c\") to LLOE(\"f\") -> expecting ['c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_or_equal(b"c"), + end: KeySelector::last_less_or_equal(b"f"), + ..Default::default() + }, + vec!["c", "d", "e"], + ) + .await?; + + // Selectors: last_less_or_equal (begin) and first_greater_or_equal (end) + // Begin: KeySelector::last_less_or_equal(b"c") -> "c" + // End: KeySelector::first_greater_or_equal(b"f") -> "f" + // Range: ["c", "f") -> "c", "d", "e" + println!("Running: LLOE(\"c\") to FGOE(\"f\") -> expecting ['c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::last_less_or_equal(b"c"), + end: KeySelector::first_greater_or_equal(b"f"), + ..Default::default() + }, + vec!["c", "d", "e"], + ) + .await?; + + // Selectors: first_greater_than (begin) and last_less_or_equal (end) + // Begin: KeySelector::first_greater_than(b"c") -> "d" + // End: KeySelector::last_less_or_equal(b"f") -> "f" + // Range: ["d", "f") -> "d", "e" + println!("Running: FGT(\"c\") to LLOE(\"f\") -> expecting ['d', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_than(b"c"), + end: KeySelector::last_less_or_equal(b"f"), + ..Default::default() + }, + vec!["d", "e"], + ) + .await?; + + // Selectors: first_greater_than (begin) and first_greater_than (end) + // Begin: KeySelector::first_greater_than(b"c") -> "d" + // End: KeySelector::first_greater_than(b"f") -> "g" + // Range: ["d", "g") -> "d", "e", "f" + println!("Running: FGT(\"c\") to FGT(\"f\") -> expecting ['d', 'e', 'f']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_than(b"c"), + end: KeySelector::first_greater_than(b"f"), + ..Default::default() + }, + vec!["d", "e", "f"], + ) + .await?; + + // Selectors: first_greater_than (begin) and first_greater_or_equal (end) + // Begin: KeySelector::first_greater_than(b"c") -> "d" + // End: KeySelector::first_greater_or_equal(b"f") -> "f" + // Range: ["d", "f") -> "d", "e" + println!("Running: FGT(\"c\") to FGOE(\"f\") -> expecting ['d', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_than(b"c"), + end: KeySelector::first_greater_or_equal(b"f"), + ..Default::default() + }, + vec!["d", "e"], + ) + .await?; + + // Selectors: first_greater_or_equal (begin) and last_less_than (end) + // Begin: KeySelector::first_greater_or_equal(b"c") -> "c" + // End: KeySelector::last_less_than(b"f") -> "e" + // Range: ["c", "e") -> "c", "d" + println!("Running: FGOE(\"c\") to LLT(\"f\") -> expecting ['c', 'd']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"c"), + end: KeySelector::last_less_than(b"f"), + ..Default::default() + }, + vec!["c", "d"], + ) + .await?; + + // Selectors: first_greater_or_equal (begin) and last_less_or_equal (end) + // Begin: KeySelector::first_greater_or_equal(b"c") -> "c" + // End: KeySelector::last_less_or_equal(b"f") -> "f" + // Range: ["c", "f") -> "c", "d", "e" + println!("Running: FGOE(\"c\") to LLOE(\"f\") -> expecting ['c', 'd', 'e']"); + run_range_query_test( + db, + RangeOption { + begin: KeySelector::first_greater_or_equal(b"c"), + end: KeySelector::last_less_or_equal(b"f"), + ..Default::default() + }, + vec!["c", "d", "e"], + ) + .await?; + + println!("All key selector examples ran successfully!"); + Ok(()) +} + +#[tokio::main] +async fn main() { + foundationdb::boot().expect("failed to initialize FoundationDB"); + // The network is stopped and joined automatically at process exit, which is + // fine for tests and short-lived tools like this example. In a production + // application, prefer a clean teardown: the network thread is the event loop + // driving every transaction and you may still have on-going operations at + // exit time. Finish or cancel your work, drop the Database handles, then + // call `foundationdb::api::stop_network()` yourself (terminal: any + // FoundationDB use afterwards fails with error 2025). + + match Database::default() { + Ok(db) => { + if let Err(e) = key_selector_example(&db).await { + eprintln!("Error in key_selector_example: {e:?}"); + // Consider panic! or std::process::exit(1) for CI environments + } + } + Err(e) => { + eprintln!("Failed to open FDB database: {e:?}"); + // Consider panic! or std::process::exit(1) + } + } +} diff --git a/bindings/rust/foundationdb/examples/multi_version.rs b/bindings/rust/foundationdb/examples/multi_version.rs new file mode 100644 index 00000000000..0e396f8392d --- /dev/null +++ b/bindings/rust/foundationdb/examples/multi_version.rs @@ -0,0 +1,101 @@ +use byteorder::ByteOrder; +use foundationdb::api::FdbApiBuilder; +use foundationdb::options::NetworkOption; +use foundationdb::tuple::Subspace; +use foundationdb::{Database, FdbBindingError, options}; + +/// This example demonstrate usage of multi_version compatibility client. +/// +/// While you still need to compile the crate with a specific FoundationDB library version, +/// it allows you to connect to a cluster with a different API version. Be aware that using +/// this feature might lead to divergent behaviors. +/// +/// Ref: https://apple.github.io/foundationdb/api-general.html#multi-version-client-api +const NETWORK_OPTION_EXTERNAL_CLIENT_DIRECTORY: &str = + "FDB_NETWORK_OPTION_EXTERNAL_CLIENT_DIRECTORY"; + +#[tokio::main] +async fn main() { + let mut network_builder = FdbApiBuilder::default() + .build() + .expect("Failed to build API"); + // You can either use FoundationDB network option through environment variables + // or through network options in code. + // directory specified should contain at least one libfdb.so + if std::env::var(NETWORK_OPTION_EXTERNAL_CLIENT_DIRECTORY).is_err() { + network_builder = network_builder + .set_option(NetworkOption::ExternalClientDirectory( + "/usr/lib/foundationdb/".to_string(), + )) + .expect("Failed to add external library directory"); + } + network_builder.boot().expect("failed to start the network"); + // The network is stopped and joined automatically at process exit, which is + // fine for tests and short-lived tools like this example. In a production + // application, prefer a clean teardown: the network thread is the event loop + // driving every transaction and you may still have on-going operations at + // exit time. Finish or cancel your work, drop the Database handles, then + // call `foundationdb::api::stop_network()` yourself (terminal: any + // FoundationDB use afterwards fails with error 2025). + + // You can replace `None` with an `Option` with the path to your `fdb.cluster` file + let db = Database::new_compat(None) + .await + .expect("failed to get database"); + db.set_option(options::DatabaseOption::TransactionTimeout(5000)) + .expect("failed to set transaction timeout"); + db.set_option(options::DatabaseOption::TransactionRetryLimit(3)) + .expect("failed to set transaction retry limit"); + + // used to catch the first cluster_version_changed error when using external clients + // when using external clients, it will throw cluster_version_changed for the first time establish the connection to + // the cluster. Thus, we catch it by doing a get version request to establish the connection + // The 3000ms timeout is a guard to avoid waiting forever when the cli cannot talk to any coordinators + let _ = db + .run(|trx, _| async move { + trx.set_option(options::TransactionOption::Timeout(3000))?; + let maybe_version = trx.get_read_version().await; + + match maybe_version { + Ok(_) => Ok(()), + // 1039: cluster_version_changed + Err(err) if err.code() == 1039 => Err(FdbBindingError::from(err)), + Err(err) => Err(FdbBindingError::NonRetryableFdbError(err)), + } + }) + .await; + + let key = Subspace::all() + .subspace(&"examples") + .pack(&"multi_version_incr"); + + // increment or create key with "1" + db.run(|trx, _| { + let key = key.clone(); + async move { + let mut buf = [0u8; 8]; + byteorder::LE::write_i64(&mut buf, 1); + trx.atomic_op(&key, &buf, options::MutationType::Add); + Ok::<_, FdbBindingError>(()) + } + }) + .await + .expect("could not commit"); + + // read counter value + let raw_counter = db + .run(|trx, _| { + let key = key.clone(); + async move { + let result = trx.get(&key, true).await?; + Ok::<_, FdbBindingError>(result) + } + }) + .await + .expect("could not read key") + .expect("no value found"); + + let counter = byteorder::LE::read_i64(raw_counter.as_ref()); + dbg!(counter); + assert!(counter > 0); +} diff --git a/bindings/rust/foundationdb/examples/versionstamp.rs b/bindings/rust/foundationdb/examples/versionstamp.rs new file mode 100644 index 00000000000..4b7a3f87b48 --- /dev/null +++ b/bindings/rust/foundationdb/examples/versionstamp.rs @@ -0,0 +1,148 @@ +use foundationdb::{ + Database, FdbBindingError, FdbError, RangeOption, options, + tuple::{Subspace, Versionstamp, pack, pack_with_versionstamp, unpack}, +}; +use futures::StreamExt; + +#[tokio::main] +async fn main() { + foundationdb::boot().expect("failed to initialize FoundationDB"); + // The network is stopped and joined automatically at process exit, which is + // fine for tests and short-lived tools like this example. In a production + // application, prefer a clean teardown: the network thread is the event loop + // driving every transaction and you may still have on-going operations at + // exit time. Finish or cancel your work, drop the Database handles, then + // call `foundationdb::api::stop_network()` yourself (terminal: any + // FoundationDB use afterwards fails with error 2025). + + run_versionstamp_key_example() + .await + .expect("failed to run versionstamp example"); + + run_versionstamp_value_example() + .await + .expect("failed to run versionstamp example"); +} + +async fn run_versionstamp_key_example() -> Result<(), FdbBindingError> { + println!("running example for setting versionstamped keys"); + // Using versionstamps in order to create a sequential path. + let db = Database::default()?; + db.set_option(options::DatabaseOption::TransactionTimeout(5000))?; + db.set_option(options::DatabaseOption::TransactionRetryLimit(3))?; + + let subspace = Subspace::all().subspace(&"versionstamp_example"); + let (from, to) = subspace.range(); + let trx_clear = db.create_trx()?; + trx_clear.clear_range(&from, &to); + trx_clear.commit().await.map_err(FdbError::from)?; + + // Handles cloned from the runner transaction share one user-version allocator. + // Explicit Versionstamp::incomplete(42) remains supported, but can collide. + db.run(|trx, _| { + let subspace = subspace.clone(); + async move { + let component_a = trx.clone(); + let component_b = trx.clone(); + + // Allocate inside the retry closure because retries restart allocation at zero. + for (component, value) in [ + (&component_a, "component_a_1"), + (&component_b, "component_b_1"), + (&component_a, "component_a_2"), + ] { + let versionstamp = Versionstamp::incomplete(component.allocate_user_version()?); + let key = subspace.pack_with_versionstamp(&("prefix", &versionstamp)); + component.atomic_op( + &key, + &pack(&value), + options::MutationType::SetVersionstampedKey, + ); + } + + Ok::<_, FdbBindingError>(()) + } + }) + .await?; + + // The three keys share one 10-byte transaction version and have user versions 0, 1, and 2. + + let trx = db.create_trx()?; + let range = RangeOption::from((from, to)); + let mut kvs = trx.get_ranges_keyvalues(range, false); + while let Some(kv) = kvs.next().await { + let kv = kv?; + let (_, v) = subspace + .unpack::<(String, Versionstamp)>(kv.key()) + .expect("failed to unpack key"); + let value = unpack::(kv.value()).expect("failed to unpack value"); + println!( + "{:?} {}: {}", + v.transaction_version(), + v.user_version(), + value + ); + } + Ok(()) +} + +async fn run_versionstamp_value_example() -> Result<(), FdbBindingError> { + println!("running example for setting versionstamped values"); + // You can use versionstamps in values too, for example to point to a versionstamped key. + let db = Database::default()?; + db.set_option(options::DatabaseOption::TransactionTimeout(5000))?; + db.set_option(options::DatabaseOption::TransactionRetryLimit(3))?; + + let subspace = Subspace::all().subspace(&"versionstamp_example"); + let (from, to) = subspace.range(); + let trx_clear = db.create_trx()?; + trx_clear.clear_range(&from, &to); + trx_clear.commit().await.map_err(FdbError::from)?; + + // In our transaction we will create a versionstamped key, and then reference it in another + // known "index" key. + let index_key = subspace.pack(&"index"); + db.run(|trx, _| { + let subspace = subspace.clone(); + let index_key = index_key.clone(); + async move { + // Reuse one allocated stamp for the data key and its index reference. + let versionstamp = Versionstamp::incomplete(trx.allocate_user_version()?); + let key_tuple = ("data", &versionstamp); + let key = subspace.pack_with_versionstamp(&key_tuple); + + trx.atomic_op( + &key, + &pack(&"some value"), + options::MutationType::SetVersionstampedKey, + ); + trx.atomic_op( + &index_key, + &pack_with_versionstamp(&key_tuple), + options::MutationType::SetVersionstampedValue, + ); + + Ok::<_, FdbBindingError>(()) + } + }) + .await?; + + // Now we created our versionstamped key and a reference to it. + // We can read the index key and get the versionstamped key back. + let trx = db.create_trx()?; + let index_kv = trx + .get(&index_key, false) + .await? + .expect("didn't find index"); + let versionstamped_key = + unpack::<(String, Versionstamp)>(&index_kv).expect("failed to unpack value"); + + // notice that we don't pack with versionstamp here because the versionstamp we received is complete. + let versionstamped_value = trx + .get(&subspace.pack(&versionstamped_key), false) + .await? + .expect("didn't find reference"); + let value = unpack::(&versionstamped_value).expect("failed to unpack value"); + println!("got back value {value}"); + Ok(()) +} diff --git a/bindings/rust/foundationdb/src/api.rs b/bindings/rust/foundationdb/src/api.rs new file mode 100644 index 00000000000..3dd41e7367b --- /dev/null +++ b/bindings/rust/foundationdb/src/api.rs @@ -0,0 +1,532 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Configuration of foundationDB API and Network +//! +//! Provides a safe wrapper around the following C API calls: +//! +//! - [API versioning](https://apple.github.io/foundationdb/api-c.html#api-versioning) +//! - [Network](https://apple.github.io/foundationdb/api-c.html#network) +//! +//! The network lifecycle is tracked by a Rust-library-local state machine +//! (`Uninitialized -> ApiVersionSelected -> Running -> Stopped`). Booting is safe +//! and idempotent, the network is started lazily by [`crate::Database`] +//! constructors if needed, and it runs until process exit: an atexit hook stops it +//! and joins the network thread, as required by the C API. Applications that +//! manage their own shutdown can call [`disable_stop_on_exit`] and/or +//! [`stop_network`]. +//! +//!
+//! +//! The automatic stop at process exit is meant for tests and short-lived tools. +//! In a production application, prefer a clean teardown: the network thread is +//! the event loop driving every transaction and you may still have on-going +//! operations at exit time. Finish or cancel your work, drop the +//! [`crate::Database`] handles, then call [`stop_network`] yourself. +//! +//!
+ +use std::io::Write; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use std::sync::{Mutex, MutexGuard, PoisonError}; +use std::thread::JoinHandle; + +use crate::options::NetworkOption; +use crate::{FdbError, FdbResult, error}; +use foundationdb_sys as fdb_sys; + +/// Returns the max api version of the underlying Fdb C API Client +pub fn get_max_api_version() -> i32 { + unsafe { fdb_sys::fdb_get_max_api_version() } +} + +// FDB C error codes used by the lifecycle state machine +const ERR_API_VERSION_UNSET: i32 = 2200; +const ERR_API_VERSION_ALREADY_SET: i32 = 2201; +const ERR_API_VERSION_NOT_SUPPORTED: i32 = 2203; +const ERR_NETWORK_ALREADY_SETUP: i32 = 2009; +const ERR_NETWORK_CANNOT_BE_RESTARTED: i32 = 2025; + +/// Lifecycle of this Rust library's FoundationDB client. +/// +/// The C API allows selecting the API version and setting up the network once per +/// process, and the network can never be restarted once stopped. Every FFI call +/// involved lives in exactly one transition below, serialized by the `NETWORK` +/// mutex. +enum NetworkLifecycle { + Uninitialized, + ApiVersionSelected { + api_version: i32, + }, + Running { + api_version: i32, + handle: JoinHandle<()>, + }, + Stopped { + api_version: i32, + }, +} + +static NETWORK: Mutex = Mutex::new(NetworkLifecycle::Uninitialized); +// Published after successful or verified shared API selection and never changed. +// Version-dependent transaction operations must not take the lifecycle lock +// (network shutdown holds it while joining the network thread). +static SELECTED_API_VERSION: AtomicI32 = AtomicI32::new(0); +static STOP_ON_EXIT: AtomicBool = AtomicBool::new(true); +// Last error returned by fdb_run_network (0 = none). An atomic, not a mutex: the +// network thread must never take a lock shared with the thread joining it. +static NETWORK_RUN_ERROR: AtomicI32 = AtomicI32::new(0); + +pub(crate) fn selected_api_version() -> i32 { + SELECTED_API_VERSION.load(Ordering::Acquire) +} + +fn lock_network() -> MutexGuard<'static, NetworkLifecycle> { + // State is only mutated after the corresponding FFI call succeeded, so a + // poisoned lock still guards a consistent state. The one exception is a + // thread-spawn panic in start_network after fdb_setup_network succeeded + // (OS resource exhaustion): the state then stays ApiVersionSelected while + // the network is set up, and any retry fails with 2009. Deliberately not + // handled: nothing sensible can be done at that point. + NETWORK.lock().unwrap_or_else(PoisonError::into_inner) +} + +impl NetworkLifecycle { + fn select_api_version(&mut self, version: i32) -> FdbResult<()> { + match self { + NetworkLifecycle::Uninitialized => { + let header_version = fdb_sys::FDB_API_VERSION as i32; + let selection = + unsafe { fdb_sys::fdb_select_api_version_impl(version, header_version) }; + // Another Rust shared library has its own lifecycle state. Only + // the shared C library can confirm both runtime semantics and + // the header version controlling its versioned entry points. + let selection = if selection == ERR_API_VERSION_ALREADY_SET + && fdb_sys::selected_api_versions() == Some((version, header_version)) + { + 0 + } else { + selection + }; + error::eval(selection).inspect_err(|e| { + // generally means the local libfdb doesn't support the requested version + if e.code() == ERR_API_VERSION_NOT_SUPPORTED { + let max_api_version = get_max_api_version(); + if max_api_version < fdb_sys::FDB_API_VERSION as i32 { + eprintln!( + "The version of FoundationDB binding requested '{}' is not supported", + fdb_sys::FDB_API_VERSION + ); + eprintln!( + "by the installed FoundationDB C library. Maximum supported version by the local library is {max_api_version}" + ); + } + } + })?; + *self = NetworkLifecycle::ApiVersionSelected { + api_version: version, + }; + SELECTED_API_VERSION.store(version, Ordering::Release); + Ok(()) + } + NetworkLifecycle::ApiVersionSelected { api_version } + | NetworkLifecycle::Running { api_version, .. } + | NetworkLifecycle::Stopped { api_version } => { + if *api_version == version { + Ok(()) + } else { + Err(FdbError::from_code(ERR_API_VERSION_ALREADY_SET)) + } + } + } + } + + fn set_network_option(&mut self, option: NetworkOption) -> FdbResult<()> { + match self { + // unreachable through the public API: a NetworkBuilder only exists once + // an api version is selected + NetworkLifecycle::Uninitialized => Err(FdbError::from_code(ERR_API_VERSION_UNSET)), + NetworkLifecycle::ApiVersionSelected { .. } => { + // SAFETY: the api version is selected and the network is not set up + // yet; calls are serialized by the NETWORK mutex. + unsafe { option.apply() } + } + NetworkLifecycle::Running { .. } => Err(FdbError::from_code(ERR_NETWORK_ALREADY_SETUP)), + NetworkLifecycle::Stopped { .. } => { + Err(FdbError::from_code(ERR_NETWORK_CANNOT_BE_RESTARTED)) + } + } + } + + fn start_network(&mut self) -> FdbResult<()> { + if matches!(self, NetworkLifecycle::Uninitialized) { + self.select_api_version(fdb_sys::FDB_API_VERSION as i32)?; + } + match self { + NetworkLifecycle::Uninitialized => unreachable!("the api version was just selected"), + NetworkLifecycle::ApiVersionSelected { api_version } => { + let api_version = *api_version; + error::eval(unsafe { fdb_sys::fdb_setup_network() })?; + // Registered once the network is set up, i.e. after libfdb_c was + // loaded: atexit handlers run in LIFO order, so ours stops and joins + // the network before any exit-time cleanup of the library itself. + if unsafe { libc::atexit(network_atexit_hook) } != 0 { + eprintln!( + "failed to register the FoundationDB atexit hook; the network will not be stopped automatically at process exit" + ); + } + let handle = std::thread::Builder::new() + .name("fdb-network".to_string()) + .spawn(run_network_thread) + .expect("failed to spawn the fdb-network thread"); + *self = NetworkLifecycle::Running { + api_version, + handle, + }; + Ok(()) + } + NetworkLifecycle::Running { .. } => Ok(()), + NetworkLifecycle::Stopped { .. } => { + Err(FdbError::from_code(ERR_NETWORK_CANNOT_BE_RESTARTED)) + } + } + } + + fn stop_network(&mut self) -> FdbResult<()> { + match self { + NetworkLifecycle::Uninitialized => Ok(()), + NetworkLifecycle::ApiVersionSelected { api_version } => { + // Nothing is running, but an explicit stop must be terminal so that + // a later lazy boot cannot restart the network behind the caller's + // back. + *self = NetworkLifecycle::Stopped { + api_version: *api_version, + }; + Ok(()) + } + NetworkLifecycle::Running { .. } => { + // Stop before taking the variant apart: on error the state stays + // Running, handle included. + error::eval(unsafe { fdb_sys::fdb_stop_network() })?; + let NetworkLifecycle::Running { + api_version, + handle, + } = std::mem::replace(self, NetworkLifecycle::Uninitialized) + else { + unreachable!("state was just matched as Running"); + }; + if handle.join().is_err() { + // run_network_thread cannot panic; keep the exit path panic-free + // anyway (eprintln! panics if the stderr write fails). + let _ = writeln!(std::io::stderr(), "the fdb-network thread panicked"); + } + *self = NetworkLifecycle::Stopped { api_version }; + Ok(()) + } + NetworkLifecycle::Stopped { .. } => Ok(()), + } + } +} + +/// Body of the "fdb-network" thread. +/// +/// Invariant: this function must never lock `NETWORK`, as `stop_network` joins the +/// thread while holding the lock. +fn run_network_thread() { + if let Err(err) = error::eval(unsafe { fdb_sys::fdb_run_network() }) { + NETWORK_RUN_ERROR.store(err.code(), Ordering::Release); + let _ = writeln!( + std::io::stderr(), + "fdb_run_network returned an error: {err}" + ); + } +} + +/// Stops the network at process exit, as required by the C API contract. +/// +/// Panic-free: unwinding out of an `extern "C"` function would abort with a less +/// useful message. +extern "C" fn network_atexit_hook() { + if !STOP_ON_EXIT.load(Ordering::Acquire) { + return; + } + if let Err(err) = lock_network().stop_network() { + // Exiting with the network still running is undefined behavior, aborting + // is not. + let _ = writeln!( + std::io::stderr(), + "failed to stop the FoundationDB network at exit: {err}" + ); + std::process::abort(); + } +} + +/// Stops the FoundationDB network and joins the network thread. +/// +/// This is terminal for the process: once it returns `Ok`, every FoundationDB +/// operation fails with error 2025 (`network_cannot_be_restarted`), including in +/// other threads. It is idempotent. One exception to terminality: if the client +/// was never initialized at all (no boot, no API version selected, no `Database` +/// created), the call is a plain no-op and a later boot still succeeds. +/// +/// Tests and short-lived tools can simply rely on the automatic stop at process +/// exit. In a production application, prefer calling this yourself as part of a +/// clean shutdown sequence, once on-going operations are finished and the +/// [`crate::Database`] handles are dropped. +/// +/// Must not be called from the network thread itself (e.g. from a future +/// callback), as it joins that thread. +/// +/// # Examples +/// +/// ```rust +/// foundationdb::boot().expect("failed to initialize FoundationDB"); +/// // ... use FoundationDB ... +/// foundationdb::api::stop_network().expect("failed to stop the network"); +/// // The stop is terminal: any use of FoundationDB now fails with error 2025. +/// assert_eq!(foundationdb::boot().err().map(|e| e.code()), Some(2025)); +/// ``` +pub fn stop_network() -> FdbResult<()> { + lock_network().stop_network() +} + +/// Disables the automatic network stop at process exit. +/// +/// The atexit hook registered when the network starts becomes a no-op. Use it +/// when your application owns its shutdown sequence and calls [`stop_network`] +/// itself: the C API requires the network to be stopped and joined before a +/// normal process exit. +pub fn disable_stop_on_exit() { + STOP_ON_EXIT.store(false, Ordering::Release); +} + +/// Returns the error reported by the network event loop, if it stopped with one. +pub fn network_run_error() -> Option { + match NETWORK_RUN_ERROR.load(Ordering::Acquire) { + 0 => None, + code => Some(FdbError::from_code(code)), + } +} + +/// Starts the network with the default API version if nothing was booted yet. +/// +/// Lazy-boot entry point used by `Database` constructors. +pub(crate) fn ensure_network_started() -> FdbResult<()> { + lock_network().start_network() +} + +/// A Builder with which different versions of the Fdb C API can be initialized +/// +/// ``` +/// foundationdb::api::FdbApiBuilder::default().build().expect("fdb api initialized"); +/// ``` +pub struct FdbApiBuilder { + runtime_version: i32, +} + +impl FdbApiBuilder { + /// The version of run-time behavior the API is requested to provide. + pub fn runtime_version(&self) -> i32 { + self.runtime_version + } + + /// Set the version of run-time behavior the API is requested to provide. + /// + /// Must be less than or equal to header_version, `foundationdb_sys::FDB_API_VERSION`, and should almost always be equal. + /// Language bindings which themselves expose API versioning will usually pass the version requested by the application. + /// Versionstamped mutations require runtime API 520 or later in this binding. + pub fn set_runtime_version(mut self, version: i32) -> Self { + self.runtime_version = version; + self + } + + /// Selects the foundationDB API version and returns a `NetworkBuilder`. + /// + /// Re-selecting this Rust library's existing version is a no-op. With current + /// C headers and their matching library, a version selected by another shared + /// library is also accepted when both runtime and header versions match. + /// Historical headers cannot verify external selection and return error 2201. + /// This selects API behavior only; it does not adopt or start another library's + /// network thread. + /// + /// # Errors + /// + /// - error 2201 (`api_version_already_set`) if a different runtime or header + /// version was selected before, or an external selection cannot be verified + /// - error 2203 (`api_version_not_supported`) if the installed libfdb_c does + /// not support the requested version + pub fn build(self) -> FdbResult { + lock_network().select_api_version(self.runtime_version)?; + Ok(NetworkBuilder { _private: () }) + } +} + +impl Default for FdbApiBuilder { + fn default() -> Self { + FdbApiBuilder { + runtime_version: fdb_sys::FDB_API_VERSION as i32, + } + } +} + +/// A Builder to configure network options and start the network event loop. +/// +/// ``` +/// use foundationdb::api::FdbApiBuilder; +/// +/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); +/// network_builder.boot().expect("fdb network running"); +/// ``` +pub struct NetworkBuilder { + _private: (), +} + +impl NetworkBuilder { + /// Set network options. + /// + /// # Errors + /// + /// - error 2009 (`network_already_setup`) once the network is running + /// - error 2025 (`network_cannot_be_restarted`) once the network is stopped + pub fn set_option(self, option: NetworkOption) -> FdbResult { + lock_network().set_network_option(option)?; + Ok(self) + } + + /// Starts the FoundationDB network event loop in a dedicated thread. + /// + /// Safe and idempotent: booting an already running network is a no-op. The + /// network runs until process exit, where an atexit hook stops it and joins the + /// network thread, or until an explicit [`stop_network`] call. + /// + /// # Errors + /// + /// - error 2025 (`network_cannot_be_restarted`) if the network was stopped + /// + /// # Examples + /// + /// ```rust + /// use foundationdb::api::FdbApiBuilder; + /// + /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); + /// network_builder.boot().expect("fdb network running"); + /// // do some interesting things with the API... + /// ``` + /// + /// ```rust + /// use foundationdb::api::FdbApiBuilder; + /// + /// #[tokio::main] + /// async fn main() { + /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); + /// network_builder.boot().expect("fdb network running"); + /// // do some interesting things with the API... + /// } + /// ``` + pub fn boot(self) -> FdbResult { + lock_network().start_network()?; + Ok(NetworkAutoStop { _private: () }) + } +} + +/// Kept for backward compatibility: dropping it does nothing. +/// +/// The network runs until process exit (an atexit hook stops it and joins the +/// network thread), or until an explicit [`stop_network`] call. +pub struct NetworkAutoStop { + _private: (), +} + +impl Drop for NetworkAutoStop { + fn drop(&mut self) { + // Intentionally inert: the network is stopped at process exit (or by an + // explicit stop_network()), never on guard drop. Stopping on drop would + // make any later use of FoundationDB in the process fail with error 2025. + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_max_api() { + assert!(get_max_api_version() > 0); + } + + // Only exercises the FFI-free arms of the transition table, on locally + // constructed states: lib unit tests share one process, so they must neither + // touch the NETWORK static nor select a real api version. + #[test] + fn test_transition_table_ffi_free_arms() { + // ApiVersionSelected + let mut state = NetworkLifecycle::ApiVersionSelected { api_version: 710 }; + state + .select_api_version(710) + .expect("same version must be idempotent"); + assert_eq!( + state.select_api_version(620).unwrap_err().code(), + ERR_API_VERSION_ALREADY_SET + ); + state.stop_network().expect("stop before start must be Ok"); + assert!(matches!( + state, + NetworkLifecycle::Stopped { api_version: 710 } + )); + + // Running (dummy finished thread; stop_network is never called on it) + let mut state = NetworkLifecycle::Running { + api_version: 710, + handle: std::thread::spawn(|| {}), + }; + state + .start_network() + .expect("start while running must be idempotent"); + assert_eq!( + state.select_api_version(620).unwrap_err().code(), + ERR_API_VERSION_ALREADY_SET + ); + assert_eq!( + state + .set_network_option(NetworkOption::TraceEnable(String::new())) + .unwrap_err() + .code(), + ERR_NETWORK_ALREADY_SETUP + ); + + // Stopped is terminal + let mut state = NetworkLifecycle::Stopped { api_version: 710 }; + assert_eq!( + state.start_network().unwrap_err().code(), + ERR_NETWORK_CANNOT_BE_RESTARTED + ); + assert_eq!( + state + .set_network_option(NetworkOption::TraceEnable(String::new())) + .unwrap_err() + .code(), + ERR_NETWORK_CANNOT_BE_RESTARTED + ); + state.stop_network().expect("stop must be idempotent"); + state + .select_api_version(710) + .expect("re-selecting the same version stays Ok once stopped"); + + // Uninitialized + let mut state = NetworkLifecycle::Uninitialized; + state.stop_network().expect("stopping nothing must be Ok"); + assert!(matches!(state, NetworkLifecycle::Uninitialized)); + assert_eq!( + state + .set_network_option(NetworkOption::TraceEnable(String::new())) + .unwrap_err() + .code(), + ERR_API_VERSION_UNSET + ); + } +} diff --git a/bindings/rust/foundationdb/src/cluster.rs b/bindings/rust/foundationdb/src/cluster.rs new file mode 100644 index 00000000000..f8d9ff9c9b6 --- /dev/null +++ b/bindings/rust/foundationdb/src/cluster.rs @@ -0,0 +1,109 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Implementations of the FDBCluster C API +//! +//! https://apple.github.io/foundationdb/api-c.html#cluster + +use std::convert::TryFrom; +use std::future::Future; +use std::ptr::NonNull; +use std::sync::Arc; + +use crate::future::*; +use crate::{Database, FdbError, FdbResult, error}; +use foundationdb_sys as fdb_sys; + +/// An opaque type that represents a Cluster in the FoundationDB C API. +#[derive(Clone)] +pub struct Cluster { + inner: Arc, +} +struct ClusterHandle(NonNull); +// The C API permits cross-thread use of cluster handles; Arc keeps the handle alive +// until the last Cluster clone is dropped. +unsafe impl Send for ClusterHandle {} +unsafe impl Sync for ClusterHandle {} +impl Drop for ClusterHandle { + fn drop(&mut self) { + unsafe { + fdb_sys::fdb_cluster_destroy(self.0.as_ptr()); + } + } +} + +impl Cluster { + pub fn new( + path: Option<&str>, + ) -> impl Future> + Send + Sync + Unpin { + let path_str = path.map(|path| std::ffi::CString::new(path).unwrap()); + let path_ptr = path_str + .as_ref() + .map(|path| path.as_ptr()) + .unwrap_or(std::ptr::null()); + let f = unsafe { FdbFuture::new(fdb_sys::fdb_create_cluster(path_ptr)) }; + drop(path_str); + f + } + + /// Returns an FdbFuture which will be set to an FDBCluster object. + /// + /// # Arguments + /// + /// * `path` - A string giving a local path of a cluster file (often called ‘fdb.cluster’) which contains connection information for the FoundationDB cluster. See `foundationdb::default_config_path()` + /// + pub fn from_path(path: &str) -> impl Future> + Send + Sync + Unpin { + Self::new(Some(path)) + } + + pub fn default() -> impl Future> { + Self::new(None) + } + + /// Returns an `FdbFuture` which will be set to an `Database` object. + pub fn create_database( + &self, + ) -> impl Future> + Send + Sync + Unpin { + unsafe { + FdbFuture::new(fdb_sys::fdb_cluster_create_database( + self.inner.0.as_ptr(), + b"DB" as *const _, + 2, + )) + } + } +} + +impl TryFrom for Cluster { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut v: *mut fdb_sys::FDBCluster = std::ptr::null_mut(); + error::eval(unsafe { fdb_sys::fdb_future_get_cluster(f.as_ptr(), &mut v) })?; + + Ok(Cluster { + inner: Arc::new(ClusterHandle(NonNull::new(v).expect( + "fdb_future_get_cluster to not return null if there is no error", + ))), + }) + } +} + +impl TryFrom for Database { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut v: *mut fdb_sys::FDBDatabase = std::ptr::null_mut(); + error::eval(unsafe { fdb_sys::fdb_future_get_database(f.as_ptr(), &mut v) })?; + + Ok(Database { + inner: NonNull::new(v) + .expect("fdb_future_get_database to not return null if there is no error"), + }) + } +} diff --git a/bindings/rust/foundationdb/src/database.rs b/bindings/rust/foundationdb/src/database.rs new file mode 100644 index 00000000000..9b6604033b1 --- /dev/null +++ b/bindings/rust/foundationdb/src/database.rs @@ -0,0 +1,578 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Implementations of the FDBDatabase C API +//! +//! + +use std::convert::TryInto; +use std::marker::PhantomData; +use std::pin::Pin; +use std::ptr::NonNull; +use std::time::{Duration, Instant}; + +use fdb_sys::if_cfg_api_versions; +use foundationdb_macros::cfg_api_versions; +use foundationdb_sys as fdb_sys; + +use crate::options; +use crate::transaction::*; +use crate::{FdbError, FdbResult, error}; + +use crate::error::{FdbBindingError, find_fdb_error}; +use futures::prelude::*; + +/// Whether any earlier attempt in this run may have committed. +/// +/// Once true, this remains true for every subsequent attempt, even if later +/// attempts fail with errors that are known not to have committed. +/// Convert it to a boolean with `bool::from` when checking the flag. +pub struct MaybeCommitted(bool); + +impl MaybeCommitted { + pub(crate) fn new(maybe_committed: bool) -> Self { + Self(maybe_committed) + } +} + +impl From for bool { + fn from(value: MaybeCommitted) -> Self { + value.0 + } +} + +/// Represents a FoundationDB database +/// +/// A mutable, lexicographically ordered mapping from binary keys to binary values. +/// +/// Modifications to a database are performed via transactions. +pub struct Database { + pub(crate) inner: NonNull, +} +unsafe impl Send for Database {} +unsafe impl Sync for Database {} +impl Drop for Database { + fn drop(&mut self) { + unsafe { + fdb_sys::fdb_database_destroy(self.inner.as_ptr()); + } + } +} + +#[cfg_api_versions(min = 610)] +impl Database { + /// Create a database for the given configuration path if any, or the default one. + /// + /// If the client was not initialized yet, the network is started with the + /// default API version, like [`crate::boot`] would. + pub fn new(path: Option<&str>) -> FdbResult { + crate::api::ensure_network_started()?; + let path_str = + path.map(|path| std::ffi::CString::new(path).expect("path to be convertible to CStr")); + let path_ptr = path_str + .as_ref() + .map(|path| path.as_ptr()) + .unwrap_or(std::ptr::null()); + let mut v: *mut fdb_sys::FDBDatabase = std::ptr::null_mut(); + let err = unsafe { fdb_sys::fdb_create_database(path_ptr, &mut v) }; + drop(path_str); // path_str own the CString that we are getting the ptr from + error::eval(err)?; + let ptr = + NonNull::new(v).expect("fdb_create_database to not return null if there is no error"); + // Safe because the database is constructed in this scope and we know it's + // a valid pointer. + Ok(unsafe { Self::new_from_pointer(ptr) }) + } + + /// Create a new FDBDatabase from a raw pointer. Users are expected to use the `new` method. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` is a valid pointer to an `FDBDatabase` object + /// obtained from the FoundationDB C API, and that the pointer is not aliased or used + /// after being passed to this function. + pub unsafe fn new_from_pointer(ptr: NonNull) -> Self { + Self { inner: ptr } + } + + /// Create a database for the given configuration path + pub fn from_path(path: &str) -> FdbResult { + Self::new(Some(path)) + } + + /// Create a database for the default configuration path + #[allow(clippy::should_implement_trait)] + pub fn default() -> FdbResult { + Self::new(None) + } +} + +#[cfg_api_versions(min = 730)] +impl Database { + /// Retrieve a client-side status information in a JSON format. + pub fn get_client_status( + &self, + ) -> impl Future> + Send + Sync + Unpin + use<> + { + unsafe { + crate::future::FdbFuture::new(fdb_sys::fdb_database_get_client_status( + self.inner.as_ptr(), + )) + } + } +} + +impl Database { + /// Create a database for the given configuration path + /// + /// This is a compatibility api. If you only use API version ≥ 610 you should + /// use `Database::new`, `Database::from_path` or `Database::default`. + pub async fn new_compat(path: Option<&str>) -> FdbResult { + crate::api::ensure_network_started()?; + if_cfg_api_versions!(min = 510, max = 600 => { + let cluster = crate::cluster::Cluster::new(path).await?; + let database = cluster.create_database().await?; + Ok(database) + } else { + Database::new(path) + }) + } + + /// Called to set an option an on `Database`. + pub fn set_option(&self, opt: options::DatabaseOption) -> FdbResult<()> { + unsafe { opt.apply(self.inner.as_ptr()) } + } + + /// Creates a new transaction on the given database. + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub fn create_trx(&self) -> FdbResult { + let mut trx: *mut fdb_sys::FDBTransaction = std::ptr::null_mut(); + let err = + unsafe { fdb_sys::fdb_database_create_transaction(self.inner.as_ptr(), &mut trx) }; + error::eval(err)?; + Ok(Transaction::new(NonNull::new(trx).expect( + "fdb_database_create_transaction to not return null if there is no error", + ))) + } + + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub(crate) fn create_retryable_trx(&self) -> FdbResult { + Ok(RetryableTransaction::new(self.create_trx()?)) + } + + /// `transact` returns a future which retries on error. It tries to resolve a future created by + /// caller-provided function `f` inside a retry loop, providing it with a newly created + /// transaction. After caller-provided future resolves, the transaction will be committed + /// automatically. + /// + /// # Warning: Hanging on Network/DNS failures + /// + /// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster + /// or if DNS resolution fails. This can cause `transact` to hang forever. + /// To prevent this, you should set [`options::DatabaseOption::TransactionTimeout`] or + /// [`options::DatabaseOption::TransactionRetryLimit`] on the [`Database`] object, or + /// [`options::TransactionOption::Timeout`] or [`options::TransactionOption::RetryLimit`] on the transaction + /// itself. + /// + /// Note that `TransactOption` also provides `retry_limit` and `time_out`, but these are + /// Rust-side budgets that are only checked *between* retries. If the C API hangs during a call + /// like `commit()` or `on_error()`, these budgets will not be reached. + /// + /// Once [Generic Associated Types](https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md) + /// lands in stable rust, the returned future of f won't need to be boxed anymore, also the + /// lifetime limitations around f might be lowered. + pub async fn transact(&self, mut f: F, options: TransactOption) -> Result + where + F: DatabaseTransact, + { + let is_idempotent = options.is_idempotent; + let time_out = options.time_out.map(|d| Instant::now() + d); + let retry_limit = options.retry_limit; + let mut tries: u32 = 0; + let mut trx = self.create_trx()?; + let mut can_retry = move || { + tries += 1; + retry_limit.map(|limit| tries < limit).unwrap_or(true) + && time_out.map(|t| Instant::now() < t).unwrap_or(true) + }; + loop { + let r = f.transact(trx).await; + f = r.0; + trx = r.1; + trx = match r.2 { + Ok(item) => match trx.commit().await { + Ok(_) => break Ok(item), + Err(e) => { + if (is_idempotent || !e.is_maybe_committed()) && can_retry() { + e.on_error().await? + } else { + break Err(F::Error::from(e.into())); + } + } + }, + Err(user_err) => match user_err.try_into_fdb_error() { + Ok(e) => { + if (is_idempotent || !e.is_maybe_committed()) && can_retry() { + trx.on_error(e).await? + } else { + break Err(F::Error::from(e)); + } + } + Err(user_err) => break Err(user_err), + }, + }; + } + } + + /// `transact_boxed` is a version of [`Database::transact`] that accepts a closure returning a + /// pinned, boxed future. + /// + /// # Warning: Hanging on Network/DNS failures + /// + /// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster + /// or if DNS resolution fails. This can cause `transact_boxed` to hang forever. + /// To prevent this, you should set [`options::DatabaseOption::TransactionTimeout`] or + /// [`options::DatabaseOption::TransactionRetryLimit`] on the [`Database`] object, or + /// [`options::TransactionOption::Timeout`] or [`options::TransactionOption::RetryLimit`] on the transaction + /// itself. + /// + /// Note that `TransactOption` also provides `retry_limit` and `time_out`, but these are + /// Rust-side budgets that are only checked *between* retries. If the C API hangs during a call + /// like `commit()` or `on_error()`, these budgets will not be reached. + pub fn transact_boxed<'trx, F, D, T, E>( + &'trx self, + data: D, + f: F, + options: TransactOption, + ) -> impl Future> + Send + 'trx + where + for<'a> F: FnMut( + &'a Transaction, + &'a mut D, + ) -> Pin> + Send + 'a>>, + E: TransactError, + F: Send + 'trx, + T: Send + 'trx, + E: Send + 'trx, + D: Send + 'trx, + { + self.transact( + boxed::FnMutBoxed { + f, + d: data, + m: PhantomData, + }, + options, + ) + } + + /// `transact_boxed_local` is a version of [`Database::transact`] that accepts a closure returning a + /// pinned, boxed future that is not `Send`. + /// + /// # Warning: Hanging on Network/DNS failures + /// + /// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster + /// or if DNS resolution fails. This can cause `transact_boxed_local` to hang forever. + /// To prevent this, you should set [`options::DatabaseOption::TransactionTimeout`] or + /// [`options::DatabaseOption::TransactionRetryLimit`] on the [`Database`] object, or + /// [`options::TransactionOption::Timeout`] or [`options::TransactionOption::RetryLimit`] on the transaction + /// itself. + /// + /// Note that `TransactOption` also provides `retry_limit` and `time_out`, but these are + /// Rust-side budgets that are only checked *between* retries. If the C API hangs during a call + /// like `commit()` or `on_error()`, these budgets will not be reached. + pub fn transact_boxed_local<'trx, F, D, T, E>( + &'trx self, + data: D, + f: F, + options: TransactOption, + ) -> impl Future> + 'trx + where + for<'a> F: + FnMut(&'a Transaction, &'a mut D) -> Pin> + 'a>>, + E: TransactError, + F: 'trx, + T: 'trx, + E: 'trx, + D: 'trx, + { + self.transact( + boxed_local::FnMutBoxedLocal { + f, + d: data, + m: PhantomData, + }, + options, + ) + } + + /// Runs a transactional function against this Database with retry logic. + /// The associated closure will be called until a non-retryable FDBError + /// is thrown or commit(), returns success. + /// + /// Users are **not** expected to keep reference to the `RetryableTransaction`. If a weak or strong + /// reference is kept by the user, the binding will throw an error. + /// + /// # Warning: retry + /// + /// It might retry indefinitely if the transaction is highly contentious. It is recommended to + /// set [`options::TransactionOption::RetryLimit`] or [`options::TransactionOption::Timeout`] on the transaction + /// if the task needs to be guaranteed to finish. These options can be safely set on every iteration of the closure. + /// + /// # Warning: Hanging on Network/DNS failures + /// + /// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster + /// or if DNS resolution fails. This can cause `run` to hang forever. + /// To prevent this, you should set [`options::DatabaseOption::TransactionTimeout`] or + /// [`options::DatabaseOption::TransactionRetryLimit`] on the [`Database`] object, or + /// [`options::TransactionOption::Timeout`] or [`options::TransactionOption::RetryLimit`] on the transaction + /// itself. + /// + /// # Warning: Maybe committed transactions + /// + /// As with other client/server databases, in some failure scenarios a client may be unable to determine + /// whether a transaction succeeded. You should make sure your closure is idempotent. + /// + /// The closure receives a [`MaybeCommitted`] flag indicating whether any earlier attempt + /// in this run may have committed. Once true, it stays true for all subsequent attempts. + /// + /// This one can be used as boolean with + /// ```ignore + /// db.run(|trx, maybe_committed| async { + /// if maybe_committed.into() { + /// // Handle the problem if needed + /// } + /// Ok::<_, FdbBindingError>(()) + /// }).await; + ///``` + /// + /// # Typed closure errors + /// + /// The closure may return [`FdbBindingError`] or another error type that + /// preserves an [`FdbError`] in its [`source()`](std::error::Error::source) + /// chain. Native `on_error` decides whether to retry and applies backoff, + /// timeout, and retry limits. A fatal closure error or exhausted retry is + /// returned with its original type and context. + /// + /// The error type must also implement `From` so that + /// transaction creation, commit, and ownership errors can be returned. + /// A closure that never names an error type is ambiguous; pin it on the + /// tail expression, for example `Ok::<_, FdbBindingError>(value)`. + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, closure)) + )] + pub async fn run(&self, closure: F) -> Result + where + F: Fn(RetryableTransaction, MaybeCommitted) -> Fut, + Fut: Future>, + E: std::error::Error + Send + Sync + 'static + From, + { + let mut transaction = self.create_retryable_trx().map_err(FdbBindingError::from)?; + let mut maybe_committed = false; + + loop { + let value = + match closure(transaction.clone(), MaybeCommitted::new(maybe_committed)).await { + Ok(value) => value, + Err(error) => { + let Some(fdb_error) = find_fdb_error(&error) else { + return Err(error); + }; + // Later errors cannot resolve an earlier uncertain commit. + maybe_committed |= fdb_error.is_maybe_committed(); + transaction = match transaction.on_error(fdb_error).await? { + Ok(transaction) => transaction, + Err(_) => return Err(error), + }; + continue; + } + }; + + match transaction.commit().await? { + Ok(_) => return Ok(value), + Err(error) => { + maybe_committed |= error.is_maybe_committed(); + transaction = RetryableTransaction::new( + error.on_error().await.map_err(FdbBindingError::from)?, + ); + } + } + } + } + + /// Perform a no-op against FDB to check network thread liveness. This operation will not change the underlying data + /// in any way, nor will it perform any I/O against the FDB cluster. However, it will schedule some amount of work + /// onto the FDB client and wait for it to complete. The FoundationDB client operates by scheduling onto an event + /// queue that is then processed by a single thread (the "network thread"). This method can be used to determine if + /// the network thread has entered a state where it is no longer processing requests or if its time to process + /// requests has increased. If the network thread is busy, this operation may take some amount of time to complete, + /// which is why this operation returns a future. + pub async fn perform_no_op(&self) -> FdbResult<()> { + let trx = self.create_trx()?; + + // Set the read version of the transaction, then read it back. This requires no I/O, but it does + // require the network thread be running. The exact value used for the read version is unimportant. + trx.set_read_version(42); + trx.get_read_version().await?; + Ok(()) + } + + /// Returns a value where 0 indicates that the client is idle and 1 (or larger) indicates that the client is saturated. + /// By default, this value is updated every second. + #[cfg_api_versions(min = 710)] + pub async fn get_main_thread_busyness(&self) -> FdbResult { + let busyness = + unsafe { fdb_sys::fdb_database_get_main_thread_busyness(self.inner.as_ptr()) }; + Ok(busyness) + } +} +pub trait DatabaseTransact: Sized { + type Item; + type Error: TransactError; + type Future: Future)>; + fn transact(self, trx: Transaction) -> Self::Future; +} + +#[allow(clippy::needless_lifetimes)] +#[allow(clippy::type_complexity)] +mod boxed { + use super::*; + + async fn boxed_data_fut<'t, F, T, E, D>( + mut f: FnMutBoxed<'t, F, D>, + trx: Transaction, + ) -> (FnMutBoxed<'t, F, D>, Transaction, Result) + where + F: for<'a> FnMut( + &'a Transaction, + &'a mut D, + ) -> Pin> + Send + 'a>>, + E: TransactError, + { + let r = (f.f)(&trx, &mut f.d).await; + (f, trx, r) + } + + pub struct FnMutBoxed<'t, F, D> { + pub f: F, + pub d: D, + pub m: PhantomData<&'t ()>, + } + impl<'t, F, T, E, D> DatabaseTransact for FnMutBoxed<'t, F, D> + where + F: for<'a> FnMut( + &'a Transaction, + &'a mut D, + ) -> Pin> + Send + 'a>>, + F: 't + Send, + T: 't, + E: 't, + D: 't + Send, + E: TransactError, + { + type Item = T; + type Error = E; + type Future = Pin< + Box< + dyn Future)> + + Send + + 't, + >, + >; + + fn transact(self, trx: Transaction) -> Self::Future { + boxed_data_fut(self, trx).boxed() + } + } +} + +#[allow(clippy::needless_lifetimes)] +#[allow(clippy::type_complexity)] +mod boxed_local { + use super::*; + + async fn boxed_local_data_fut<'t, F, T, E, D>( + mut f: FnMutBoxedLocal<'t, F, D>, + trx: Transaction, + ) -> (FnMutBoxedLocal<'t, F, D>, Transaction, Result) + where + F: for<'a> FnMut( + &'a Transaction, + &'a mut D, + ) -> Pin> + 'a>>, + E: TransactError, + { + let r = (f.f)(&trx, &mut f.d).await; + (f, trx, r) + } + + pub struct FnMutBoxedLocal<'t, F, D> { + pub f: F, + pub d: D, + pub m: PhantomData<&'t ()>, + } + impl<'t, F, T, E, D> DatabaseTransact for FnMutBoxedLocal<'t, F, D> + where + F: for<'a> FnMut( + &'a Transaction, + &'a mut D, + ) -> Pin> + 'a>>, + F: 't, + T: 't, + E: 't, + D: 't, + E: TransactError, + { + type Item = T; + type Error = E; + type Future = Pin< + Box)> + 't>, + >; + + fn transact(self, trx: Transaction) -> Self::Future { + boxed_local_data_fut(self, trx).boxed_local() + } + } +} + +/// A trait that must be implemented to use `Database::transact` this application error types. +pub trait TransactError: From { + fn try_into_fdb_error(self) -> Result; +} +impl TransactError for T +where + T: From + TryInto, +{ + fn try_into_fdb_error(self) -> Result { + self.try_into() + } +} +impl TransactError for FdbError { + fn try_into_fdb_error(self) -> Result { + Ok(self) + } +} + +/// A set of options that controls the behavior of `Database::transact`. +#[derive(Default, Clone)] +pub struct TransactOption { + pub retry_limit: Option, + pub time_out: Option, + pub is_idempotent: bool, +} + +impl TransactOption { + /// An idempotent TransactOption + pub fn idempotent() -> Self { + Self { + is_idempotent: true, + ..TransactOption::default() + } + } +} diff --git a/bindings/rust/foundationdb/src/directory/directory_layer.rs b/bindings/rust/foundationdb/src/directory/directory_layer.rs new file mode 100644 index 00000000000..8588af0ce91 --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/directory_layer.rs @@ -0,0 +1,804 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! The default Directory implementation. + +use crate::RangeOption; +use crate::directory::directory_partition::DirectoryPartition; +use crate::directory::directory_subspace::DirectorySubspace; +use crate::directory::error::DirectoryError; +use crate::directory::node::Node; +use crate::directory::{Directory, DirectoryOutput, compare_slice, strinc}; +use crate::future::FdbSlice; +use crate::tuple::hca::HighContentionAllocator; +use crate::tuple::{Element, Subspace, TuplePack}; +use crate::{FdbResult, Transaction}; +use async_recursion::async_recursion; +use async_trait::async_trait; +use futures::TryStreamExt; +use std::cmp::Ordering; +use std::ops::Deref; +use std::sync::Arc; + +pub(crate) const DEFAULT_SUB_DIRS: i64 = 0; +const MAJOR_VERSION: u32 = 1; +const MINOR_VERSION: u32 = 0; +const PATCH_VERSION: u32 = 0; +pub(crate) const DEFAULT_NODE_PREFIX: &[u8] = b"\xFE"; +const DEFAULT_HCA_PREFIX: &[u8] = b"hca"; +pub(crate) const PARTITION_LAYER: &[u8] = b"partition"; +pub(crate) const LAYER_SUFFIX: &[u8] = b"layer"; + +/// A DirectoryLayer defines a new root directory. +/// The node subspace and content subspace control where the directory metadata and contents, +/// respectively, are stored. The default root directory has a node subspace with raw prefix \xFE +/// and a content subspace with no prefix. +#[derive(Clone)] +pub struct DirectoryLayer { + pub(crate) inner: Arc, +} + +impl std::fmt::Debug for DirectoryLayer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +#[derive(Debug)] +pub struct DirectoryLayerInner { + pub(crate) root_node: Subspace, + pub(crate) node_subspace: Subspace, + pub(crate) content_subspace: Subspace, + pub(crate) allocator: HighContentionAllocator, + pub(crate) allow_manual_prefixes: bool, + + pub(crate) path: Vec, +} + +impl Deref for DirectoryLayer { + type Target = DirectoryLayerInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Default for DirectoryLayer { + /// The default root directory stores directory layer metadata in keys beginning with 0xFE, + ///and allocates newly created directories in (unused) prefixes starting with 0x00 through 0xFD. + ///This is appropriate for otherwise empty databases, but may conflict with other formal or informal partitionings of keyspace. + /// If you already have other content in your database, you may wish to use NewDirectoryLayer to + /// construct a non-standard root directory to control where metadata and keys are stored. + fn default() -> Self { + Self::new( + Subspace::from_bytes(DEFAULT_NODE_PREFIX), + Subspace::all(), + false, + ) + } +} + +impl DirectoryLayer { + pub fn new( + node_subspace: Subspace, + content_subspace: Subspace, + allow_manual_prefixes: bool, + ) -> Self { + let root_node = node_subspace.subspace(&node_subspace.bytes()); + let allocator = HighContentionAllocator::new(root_node.subspace(&DEFAULT_HCA_PREFIX)); + + DirectoryLayer { + inner: Arc::new(DirectoryLayerInner { + root_node, + node_subspace, + content_subspace, + allocator, + allow_manual_prefixes, + path: vec![], + }), + } + } + + pub(crate) fn new_with_path( + node_subspace: Subspace, + content_subspace: Subspace, + allow_manual_prefixes: bool, + path: &[String], + ) -> Self { + let root_node = node_subspace.subspace(&node_subspace.bytes()); + let allocator = HighContentionAllocator::new(root_node.subspace(&DEFAULT_HCA_PREFIX)); + + DirectoryLayer { + inner: Arc::new(DirectoryLayerInner { + root_node, + node_subspace, + content_subspace, + allocator, + allow_manual_prefixes, + path: Vec::from(path), + }), + } + } + + pub fn get_path(&self) -> &[String] { + self.path.as_slice() + } + + fn node_with_optional_prefix(&self, prefix: Option) -> Option { + prefix.map(|fdb_slice| self.node_with_prefix(&fdb_slice.deref())) + } + + fn node_with_prefix(&self, prefix: &T) -> Subspace { + self.inner.node_subspace.subspace(prefix) + } + + async fn find( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + let mut current_path = vec![]; + let mut node_subspace = self.root_node.clone(); + let mut layer = vec![]; + let mut loaded = false; + + // walking through the provided path + for path_name in path.iter() { + current_path.push(path_name.clone()); + let key = node_subspace.subspace(&(DEFAULT_SUB_DIRS, path_name.to_owned())); + + // finding the next node + let fdb_slice_value = trx.get(key.bytes(), false).await?; + + loaded = true; + node_subspace = match self.node_with_optional_prefix(fdb_slice_value) { + None => return Ok(None), + Some(subspace) => subspace, + }; + + layer = Node::load_metadata(trx, &node_subspace).await?; + if layer.as_slice().eq(PARTITION_LAYER) { + break; + } + } + + if !loaded { + layer = Node::load_metadata(trx, &node_subspace).await?; + } + + Ok(Some(Node { + subspace: node_subspace, + current_path, + target_path: Vec::from(path), + directory_layer: self.clone(), + layer, + })) + } + + fn to_absolute_path(&self, sub_path: &[String]) -> Vec { + let mut path: Vec = Vec::with_capacity(self.path.len() + sub_path.len()); + + path.extend_from_slice(&self.path); + path.extend_from_slice(sub_path); + + path + } + + pub(crate) fn contents_of_node( + &self, + subspace: &Subspace, + path: &[String], + layer: &[u8], + ) -> Result { + let prefix: Vec = self.node_subspace.unpack(subspace.bytes())?; + + if layer.eq(PARTITION_LAYER) { + Ok(DirectoryOutput::DirectoryPartition( + DirectoryPartition::new(&self.to_absolute_path(path), prefix, self.clone()), + )) + } else { + Ok(DirectoryOutput::DirectorySubspace(DirectorySubspace::new( + &self.to_absolute_path(path), + prefix, + self, + layer.to_owned(), + ))) + } + } + + /// `create_or_open_internal` is the function used to open and/or create a directory. + #[async_recursion] + async fn create_or_open_internal( + &self, + trx: &Transaction, + path: &[String], + prefix: Option<&'async_recursion [u8]>, + layer: Option<&'async_recursion [u8]>, + allow_create: bool, + allow_open: bool, + ) -> Result { + self.check_version(trx, false).await?; + + if prefix.is_some() && !self.allow_manual_prefixes { + if self.path.is_empty() { + return Err(DirectoryError::PrefixNotAllowed); + } + + return Err(DirectoryError::CannotPrefixInPartition); + } + + if path.is_empty() { + return Err(DirectoryError::NoPathProvided); + } + + if let Some(node) = self.find(trx, path).await? { + if node.is_in_partition(false) { + let sub_path = node.get_partition_subpath(); + match node.get_contents()? { + DirectoryOutput::DirectorySubspace(_) => unreachable!("already in partition"), + DirectoryOutput::DirectoryPartition(directory_partition) => { + let dir_space = directory_partition + .directory_subspace + .directory_layer + .create_or_open_internal( + trx, + &sub_path, + prefix, + layer, + allow_create, + allow_open, + ) + .await?; + Ok(dir_space) + } + } + } else { + self.open_internal(layer, &node, allow_open).await + } + } else { + self.create_internal(trx, path, layer, prefix, allow_create) + .await + } + } + + async fn open_internal( + &self, + layer: Option<&[u8]>, + node: &Node, + allow_open: bool, + ) -> Result { + if !allow_open { + return Err(DirectoryError::DirAlreadyExists); + } + + match layer { + None => {} + Some(layer) if !layer.is_empty() => match compare_slice(layer, &node.layer) { + Ordering::Equal => {} + _ => { + return Err(DirectoryError::IncompatibleLayer); + } + }, + Some(_) => {} + } + + node.get_contents() + } + + async fn create_internal( + &self, + trx: &Transaction, + path: &[String], + layer: Option<&[u8]>, + prefix: Option<&[u8]>, + allow_create: bool, + ) -> Result { + let path_last = path.last().ok_or(DirectoryError::NoPathProvided)?; + + if !allow_create { + return Err(DirectoryError::DirectoryDoesNotExists); + } + + let layer = layer.unwrap_or_default(); + + self.check_version(trx, true).await?; + let new_prefix = self.get_prefix(trx, prefix).await?; + + let is_prefix_free = self + .is_prefix_free(trx, new_prefix.as_slice(), prefix.is_none()) + .await?; + + if !is_prefix_free { + return Err(DirectoryError::DirectoryPrefixInUse); + } + + let parent_node = self.get_parent_node(trx, path).await?; + let node = self.node_with_prefix(&new_prefix); + + let key = parent_node.subspace(&(DEFAULT_SUB_DIRS, path_last)); + let key_layer = node.pack(&LAYER_SUFFIX); + + trx.set(key.bytes(), &new_prefix); + trx.set(&key_layer, layer); + + self.contents_of_node(&node, path, layer) + } + + async fn get_parent_node( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + match path.split_last() { + None => Ok(self.root_node.clone()), + Some((_, remains)) => { + if remains.is_empty() { + return Ok(self.root_node.clone()); + } + let parent = self + .create_or_open_internal(trx, remains, None, None, true, true) + .await?; + + Ok(self.node_with_prefix(&parent.bytes()?)) + } + } + } + + async fn is_prefix_free( + &self, + trx: &Transaction, + prefix: &[u8], + snapshot: bool, + ) -> Result { + if prefix.is_empty() { + return Ok(false); + } + + let node = self.node_containing_key(trx, prefix, snapshot).await?; + + if node.is_some() { + return Ok(false); + } + + let mut range_option = RangeOption::from(( + self.node_subspace.pack(&prefix), + self.node_subspace.pack(&strinc(prefix.to_vec())), + )); + range_option.limit = Some(1); + + let result = trx + .get_ranges_keyvalues(range_option, snapshot) + .try_next() + .await?; + + Ok(result.is_none()) + } + + async fn node_containing_key( + &self, + trx: &Transaction, + key: &[u8], + snapshot: bool, + ) -> Result, DirectoryError> { + if key.starts_with(self.node_subspace.bytes()) { + return Ok(Some(self.root_node.clone())); + } + + let mut key_after = key.to_vec(); + // pushing 0x00 to simulate keyAfter + key_after.push(0); + + let range_end = self.node_subspace.pack(&key_after); + + let mut range_option = RangeOption::from((self.node_subspace.range().0, range_end)); + range_option.reverse = true; + range_option.limit = Some(1); + + // checking range + let fdb_value = trx + .get_ranges_keyvalues(range_option, snapshot) + .try_next() + .await?; + + if let Some(fdb_key_value) = fdb_value { + let previous_prefix: Vec = self.node_subspace.unpack(fdb_key_value.key())?; + + if let Some(Element::Bytes(previous_prefix)) = previous_prefix.first() { + if key.starts_with(previous_prefix) { + return Ok(Some(self.node_with_prefix(previous_prefix))); + }; + }; + } + Ok(None) + } + + async fn get_prefix( + &self, + trx: &Transaction, + prefix: Option<&[u8]>, + ) -> Result, DirectoryError> { + match prefix { + None => { + // no prefix provided, allocating one + let allocator = self.allocator.allocate(trx).await?; + let subspace = self.content_subspace.subspace(&allocator); + + // A directory owns every raw key under its prefix, including the + // bare prefix and suffixes outside the tuple-element range. + let mut range_option = RangeOption::from(( + subspace.bytes().to_vec(), + strinc(subspace.bytes().to_vec()), + )); + range_option.limit = Some(1); + + let result = trx + .get_ranges_keyvalues(range_option, false) + .try_next() + .await?; + + if result.is_some() { + return Err(DirectoryError::PrefixNotEmpty); + } + + Ok(subspace.into_bytes()) + } + Some(v) => Ok(v.to_vec()), + } + } + + /// `check_version` is checking the Directory's version in FDB. + async fn check_version( + &self, + trx: &Transaction, + allow_creation: bool, + ) -> Result<(), DirectoryError> { + let version = self.get_version_value(trx).await?; + match version { + None => { + if allow_creation { + self.initialize_directory(trx).await + } else { + Ok(()) + } + } + Some(versions) => { + if versions.len() < 12 { + return Err(DirectoryError::Version( + "incorrect version length".to_string(), + )); + } + let mut arr = [0u8; 4]; + arr.copy_from_slice(&versions[0..4]); + let major: u32 = u32::from_le_bytes(arr); + + arr.copy_from_slice(&versions[4..8]); + let minor: u32 = u32::from_le_bytes(arr); + + arr.copy_from_slice(&versions[8..12]); + let patch: u32 = u32::from_le_bytes(arr); + + if major > MAJOR_VERSION { + let msg = format!( + "cannot load directory with version {major}.{minor}.{patch} using directory layer {MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}" + ); + return Err(DirectoryError::Version(msg)); + } + + if minor > MINOR_VERSION && allow_creation { + let msg = format!( + "directory with version {major}.{minor}.{patch} is read-only when opened using directory layer {MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}" + ); + return Err(DirectoryError::Version(msg)); + } + + Ok(()) + } + } + } + + /// `initialize_directory` is initializing the directory + async fn initialize_directory(&self, trx: &Transaction) -> Result<(), DirectoryError> { + let mut value = vec![]; + value.extend(&MAJOR_VERSION.to_le_bytes()); + value.extend(&MINOR_VERSION.to_le_bytes()); + value.extend(&PATCH_VERSION.to_le_bytes()); + let version_subspace: &[u8] = b"version"; + let directory_version_key = self.root_node.subspace(&version_subspace); + trx.set(directory_version_key.bytes(), &value); + + Ok(()) + } + + async fn get_version_value(&self, trx: &Transaction) -> FdbResult> { + let version_subspace: &[u8] = b"version"; + let version_key = self.root_node.subspace(&version_subspace); + + trx.get(version_key.bytes(), false).await + } + + async fn exists_internal( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + self.check_version(trx, false).await?; + + match self.find(trx, path).await? { + None => Ok(false), + Some(node) if node.is_in_partition(false) => { + node.get_contents()? + .exists(trx, &node.get_partition_subpath()) + .await + } + Some(_node) => Ok(true), + } + } + + async fn list_internal( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + self.check_version(trx, false).await?; + + let node = self + .find(trx, path) + .await? + .ok_or(DirectoryError::PathDoesNotExists)?; + if node.is_in_partition(true) { + match node.get_contents()? { + DirectoryOutput::DirectorySubspace(_) => unreachable!("already in partition"), + DirectoryOutput::DirectoryPartition(directory_partition) => { + return directory_partition + .directory_subspace + .directory_layer + .list(trx, &node.get_partition_subpath()) + .await; + } + }; + } + + node.list_sub_folders(trx).await + } + + async fn move_to_internal( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result { + self.check_version(trx, true).await?; + + if old_path.len() <= new_path.len() + && compare_slice(old_path, &new_path[..old_path.len()]) == Ordering::Equal + { + return Err(DirectoryError::CannotMoveBetweenSubdirectory); + } + + let old_node = self.find(trx, old_path).await?; + let new_node = self.find(trx, new_path).await?; + + let old_node = match old_node { + None => return Err(DirectoryError::PathDoesNotExists), + Some(n) => n, + }; + let old_node_exists_in_partition = old_node.is_in_partition(false); + + match new_node { + None => { + if old_node_exists_in_partition { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + } + Some(new_node) => { + let new_node_exists_in_partition = new_node.is_in_partition(false); + if old_node_exists_in_partition || new_node_exists_in_partition { + if !old_node_exists_in_partition + || !new_node_exists_in_partition + || !old_node.current_path.eq(&new_node.current_path) + { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + + return new_node + .get_contents()? + .move_to( + trx, + &old_node.get_partition_subpath(), + &new_node.get_partition_subpath(), + ) + .await; + } + return Err(DirectoryError::DirAlreadyExists); + } + } + + let (new_path_last, parent_path) = new_path + .split_last() + .ok_or(DirectoryError::DirAlreadyExists)?; + + let parent_node = self + .find(trx, parent_path) + .await? + .ok_or(DirectoryError::ParentDirDoesNotExists)?; + + let key = parent_node + .subspace + .subspace(&(DEFAULT_SUB_DIRS, new_path_last)); + let value: Vec = self.node_subspace.unpack(old_node.subspace.bytes())?; + trx.set(key.bytes(), &value); + + self.remove_from_parent(trx, old_path).await?; + + self.contents_of_node(&old_node.subspace, new_path, &old_node.layer) + } + + async fn remove_from_parent( + &self, + trx: &Transaction, + path: &[String], + ) -> Result<(), DirectoryError> { + let (last_element, parent_path) = path + .split_last() + .ok_or(DirectoryError::BadDestinationDirectory)?; + + match self.find(trx, parent_path).await? { + None => {} + Some(parent_node) => { + let key = parent_node.subspace.pack(&(DEFAULT_SUB_DIRS, last_element)); + trx.clear(&key); + } + } + + Ok(()) + } + + #[async_recursion] + async fn remove_internal( + &self, + trx: &Transaction, + path: &[String], + fail_on_nonexistent: bool, + ) -> Result { + self.check_version(trx, true).await?; + + if path.is_empty() { + return Err(DirectoryError::CannotModifyRootDirectory); + } + + let node = match self.find(trx, path).await? { + Some(node) => node, + None if fail_on_nonexistent => return Err(DirectoryError::DirectoryDoesNotExists), + None => return Ok(false), + }; + + if node.is_in_partition(false) { + match node.get_contents()? { + DirectoryOutput::DirectorySubspace(_) => { + unreachable!("already directory partition") + } + DirectoryOutput::DirectoryPartition(d) => { + return d + .directory_subspace + .directory_layer + .remove_internal(trx, &node.get_partition_subpath(), fail_on_nonexistent) + .await; + } + } + } + + self.remove_recursive(trx, &node.subspace).await?; + self.remove_from_parent(trx, path).await?; + + Ok(true) + } + + #[async_recursion] + async fn remove_recursive( + &self, + trx: &Transaction, + node_sub: &Subspace, + ) -> Result<(), DirectoryError> { + let sub_dir = node_sub.subspace(&DEFAULT_SUB_DIRS); + let range_option = RangeOption::from(&sub_dir); + + let mut stream = std::pin::pin!(trx.get_ranges_keyvalues(range_option, false)); + while let Some(row_key) = stream.try_next().await? { + let sub_node = self.node_with_prefix(&row_key.value()); + self.remove_recursive(trx, &sub_node).await?; + } + + let node_prefix: Vec = self.node_subspace.unpack(node_sub.bytes())?; + + trx.clear_range(&node_prefix, &strinc(node_prefix.to_owned())); + trx.clear_subspace_range(node_sub); + + Ok(()) + } +} + +#[async_trait] +impl Directory for DirectoryLayer { + async fn create_or_open( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.create_or_open_internal(txn, path, prefix, layer, true, true) + .await + } + + async fn create( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.create_or_open_internal(txn, path, prefix, layer, true, false) + .await + } + + async fn open( + &self, + txn: &Transaction, + path: &[String], + layer: Option<&[u8]>, + ) -> Result { + self.create_or_open_internal(txn, path, None, layer, false, true) + .await + } + + async fn exists(&self, trx: &Transaction, path: &[String]) -> Result { + self.exists_internal(trx, path).await + } + + async fn move_directory( + &self, + _trx: &Transaction, + _new_path: &[String], + ) -> Result { + Err(DirectoryError::CannotMoveRootDirectory) + } + + /// `move_to` the directory from old_path to new_path(both relative to this + /// Directory), and returns the directory (at its new location) and its + /// contents as a Subspace. Move will return an error if a directory + /// does not exist at oldPath, a directory already exists at newPath, or the + /// parent directory of newPath does not exist. + async fn move_to( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result { + self.move_to_internal(trx, old_path, new_path).await + } + + async fn remove(&self, trx: &Transaction, path: &[String]) -> Result { + self.remove_internal(trx, path, true).await + } + + async fn remove_if_exists( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + self.remove_internal(trx, path, false).await + } + + async fn list( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + self.list_internal(trx, path).await + } +} diff --git a/bindings/rust/foundationdb/src/directory/directory_partition.rs b/bindings/rust/foundationdb/src/directory/directory_partition.rs new file mode 100644 index 00000000000..66e4253c068 --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/directory_partition.rs @@ -0,0 +1,247 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! A resulting Subspace whose prefix is preprended to all of its descendant directories's prefixes. + +use crate::Transaction; +use crate::directory::directory_layer::{DEFAULT_NODE_PREFIX, DirectoryLayer, PARTITION_LAYER}; +use crate::directory::directory_subspace::DirectorySubspace; +use crate::directory::error::DirectoryError; +use crate::directory::{Directory, DirectoryOutput}; +use crate::tuple::Subspace; +use async_trait::async_trait; +use std::ops::Deref; +use std::sync::Arc; + +/// A `DirectoryPartition` is a DirectorySubspace whose prefix is preprended to all of its descendant +/// directories's prefixes. It cannot be used as a Subspace. Instead, you must create at +/// least one subdirectory to store content. +#[derive(Clone)] +pub struct DirectoryPartition { + pub(crate) inner: Arc, +} + +#[derive(Debug)] +pub struct DirectoryPartitionInner { + pub(crate) directory_subspace: DirectorySubspace, + pub(crate) parent_directory_layer: DirectoryLayer, +} + +impl Deref for DirectoryPartition { + type Target = DirectoryPartitionInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::fmt::Debug for DirectoryPartition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl DirectoryPartition { + // https://github.com/apple/foundationdb/blob/master/bindings/flow/DirectoryPartition.h#L34-L43 + pub(crate) fn new( + path: &[String], + prefix: Vec, + parent_directory_layer: DirectoryLayer, + ) -> Self { + let mut node_subspace_bytes = vec![]; + node_subspace_bytes.extend_from_slice(&prefix); + node_subspace_bytes.extend_from_slice(DEFAULT_NODE_PREFIX); + + let new_directory_layer = DirectoryLayer::new_with_path( + Subspace::from_bytes(node_subspace_bytes), + Subspace::from_bytes(prefix.as_slice()), + false, + path, + ); + + DirectoryPartition { + inner: Arc::new(DirectoryPartitionInner { + directory_subspace: DirectorySubspace::new( + path, + prefix, + &new_directory_layer, + Vec::from(PARTITION_LAYER), + ), + parent_directory_layer, + }), + } + } +} + +impl DirectoryPartition { + pub fn get_path(&self) -> &[String] { + self.inner.directory_subspace.get_path() + } + + fn get_directory_layer_for_path(&self, path: &[String]) -> DirectoryLayer { + if path.is_empty() { + self.parent_directory_layer.clone() + } else { + self.directory_subspace.directory_layer.clone() + } + } + + fn get_partition_subpath( + &self, + path: &[String], + directory_layer: Option, + ) -> Result, DirectoryError> { + let directory = match directory_layer { + None => self.directory_subspace.directory_layer.clone(), + Some(d) => d, + }; + + if directory.path.len() > self.directory_subspace.get_path().len() { + return Err(DirectoryError::CannotCreateSubpath); + } + + let mut new_path = vec![]; + + new_path.extend_from_slice(&self.directory_subspace.get_path()[directory.path.len()..]); + new_path.extend_from_slice(path); + + Ok(new_path) + } + + pub fn get_layer(&self) -> &[u8] { + b"partition" + } +} + +#[async_trait] +impl Directory for DirectoryPartition { + async fn create_or_open( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.inner + .directory_subspace + .create_or_open(txn, path, prefix, layer) + .await + } + + async fn create( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.inner + .directory_subspace + .create(txn, path, prefix, layer) + .await + } + + async fn open( + &self, + txn: &Transaction, + path: &[String], + layer: Option<&[u8]>, + ) -> Result { + self.inner.directory_subspace.open(txn, path, layer).await + } + + async fn exists(&self, trx: &Transaction, path: &[String]) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + + directory_layer + .exists( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn move_directory( + &self, + trx: &Transaction, + new_path: &[String], + ) -> Result { + let directory_layer = self.get_directory_layer_for_path(&[]); + let directory_layer_path = &directory_layer.path; + + if directory_layer_path.len() > new_path.len() { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + + for (i, path) in directory_layer_path.iter().enumerate() { + match new_path.get(i) { + None => return Err(DirectoryError::CannotMoveBetweenPartition), + Some(new_path_item) => { + if !new_path_item.eq(path) { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + } + } + } + + let mut new_relative_path = vec![]; + new_relative_path.extend_from_slice(&new_path[directory_layer_path.len()..]); + + directory_layer + .move_to( + trx, + &self.get_partition_subpath(&[], Some(directory_layer.clone()))?, + &new_relative_path, + ) + .await + } + + async fn move_to( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result { + self.inner + .directory_subspace + .move_to(trx, old_path, new_path) + .await + } + + async fn remove(&self, trx: &Transaction, path: &[String]) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + directory_layer + .remove( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn remove_if_exists( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + directory_layer + .remove_if_exists( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn list( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + self.inner.directory_subspace.list(trx, path).await + } +} diff --git a/bindings/rust/foundationdb/src/directory/directory_subspace.rs b/bindings/rust/foundationdb/src/directory/directory_subspace.rs new file mode 100644 index 00000000000..24926bda529 --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/directory_subspace.rs @@ -0,0 +1,242 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! The resulting Subspace generated with a Directory + +use crate::Transaction; +use crate::directory::directory_layer::DirectoryLayer; +use crate::directory::error::DirectoryError; +use crate::directory::{Directory, DirectoryOutput}; +use crate::tuple::{PackResult, Subspace, TuplePack, TupleUnpack}; +use async_trait::async_trait; + +/// A `DirectorySubspace` represents the contents of a directory, but it also remembers +/// the path with which it was opened and offers convenience methods to operate on the directory at that path. +/// An instance of `DirectorySubspace` can be used for all the usual subspace operations. +/// It can also be used to operate on the directory with which it was opened. +#[derive(Debug, Clone)] +pub struct DirectorySubspace { + pub(crate) directory_layer: DirectoryLayer, + subspace: Subspace, + path: Vec, + layer: Vec, +} + +impl DirectorySubspace { + pub fn new( + path: &[String], + prefix: Vec, + directory_layer: &DirectoryLayer, + layer: Vec, + ) -> Self { + DirectorySubspace { + directory_layer: directory_layer.clone(), + subspace: Subspace::from_bytes(prefix), + path: Vec::from(path), + layer, + } + } + + // https://github.com/apple/foundationdb/blob/master/bindings/flow/DirectorySubspace.cpp#L105 + fn get_partition_subpath( + &self, + path: &[String], + directory_layer: Option, + ) -> Result, DirectoryError> { + let directory = match directory_layer { + None => self.directory_layer.clone(), + Some(d) => d, + }; + + if directory.path.len() > self.path.len() { + return Err(DirectoryError::CannotCreateSubpath); + } + + let mut new_path = vec![]; + + new_path.extend_from_slice(&self.path[directory.path.len()..]); + new_path.extend_from_slice(path); + + Ok(new_path) + } +} + +impl DirectorySubspace { + pub fn subspace(&self, t: &T) -> Subspace { + self.subspace.subspace(t) + } + + pub fn bytes(&self) -> &[u8] { + self.subspace.bytes() + } + + pub fn pack(&self, t: &T) -> Vec { + self.subspace.pack(t) + } + + pub fn unpack<'de, T: TupleUnpack<'de>>(&self, key: &'de [u8]) -> PackResult { + self.subspace.unpack(key) + } + + pub fn range(&self) -> (Vec, Vec) { + self.subspace.range() + } + + pub fn get_path(&self) -> &[String] { + self.path.as_slice() + } + + pub fn set_path(&mut self, path: Vec) { + self.path = path; + } + + pub fn get_layer(&self) -> &[u8] { + self.layer.as_slice() + } + + pub fn is_start_of(&self, key: &[u8]) -> bool { + self.subspace.is_start_of(key) + } + + fn get_directory_layer_for_path(&self, _: &[String]) -> DirectoryLayer { + self.directory_layer.clone() + } +} + +#[async_trait] +impl Directory for DirectorySubspace { + async fn create_or_open( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.directory_layer + .create_or_open(txn, &self.get_partition_subpath(path, None)?, prefix, layer) + .await + } + + async fn create( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + self.directory_layer + .create(txn, &self.get_partition_subpath(path, None)?, prefix, layer) + .await + } + + async fn open( + &self, + txn: &Transaction, + path: &[String], + layer: Option<&[u8]>, + ) -> Result { + self.directory_layer + .open(txn, &self.get_partition_subpath(path, None)?, layer) + .await + } + + async fn exists(&self, trx: &Transaction, path: &[String]) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + + directory_layer + .exists( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn move_directory( + &self, + trx: &Transaction, + new_path: &[String], + ) -> Result { + let directory_layer = self.get_directory_layer_for_path(&[]); + let directory_layer_path = &directory_layer.path; + + if directory_layer_path.len() > new_path.len() { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + + for (i, path) in directory_layer_path.iter().enumerate() { + match new_path.get(i) { + None => return Err(DirectoryError::CannotMoveBetweenPartition), + Some(new_path_item) => { + if !new_path_item.eq(path) { + return Err(DirectoryError::CannotMoveBetweenPartition); + } + } + } + } + + let mut new_relative_path = vec![]; + new_relative_path.extend_from_slice(&new_path[directory_layer_path.len()..]); + + directory_layer + .move_to( + trx, + &self.get_partition_subpath(&[], Some(directory_layer.clone()))?, + &new_relative_path, + ) + .await + } + + async fn move_to( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result { + self.directory_layer + .move_to( + trx, + &self.get_partition_subpath(old_path, None)?, + &self.get_partition_subpath(new_path, None)?, + ) + .await + } + + async fn remove(&self, trx: &Transaction, path: &[String]) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + directory_layer + .remove( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn remove_if_exists( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + let directory_layer = self.get_directory_layer_for_path(path); + directory_layer + .remove_if_exists( + trx, + &self.get_partition_subpath(path, Some(directory_layer.clone()))?, + ) + .await + } + + async fn list( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + self.directory_layer + .list(trx, &self.get_partition_subpath(path, None)?) + .await + } +} diff --git a/bindings/rust/foundationdb/src/directory/error.rs b/bindings/rust/foundationdb/src/directory/error.rs new file mode 100644 index 00000000000..6a4a8a3af00 --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/error.rs @@ -0,0 +1,103 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Errors that can be thrown by Directory. + +use crate::error; +use crate::tuple::PackError; +use crate::tuple::hca::HcaError; +use std::io; + +/// The enumeration holding all possible errors from a Directory. +#[derive(Debug)] +pub enum DirectoryError { + /// cannot modify the root directory + CannotModifyRootDirectory, + /// prefix is already used + DirectoryPrefixInUse, + /// Directory does not exists + DirectoryDoesNotExists, + /// missing path. + NoPathProvided, + /// tried to create an already existing path. + DirAlreadyExists, + /// missing directory. + PathDoesNotExists, + /// Parent does not exists + ParentDirDoesNotExists, + /// the layer is incompatible. + IncompatibleLayer, + /// the destination directory cannot be a subdirectory of the source directory. + BadDestinationDirectory, + /// Bad directory version. + Version(String), + /// cannot specify a prefix unless manual prefixes are enabled + PrefixNotAllowed, + /// cannot specify a prefix in a partition. + CannotPrefixInPartition, + /// the root directory cannot be moved + CannotMoveRootDirectory, + CannotMoveBetweenPartition, + /// the destination directory cannot be a subdirectory of the source directory + CannotMoveBetweenSubdirectory, + /// Prefix is not empty + PrefixNotEmpty, + /// Thrown when the subpath cannot be computed due to length errors + CannotCreateSubpath, + /// cannot open subspace in the root of a directory partition + CannotOpenDirectoryPartition, + /// cannot get key for the root of a directory partition + CannotGetKeyDirectoryPartition, + /// cannot pack for the root of a directory partition + CannotPackDirectoryPartition, + /// cannot unpack keys using the root of a directory partition + CannotUnpackDirectoryPartition, + /// cannot get range for the root of a directory partition + CannotRangeDirectoryPartition, + IoError(io::Error), + FdbError(error::FdbError), + HcaError(HcaError), + PackError(PackError), + Other(String), +} + +impl std::fmt::Display for DirectoryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for DirectoryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + DirectoryError::IoError(e) => Some(e), + DirectoryError::FdbError(e) => Some(e), + DirectoryError::HcaError(e) => Some(e), + DirectoryError::PackError(e) => Some(e), + _ => None, + } + } +} + +impl From for DirectoryError { + fn from(err: error::FdbError) -> Self { + DirectoryError::FdbError(err) + } +} + +impl From for DirectoryError { + fn from(err: HcaError) -> Self { + DirectoryError::HcaError(err) + } +} + +impl From for DirectoryError { + fn from(err: PackError) -> Self { + DirectoryError::PackError(err) + } +} diff --git a/bindings/rust/foundationdb/src/directory/mod.rs b/bindings/rust/foundationdb/src/directory/mod.rs new file mode 100644 index 00000000000..1eeeaf66b03 --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/mod.rs @@ -0,0 +1,389 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Directory provides a tool for managing related subspaces. +//! +//! The FoundationDB API provides directories as a tool for managing related Subspaces. +//! For general guidance on directory usage, see the discussion in the [Developer Guide](https://apple.github.io/foundationdb/developer-guide.html#directories). +//! +//! Directories are identified by hierarchical paths analogous to the paths in a Unix-like file system. +//! A path is represented as a slice of strings. Each directory has an associated subspace used to +//! store its content. The directory layer maps each path to a short prefix used for the +//! corresponding subspace. In effect, directories provide a level of indirection for access to subspaces. +//! Directory operations are transactional. +//! +//! It is a direct backport of the [Flow implementation](https://github.com/apple/foundationdb/tree/master/bindings/flow). +//! +//! Examples: +//! +//! ```rust +//! use futures::prelude::*; +//! use foundationdb::directory::Directory; +//! +//! async fn async_main() -> foundationdb::FdbResult<()> { +//! let db = foundationdb::Database::default()?; +//! +//! // creates a transaction +//! let trx = db.create_trx()?; +//! +//! // creates a directory +//! let directory = foundationdb::directory::DirectoryLayer::default(); +//! +//! let path = vec![String::from("my-awesome-app"), String::from("my-awesome-user")]; +//! +//! // use the directory to create a subspace to use +//! let content_subspace = directory.create_or_open( +//! // the transaction used to read/write the directory. +//! &trx, +//! // the path used, which can view as a UNIX path like `/app/my-app`. +//! &path, +//! // do not use any custom prefix or layer +//! None, None, +//! ).await; +//! assert_eq!(true, content_subspace.is_ok()); +//! +//! // Don't forget to commit your transaction to persist the subspace +//! trx.commit().await?; +//! +//! Ok(()) +//! } +//! +//! foundationdb::boot().expect("failed to initialize FoundationDB"); +//! futures::executor::block_on(async_main()).expect("failed to run"); +//! ``` + +mod directory_layer; +mod directory_partition; +mod directory_subspace; +mod error; +mod node; + +use crate::Transaction; +use crate::tuple::{PackResult, Subspace, TuplePack, TupleUnpack}; +use async_trait::async_trait; +use core::cmp; +pub use directory_layer::DirectoryLayer; +pub use directory_partition::DirectoryPartition; +pub use directory_subspace::DirectorySubspace; +pub use error::DirectoryError; +use std::cmp::Ordering; + +/// `Directory` represents a subspace of keys in a FoundationDB database, identified by a hierarchical path. +/// +/// # Warning: Hanging on Network/DNS failures +/// +/// Directory operations are transactional and rely on the [`Transaction`] passed to them. +/// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster +/// or if DNS resolution fails. This can cause directory operations to hang forever. +/// To prevent this, you should set [`crate::options::DatabaseOption::TransactionTimeout`] or +/// [`crate::options::DatabaseOption::TransactionRetryLimit`] on the [`crate::Database`] object, or +/// [`crate::options::TransactionOption::Timeout`] or [`crate::options::TransactionOption::RetryLimit`] on the transaction +/// itself. +#[async_trait] +pub trait Directory { + /// Creates or opens the subdirectory of this Directory located at path (creating parent directories, if necessary). + /// + /// # Warning + /// + /// If you need to create several paths with the same prefix, you **must** use several transactions(See [this link](https://github.com/apple/foundationdb/issues/895#issuecomment-436704180) for context) + async fn create_or_open( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result; + + /// Creates a subdirectory of this Directory located at path (creating parent directories if necessary). + /// + /// # Warning + /// + /// If you need to create several paths with the same prefix, you **must** use several transactions(See [this link](https://github.com/apple/foundationdb/issues/895#issuecomment-436704180) for context) + async fn create( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result; + + /// Opens the subdirectory of this Directory located at path. + async fn open( + &self, + txn: &Transaction, + path: &[String], + layer: Option<&[u8]>, + ) -> Result; + + /// Checks if the subdirectory of this Directory located at path exists. + async fn exists(&self, trx: &Transaction, path: &[String]) -> Result; + + /// Moves this Directory to the specified newAbsolutePath. + async fn move_directory( + &self, + trx: &Transaction, + new_path: &[String], + ) -> Result; + + /// Moves the subdirectory of this Directory located at oldpath to newpath. + async fn move_to( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result; + + /// Removes the subdirectory of this Directory located at path and all of its subdirectories, as well as all of their contents. + async fn remove(&self, trx: &Transaction, path: &[String]) -> Result; + + /// Removes the subdirectory of this Directory located at path (if the path exists) and all of its subdirectories, as well as all of their contents. + async fn remove_if_exists( + &self, + trx: &Transaction, + path: &[String], + ) -> Result; + + /// List the subdirectories of this directory at a given subpath. + async fn list(&self, trx: &Transaction, path: &[String]) + -> Result, DirectoryError>; +} + +pub(crate) fn compare_slice(a: &[T], b: &[T]) -> cmp::Ordering { + for (ai, bi) in a.iter().zip(b.iter()) { + match ai.cmp(bi) { + Ordering::Equal => continue, + ord => return ord, + } + } + + // if every single element was equal, compare length + a.len().cmp(&b.len()) +} + +/// DirectoryOutput represents the different output of a Directory. +#[derive(Clone, Debug)] +pub enum DirectoryOutput { + /// Under classic usage, you will obtain an `DirectorySubspace` + DirectorySubspace(DirectorySubspace), + /// You can open an `DirectoryPartition` by using the "partition" layer + DirectoryPartition(DirectoryPartition), +} + +impl DirectoryOutput { + pub fn subspace(&self, t: &T) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => Ok(d.subspace(t)), + DirectoryOutput::DirectoryPartition(_) => { + Err(DirectoryError::CannotOpenDirectoryPartition) + } + } + } + + pub fn bytes(&self) -> Result<&[u8], DirectoryError> { + match self { + DirectoryOutput::DirectorySubspace(d) => Ok(d.bytes()), + DirectoryOutput::DirectoryPartition(_) => { + Err(DirectoryError::CannotOpenDirectoryPartition) + } + } + } + + pub fn pack(&self, t: &T) -> Result, DirectoryError> { + match self { + DirectoryOutput::DirectorySubspace(d) => Ok(d.pack(t)), + DirectoryOutput::DirectoryPartition(_) => { + Err(DirectoryError::CannotPackDirectoryPartition) + } + } + } + + pub fn unpack<'de, T: TupleUnpack<'de>>( + &self, + key: &'de [u8], + ) -> Result, DirectoryError> { + match self { + DirectoryOutput::DirectorySubspace(d) => Ok(d.unpack(key)), + DirectoryOutput::DirectoryPartition(_) => { + Err(DirectoryError::CannotPackDirectoryPartition) + } + } + } + + pub fn range(&self) -> Result<(Vec, Vec), DirectoryError> { + match self { + DirectoryOutput::DirectorySubspace(d) => Ok(d.range()), + DirectoryOutput::DirectoryPartition(_) => { + Err(DirectoryError::CannotRangeDirectoryPartition) + } + } + } + + pub fn get_path(&self) -> &[String] { + match self { + DirectoryOutput::DirectorySubspace(d) => d.get_path(), + DirectoryOutput::DirectoryPartition(d) => d.get_path(), + } + } + + pub fn get_layer(&self) -> &[u8] { + match self { + DirectoryOutput::DirectorySubspace(d) => d.get_layer(), + DirectoryOutput::DirectoryPartition(d) => d.get_layer(), + } + } +} + +#[async_trait] +impl Directory for DirectoryOutput { + async fn create_or_open( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => { + d.create_or_open(txn, path, prefix, layer).await + } + DirectoryOutput::DirectoryPartition(d) => { + d.create_or_open(txn, path, prefix, layer).await + } + } + } + + async fn create( + &self, + txn: &Transaction, + path: &[String], + prefix: Option<&[u8]>, + layer: Option<&[u8]>, + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.create(txn, path, prefix, layer).await, + DirectoryOutput::DirectoryPartition(d) => d.create(txn, path, prefix, layer).await, + } + } + + async fn open( + &self, + txn: &Transaction, + path: &[String], + layer: Option<&[u8]>, + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.open(txn, path, layer).await, + DirectoryOutput::DirectoryPartition(d) => d.open(txn, path, layer).await, + } + } + + async fn exists(&self, trx: &Transaction, path: &[String]) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.exists(trx, path).await, + DirectoryOutput::DirectoryPartition(d) => d.exists(trx, path).await, + } + } + + async fn move_directory( + &self, + trx: &Transaction, + new_path: &[String], + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.move_directory(trx, new_path).await, + DirectoryOutput::DirectoryPartition(d) => d.move_directory(trx, new_path).await, + } + } + + async fn move_to( + &self, + trx: &Transaction, + old_path: &[String], + new_path: &[String], + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.move_to(trx, old_path, new_path).await, + DirectoryOutput::DirectoryPartition(d) => d.move_to(trx, old_path, new_path).await, + } + } + + async fn remove(&self, trx: &Transaction, path: &[String]) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.remove(trx, path).await, + DirectoryOutput::DirectoryPartition(d) => d.remove(trx, path).await, + } + } + + async fn remove_if_exists( + &self, + trx: &Transaction, + path: &[String], + ) -> Result { + match self { + DirectoryOutput::DirectorySubspace(d) => d.remove_if_exists(trx, path).await, + DirectoryOutput::DirectoryPartition(d) => d.remove_if_exists(trx, path).await, + } + } + + async fn list( + &self, + trx: &Transaction, + path: &[String], + ) -> Result, DirectoryError> { + match self { + DirectoryOutput::DirectorySubspace(d) => d.list(trx, path).await, + DirectoryOutput::DirectoryPartition(d) => d.list(trx, path).await, + } + } +} + +// Strinc returns the first key that would sort outside the range prefixed by prefix. +pub(crate) fn strinc(key: Vec) -> Vec { + let mut key = key; + + for i in (0..key.len()).rev() { + if key[i] != 0xff { + key[i] += 1; + break; + } else { + // stripping key from trailing 0xFF bytes + key.remove(i); + } + } + key +} + +#[cfg(test)] +mod tests { + use super::*; + + // https://github.com/apple/foundationdb/blob/e34df983ee8c0db333babf36fb620318d026553d/bindings/c/test/unit/unit_tests.cpp#L95 + #[test] + fn test_strinc() { + assert_eq!(strinc(Vec::from("a".as_bytes())), Vec::from("b".as_bytes())); + assert_eq!(strinc(Vec::from("y".as_bytes())), Vec::from("z".as_bytes())); + assert_eq!( + strinc(Vec::from("!".as_bytes())), + Vec::from("\"".as_bytes()) + ); + assert_eq!(strinc(Vec::from("*".as_bytes())), Vec::from("+".as_bytes())); + assert_eq!( + strinc(Vec::from("fdb".as_bytes())), + Vec::from("fdc".as_bytes()) + ); + assert_eq!( + strinc(Vec::from("foundation database 6".as_bytes())), + Vec::from("foundation database 7".as_bytes()) + ); + + assert_eq!(strinc(vec![61u8, 62u8, 255u8]), vec![61u8, 63u8]); + assert_eq!(strinc(vec![253u8, 255u8]), vec![254u8]); + assert_eq!(strinc(vec![253u8, 255u8, 255u8]), vec![254u8]); + assert_eq!(strinc(vec![255u8, 255u8, 255u8]), Vec::::new()); + } +} diff --git a/bindings/rust/foundationdb/src/directory/node.rs b/bindings/rust/foundationdb/src/directory/node.rs new file mode 100644 index 00000000000..f327ed64c7d --- /dev/null +++ b/bindings/rust/foundationdb/src/directory/node.rs @@ -0,0 +1,70 @@ +use crate::RangeOption; +use crate::Transaction; +use crate::directory::DirectoryOutput; +use crate::directory::directory_layer::{ + DEFAULT_SUB_DIRS, DirectoryLayer, LAYER_SUFFIX, PARTITION_LAYER, +}; +use crate::directory::error::DirectoryError; +use crate::tuple::Subspace; +use futures::TryStreamExt; + +#[derive(Debug, Clone)] +pub(super) struct Node { + pub(super) subspace: Subspace, + pub(super) current_path: Vec, + pub(super) target_path: Vec, + pub(super) directory_layer: DirectoryLayer, + pub(super) layer: Vec, +} + +impl Node { + // `load_metadata` is loading extra information for the node, like the layer + pub(crate) async fn load_metadata( + trx: &Transaction, + subspace: &Subspace, + ) -> Result, DirectoryError> { + let key = subspace.pack(&LAYER_SUFFIX); + let layer = match trx.get(&key, false).await { + Err(err) => return Err(DirectoryError::FdbError(err)), + Ok(fdb_slice) => fdb_slice.as_deref().unwrap_or_default().to_vec(), + }; + Ok(layer) + } + + pub(crate) fn get_partition_subpath(&self) -> Vec { + Vec::from(&self.target_path[self.current_path.len()..]) + } + + /// list sub-folders for a node + pub(crate) async fn list_sub_folders( + &self, + trx: &Transaction, + ) -> Result, DirectoryError> { + let range_option = RangeOption::from(&self.subspace.subspace(&DEFAULT_SUB_DIRS)); + + let results: Vec = trx + .get_ranges_keyvalues(range_option, false) + .map_ok(|fdb_value| { + let subspace = Subspace::from_bytes(fdb_value.key()); + // stripping from subspace + let sub_directory: (i64, String) = self.subspace.unpack(subspace.bytes())?; + Ok(sub_directory.1) + }) + .try_collect::>>() + .await? + .into_iter() + .collect::, DirectoryError>>()?; + + Ok(results) + } + + pub(crate) fn is_in_partition(&self, include_empty_subpath: bool) -> bool { + self.layer.as_slice().eq(PARTITION_LAYER) + && (include_empty_subpath || self.target_path.len() > self.current_path.len()) + } + + pub(crate) fn get_contents(&self) -> Result { + self.directory_layer + .contents_of_node(&self.subspace, &self.current_path, &self.layer) + } +} diff --git a/bindings/rust/foundationdb/src/error.rs b/bindings/rust/foundationdb/src/error.rs new file mode 100644 index 00000000000..98918c54e87 --- /dev/null +++ b/bindings/rust/foundationdb/src/error.rs @@ -0,0 +1,209 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Error types for the Fdb crate + +use crate::directory::DirectoryError; +use crate::options; +use crate::tuple::PackError; +use crate::tuple::hca::HcaError; +use foundationdb_sys as fdb_sys; +use std::ffi::CStr; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; + +pub(crate) fn eval(error_code: fdb_sys::fdb_error_t) -> FdbResult<()> { + let rust_code: i32 = error_code; + if rust_code == 0 { + Ok(()) + } else { + Err(FdbError::from_code(error_code)) + } +} + +/// The Standard Error type of FoundationDB +#[derive(Debug, Copy, Clone)] +pub struct FdbError { + /// The FoundationDB error code + error_code: i32, +} + +impl FdbError { + /// Converts from a raw foundationDB error code + pub fn from_code(error_code: fdb_sys::fdb_error_t) -> Self { + Self { error_code } + } + + pub fn message(self) -> &'static str { + let error_str = + unsafe { CStr::from_ptr::<'static>(fdb_sys::fdb_get_error(self.error_code)) }; + error_str + .to_str() + .expect("bad error string from FoundationDB") + } + + fn is_error_predicate(self, predicate: options::ErrorPredicate) -> bool { + // This cast to `i32` isn't unnecessary in all configurations. + #[allow(clippy::unnecessary_cast)] + let check = + unsafe { fdb_sys::fdb_error_predicate(predicate.code() as i32, self.error_code) }; + + check != 0 + } + + /// Indicates the transaction may have succeeded, though not in a way the system can verify. + pub fn is_maybe_committed(self) -> bool { + self.is_error_predicate(options::ErrorPredicate::MaybeCommitted) + } + + /// Indicates the operations in the transactions should be retried because of transient error. + pub fn is_retryable(self) -> bool { + self.is_error_predicate(options::ErrorPredicate::Retryable) + } + + /// Indicates the transaction has not committed, though in a way that can be retried. + pub fn is_retryable_not_committed(self) -> bool { + self.is_error_predicate(options::ErrorPredicate::RetryableNotCommitted) + } + + /// Raw foundationdb error code + pub fn code(self) -> i32 { + self.error_code + } +} + +impl fmt::Display for FdbError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + std::fmt::Display::fmt(&self.message(), f) + } +} + +impl std::error::Error for FdbError {} + +/// Alias for `Result<..., FdbError>` +pub type FdbResult = Result; + +/// This error represent all errors that can be throwed by `db.run`. +/// Layer developers may use the `CustomError`. +#[non_exhaustive] +pub enum FdbBindingError { + NonRetryableFdbError(FdbError), + HcaError(HcaError), + DirectoryError(DirectoryError), + PackError(PackError), + /// A reference to the `RetryableTransaction` has been kept + ReferenceToTransactionKept, + /// All 65536 user versions have been allocated for the transaction attempt. + UserVersionExhausted, + /// A custom error that layer developers can use + /// + /// The retry loop of [`crate::Database::run`] recovers the underlying + /// [`FdbError`] by walking the boxed error's `source()` chain, so box the + /// error itself (or a type that keeps the `FdbError` as its `source()`). + /// Never box a stringified error (`e.to_string().into()`): the conversion + /// destroys the source chain, and a retryable error becomes fatal. + CustomError(Box), +} + +/// Walks an error's `source()` chain, returning the first `FdbError` found. +/// +/// The depth cap guards against pathological or cyclic source chains. +pub(crate) fn find_fdb_error(err: &(dyn std::error::Error + 'static)) -> Option { + const MAX_DEPTH: usize = 128; + let mut current = Some(err); + for _ in 0..=MAX_DEPTH { + let e = current?; + if let Some(fdb) = e.downcast_ref::() { + return Some(*fdb); + } + current = e.source(); + } + None +} + +impl FdbBindingError { + /// Returns the underlying `FdbError`, if any. + /// + /// A `CustomError` boxing an `FdbError` or an `FdbBindingError` is checked + /// first, then the whole `source()` chain is walked, so any error that + /// carries an `FdbError` as its `source()` (however deeply nested) is found. + pub fn get_fdb_error(&self) -> Option { + if let Self::CustomError(e) = self { + if let Some(e) = e.downcast_ref::() { + return Some(*e); + } + if let Some(e) = e.downcast_ref::() { + return e.get_fdb_error(); + } + } + find_fdb_error(self) + } +} + +impl From for FdbBindingError { + fn from(e: FdbError) -> Self { + Self::NonRetryableFdbError(e) + } +} + +impl From for FdbBindingError { + fn from(e: HcaError) -> Self { + Self::HcaError(e) + } +} + +impl From for FdbBindingError { + fn from(e: DirectoryError) -> Self { + Self::DirectoryError(e) + } +} + +impl FdbBindingError { + /// create a new custom error + pub fn new_custom_error(e: Box) -> Self { + Self::CustomError(e) + } +} + +impl Debug for FdbBindingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + FdbBindingError::NonRetryableFdbError(err) => write!(f, "{err:?}"), + FdbBindingError::HcaError(err) => write!(f, "{err:?}"), + FdbBindingError::DirectoryError(err) => write!(f, "{err:?}"), + FdbBindingError::PackError(err) => write!(f, "{err:?}"), + FdbBindingError::ReferenceToTransactionKept => { + write!(f, "Reference to transaction kept") + } + FdbBindingError::UserVersionExhausted => { + write!(f, "Transaction user version allocator exhausted") + } + FdbBindingError::CustomError(err) => write!(f, "{err:?}"), + } + } +} + +impl Display for FdbBindingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + std::fmt::Debug::fmt(&self, f) + } +} + +impl std::error::Error for FdbBindingError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::NonRetryableFdbError(e) => Some(e), + Self::HcaError(e) => Some(e), + Self::DirectoryError(e) => Some(e), + Self::PackError(e) => Some(e), + Self::CustomError(e) => Some(e.as_ref()), + Self::ReferenceToTransactionKept => None, + Self::UserVersionExhausted => None, + } + } +} diff --git a/bindings/rust/foundationdb/src/fdb_keys.rs b/bindings/rust/foundationdb/src/fdb_keys.rs new file mode 100644 index 00000000000..54364b7943b --- /dev/null +++ b/bindings/rust/foundationdb/src/fdb_keys.rs @@ -0,0 +1,181 @@ +// Copyright 2022 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Definitions of FDBKeys, used in api version 700 and more. + +use crate::error; +use crate::from_raw_fdb_slice; +use crate::future::FdbFutureHandle; +use crate::{FdbError, FdbResult}; +use foundationdb_sys as fdb_sys; +use std::fmt; +use std::ops::Deref; + +/// An slice of keys owned by a FoundationDB future +pub struct FdbKeys { + _f: FdbFutureHandle, + keys: *const FdbKey, + len: i32, +} +unsafe impl Sync for FdbKeys {} +unsafe impl Send for FdbKeys {} + +impl TryFrom for FdbKeys { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut keys = std::ptr::null(); + let mut len = 0; + + error::eval(unsafe { fdb_sys::fdb_future_get_key_array(f.as_ptr(), &mut keys, &mut len) })?; + + Ok(FdbKeys { + _f: f, + keys: keys as *const FdbKey, + len, + }) + } +} + +impl Deref for FdbKeys { + type Target = [FdbKey]; + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbKey, fdb_sys::FDBKey); + assert_eq_align!(FdbKey, u8); + from_raw_fdb_slice(self.keys, self.len as usize) + } +} + +impl AsRef<[FdbKey]> for FdbKeys { + fn as_ref(&self) -> &[FdbKey] { + self.deref() + } +} + +impl<'a> IntoIterator for &'a FdbKeys { + type Item = &'a FdbKey; + type IntoIter = std::slice::Iter<'a, FdbKey>; + + fn into_iter(self) -> Self::IntoIter { + self.deref().iter() + } +} + +/// An iterator of keyvalues owned by a foundationDB future +pub struct FdbKeysIter { + f: std::rc::Rc, + keys: *const FdbKey, + len: i32, + pos: i32, +} + +impl Iterator for FdbKeysIter { + type Item = FdbRowKey; + fn next(&mut self) -> Option { + #[allow(clippy::iter_nth_zero)] + self.nth(0) + } + + fn size_hint(&self) -> (usize, Option) { + let rem = (self.len - self.pos) as usize; + (rem, Some(rem)) + } + + fn nth(&mut self, n: usize) -> Option { + let pos = (self.pos as usize).checked_add(n); + match pos { + Some(pos) if pos < self.len as usize => { + // safe because pos < self.len + let row_key = unsafe { self.keys.add(pos) }; + self.pos = pos as i32 + 1; + + Some(FdbRowKey { + _f: self.f.clone(), + row_key, + }) + } + _ => { + self.pos = self.len; + None + } + } + } +} + +impl IntoIterator for FdbKeys { + type Item = FdbRowKey; + type IntoIter = FdbKeysIter; + + fn into_iter(self) -> Self::IntoIter { + FdbKeysIter { + f: std::rc::Rc::new(self._f), + keys: self.keys, + len: self.len, + pos: 0, + } + } +} +/// A row key you can own +/// +/// Until dropped, this might prevent multiple key/values from beeing freed. +/// (i.e. the future that own the data is dropped once all data it provided is dropped) +pub struct FdbRowKey { + _f: std::rc::Rc, + row_key: *const FdbKey, +} + +impl Deref for FdbRowKey { + type Target = FdbKey; + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbKey, fdb_sys::FDBKey); + assert_eq_align!(FdbKey, u8); + unsafe { &*(self.row_key) } + } +} +impl AsRef for FdbRowKey { + fn as_ref(&self) -> &FdbKey { + self.deref() + } +} +impl PartialEq for FdbRowKey { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} + +impl Eq for FdbRowKey {} +impl fmt::Debug for FdbRowKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.deref().fmt(f) + } +} + +#[repr(C, packed)] +/// An FdbKey, owned by a FoundationDB Future +pub struct FdbKey(fdb_sys::FDBKey); + +impl FdbKey { + /// retrieves the associated key + pub fn key(&self) -> &[u8] { + from_raw_fdb_slice(self.0.key, self.0.key_length as usize) + } +} + +impl PartialEq for FdbKey { + fn eq(&self, other: &Self) -> bool { + self.key() == other.key() + } +} + +impl Eq for FdbKey {} + +impl fmt::Debug for FdbKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?})", crate::tuple::Bytes::from(self.key()),) + } +} diff --git a/bindings/rust/foundationdb/src/future.rs b/bindings/rust/foundationdb/src/future.rs new file mode 100644 index 00000000000..93576153d6d --- /dev/null +++ b/bindings/rust/foundationdb/src/future.rs @@ -0,0 +1,661 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Most functions in the FoundationDB API are asynchronous, meaning that they +//! may return to the caller before actually delivering their Fdbresult. +//! +//! These functions always return FDBFuture*. An FDBFuture object represents a +//! Fdbresult value or error to be delivered at some future time. You can wait for +//! a Future to be “ready” – to have a value or error delivered – by setting a +//! callback function, or by blocking a thread, or by polling. Once a Future is +//! ready, you can extract either an error code or a value of the appropriate +//! type (the documentation for the original function will tell you which +//! fdb_future_get_*() function you should call). +//! +//! Futures make it easy to do multiple operations in parallel, by calling several +//! asynchronous functions before waiting for any of the Fdbresults. This can be +//! important for reducing the latency of transactions. +//! + +use std::convert::TryFrom; +use std::ffi::CStr; +use std::fmt; +use std::ops::Deref; +use std::os::raw::c_char; +use std::pin::Pin; +use std::ptr::NonNull; +use std::sync::Arc; + +#[cfg_api_versions(min = 700)] +pub use crate::fdb_keys::FdbKeys; +use crate::from_raw_fdb_slice; +#[cfg_api_versions(min = 710)] +pub use crate::mapped_key_values::MappedKeyValues; +use fdb_sys::if_cfg_api_versions; +use foundationdb_macros::cfg_api_versions; +use foundationdb_sys as fdb_sys; +use futures::prelude::*; +use futures::task::{AtomicWaker, Context, Poll}; + +use crate::{FdbError, FdbResult, error}; + +/// An opaque type that represents a Future in the FoundationDB C API. +pub struct FdbFutureHandle(NonNull); + +impl FdbFutureHandle { + pub const fn as_ptr(&self) -> *mut fdb_sys::FDBFuture { + self.0.as_ptr() + } +} +unsafe impl Sync for FdbFutureHandle {} +unsafe impl Send for FdbFutureHandle {} +impl Drop for FdbFutureHandle { + fn drop(&mut self) { + // `fdb_future_destroy` cancels the future, so we don't need to call + // `fdb_future_cancel` explicitly. + unsafe { fdb_sys::fdb_future_destroy(self.as_ptr()) } + } +} + +/// An opaque type that represents a pending Future that will be converted to a +/// predefined result type. +/// +/// Non owned result type (Fdb +pub struct FdbFuture { + f: Option, + waker: Option>, + phantom: std::marker::PhantomData, +} + +impl FdbFuture +where + T: TryFrom + Unpin, +{ + /// Takes ownership of a future returned by the FoundationDB C API. + /// + /// # Safety + /// + /// `f` must be a non-null, valid `FDBFuture` whose result can be extracted by + /// `T::try_from`. Ownership of the future is transferred to this wrapper: + /// no other owner may destroy it or release its result memory. In particular, + /// the same pointer must not be passed to this constructor more than once. + /// + /// Constructing an owning wrapper requires an explicit unsafe call: + /// + /// ```compile_fail,E0133 + /// use foundationdb::future::FdbFuture; + /// + /// let future = FdbFuture::<()>::new(std::ptr::null_mut()); + /// ``` + pub unsafe fn new(f: *mut fdb_sys::FDBFuture) -> Self { + Self { + f: Some(FdbFutureHandle( + NonNull::new(f).expect("FDBFuture to not be null"), + )), + waker: None, + phantom: std::marker::PhantomData, + } + } +} + +impl Future for FdbFuture +where + T: TryFrom + Unpin, +{ + type Output = FdbResult; + + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, cx)) + )] + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let f_ptr = self.f.as_ref().expect("cannot poll after resolve").as_ptr(); + if poll_ready( + &mut self.waker, + cx, + || unsafe { fdb_sys::fdb_future_is_ready(f_ptr) != 0 }, + |network_waker| { + let network_waker_ptr = Arc::into_raw(network_waker); + unsafe { + fdb_sys::fdb_future_set_callback( + f_ptr, + Some(fdb_future_callback), + network_waker_ptr as *mut _, + ); + } + }, + ) + .is_pending() + { + return Poll::Pending; + } + Poll::Ready( + error::eval(unsafe { fdb_sys::fdb_future_get_error(f_ptr) }) + .and_then(|()| T::try_from(self.f.take().expect("self.f.is_some()"))), + ) + } +} + +fn poll_ready( + waker: &mut Option>, + cx: &Context<'_>, + mut is_ready: impl FnMut() -> bool, + set_callback: impl FnOnce(Arc), +) -> Poll<()> { + if is_ready() { + return Poll::Ready(()); + } + let mut register = false; + let waker = waker.get_or_insert_with(|| { + register = true; + Arc::new(AtomicWaker::new()) + }); + waker.register(cx.waker()); + if register { + set_callback(Arc::clone(waker)); + } + // Completion may have consumed the previous task's waker before registration. + // Recheck readiness so the one-shot callback cannot leave this task pending. + if is_ready() { + Poll::Ready(()) + } else { + Poll::Pending + } +} + +pub static CUSTOM_EXECUTOR_HOOK: std::sync::OnceLock = std::sync::OnceLock::new(); + +// The callback from fdb C API can be called from multiple threads. so this callback should be +// thread-safe. +extern "C" fn fdb_future_callback( + _f: *mut fdb_sys::FDBFuture, + callback_parameter: *mut ::std::os::raw::c_void, +) { + // resolve all wakeup chain + let network_waker: Arc = unsafe { Arc::from_raw(callback_parameter as *const _) }; + network_waker.wake(); + + // polling phase for a custom executor (e.g. the simulation) + if let Some(poll_pending_tasks) = CUSTOM_EXECUTOR_HOOK.get() { + poll_pending_tasks(); + } +} + +/// A slice of bytes owned by a foundationDB future +pub struct FdbSlice { + _f: FdbFutureHandle, + value: *const u8, + len: i32, +} +unsafe impl Sync for FdbSlice {} +unsafe impl Send for FdbSlice {} + +impl Deref for FdbSlice { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + from_raw_fdb_slice(self.value, self.len as usize) + } +} +impl AsRef<[u8]> for FdbSlice { + fn as_ref(&self) -> &[u8] { + self.deref() + } +} + +impl TryFrom for FdbSlice { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut value = std::ptr::null(); + let mut len = 0; + + error::eval(unsafe { fdb_sys::fdb_future_get_key(f.as_ptr(), &mut value, &mut len) })?; + + Ok(FdbSlice { _f: f, value, len }) + } +} + +impl TryFrom for Option { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut present = 0; + let mut value = std::ptr::null(); + let mut len = 0; + + error::eval(unsafe { + fdb_sys::fdb_future_get_value(f.as_ptr(), &mut present, &mut value, &mut len) + })?; + + Ok(if present == 0 { + None + } else { + Some(FdbSlice { _f: f, value, len }) + }) + } +} + +/// A slice of addresses owned by a foundationDB future +pub struct FdbAddresses { + _f: FdbFutureHandle, + strings: *const FdbAddress, + len: i32, +} +unsafe impl Sync for FdbAddresses {} +unsafe impl Send for FdbAddresses {} + +impl TryFrom for FdbAddresses { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut strings: *mut *const c_char = std::ptr::null_mut(); + let mut len = 0; + + error::eval(unsafe { + fdb_sys::fdb_future_get_string_array(f.as_ptr(), &mut strings, &mut len) + })?; + + Ok(FdbAddresses { + _f: f, + strings: strings as *const FdbAddress, + len, + }) + } +} + +impl Deref for FdbAddresses { + type Target = [FdbAddress]; + + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbAddress, *const c_char); + assert_eq_align!(FdbAddress, u8); + from_raw_fdb_slice(self.strings, self.len as usize) + } +} +impl AsRef<[FdbAddress]> for FdbAddresses { + fn as_ref(&self) -> &[FdbAddress] { + self.deref() + } +} + +/// An address owned by a foundationDB future +/// +/// Because the data it represent is owned by the future in FdbAddresses, you +/// can never own a FdbAddress directly, you can only have references to it. +/// This way, you can never obtain a lifetime greater than the lifetime of the +/// slice that gave you access to it. +#[repr(C, packed)] +pub struct FdbAddress { + c_str: *const c_char, +} + +impl Deref for FdbAddress { + type Target = CStr; + + fn deref(&self) -> &CStr { + unsafe { std::ffi::CStr::from_ptr(self.c_str) } + } +} +impl AsRef for FdbAddress { + fn as_ref(&self) -> &CStr { + self.deref() + } +} + +/// An slice of keyvalues owned by a foundationDB future +pub struct FdbValues { + _f: FdbFutureHandle, + keyvalues: *const FdbKeyValue, + len: i32, + more: bool, +} +unsafe impl Sync for FdbValues {} +unsafe impl Send for FdbValues {} + +impl FdbValues { + /// `true` if there is another range after this one + pub fn more(&self) -> bool { + self.more + } +} + +impl TryFrom for FdbValues { + type Error = FdbError; + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut keyvalues = std::ptr::null(); + let mut len = 0; + let mut more = 0; + + unsafe { + error::eval(fdb_sys::fdb_future_get_keyvalue_array( + f.as_ptr(), + &mut keyvalues, + &mut len, + &mut more, + ))? + } + + Ok(FdbValues { + _f: f, + keyvalues: keyvalues as *const FdbKeyValue, + len, + more: more != 0, + }) + } +} + +impl Deref for FdbValues { + type Target = [FdbKeyValue]; + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbKeyValue, fdb_sys::FDBKeyValue); + assert_eq_align!(FdbKeyValue, u8); + + from_raw_fdb_slice(self.keyvalues, self.len as usize) + } +} +impl AsRef<[FdbKeyValue]> for FdbValues { + fn as_ref(&self) -> &[FdbKeyValue] { + self.deref() + } +} + +impl<'a> IntoIterator for &'a FdbValues { + type Item = &'a FdbKeyValue; + type IntoIter = std::slice::Iter<'a, FdbKeyValue>; + + fn into_iter(self) -> Self::IntoIter { + self.deref().iter() + } +} +impl IntoIterator for FdbValues { + type Item = FdbValue; + type IntoIter = FdbValuesIter; + + fn into_iter(self) -> Self::IntoIter { + FdbValuesIter { + f: Arc::new(self._f), + keyvalues: self.keyvalues, + len: self.len, + pos: 0, + } + } +} + +/// An iterator of keyvalues owned by a foundationDB future +pub struct FdbValuesIter { + f: Arc, + keyvalues: *const FdbKeyValue, + len: i32, + pos: i32, +} + +unsafe impl Send for FdbValuesIter {} + +impl Iterator for FdbValuesIter { + type Item = FdbValue; + fn next(&mut self) -> Option { + #[allow(clippy::iter_nth_zero)] + self.nth(0) + } + + fn nth(&mut self, n: usize) -> Option { + let pos = (self.pos as usize).checked_add(n); + match pos { + Some(pos) if pos < self.len as usize => { + // safe because pos < self.len + let keyvalue = unsafe { self.keyvalues.add(pos) }; + self.pos = pos as i32 + 1; + + Some(FdbValue { + _f: self.f.clone(), + keyvalue, + }) + } + _ => { + self.pos = self.len; + None + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let rem = (self.len - self.pos) as usize; + (rem, Some(rem)) + } +} +impl ExactSizeIterator for FdbValuesIter { + #[inline] + fn len(&self) -> usize { + (self.len - self.pos) as usize + } +} +impl DoubleEndedIterator for FdbValuesIter { + fn next_back(&mut self) -> Option { + self.nth_back(0) + } + + fn nth_back(&mut self, n: usize) -> Option { + if n < self.len() { + self.len -= 1 + n as i32; + // safe because len < original len + let keyvalue = unsafe { self.keyvalues.add(self.len as usize) }; + Some(FdbValue { + _f: self.f.clone(), + keyvalue, + }) + } else { + self.pos = self.len; + None + } + } +} + +/// A keyvalue you can own +/// +/// Until dropped, this might prevent multiple key/values from beeing freed. +/// (i.e. the future that own the data is dropped once all data it provided is dropped) +pub struct FdbValue { + _f: Arc, + keyvalue: *const FdbKeyValue, +} + +unsafe impl Send for FdbValue {} + +impl Deref for FdbValue { + type Target = FdbKeyValue; + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbKeyValue, fdb_sys::FDBKeyValue); + assert_eq_align!(FdbKeyValue, u8); + unsafe { &*self.keyvalue } + } +} +impl AsRef for FdbValue { + fn as_ref(&self) -> &FdbKeyValue { + self.deref() + } +} +impl PartialEq for FdbValue { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} +impl Eq for FdbValue {} +impl fmt::Debug for FdbValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.deref().fmt(f) + } +} + +/// A keyvalue owned by a foundationDB future +/// +/// Because the data it represent is owned by the future in FdbValues, you +/// can never own a FdbKeyValue directly, you can only have references to it. +/// This way, you can never obtain a lifetime greater than the lifetime of the +/// slice that gave you access to it. +#[repr(C, packed)] +pub struct FdbKeyValue(fdb_sys::FDBKeyValue); + +impl FdbKeyValue { + /// key + pub fn key(&self) -> &[u8] { + // This cast to `*const u8` isn't unnecessary in all configurations. + #[allow(clippy::unnecessary_cast)] + from_raw_fdb_slice(self.0.key as *const u8, self.0.key_length as usize) + } + + /// value + pub fn value(&self) -> &[u8] { + // This cast to `*const u8` isn't unnecessary in all configurations. + #[allow(clippy::unnecessary_cast)] + from_raw_fdb_slice(self.0.value as *const u8, self.0.value_length as usize) + } +} + +impl PartialEq for FdbKeyValue { + fn eq(&self, other: &Self) -> bool { + (self.key(), self.value()) == (other.key(), other.value()) + } +} +impl Eq for FdbKeyValue {} +impl fmt::Debug for FdbKeyValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({:?}, {:?})", + crate::tuple::Bytes::from(self.key()), + crate::tuple::Bytes::from(self.value()) + ) + } +} + +impl TryFrom for i64 { + type Error = FdbError; + + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut version: i64 = 0; + error::eval(unsafe { + if_cfg_api_versions!(min = 620 => { + fdb_sys::fdb_future_get_int64(f.as_ptr(), &mut version) + } else { + fdb_sys::fdb_future_get_version(f.as_ptr(), &mut version) + }) + })?; + Ok(version) + } +} + +impl TryFrom for () { + type Error = FdbError; + fn try_from(_f: FdbFutureHandle) -> FdbResult { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::{Cell, RefCell}; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::task::{Wake, Waker}; + + #[derive(Default)] + struct WakeCount(AtomicUsize); + + impl Wake for WakeCount { + fn wake(self: Arc) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + + #[derive(Default)] + struct PendingFuture { + ready: Cell, + callback: RefCell>>, + } + + impl PendingFuture { + fn complete(&self) { + self.ready.set(true); + self.callback.borrow_mut().take().unwrap().wake(); + } + + fn poll( + &self, + waker: &mut Option>, + cx: &Context<'_>, + complete_after_read: bool, + ) -> Poll<()> { + poll_ready( + waker, + cx, + || { + let ready = self.ready.get(); + if complete_after_read && !ready { + // The C readiness check has observed false, then the + // network callback completes before the caller registers. + self.complete(); + } + ready + }, + |callback| { + assert!(self.callback.replace(Some(callback)).is_none()); + }, + ) + } + } + + #[test] + fn completion_before_registering_a_new_task_is_ready() { + let future = PendingFuture::default(); + let mut registration = None; + let old_task = Arc::new(WakeCount::default()); + let old_waker = Waker::from(Arc::clone(&old_task)); + let new_task = Arc::new(WakeCount::default()); + let new_waker = Waker::from(Arc::clone(&new_task)); + + assert!( + future + .poll(&mut registration, &Context::from_waker(&old_waker), false) + .is_pending() + ); + assert!( + future + .poll(&mut registration, &Context::from_waker(&new_waker), true) + .is_ready() + ); + assert_eq!(old_task.0.load(Ordering::SeqCst), 1); + assert_eq!(new_task.0.load(Ordering::SeqCst), 0); + assert!(future.callback.borrow().is_none()); + } + + #[test] + fn completion_after_registering_a_new_task_wakes_it() { + let future = PendingFuture::default(); + let mut registration = None; + let old_task = Arc::new(WakeCount::default()); + let old_waker = Waker::from(Arc::clone(&old_task)); + let new_task = Arc::new(WakeCount::default()); + let new_waker = Waker::from(Arc::clone(&new_task)); + + for waker in [&old_waker, &new_waker] { + assert!( + future + .poll(&mut registration, &Context::from_waker(waker), false) + .is_pending() + ); + } + future.complete(); + assert_eq!(old_task.0.load(Ordering::SeqCst), 0); + assert_eq!(new_task.0.load(Ordering::SeqCst), 1); + assert!( + future + .poll(&mut registration, &Context::from_waker(&new_waker), false) + .is_ready() + ); + } +} diff --git a/bindings/rust/foundationdb/src/keyselector.rs b/bindings/rust/foundationdb/src/keyselector.rs new file mode 100644 index 00000000000..7ebd02c6136 --- /dev/null +++ b/bindings/rust/foundationdb/src/keyselector.rs @@ -0,0 +1,107 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! A `KeySelector` identifies a particular key in the database. + +use crate::tuple::Bytes; +use std::borrow::Cow; + +/// A `KeySelector` identifies a particular key in the database. +/// +/// FoundationDB's lexicographically ordered data model permits finding keys based on their order +/// (for example, finding the first key in the database greater than a given key). Key selectors +/// represent a description of a key in the database that could be resolved to an actual key by +/// `Transaction::get_key` or used directly as the beginning or end of a range in +/// `Transaction::getRange`. +/// +/// Note that the way the key selectors are resolved is somewhat non-intuitive, so users who wish +/// to use a key selector other than the default ones described below should probably consult that +/// documentation before proceeding. +/// +/// Generally one of the following static methods should be used to construct a KeySelector: +/// +/// - `last_less_than` +/// - `last_less_or_equal` +/// - `first_greater_than` +/// - `first_greater_or_equal` +/// +/// A dedicated [example](https://github.com/foundationdb-rs/foundationdb-rs/blob/main/foundationdb/examples/key_selectors.rs) is available on Github. +#[derive(Clone, Debug)] +pub struct KeySelector<'a> { + key: Bytes<'a>, + or_equal: bool, + offset: i32, +} + +impl<'a> KeySelector<'a> { + /// Constructs a new KeySelector from the given parameters. + pub const fn new(key: Cow<'a, [u8]>, or_equal: bool, offset: i32) -> Self { + Self { + key: Bytes(key), + or_equal, + offset, + } + } + + /// Returns a the key that serves as the anchor for this `KeySelector` + pub fn key(&self) -> &[u8] { + self.key.as_ref() + } + + /// True if this is an `or_equal` `KeySelector` + pub fn or_equal(&self) -> bool { + self.or_equal + } + + /// Returns the key offset parameter for this `KeySelector` + pub fn offset(&self) -> i32 { + self.offset + } + + /// Creates a `KeySelector` that picks the last key less than the parameter + pub fn last_less_than>>(key: K) -> Self { + Self::new(key.into(), false, 0) + } + + /// Creates a `KeySelector` that picks the last key less than or equal to the parameter + pub fn last_less_or_equal>>(key: K) -> Self { + Self::new(key.into(), true, 0) + } + + /// Creates a `KeySelector` that picks the first key greater than the parameter + pub fn first_greater_than>>(key: K) -> Self { + Self::new(key.into(), true, 1) + } + + /// Creates a `KeySelector` that picks the first key greater than or equal to the parameter + pub fn first_greater_or_equal>>(key: K) -> Self { + Self::new(key.into(), false, 1) + } + + fn make_key(&mut self, key: &[u8]) { + match &mut self.key { + Bytes(Cow::Borrowed(..)) => self.key = Bytes::from(key.to_owned()), + Bytes(Cow::Owned(vec)) => { + vec.clear(); + vec.extend_from_slice(key); + } + }; + } + + pub(crate) fn make_first_greater_or_equal(&mut self, key: &[u8]) { + self.make_key(key); + self.or_equal = false; + self.offset = 1; + } + + pub(crate) fn make_first_greater_than(&mut self, key: &[u8]) { + self.make_key(key); + self.or_equal = true; + self.offset = 1; + } +} diff --git a/bindings/rust/foundationdb/src/lib.rs b/bindings/rust/foundationdb/src/lib.rs new file mode 100644 index 00000000000..61af11dbe69 --- /dev/null +++ b/bindings/rust/foundationdb/src/lib.rs @@ -0,0 +1,127 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +#![doc = include_str!("../README.md")] + +use foundationdb_sys::if_cfg_api_versions; + +#[macro_use] +extern crate static_assertions; + +pub mod api; +if_cfg_api_versions! {min = 510, max = 600 => + pub mod cluster; +} +mod database; +pub mod directory; +mod error; +if_cfg_api_versions! {min = 700 => + #[deny(missing_docs)] + pub mod fdb_keys; +} +pub mod future; +mod keyselector; +if_cfg_api_versions! {min = 710 => + #[deny(missing_docs)] + pub mod mapped_key_values; +} + +/// Generated configuration types for use with the various `set_option` functions +#[allow(clippy::all)] +pub mod options; + +mod transaction; +mod tuple_ext; + +pub mod tuple { + pub use crate::tuple_ext::*; + pub use foundationdb_tuple::*; +} + +if_cfg_api_versions! {min = 510, max = 600 => + pub use crate::cluster::Cluster; +} + +pub use crate::database::*; +pub use crate::error::{FdbBindingError, FdbError, FdbResult}; +pub use crate::keyselector::*; +pub use crate::transaction::*; + +/// Initialize the FoundationDB Client API and start the network thread. +/// +/// Safe and idempotent: calling it multiple times returns `Ok` as long as the +/// (default) API version matches. Calling it is optional: creating a [`Database`] +/// initializes the client automatically. The network runs until process exit, +/// where an atexit hook stops it and joins the network thread, or until an +/// explicit [`api::stop_network`] call. +/// +/// The returned guard is kept for backward compatibility, dropping it does +/// nothing. +/// +///
+/// +/// The automatic stop at process exit is a convenience suited to tests and +/// short-lived tools. In a production application, prefer handling the network +/// stop yourself: the network thread is the event loop driving every +/// transaction, you may still have on-going operations at exit time, and you +/// usually want a clean teardown. Finish or cancel your work, drop your +/// [`Database`] handles, then call [`api::stop_network`]. +/// +///
+/// +/// # Errors +/// +/// - error 2201 (`api_version_already_set`) if a different API version was +/// selected before +/// - error 2025 (`network_cannot_be_restarted`) if the network was stopped +/// +/// # Examples +/// +/// ```rust +/// foundationdb::boot().expect("failed to initialize FoundationDB"); +/// // do some interesting things with the API... +/// ``` +/// +/// ```rust +/// #[tokio::main] +/// async fn main() { +/// foundationdb::boot().expect("failed to initialize FoundationDB"); +/// // do some interesting things with the API... +/// } +/// ``` +pub fn boot() -> FdbResult { + api::FdbApiBuilder::default().build()?.boot() +} + +/// Returns the default Fdb cluster configuration file path +#[cfg(target_os = "linux")] +pub fn default_config_path() -> &'static str { + "/etc/foundationdb/fdb.cluster" +} + +/// Returns the default Fdb cluster configuration file path +#[cfg(target_os = "macos")] +pub fn default_config_path() -> &'static str { + "/usr/local/etc/foundationdb/fdb.cluster" +} + +/// Returns the default Fdb cluster configuration file path +#[cfg(target_os = "windows")] +pub fn default_config_path() -> &'static str { + "C:/ProgramData/foundationdb/fdb.cluster" +} + +/// slice::from_raw_parts assumes the pointer to be aligned and non-null. +/// Since Rust nightly (mid-February 2024), it is enforced with debug asserts, +/// but the FDBServer can return a null pointer if the slice is empty: +/// ptr: 0x0, len: 0 +/// To avoid the assert, an empty slice is directly returned in that situation +fn from_raw_fdb_slice>(ptr: *const T, len: U) -> &'static [T] { + if ptr.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(ptr, len.into()) } +} diff --git a/bindings/rust/foundationdb/src/mapped_key_values.rs b/bindings/rust/foundationdb/src/mapped_key_values.rs new file mode 100644 index 00000000000..a9e70f66b35 --- /dev/null +++ b/bindings/rust/foundationdb/src/mapped_key_values.rs @@ -0,0 +1,270 @@ +// Copyright 2022 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Definitions of MappedKeyValues, used in api version 710 and more. +//! +//! GetMappedRange is an experimental feature introduced in FDB 7.1. It is intended to improve the +//! client throughput and reduce latency for a commonly used traffic pattern. +//! An experiment with Record Layer shows that this optimization can get 4x client throughput on a certain workload. +//! +//! More info can be found in the [relevant documentation](https://github.com/apple/foundationdb/wiki/Everything-about-GetMappedRange). + +use crate::from_raw_fdb_slice; +use crate::future::{FdbFutureHandle, FdbKeyValue}; +use crate::{error, KeySelector}; +use crate::{FdbError, FdbResult}; +use foundationdb_sys as fdb_sys; +use std::fmt; + +use std::ops::Deref; +use std::sync::Arc; + +/// An slice of mapped keyvalues owned by a foundationDB future produced by the `get_mapped` method. +pub struct MappedKeyValues { + _f: FdbFutureHandle, + mapped_keyvalues: *const FdbMappedKeyValue, + len: i32, + more: bool, +} +unsafe impl Sync for MappedKeyValues {} +unsafe impl Send for MappedKeyValues {} + +impl MappedKeyValues { + /// `true` if there is another range after this one + pub fn more(&self) -> bool { + self.more + } +} + +impl TryFrom for MappedKeyValues { + type Error = FdbError; + fn try_from(f: FdbFutureHandle) -> FdbResult { + let mut mapped_keyvalues = std::ptr::null(); + let mut len = 0; + let mut more = 0; + + unsafe { + error::eval(fdb_sys::fdb_future_get_mappedkeyvalue_array( + f.as_ptr(), + &mut mapped_keyvalues, + &mut len, + &mut more, + ))? + } + + Ok(MappedKeyValues { + _f: f, + mapped_keyvalues: mapped_keyvalues as *const FdbMappedKeyValue, + len, + more: more != 0, + }) + } +} + +#[repr(C, packed)] +/// A KeyValue produced by a mapped operation, ownder by a Foundation Future. +pub struct FdbMappedKeyValue(fdb_sys::FDBMappedKeyValue); + +impl PartialEq for FdbMappedKeyValue { + fn eq(&self, other: &Self) -> bool { + (self.parent_key(), self.parent_value()) == (other.parent_key(), other.parent_value()) + } +} +impl Eq for FdbMappedKeyValue {} +impl fmt::Debug for FdbMappedKeyValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({:?}, {:?})", + crate::tuple::Bytes::from(self.parent_key()), + crate::tuple::Bytes::from(self.parent_value()) + ) + } +} + +impl FdbMappedKeyValue { + /// Retrieves the "parent" key that generated the secondary scan. + pub fn parent_key(&self) -> &[u8] { + from_raw_fdb_slice(self.0.key.key, self.0.key.key_length as usize) + } + + /// Retrieves the "parent" value that generated the secondary scan. + pub fn parent_value(&self) -> &[u8] { + from_raw_fdb_slice(self.0.value.key, self.0.value.key_length as usize) + } + + /// Retrieves the beginning of the range + pub fn begin_range(&self) -> &[u8] { + from_raw_fdb_slice( + self.0.getRange.begin.key.key, + self.0.getRange.begin.key.key_length as usize, + ) + } + + /// Retrieves the end of the range + pub fn end_range(&self) -> &[u8] { + from_raw_fdb_slice( + self.0.getRange.end.key.key, + self.0.getRange.end.key.key_length as usize, + ) + } + + /// Retrieves the beginning of the range as a [`KeySelector`] + pub fn begin_selector(&self) -> KeySelector<'_> { + KeySelector::first_greater_or_equal(self.begin_range()) + } + + /// Retrieves the end of the range as a [`KeySelector`] + pub fn end_selector(&self) -> KeySelector<'_> { + KeySelector::first_greater_or_equal(self.end_range()) + } + + /// retrieves the associated slice of [`FdbKeyValue`] + pub fn key_values(&self) -> &[FdbKeyValue] { + from_raw_fdb_slice( + self.0.getRange.data as *const FdbKeyValue, + self.0.getRange.m_size as usize, + ) + } +} + +impl Deref for MappedKeyValues { + type Target = [FdbMappedKeyValue]; + + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbMappedKeyValue, fdb_sys::FDBMappedKeyValue); + assert_eq_align!(FdbMappedKeyValue, u8); + from_raw_fdb_slice(self.mapped_keyvalues, self.len as usize) + } +} + +impl AsRef<[FdbMappedKeyValue]> for MappedKeyValues { + fn as_ref(&self) -> &[FdbMappedKeyValue] { + self.deref() + } +} + +impl<'a> IntoIterator for &'a MappedKeyValues { + type Item = &'a FdbMappedKeyValue; + type IntoIter = std::slice::Iter<'a, FdbMappedKeyValue>; + + fn into_iter(self) -> Self::IntoIter { + self.deref().iter() + } +} + +/// An FdbMappedValue that you can own. +pub struct FdbMappedValue { + _f: Arc, + mapped_keyvalue: *const FdbMappedKeyValue, +} + +impl IntoIterator for MappedKeyValues { + type Item = FdbMappedValue; + type IntoIter = FdbMappedValuesIter; + + fn into_iter(self) -> Self::IntoIter { + FdbMappedValuesIter { + f: Arc::new(self._f), + mapped_keyvalues: self.mapped_keyvalues, + len: self.len, + pos: 0, + } + } +} + +unsafe impl Send for FdbMappedValue {} + +impl Deref for FdbMappedValue { + type Target = FdbMappedKeyValue; + fn deref(&self) -> &Self::Target { + assert_eq_size!(FdbMappedKeyValue, fdb_sys::FDBMappedKeyValue); + assert_eq_align!(FdbMappedKeyValue, u8); + unsafe { &*self.mapped_keyvalue } + } +} +impl AsRef for FdbMappedValue { + fn as_ref(&self) -> &FdbMappedKeyValue { + self.deref() + } +} +impl PartialEq for FdbMappedValue { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} +impl Eq for FdbMappedValue {} + +/// An iterator of mapped keyvalues owned by a foundationDB future +pub struct FdbMappedValuesIter { + f: Arc, + mapped_keyvalues: *const FdbMappedKeyValue, + len: i32, + pos: i32, +} + +unsafe impl Send for FdbMappedValuesIter {} + +impl Iterator for FdbMappedValuesIter { + type Item = FdbMappedValue; + fn next(&mut self) -> Option { + #[allow(clippy::iter_nth_zero)] + self.nth(0) + } + + fn nth(&mut self, n: usize) -> Option { + let pos = (self.pos as usize).checked_add(n); + match pos { + Some(pos) if pos < self.len as usize => { + // safe because pos < self.len + let mapped_keyvalue = unsafe { self.mapped_keyvalues.add(pos) }; + self.pos = pos as i32 + 1; + + Some(FdbMappedValue { + _f: self.f.clone(), + mapped_keyvalue, + }) + } + _ => { + self.pos = self.len; + None + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let rem = (self.len - self.pos) as usize; + (rem, Some(rem)) + } +} +impl ExactSizeIterator for FdbMappedValuesIter { + #[inline] + fn len(&self) -> usize { + (self.len - self.pos) as usize + } +} +impl DoubleEndedIterator for FdbMappedValuesIter { + fn next_back(&mut self) -> Option { + self.nth_back(0) + } + + fn nth_back(&mut self, n: usize) -> Option { + if n < self.len() { + self.len -= 1 + n as i32; + // safe because len < original len + let keyvalue = unsafe { self.mapped_keyvalues.add(self.len as usize) }; + Some(FdbMappedValue { + _f: self.f.clone(), + mapped_keyvalue: keyvalue, + }) + } else { + self.pos = self.len; + None + } + } +} diff --git a/bindings/rust/foundationdb/src/options.rs b/bindings/rust/foundationdb/src/options.rs new file mode 100644 index 00000000000..dbb879f98f6 --- /dev/null +++ b/bindings/rust/foundationdb/src/options.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/options.rs")); diff --git a/bindings/rust/foundationdb/src/transaction.rs b/bindings/rust/foundationdb/src/transaction.rs new file mode 100644 index 00000000000..db97704adf2 --- /dev/null +++ b/bindings/rust/foundationdb/src/transaction.rs @@ -0,0 +1,1633 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Implementations of the FDBTransaction C API +//! +//! + +use foundationdb_sys as fdb_sys; +use std::fmt; +use std::ops::{Deref, Range, RangeInclusive}; +use std::ptr::NonNull; +use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; + +use crate::future::*; +use crate::keyselector::*; +use crate::options; + +use crate::{FdbError, FdbResult, error}; +use foundationdb_macros::cfg_api_versions; + +use crate::error::FdbBindingError; + +use futures::{ + Future, FutureExt, Stream, TryFutureExt, TryStreamExt, future, future::Either, stream, +}; + +#[cfg_api_versions(min = 610)] +const METADATA_VERSION_KEY: &[u8] = b"\xff/metadataVersion"; + +/// Special keyspace prefix for conflicting keys. +const CONFLICTING_KEYS_PREFIX: &[u8] = b"\xff\xff/transaction/conflicting_keys/"; +// Matches C++ SystemData.cpp conflictingKeysRange end key. +const CONFLICTING_KEYS_END: &[u8] = b"\xff\xff/transaction/conflicting_keys/\xff\xff"; + +/// Special keyspace prefix for the read conflict ranges of a transaction. +#[cfg_api_versions(min = 630)] +const READ_CONFLICT_RANGE_PREFIX: &[u8] = b"\xff\xff/transaction/read_conflict_range/"; +#[cfg_api_versions(min = 630)] +const READ_CONFLICT_RANGE_END: &[u8] = b"\xff\xff/transaction/read_conflict_range/\xff\xff"; + +/// Special keyspace prefix for the write conflict ranges of a transaction. +#[cfg_api_versions(min = 630)] +const WRITE_CONFLICT_RANGE_PREFIX: &[u8] = b"\xff\xff/transaction/write_conflict_range/"; +#[cfg_api_versions(min = 630)] +const WRITE_CONFLICT_RANGE_END: &[u8] = b"\xff\xff/transaction/write_conflict_range/\xff\xff"; + +/// A key range reported by one of the conflict-range special keyspaces. +/// +/// Returned by [`Transaction::conflicting_keys`] (the ranges that made a commit +/// fail), [`Transaction::read_conflict_ranges`] and +/// [`Transaction::write_conflict_ranges`] (the ranges the transaction has +/// accumulated so far). +/// +/// Those keyspaces all encode ranges using boundary markers: +/// - Value `b"1"` marks the inclusive start of a range +/// - Value `b"0"` marks the exclusive end of a range +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ConflictRange { + begin: Vec, + end: Vec, +} + +impl ConflictRange { + /// The inclusive begin of the range. + pub fn begin(&self) -> &[u8] { + &self.begin + } + + /// The exclusive end of the range. + pub fn end(&self) -> &[u8] { + &self.end + } +} + +impl fmt::Display for ConflictRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}..{}", + String::from_utf8_lossy(&self.begin), + String::from_utf8_lossy(&self.end), + ) + } +} + +/// The range type returned by [`Transaction::conflicting_keys`], an alias of +/// [`ConflictRange`] which all three conflict-range readers share. +pub type ConflictingKeyRange = ConflictRange; + +/// Incremental parser of the `b"1"` / `b"0"` boundary encoding used by the +/// `\xff\xff/transaction/*` conflict-range special keyspaces. +/// +/// A range is only emitted once its end marker arrives, and the open begin +/// marker survives across [`feed`](Self::feed) calls: a `b"1"` and its matching +/// `b"0"` can land in two different `get_range` batches, so the parser is fed +/// batch after batch and the caller paginates until the keyspace is exhausted. +/// +/// A begin marker whose end marker never arrives is dropped: only complete +/// ranges are returned, in every build profile. +struct ConflictRangeParser { + prefix_len: usize, + ranges: Vec, + open_begin: Option>, +} + +impl ConflictRangeParser { + /// `prefix` is the special keyspace prefix to strip from the keys fed in. + fn new(prefix: &[u8]) -> Self { + Self { + prefix_len: prefix.len(), + ranges: Vec::new(), + open_begin: None, + } + } + + /// Feeds one batch of `(key, value)` pairs, in keyspace order. + fn feed<'a, I>(&mut self, batch: I) + where + I: IntoIterator, + { + for (raw_key, value) in batch { + let key = raw_key.get(self.prefix_len..).unwrap_or_default().to_vec(); + + match value { + b"1" => { + #[cfg(feature = "trace")] + if self.open_begin.is_some() { + tracing::warn!( + "'1' marker following an unpaired one in a conflict range keyspace, range dropped" + ); + } + + self.open_begin = Some(key); + } + b"0" => { + if let Some(begin) = self.open_begin.take() { + self.ranges.push(ConflictRange { begin, end: key }); + } + } + _ => {} + } + } + } + + /// Returns the complete ranges parsed so far, dropping a begin marker left + /// open by the end of the stream. + fn finish(self) -> Vec { + #[cfg(feature = "trace")] + if self.open_begin.is_some() { + tracing::warn!( + "unpaired '1' marker at the end of a conflict range keyspace, range dropped" + ); + } + + self.ranges + } +} + +/// A committed transaction. +#[derive(Debug)] +#[repr(transparent)] +pub struct TransactionCommitted { + tr: Transaction, +} + +impl TransactionCommitted { + /// Retrieves the database version number at which a given transaction was committed. + /// + /// Read-only transactions do not modify the database when committed and will have a committed + /// version of -1. Keep in mind that a transaction which reads keys and then sets them to their + /// current values may be optimized to a read-only transaction. + /// + /// Note that database versions are not necessarily unique to a given transaction and so cannot + /// be used to determine in what order two transactions completed. The only use for this + /// function is to manually enforce causal consistency when calling `set_read_version()` on + /// another subsequent transaction. + /// + /// Most applications will not call this function. + pub fn committed_version(&self) -> FdbResult { + let mut version: i64 = 0; + error::eval(unsafe { + fdb_sys::fdb_transaction_get_committed_version(self.tr.inner.as_ptr(), &mut version) + })?; + Ok(version) + } + + /// Reset the transaction to its initial state. + /// + /// This will not affect previously committed data. + /// + /// This is similar to dropping the transaction and creating a new one. + /// User versions allocated before reset are invalid for the new attempt, + /// which starts allocating again at zero. + pub fn reset(mut self) -> Transaction { + self.tr.reset(); + self.tr + } +} +impl From for Transaction { + fn from(tc: TransactionCommitted) -> Transaction { + tc.reset() + } +} + +/// A failed to commit transaction. +pub struct TransactionCommitError { + tr: Transaction, + err: FdbError, +} + +impl TransactionCommitError { + /// Implements the recommended retry and backoff behavior for a transaction. This function knows + /// which of the error codes generated by other `Transaction` functions represent temporary + /// error conditions and which represent application errors that should be handled by the + /// application. It also implements an exponential backoff strategy to avoid swamping the + /// database cluster with excessive retries when there is a high level of conflict between + /// transactions. + /// + /// You should not call this method most of the times and use `Database::transact` which + /// implements a retry loop strategy for you. + /// + /// On success the transaction enters a new attempt. User versions + /// allocated by [`Transaction::allocate_user_version`] are invalid for the + /// new attempt, which starts allocating again at zero. + pub fn on_error(self) -> impl Future> { + unsafe { + FdbFuture::<()>::new(fdb_sys::fdb_transaction_on_error( + self.tr.inner.as_ptr(), + self.err.code(), + )) + } + .map_ok(move |()| { + self.tr.reset_user_version_allocator(); + self.tr + }) + } + + /// Reads the conflicting key ranges that caused this commit failure. + /// + /// Only returns meaningful results if + /// [`TransactionOption::ReportConflictingKeys`](crate::options::TransactionOption::ReportConflictingKeys) + /// was set on the transaction **and** the error is `not_committed` (code 1020). + /// + /// Must be called **before** [`on_error`](Self::on_error) which resets the transaction. + /// + /// # Errors + /// + /// Returns an `FdbError` if the special keyspace read fails. + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub async fn conflicting_keys(&self) -> FdbResult> { + self.tr.conflicting_keys().await + } + + /// Reset the transaction to its initial state. + /// + /// This is similar to dropping the transaction and creating a new one. + /// User versions allocated before reset are invalid for the new attempt, + /// which starts allocating again at zero. + pub fn reset(mut self) -> Transaction { + self.tr.reset(); + self.tr + } +} + +impl Deref for TransactionCommitError { + type Target = FdbError; + fn deref(&self) -> &FdbError { + &self.err + } +} + +impl From for FdbError { + fn from(tce: TransactionCommitError) -> FdbError { + tce.err + } +} + +impl fmt::Debug for TransactionCommitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TransactionCommitError({})", self.err) + } +} + +impl fmt::Display for TransactionCommitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.err.fmt(f) + } +} + +impl std::error::Error for TransactionCommitError {} + +/// The result of `Transaction::Commit` +type TransactionResult = Result; + +/// A cancelled transaction +#[derive(Debug)] +#[repr(transparent)] +pub struct TransactionCancelled { + tr: Transaction, +} +impl TransactionCancelled { + /// Reset the transaction to its initial state. + /// + /// This is similar to dropping the transaction and creating a new one. + /// User versions allocated before reset are invalid for the new attempt, + /// which starts allocating again at zero. + pub fn reset(mut self) -> Transaction { + self.tr.reset(); + self.tr + } +} +impl From for Transaction { + fn from(tc: TransactionCancelled) -> Transaction { + tc.reset() + } +} + +/// In FoundationDB, a transaction is a mutable snapshot of a database. +/// +/// All read and write operations on a transaction see and modify an otherwise-unchanging version of the database and only change the underlying database if and when the transaction is committed. Read operations do see the effects of previous write operations on the same transaction. Committing a transaction usually succeeds in the absence of conflicts. +/// +/// Applications must provide error handling and an appropriate retry loop around the application code for a transaction. See the documentation for [fdb_transaction_on_error()](https://apple.github.io/foundationdb/api-c.html#transaction). +/// +/// Transactions group operations into a unit with the properties of atomicity, isolation, and durability. Transactions also provide the ability to maintain an application’s invariants or integrity constraints, supporting the property of consistency. Together these properties are known as ACID. +/// +/// Transactions are also causally consistent: once a transaction has been successfully committed, all subsequently created transactions will see the modifications made by it. +#[derive(Debug)] +pub struct Transaction { + // Order of fields should not be changed, because Rust drops field top-to-bottom, and + // transaction should be dropped before cluster. + inner: NonNull, + /// The next user version to issue for an incomplete versionstamp in the + /// current transaction attempt. + user_version: AtomicU32, +} +unsafe impl Send for Transaction {} +unsafe impl Sync for Transaction {} + +/// Converts Rust `bool` into `fdb_sys::fdb_bool_t` +#[inline] +fn fdb_bool(v: bool) -> fdb_sys::fdb_bool_t { + if v { 1 } else { 0 } +} +#[inline] +fn fdb_len(len: usize, context: &'static str) -> std::os::raw::c_int { + assert!(len <= i32::MAX as usize, "{context}.len() > i32::MAX"); + len as i32 +} +#[inline] +fn fdb_iteration(iteration: usize) -> std::os::raw::c_int { + if iteration > i32::MAX as usize { + 0 // this will cause client_invalid_operation + } else { + iteration as i32 + } +} +#[inline] +fn fdb_limit(v: usize) -> std::os::raw::c_int { + if v > i32::MAX as usize { + i32::MAX + } else { + v as i32 + } +} + +/// `RangeOption` represents a query parameters for range scan query. +/// +/// You can construct `RangeOption` easily: +/// +/// ``` +/// use foundationdb::RangeOption; +/// +/// let opt = RangeOption::from((b"begin".as_ref(), b"end".as_ref())); +/// let opt: RangeOption = (b"begin".as_ref()..b"end".as_ref()).into(); +/// let opt = RangeOption { +/// limit: Some(10), +/// ..RangeOption::from((b"begin".as_ref(), b"end".as_ref())) +/// }; +/// ``` +#[derive(Debug, Clone)] +pub struct RangeOption<'a> { + /// The beginning of the range. + pub begin: KeySelector<'a>, + /// The end of the range. + pub end: KeySelector<'a>, + /// If non-zero, indicates the maximum number of key-value pairs to return. + /// `None` and `Some(0)` both mean unlimited. + pub limit: Option, + /// If non-zero, indicates a (soft) cap on the combined number of bytes of keys and values to + /// return for each item. + pub target_bytes: usize, + /// One of the options::StreamingMode values indicating how the caller would like the data in + /// the range returned. + pub mode: options::StreamingMode, + /// If true, key-value pairs will be returned in reverse lexicographical order beginning at + /// the end of the range. + pub reverse: bool, + #[doc(hidden)] + pub __non_exhaustive: std::marker::PhantomData<()>, +} + +impl RangeOption<'_> { + /// Reverses the range direction. + pub fn rev(mut self) -> Self { + self.reverse = !self.reverse; + self + } + + pub fn next_range(mut self, kvs: &FdbValues) -> Option { + if !kvs.more() { + return None; + } + + let last = kvs.last()?; + let last_key = last.key(); + + if let Some(limit) = self.limit.as_mut().filter(|limit| **limit != 0) { + *limit = limit.saturating_sub(kvs.len()); + if *limit == 0 { + return None; + } + } + + if self.reverse { + self.end.make_first_greater_or_equal(last_key); + } else { + self.begin.make_first_greater_than(last_key); + } + Some(self) + } + + #[cfg_api_versions(min = 710)] + pub(crate) fn next_mapped_range(mut self, kvs: &MappedKeyValues) -> Option { + if !kvs.more() { + return None; + } + + let last = kvs.last()?; + let last_key = last.parent_key(); + + if let Some(limit) = self.limit.as_mut().filter(|limit| **limit != 0) { + *limit = limit.saturating_sub(kvs.len()); + if *limit == 0 { + return None; + } + } + + if self.reverse { + self.end.make_first_greater_or_equal(last_key); + } else { + self.begin.make_first_greater_than(last_key); + } + Some(self) + } +} + +impl Default for RangeOption<'_> { + fn default() -> Self { + Self { + begin: KeySelector::first_greater_or_equal([].as_ref()), + end: KeySelector::first_greater_or_equal([].as_ref()), + limit: None, + target_bytes: 0, + mode: options::StreamingMode::Iterator, + reverse: false, + __non_exhaustive: std::marker::PhantomData, + } + } +} + +impl<'a> From<(KeySelector<'a>, KeySelector<'a>)> for RangeOption<'a> { + fn from((begin, end): (KeySelector<'a>, KeySelector<'a>)) -> Self { + Self { + begin, + end, + ..Self::default() + } + } +} +impl From<(Vec, Vec)> for RangeOption<'static> { + fn from((begin, end): (Vec, Vec)) -> Self { + Self { + begin: KeySelector::first_greater_or_equal(begin), + end: KeySelector::first_greater_or_equal(end), + ..Self::default() + } + } +} +impl<'a> From<(&'a [u8], &'a [u8])> for RangeOption<'a> { + fn from((begin, end): (&'a [u8], &'a [u8])) -> Self { + Self { + begin: KeySelector::first_greater_or_equal(begin), + end: KeySelector::first_greater_or_equal(end), + ..Self::default() + } + } +} +impl<'a> From>> for RangeOption<'a> { + fn from(range: Range>) -> Self { + RangeOption::from((range.start, range.end)) + } +} + +impl<'a> From> for RangeOption<'a> { + fn from(range: Range<&'a [u8]>) -> Self { + RangeOption::from((range.start, range.end)) + } +} + +impl From>> for RangeOption<'static> { + fn from(range: Range>) -> Self { + RangeOption::from((range.start, range.end)) + } +} + +impl<'a> From> for RangeOption<'a> { + fn from(range: RangeInclusive<&'a [u8]>) -> Self { + let (start, end) = range.into_inner(); + (KeySelector::first_greater_or_equal(start)..KeySelector::first_greater_than(end)).into() + } +} + +impl From>> for RangeOption<'static> { + fn from(range: RangeInclusive>) -> Self { + let (start, end) = range.into_inner(); + (KeySelector::first_greater_or_equal(start)..KeySelector::first_greater_than(end)).into() + } +} + +impl Transaction { + pub(crate) fn new(inner: NonNull) -> Self { + Self { + inner, + user_version: AtomicU32::new(0), + } + } + + /// Allocates a user version for an incomplete [`Versionstamp`](crate::tuple::Versionstamp). + /// + /// Values start at zero and increase through `65535`. Values issued by + /// this allocator are unique within the current transaction attempt, across + /// all handles that share that transaction. Explicit user versions supplied + /// to `Versionstamp::incomplete` remain supported, but can collide with + /// allocator-issued versions or with each other. + /// + /// The allocator performs no database operations and adds no conflict + /// ranges. It has no reservation mechanism: allocate a value when building + /// the versionstamped mutation that will use it. + /// + /// `reset` and a successful `on_error` start a new transaction attempt and + /// restart allocation at zero. A cancelled transaction retains its issued + /// values until it is reset. Versions from a previous attempt therefore + /// must not be reused in mutations for the new attempt. + /// + /// # Errors + /// + /// Returns [`FdbBindingError::UserVersionExhausted`] after all 65536 user + /// versions have been issued for this attempt. + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub fn allocate_user_version(&self) -> Result { + self.user_version + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| { + (current <= u16::MAX as u32).then_some(current + 1) + }) + .map(|current| current as u16) + .map_err(|_| FdbBindingError::UserVersionExhausted) + } +} + +impl Transaction { + /// Called to set an option on an FDBTransaction. + pub fn set_option(&self, opt: options::TransactionOption) -> FdbResult<()> { + unsafe { opt.apply(self.inner.as_ptr()) } + } + + /// Pass through an option given a code and raw data. Useful when creating a passthrough layer + /// where the code and data will be provided as raw, in order to avoid deserializing to an option + /// and serializing it back to code and data. + /// In general, you should use `set_option`. + pub fn set_raw_option( + &self, + code: fdb_sys::FDBTransactionOption, + data: Option>, + ) -> FdbResult<()> { + let (data_ptr, size) = data + .as_ref() + .map(|data| { + ( + data.as_ptr(), + i32::try_from(data.len()).expect("len to fit in i32"), + ) + }) + .unwrap_or_else(|| (std::ptr::null(), 0)); + let err = unsafe { + fdb_sys::fdb_transaction_set_option(self.inner.as_ptr(), code, data_ptr, size) + }; + if err != 0 { + Err(FdbError::from_code(err)) + } else { + Ok(()) + } + } + + /// Modify the database snapshot represented by transaction to change the given + /// key to have the given value. + /// + /// If the given key was not previously present in the database it is inserted. + /// The modification affects the actual database only if transaction is later + /// committed with `Transaction::commit`. + /// + /// # Arguments + /// + /// * `key` - the name of the key to be inserted into the database. + /// * `value` - the value to be inserted into the database + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, key, value)) + )] + pub fn set(&self, key: &[u8], value: &[u8]) { + unsafe { + fdb_sys::fdb_transaction_set( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + value.as_ptr(), + fdb_len(value.len(), "value"), + ) + } + } + + /// Modify the database snapshot represented by transaction to remove the given key from the + /// database. + /// + /// If the key was not previously present in the database, there is no effect. The modification + /// affects the actual database only if transaction is later committed with + /// `Transaction::commit`. + /// + /// # Arguments + /// + /// * `key` - the name of the key to be removed from the database. + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, key)) + )] + pub fn clear(&self, key: &[u8]) { + unsafe { + fdb_sys::fdb_transaction_clear( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + ) + } + } + + /// Reads a value from the database snapshot represented by transaction. + /// + /// Returns an FDBFuture which will be set to the value of key in the database if there is any. + /// + /// # Arguments + /// + /// * `key` - the name of the key to be looked up in the database + /// * `snapshot` - `true` if this is a [snapshot read](https://apple.github.io/foundationdb/api-c.html#snapshots) + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, key)) + )] + pub fn get( + &self, + key: &[u8], + snapshot: bool, + ) -> impl Future>> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::>::new(fdb_sys::fdb_transaction_get( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + fdb_bool(snapshot), + )) + } + } + + /// Modify the database snapshot represented by transaction to perform the operation indicated + /// by operationType with operand param to the value stored by the given key. + /// + /// An atomic operation is a single database command that carries out several logical steps: + /// reading the value of a key, performing a transformation on that value, and writing the + /// result. Different atomic operations perform different transformations. Like other database + /// operations, an atomic operation is used within a transaction; however, its use within a + /// transaction will not cause the transaction to conflict. + /// + /// Atomic operations do not expose the current value of the key to the client but simply send + /// the database the transformation to apply. In regard to conflict checking, an atomic + /// operation is equivalent to a write without a read. It can only cause other transactions + /// performing reads of the key to conflict. + /// + /// By combining these logical steps into a single, read-free operation, FoundationDB can + /// guarantee that the transaction will not conflict due to the operation. This makes atomic + /// operations ideal for operating on keys that are frequently modified. A common example is + /// the use of a key-value pair as a counter. + /// + /// # Warning + /// + /// If a transaction uses both an atomic operation and a strictly serializable read on the same + /// key, the benefits of using the atomic operation (for both conflict checking and performance) + /// are lost. + /// + /// # Panics + /// + /// `SetVersionstampedKey` and `SetVersionstampedValue` require runtime API 520 + /// or later. Earlier APIs use different key and value encodings, which this + /// binding does not support; both mutations panic before reaching the native + /// client, including when given manually encoded operands. + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, key, param)) + )] + pub fn atomic_op(&self, key: &[u8], param: &[u8], op_type: options::MutationType) { + if matches!( + op_type, + options::MutationType::SetVersionstampedKey + | options::MutationType::SetVersionstampedValue + ) { + assert!( + crate::api::selected_api_version() >= 520, + "versionstamped mutations require runtime API 520 or later" + ); + } + unsafe { + fdb_sys::fdb_transaction_atomic_op( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + param.as_ptr(), + fdb_len(param.len(), "param"), + op_type.code(), + ) + } + } + + /// Resolves a key selector against the keys in the database snapshot represented by + /// transaction. + /// + /// Returns an FDBFuture which will be set to the key in the database matching the key + /// selector. + /// + /// # Arguments + /// + /// * `selector`: the key selector + /// * `snapshot`: `true` if this is a [snapshot read](https://apple.github.io/foundationdb/api-c.html#snapshots) + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, selector, snapshot)) + )] + pub fn get_key( + &self, + selector: &KeySelector, + snapshot: bool, + ) -> impl Future> + Send + Sync + Unpin + use<> { + let key = selector.key(); + + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_key( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + fdb_bool(selector.or_equal()), + selector.offset(), + fdb_bool(snapshot), + )) + } + } + + /// Reads all key-value pairs in the database snapshot represented by transaction (potentially + /// limited by limit, target_bytes, or mode) which have a key lexicographically greater than or + /// equal to the key resolved by the begin key selector and lexicographically less than the key + /// resolved by the end key selector. + /// + /// Returns a stream of KeyValue slices. + /// + /// This method is a little more efficient than `get_ranges_keyvalues` but a little harder to + /// use. + /// + /// # Arguments + /// + /// * `opt`: the range, limit, target_bytes and mode + /// * `snapshot`: `true` if this is a [snapshot read](https://apple.github.io/foundationdb/api-c.html#snapshots) + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, opt, snapshot)) + )] + pub fn get_ranges<'a>( + &'a self, + opt: RangeOption<'a>, + snapshot: bool, + ) -> impl Stream> + Send + Sync + Unpin + 'a { + stream::unfold((1, Some(opt)), move |(iteration, maybe_opt)| { + if let Some(opt) = maybe_opt { + Either::Left(self.get_range(&opt, iteration as usize, snapshot).map( + move |maybe_values| { + let next_opt = match &maybe_values { + Ok(values) => opt.next_range(values), + Err(..) => None, + }; + Some((maybe_values, (iteration + 1, next_opt))) + }, + )) + } else { + Either::Right(future::ready(None)) + } + }) + } + + /// Reads all key-value pairs in the database snapshot represented by transaction (potentially + /// limited by limit, target_bytes, or mode) which have a key lexicographically greater than or + /// equal to the key resolved by the begin key selector and lexicographically less than the key + /// resolved by the end key selector. + /// + /// Returns a stream of KeyValue. + /// + /// # Arguments + /// + /// * `opt`: the range, limit, target_bytes and mode + /// * `snapshot`: `true` if this is a [snapshot read](https://apple.github.io/foundationdb/api-c.html#snapshots) + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, opt, snapshot)) + )] + pub fn get_ranges_keyvalues<'a>( + &'a self, + opt: RangeOption<'a>, + snapshot: bool, + ) -> impl Stream> + Unpin + 'a { + self.get_ranges(opt, snapshot) + .map_ok(|values| stream::iter(values.into_iter().map(Ok))) + .try_flatten() + } + + /// Reads all key-value pairs in the database snapshot represented by transaction (potentially + /// limited by limit, target_bytes, or mode) which have a key lexicographically greater than or + /// equal to the key resolved by the begin key selector and lexicographically less than the key + /// resolved by the end key selector. + /// + ///
+ /// This method returns only a single batch of results, not necessarily all + /// key-value pairs in the range. It is a low-level primitive for manual pagination. Most + /// callers should use + /// get_ranges_keyvalues, + /// which automatically pages through the full range and yields every key-value pair as a stream. + ///
+ /// + /// # Arguments + /// + /// * `opt`: the range, limit, target_bytes and mode + /// * `iteration`: If opt.mode is Iterator, this parameter should start at 1 and be incremented + /// by 1 for each successive call while reading this range. In all other cases it is ignored. + /// * `snapshot`: `true` if this is a [snapshot read](https://apple.github.io/foundationdb/api-c.html#snapshots) + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, opt, snapshot)) + )] + pub fn get_range( + &self, + opt: &RangeOption, + iteration: usize, + snapshot: bool, + ) -> impl Future> + Send + Sync + Unpin + use<> { + let begin = &opt.begin; + let end = &opt.end; + let key_begin = begin.key(); + let key_end = end.key(); + + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_range( + self.inner.as_ptr(), + key_begin.as_ptr(), + fdb_len(key_begin.len(), "key_begin"), + fdb_bool(begin.or_equal()), + begin.offset(), + key_end.as_ptr(), + fdb_len(key_end.len(), "key_end"), + fdb_bool(end.or_equal()), + end.offset(), + fdb_limit(opt.limit.unwrap_or(0)), + fdb_limit(opt.target_bytes), + opt.mode.code(), + fdb_iteration(iteration), + fdb_bool(snapshot), + fdb_bool(opt.reverse), + )) + } + } + + /// Mapped Range is an experimental feature introduced in FDB 7.1. + /// It is intended to improve the client throughput and reduce latency for querying data through a Subspace used as a "index". + /// In such a case, querying records by scanning an index in relational databases can be + /// translated to a GetRange request on the index entries followed up by multiple GetValue requests for the record entries in FDB. + /// + /// This method is allowing FoundationDB "follow up" a GetRange request with GetValue requests, + /// this can happen in one request without additional back and forth. Considering the overhead + /// of each request, this saves time and resources on serialization, deserialization, and network. + /// + /// A mapped request will: + /// + /// * Do a range query (same as a `Transaction.get_range` request) and get the result. We call it the primary query. + /// * For each key-value pair in the primary query result, translate it to a `get_range` query and get the result. We call them secondary queries. + /// * Put all results in a nested structure and return them. + /// + /// **WARNING** : This feature is considered experimental at this time. It is only allowed when + /// using snapshot isolation AND disabling read-your-writes. + /// + /// More info can be found in the relevant [documentation](https://github.com/apple/foundationdb/wiki/Everything-about-GetMappedRange#input). + /// + /// This is the "raw" version, users are expected to use [Transaction::get_mapped_ranges] + #[cfg_api_versions(min = 710)] + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, opt, mapper, snapshot)) + )] + pub fn get_mapped_range( + &self, + opt: &RangeOption, + mapper: &[u8], + iteration: usize, + snapshot: bool, + ) -> impl Future> + Send + Sync + Unpin + use<> { + let begin = &opt.begin; + let end = &opt.end; + let key_begin = begin.key(); + let key_end = end.key(); + + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_mapped_range( + self.inner.as_ptr(), + key_begin.as_ptr(), + fdb_len(key_begin.len(), "key_begin"), + fdb_bool(begin.or_equal()), + begin.offset(), + key_end.as_ptr(), + fdb_len(key_end.len(), "key_end"), + fdb_bool(end.or_equal()), + end.offset(), + mapper.as_ptr(), + fdb_len(mapper.len(), "mapper_length"), + fdb_limit(opt.limit.unwrap_or(0)), + fdb_limit(opt.target_bytes), + opt.mode.code(), + fdb_iteration(iteration), + fdb_bool(snapshot), + fdb_bool(opt.reverse), + )) + } + } + + /// Mapped Range is an experimental feature introduced in FDB 7.1. + /// It is intended to improve the client throughput and reduce latency for querying data through a Subspace used as a "index". + /// In such a case, querying records by scanning an index in relational databases can be + /// translated to a GetRange request on the index entries followed up by multiple GetValue requests for the record entries in FDB. + /// + /// This method is allowing FoundationDB "follow up" a GetRange request with GetValue requests, + /// this can happen in one request without additional back and forth. Considering the overhead + /// of each request, this saves time and resources on serialization, deserialization, and network. + /// + /// A mapped request will: + /// + /// * Do a range query (same as a `Transaction.get_range` request) and get the result. We call it the primary query. + /// * For each key-value pair in the primary query result, translate it to a `get_range` query and get the result. We call them secondary queries. + /// * Put all results in a nested structure and return them. + /// + /// **WARNING** : This feature is considered experimental at this time. It is only allowed when + /// using snapshot isolation AND disabling read-your-writes. + /// + /// More info can be found in the relevant [documentation](https://github.com/apple/foundationdb/wiki/Everything-about-GetMappedRange#input). + #[cfg_api_versions(min = 710)] + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, opt, mapper, snapshot)) + )] + pub fn get_mapped_ranges<'a>( + &'a self, + opt: RangeOption<'a>, + mapper: &'a [u8], + snapshot: bool, + ) -> impl Stream> + Send + Sync + Unpin + 'a { + stream::unfold((1, Some(opt)), move |(iteration, maybe_opt)| { + if let Some(opt) = maybe_opt { + Either::Left( + self.get_mapped_range(&opt, mapper, iteration as usize, snapshot) + .map(move |maybe_values| { + let next_opt = match &maybe_values { + Ok(values) => opt.next_mapped_range(values), + Err(..) => None, + }; + Some((maybe_values, (iteration + 1, next_opt))) + }), + ) + } else { + Either::Right(future::ready(None)) + } + }) + } + + /// Modify the database snapshot represented by transaction to remove all keys (if any) which + /// are lexicographically greater than or equal to the given begin key and lexicographically + /// less than the given end_key. + /// + /// The modification affects the actual database only if transaction is later committed with + /// `Transaction::commit`. + #[cfg_attr( + feature = "trace", + tracing::instrument(level = "debug", skip(self, begin, end)) + )] + pub fn clear_range(&self, begin: &[u8], end: &[u8]) { + unsafe { + fdb_sys::fdb_transaction_clear_range( + self.inner.as_ptr(), + begin.as_ptr(), + fdb_len(begin.len(), "begin"), + end.as_ptr(), + fdb_len(end.len(), "end"), + ) + } + } + + /// Get the estimated byte size of the key range based on the byte sample collected by FDB + #[cfg_api_versions(min = 630)] + pub fn get_estimated_range_size_bytes( + &self, + begin: &[u8], + end: &[u8], + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_estimated_range_size_bytes( + self.inner.as_ptr(), + begin.as_ptr(), + fdb_len(begin.len(), "begin"), + end.as_ptr(), + fdb_len(end.len(), "end"), + )) + } + } + + /// Attempts to commit the sets and clears previously applied to the database snapshot + /// represented by transaction to the actual database. + /// + /// The commit may or may not succeed – in particular, if a conflicting transaction previously + /// committed, then the commit must fail in order to preserve transactional isolation. If the + /// commit does succeed, the transaction is durably committed to the database and all + /// subsequently started transactions will observe its effects. + /// + /// It is not necessary to commit a read-only transaction – you can simply drop it. + /// + /// Callers will usually want to retry a transaction if the commit or a another method on the + /// transaction returns a retryable error (see `on_error` and/or `Database::transact`). + /// + /// As with other client/server databases, in some failure scenarios a client may be unable to + /// determine whether a transaction succeeded. In these cases, `Transaction::commit` will return + /// an error and `is_maybe_committed()` will returns true on that error. The `on_error` function + /// treats this error as retryable, so retry loops that don’t check for `is_maybe_committed()` + /// could execute the transaction twice. In these cases, you must consider the idempotence of + /// the transaction. For more information, see [Transactions with unknown results](https://apple.github.io/foundationdb/developer-guide.html#developer-guide-unknown-results). + /// + /// Normally, commit will wait for outstanding reads to return. However, if those reads were + /// snapshot reads or the transaction option for disabling “read-your-writes” has been invoked, + /// any outstanding reads will immediately return errors. + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub fn commit(self) -> impl Future + Send + Sync + Unpin { + unsafe { FdbFuture::<()>::new(fdb_sys::fdb_transaction_commit(self.inner.as_ptr())) }.map( + move |r| match r { + Ok(()) => Ok(TransactionCommitted { tr: self }), + Err(err) => Err(TransactionCommitError { tr: self, err }), + }, + ) + } + + /// Implements the recommended retry and backoff behavior for a transaction. This function knows + /// which of the error codes generated by other `Transaction` functions represent temporary + /// error conditions and which represent application errors that should be handled by the + /// application. It also implements an exponential backoff strategy to avoid swamping the + /// database cluster with excessive retries when there is a high level of conflict between + /// transactions. + /// + /// It is not necessary to call `reset()` when handling an error with `on_error()` since the + /// transaction has already been reset. + /// + /// You should not call this method most of the times and use `Database::transact` which + /// implements a retry loop strategy for you. + /// + /// On success the transaction enters a new attempt. User versions + /// allocated by [`Self::allocate_user_version`] are invalid for the new + /// attempt, which starts allocating again at zero. + /// + /// On failure, the transaction is dropped. Use [`Self::on_error_with_transaction`] + /// to retain ownership on both success and failure. + pub fn on_error( + self, + err: FdbError, + ) -> impl Future> + Send + Sync + Unpin { + self.on_error_with_transaction(err) + .map(|(transaction, result)| result.map(|()| transaction)) + } + + /// Applies the same retry and backoff behavior as [`Self::on_error`], returning + /// ownership of the transaction even when the error cannot be retried. + /// + /// On success, the returned transaction has been reset for a new attempt. + /// On failure, it retains the state left by the native error handler, which + /// may be canceled. Call [`Self::reset`] before using it for another attempt. + /// This method does not replace or explicitly reset the failed transaction. + pub fn on_error_with_transaction( + self, + err: FdbError, + ) -> impl Future)> + Send + Sync + Unpin { + unsafe { + FdbFuture::<()>::new(fdb_sys::fdb_transaction_on_error( + self.inner.as_ptr(), + err.code(), + )) + } + .map(move |result| { + if result.is_ok() { + self.reset_user_version_allocator(); + } + (self, result) + }) + } + + /// Cancels the transaction. All pending or future uses of the transaction will return a + /// transaction_cancelled error. The transaction can be used again after it is reset. User + /// versions already allocated remain consumed until that reset starts a new attempt. + pub fn cancel(self) -> TransactionCancelled { + unsafe { + fdb_sys::fdb_transaction_cancel(self.inner.as_ptr()); + } + TransactionCancelled { tr: self } + } + + /// Returns a list of public network addresses as strings, one for each of the storage servers + /// responsible for storing key_name and its associated value. + pub fn get_addresses_for_key( + &self, + key: &[u8], + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::new(fdb_sys::fdb_transaction_get_addresses_for_key( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + )) + } + } + + /// A watch's behavior is relative to the transaction that created it. A watch will report a + /// change in relation to the key’s value as readable by that transaction. The initial value + /// used for comparison is either that of the transaction’s read version or the value as + /// modified by the transaction itself prior to the creation of the watch. If the value changes + /// and then changes back to its initial value, the watch might not report the change. + /// + /// Until the transaction that created it has been committed, a watch will not report changes + /// made by other transactions. In contrast, a watch will immediately report changes made by + /// the transaction itself. Watches cannot be created if the transaction has set the + /// READ_YOUR_WRITES_DISABLE transaction option, and an attempt to do so will return an + /// watches_disabled error. + /// + /// If the transaction used to create a watch encounters an error during commit, then the watch + /// will be set with that error. A transaction whose commit result is unknown will set all of + /// its watches with the commit_unknown_result error. If an uncommitted transaction is reset or + /// destroyed, then any watches it created will be set with the transaction_cancelled error. + /// + /// Returns an future representing an empty value that will be set once the watch has + /// detected a change to the value at the specified key. + /// + /// By default, each database connection can have no more than 10,000 watches that have not yet + /// reported a change. When this number is exceeded, an attempt to create a watch will return a + /// too_many_watches error. This limit can be changed using the MAX_WATCHES database option. + /// Because a watch outlives the transaction that creates it, any watch that is no longer + /// needed should be cancelled by dropping its future. + pub fn watch( + &self, + key: &[u8], + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::new(fdb_sys::fdb_transaction_watch( + self.inner.as_ptr(), + key.as_ptr(), + fdb_len(key.len(), "key"), + )) + } + } + + /// Returns an FDBFuture which will be set to the approximate transaction size so far in the + /// returned future, which is the summation of the estimated size of mutations, read conflict + /// ranges, and write conflict ranges. + /// + /// This can be called multiple times before the transaction is committed. + #[cfg_api_versions(min = 620)] + pub fn get_approximate_size( + &self, + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::new(fdb_sys::fdb_transaction_get_approximate_size( + self.inner.as_ptr(), + )) + } + } + + /// Gets a list of keys that can split the given range into (roughly) equally sized chunks based on chunk_size. + /// Note: the returned split points contain the start key and end key of the given range. + #[cfg_api_versions(min = 700)] + pub fn get_range_split_points( + &self, + begin: &[u8], + end: &[u8], + chunk_size: i64, + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_range_split_points( + self.inner.as_ptr(), + begin.as_ptr(), + fdb_len(begin.len(), "begin"), + end.as_ptr(), + fdb_len(end.len(), "end"), + chunk_size, + )) + } + } + + /// Returns an FDBFuture which will be set to the versionstamp which was used by any + /// versionstamp operations in this transaction. + /// + /// The future will be ready only after the successful completion of a call to `commit()` on + /// this Transaction. Read-only transactions do not modify the database when committed and will + /// result in the future completing with an error. Keep in mind that a transaction which reads + /// keys and then sets them to their current values may be optimized to a read-only transaction. + /// + /// Most applications will not call this function. + pub fn get_versionstamp( + &self, + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::new(fdb_sys::fdb_transaction_get_versionstamp( + self.inner.as_ptr(), + )) + } + } + + /// The transaction obtains a snapshot read version automatically at the time of the first call + /// to `get_*()` (including this one) and (unless causal consistency has been deliberately + /// compromised by transaction options) is guaranteed to represent all transactions which were + /// reported committed before that call. + pub fn get_read_version( + &self, + ) -> impl Future> + Send + Sync + Unpin + use<> { + unsafe { + FdbFuture::::new(fdb_sys::fdb_transaction_get_read_version( + self.inner.as_ptr(), + )) + } + } + + /// Sets the snapshot read version used by a transaction. + /// + /// This is not needed in simple cases. + /// If the given version is too old, subsequent reads will fail with error_code_past_version; + /// if it is too new, subsequent reads may be delayed indefinitely and/or fail with + /// error_code_future_version. If any of get_*() have been called on this transaction already, + /// the result is undefined. + pub fn set_read_version(&self, version: i64) { + unsafe { fdb_sys::fdb_transaction_set_read_version(self.inner.as_ptr(), version) } + } + + /// The metadata version key `\xff/metadataVersion` is a key intended to help layers deal with hot keys. + /// The value of this key is sent to clients along with the read version from the proxy, + /// so a client can read its value without communicating with a storage server. + /// To retrieve the metadataVersion, you need to set `TransactionOption::ReadSystemKeys` + #[cfg_api_versions(min = 610)] + pub async fn get_metadata_version(&self, snapshot: bool) -> FdbResult> { + match self.get(METADATA_VERSION_KEY, snapshot).await { + Ok(Some(fdb_slice)) => { + let value = fdb_slice.deref(); + // as we cannot write the metadata-key directly(we must mutate with an atomic_op), + // can we assume that it will always be the correct size? + if value.len() < 8 { + return Ok(None); + } + + // The 80-bits versionstamps are 10 bytes longs, and are composed of: + // * 8 bytes (Transaction Version) + // * followed by 2 bytes (Transaction Batch Order) + // More details can be found here: https://forums.foundationdb.org/t/implementing-versionstamps-in-bindings/250 + let mut arr = [0u8; 8]; + arr.copy_from_slice(&value[0..8]); + let transaction_version: i64 = i64::from_be_bytes(arr); + + Ok(Some(transaction_version)) + } + Ok(None) => Ok(None), + + Err(err) => Err(err), + } + } + + #[cfg_api_versions(min = 610)] + pub fn update_metadata_version(&self) { + // The param is transformed by removing the final four bytes from ``param`` and reading + // those as a little-Endian 32-bit integer to get a position ``pos``. + // The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the + // versionstamp of the transaction used. The first byte of the parameter is position 0. + // As we only have the metadata value, we can just create an 14-bytes Vec filled with 0u8. + let param = vec![0u8; 14]; + self.atomic_op( + METADATA_VERSION_KEY, + param.as_slice(), + options::MutationType::SetVersionstampedValue, + ) + } + + /// Reset transaction to its initial state. + /// + /// In order to protect against a race condition with cancel(), this call require a mutable + /// access to the transaction. + /// + /// This is similar to dropping the transaction and creating a new one. + /// + /// It is not necessary to call `reset()` when handling an error with `on_error()` since the + /// transaction has already been reset. + /// + /// This starts a new attempt. User versions allocated before + /// reset are invalid for the new attempt, which starts allocating again at + /// zero. + pub fn reset(&mut self) { + unsafe { fdb_sys::fdb_transaction_reset(self.inner.as_ptr()) } + self.reset_user_version_allocator(); + } + + fn reset_user_version_allocator(&self) { + self.user_version.store(0, Ordering::Relaxed); + } + + /// Reads the conflicting key ranges from the special keyspace after a commit conflict. + /// + /// This method reads from `\xff\xff/transaction/conflicting_keys/` and parses the + /// boundary encoding where `b"1"` marks range starts and `b"0"` marks range ends. + /// + /// The special keyspace read is resolved client-side — no network round-trip to the + /// cluster. The future still goes through the FDB network thread event loop, but the + /// data comes from an in-memory map populated during the commit response. Returns + /// an empty `Vec` if + /// [`TransactionOption::ReportConflictingKeys`](crate::options::TransactionOption::ReportConflictingKeys) + /// was not set. + /// + /// Requires API version 630 or later: the special keyspace does not exist on + /// older versions, where this read fails. + /// + /// Only complete ranges are returned. A begin marker whose end marker never + /// arrives is dropped, identically in debug and release builds. + /// + /// # Errors + /// + /// Returns an `FdbError` if the special keyspace read fails. + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub async fn conflicting_keys(&self) -> FdbResult> { + self.read_conflict_range_keyspace(CONFLICTING_KEYS_PREFIX, CONFLICTING_KEYS_END) + .await + } + + /// Reads the read conflict ranges accumulated by this transaction so far. + /// + /// These are the ranges the resolver will check for conflicts at commit + /// time: every key read by the transaction, plus the ranges added with + /// [`add_conflict_range`](Self::add_conflict_range). A point read of `k` + /// shows up as `k..k\x00`. + /// + ///
+ /// Reading this keyspace waits for every pending read of the transaction to + /// settle, since a read that has not returned yet has not registered its + /// conflict range. Called in the middle of a batch of concurrent reads, it + /// is therefore as slow as the slowest of them. + ///
+ /// + /// Only complete ranges are returned, see [`conflicting_keys`](Self::conflicting_keys). + /// + /// # Errors + /// + /// Returns an `FdbError` if the special keyspace read fails. + #[cfg_api_versions(min = 630)] + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub async fn read_conflict_ranges(&self) -> FdbResult> { + self.read_conflict_range_keyspace(READ_CONFLICT_RANGE_PREFIX, READ_CONFLICT_RANGE_END) + .await + } + + /// Reads the write conflict ranges accumulated by this transaction so far. + /// + /// These are the ranges the transaction will conflict *other* transactions + /// on: every key it wrote, plus the ranges added with + /// [`add_conflict_range`](Self::add_conflict_range). A single `set` of `k` + /// shows up as `k..k\x00`. + /// + ///
+ /// Before the commit, this is an approximate superset of the final + /// write conflict ranges when the transaction uses versionstamped keys: the + /// versionstamp is only known at commit time, so the incomplete key is + /// reported with its placeholder bytes. + ///
+ /// + /// Only complete ranges are returned, see [`conflicting_keys`](Self::conflicting_keys). + /// + /// # Errors + /// + /// Returns an `FdbError` if the special keyspace read fails. + #[cfg_api_versions(min = 630)] + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub async fn write_conflict_ranges(&self) -> FdbResult> { + self.read_conflict_range_keyspace(WRITE_CONFLICT_RANGE_PREFIX, WRITE_CONFLICT_RANGE_END) + .await + } + + /// Reads one conflict-range special keyspace to its end and parses it. + /// + /// The keyspace is paginated because a marker pair can straddle two batches, + /// which [`ConflictRangeParser`] handles by carrying the open begin marker + /// from one batch to the next. + async fn read_conflict_range_keyspace( + &self, + prefix: &[u8], + end: &[u8], + ) -> FdbResult> { + let mut parser = ConflictRangeParser::new(prefix); + let mut next = Some(RangeOption::from((prefix, end))); + let mut iteration = 1; + + while let Some(opt) = next { + let values = self.get_range(&opt, iteration, false).await?; + parser.feed(values.iter().map(|kv| (kv.key(), kv.value()))); + next = opt.next_range(&values); + iteration += 1; + } + + Ok(parser.finish()) + } + + /// Adds a conflict range to a transaction without performing the associated read or write. + /// + /// # Note + /// + /// Most applications will use the serializable isolation that transactions provide by default + /// and will not need to manipulate conflict ranges. + pub fn add_conflict_range( + &self, + begin: &[u8], + end: &[u8], + ty: options::ConflictRangeType, + ) -> FdbResult<()> { + error::eval(unsafe { + fdb_sys::fdb_transaction_add_conflict_range( + self.inner.as_ptr(), + begin.as_ptr(), + fdb_len(begin.len(), "begin"), + end.as_ptr(), + fdb_len(end.len(), "end"), + ty.code(), + ) + }) + } +} + +impl Drop for Transaction { + fn drop(&mut self) { + unsafe { + fdb_sys::fdb_transaction_destroy(self.inner.as_ptr()); + } + } +} + +/// A retryable transaction, generated by Database.run +#[derive(Clone)] +pub struct RetryableTransaction { + inner: Arc, +} + +impl Deref for RetryableTransaction { + type Target = Transaction; + fn deref(&self) -> &Transaction { + self.inner.deref() + } +} + +impl RetryableTransaction { + pub(crate) fn new(t: Transaction) -> RetryableTransaction { + RetryableTransaction { inner: Arc::new(t) } + } + + pub(crate) fn take(self) -> Result { + // checking weak references + if Arc::weak_count(&self.inner) != 0 { + return Err(FdbBindingError::ReferenceToTransactionKept); + } + Arc::try_unwrap(self.inner).map_err(|_| FdbBindingError::ReferenceToTransactionKept) + } + + pub(crate) async fn on_error( + self, + err: FdbError, + ) -> Result, FdbBindingError> { + Ok(self + .take()? + .on_error(err) + .await + .map(RetryableTransaction::new)) + } + + #[cfg_attr(feature = "trace", tracing::instrument(level = "debug", skip(self)))] + pub(crate) async fn commit( + self, + ) -> Result, FdbBindingError> { + Ok(self.take()?.commit().await) + } +} + +#[cfg(test)] +mod tests { + use super::{CONFLICTING_KEYS_PREFIX, ConflictRangeParser}; + + /// Builds a marker as the special keyspace returns it: the prefix, the key + /// it is a boundary of, and the `1`/`0` value. + fn marker(key: &str, value: &'static [u8]) -> (Vec, &'static [u8]) { + let mut full = CONFLICTING_KEYS_PREFIX.to_vec(); + full.extend_from_slice(key.as_bytes()); + (full, value) + } + + fn feed(parser: &mut ConflictRangeParser, batch: &[(Vec, &'static [u8])]) { + parser.feed(batch.iter().map(|(k, v)| (k.as_slice(), *v))); + } + + #[test] + fn parses_ranges_within_one_batch() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed( + &mut parser, + &[ + marker("a", b"1"), + marker("b", b"0"), + marker("c", b"1"), + marker("d", b"0"), + ], + ); + + let ranges = parser.finish(); + assert_eq!(ranges.len(), 2); + assert_eq!(ranges[0].begin(), b"a"); + assert_eq!(ranges[0].end(), b"b"); + assert_eq!(ranges[1].begin(), b"c"); + assert_eq!(ranges[1].end(), b"d"); + } + + /// The whole point of the incremental parser: a begin marker in one batch + /// and its end marker in the next one still make a single range. + #[test] + fn open_range_survives_across_batches() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed(&mut parser, &[marker("a", b"1")]); + feed(&mut parser, &[marker("b", b"0"), marker("c", b"1")]); + feed(&mut parser, &[marker("d", b"0")]); + + let ranges = parser.finish(); + assert_eq!(ranges.len(), 2); + assert_eq!(ranges[0].begin(), b"a"); + assert_eq!(ranges[0].end(), b"b"); + assert_eq!(ranges[1].begin(), b"c"); + assert_eq!(ranges[1].end(), b"d"); + } + + #[test] + fn empty_input_yields_no_range() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed(&mut parser, &[]); + assert!(parser.finish().is_empty()); + } + + /// A begin marker whose end marker never arrives is dropped, in every build + /// profile: the guarantee is that only complete ranges come out. + #[test] + fn unmatched_begin_at_end_of_stream_is_dropped() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed( + &mut parser, + &[marker("a", b"1"), marker("b", b"0"), marker("c", b"1")], + ); + + let ranges = parser.finish(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].begin(), b"a"); + assert_eq!(ranges[0].end(), b"b"); + } + + /// A begin marker arriving while one is still open replaces it: the range + /// the dropped marker opened is never emitted. + #[test] + fn a_second_begin_marker_replaces_the_open_one() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed( + &mut parser, + &[marker("a", b"1"), marker("b", b"1"), marker("c", b"0")], + ); + + let ranges = parser.finish(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].begin(), b"b"); + assert_eq!(ranges[0].end(), b"c"); + } + + /// Values that are neither `1` nor `0` are not boundaries and are ignored. + #[test] + fn unknown_marker_values_are_ignored() { + let mut parser = ConflictRangeParser::new(CONFLICTING_KEYS_PREFIX); + feed( + &mut parser, + &[marker("a", b"1"), marker("x", b"?"), marker("b", b"0")], + ); + + let ranges = parser.finish(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].begin(), b"a"); + assert_eq!(ranges[0].end(), b"b"); + } +} diff --git a/bindings/rust/foundationdb/src/tuple_ext/hca.rs b/bindings/rust/foundationdb/src/tuple_ext/hca.rs new file mode 100644 index 00000000000..bb77472a8fd --- /dev/null +++ b/bindings/rust/foundationdb/src/tuple_ext/hca.rs @@ -0,0 +1,241 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors. +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! The directory layer offers subspace indirection, where logical application subspaces are mapped to short, auto-generated key prefixes. This prefix assignment is done by the High Contention Allocator, which allows many clients to allocate short directory prefixes efficiently. +//! +//! The allocation process works over candidate value windows. It uses two subspaces to operate, the "counters" subspace and "recents" subspace (derived from the subspace used to create the HCA). +//! +//! "counters" contains a single key : "counters : window_start", whose value is the number of allocations in the current window. "window_start" is an integer that marks the lower bound of values that can be assigned from the current window. +//! "recents" can contain many keys : "recents : candidate", where each "candidate" is an integer that has been assigned to some client +//! +//! Assignment has two stages that are executed in a loop until they both succeed. +//! +//! 1. Find the current window. The client scans "counters : *" to get the current "window_start" and how many allocations have been made in the current window. +//! If the window is more than half-full (using pre-defined window sizes), the window is advanced: "counters : *" and "recents : *" are both cleared, and a new "counters : window_start + window_size" key is created with a value of 0. (1) is retried +//! If the window still has space, it moves to (2). +//! +//! 2. Find a candidate value inside that window. The client picks a candidate number between "[window_start, window_start + window_size)" and tries to set the key "recents : candidate". +//! If the write succeeds, the candidate is returned as the allocated value. Success! +//! If the write fails because the window has been advanced, it repeats (1). +//! If the write fails because the value was already set, it repeats (2). + +use std::fmt; +use std::sync::{Mutex, PoisonError}; + +use futures::future; +use rand::{RngExt, rngs::SmallRng}; + +use crate::options::{ConflictRangeType, MutationType, TransactionOption}; +use crate::tuple::{PackError, Subspace}; +use crate::*; + +const ONE_BYTES: &[u8] = &[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + +// Allocators for the same subspace may share a transaction, so their +// read-before-write sequences must be serialized across allocator instances. +static ALLOCATION_MUTEX: Mutex<()> = Mutex::new(()); + +pub enum HcaError { + FdbError(FdbError), + PackError(PackError), + InvalidDirectoryLayerMetadata, + PoisonError, +} + +impl fmt::Debug for HcaError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HcaError::FdbError(err) => err.fmt(f), + HcaError::PackError(err) => err.fmt(f), + HcaError::InvalidDirectoryLayerMetadata => { + write!(f, "invalid directory layer metadata") + } + HcaError::PoisonError => write!(f, "mutex poisoned"), + } + } +} + +impl fmt::Display for HcaError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl std::error::Error for HcaError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + HcaError::FdbError(e) => Some(e), + HcaError::PackError(e) => Some(e), + HcaError::InvalidDirectoryLayerMetadata | HcaError::PoisonError => None, + } + } +} + +impl From for HcaError { + fn from(err: FdbError) -> Self { + Self::FdbError(err) + } +} +impl From for HcaError { + fn from(err: PackError) -> Self { + Self::PackError(err) + } +} +impl From> for HcaError { + fn from(_err: PoisonError) -> Self { + Self::PoisonError + } +} + +impl TransactError for HcaError { + fn try_into_fdb_error(self) -> Result { + match self { + HcaError::FdbError(err) => Ok(err), + _ => Err(self), + } + } +} + +/// Represents a High Contention Allocator for a given subspace +#[derive(Debug)] +pub struct HighContentionAllocator { + counters: Subspace, + recent: Subspace, +} + +impl HighContentionAllocator { + /// Constructs an allocator that will use the input subspace for assigning values. + /// The given subspace should not be used by anything other than the allocator + pub fn new(subspace: Subspace) -> HighContentionAllocator { + HighContentionAllocator { + counters: subspace.subspace(&0i64), + recent: subspace.subspace(&1i64), + } + } + + /// Returns a byte string that + /// 1) has never and will never be returned by another call to this method on the same subspace + /// 2) is nearly as short as possible given the above + pub async fn allocate(&self, trx: &Transaction) -> Result { + let (begin, end) = self.counters.range(); + let begin = KeySelector::first_greater_or_equal(begin); + let end = KeySelector::first_greater_than(end); + let counters_range = RangeOption { + begin, + end, + limit: Some(1), + reverse: true, + ..RangeOption::default() + }; + let mut rng: SmallRng = rand::make_rng(); + + loop { + let kvs = trx.get_range(&counters_range, 1, true).await?; + + let mut start: i64 = if let Some(first) = kvs.first() { + self.counters.unpack(first.key())? + } else { + 0 + }; + + let mut window_advanced = false; + + let window = loop { + let counters_start = self.counters.subspace(&start); + + let count_future = { + let _mutex_guard = ALLOCATION_MUTEX.lock()?; + if window_advanced { + trx.clear_range(self.counters.bytes(), counters_start.bytes()); + trx.set_option(TransactionOption::NextWriteNoWriteConflictRange)?; + trx.clear_range(self.recent.bytes(), self.recent.subspace(&start).bytes()); + }; + + // Increment the allocation count for the current window + trx.atomic_op(counters_start.bytes(), ONE_BYTES, MutationType::Add); + trx.get(counters_start.bytes(), true) + }; + + let count_value = count_future.await?; + let count = if let Some(count_value) = count_value { + if count_value.len() != 8 { + return Err(HcaError::InvalidDirectoryLayerMetadata); + } + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&count_value); + i64::from_le_bytes(bytes) + } else { + 0 + }; + + let window = Self::window_size(start); + if count * 2 < window { + break window; + } + + start += window; + window_advanced = true; + }; + + loop { + // As of the snapshot being read from, the window is less than half + // full, so this should be expected to take 2 tries. Under high + // contention (and when the window advances), there is an additional + // subsequent risk of conflict for this transaction. + let candidate: i64 = rng.random_range(start..start + window); + let recent_candidate = self.recent.subspace(&candidate); + + let (latest_counter, candidate_value) = { + let _mutex_guard = ALLOCATION_MUTEX.lock()?; + let latest_counter = trx.get_range(&counters_range, 1, true); + let candidate_value = trx.get(recent_candidate.bytes(), false); + trx.set_option(TransactionOption::NextWriteNoWriteConflictRange)?; + trx.set(recent_candidate.bytes(), &[]); + (latest_counter, candidate_value) + }; + + let (latest_counter, candidate_value) = + future::try_join(latest_counter, candidate_value).await?; + + let current_window_start: i64 = if let Some(first) = latest_counter.first() { + self.counters.unpack(first.key())? + } else { + 0 + }; + + if current_window_start > start { + break; + } + + if candidate_value.is_none() { + let mut after = recent_candidate.bytes().to_vec(); + after.push(0x00); + trx.add_conflict_range( + recent_candidate.bytes(), + &after, + ConflictRangeType::Write, + )?; + return Ok(candidate); + } + } + } + } + + fn window_size(start: i64) -> i64 { + // Larger window sizes are better for high contention, smaller sizes for + // keeping the keys small. But if there are many allocations, the keys + // can't be too small. So start small and scale up. We don't want this to + // ever get *too* big because we have to store about window_size/2 recent + // items. + match start { + _ if start < 255 => 64, + _ if start < 65535 => 1024, + _ => 8192, + } + } +} diff --git a/bindings/rust/foundationdb/src/tuple_ext/mod.rs b/bindings/rust/foundationdb/src/tuple_ext/mod.rs new file mode 100644 index 00000000000..01c14e46ead --- /dev/null +++ b/bindings/rust/foundationdb/src/tuple_ext/mod.rs @@ -0,0 +1,2 @@ +pub mod hca; +mod subspace; diff --git a/bindings/rust/foundationdb/src/tuple_ext/subspace.rs b/bindings/rust/foundationdb/src/tuple_ext/subspace.rs new file mode 100644 index 00000000000..f3e69cf1c84 --- /dev/null +++ b/bindings/rust/foundationdb/src/tuple_ext/subspace.rs @@ -0,0 +1,22 @@ +use crate::{KeySelector, RangeOption, Transaction}; +use foundationdb_tuple::Subspace; +use std::borrow::Cow; + +impl From<&Subspace> for RangeOption<'static> { + fn from(subspace: &Subspace) -> Self { + let (begin, end) = subspace.range(); + + Self { + begin: KeySelector::first_greater_or_equal(Cow::Owned(begin)), + end: KeySelector::first_greater_or_equal(Cow::Owned(end)), + ..Self::default() + } + } +} + +impl Transaction { + pub fn clear_subspace_range(&self, subspace: &Subspace) { + let (begin, end) = subspace.range(); + self.clear_range(&begin, &end) + } +} diff --git a/bindings/rust/foundationdb/tests/api.rs b/bindings/rust/foundationdb/tests/api.rs new file mode 100644 index 00000000000..5102d36e53f --- /dev/null +++ b/bindings/rust/foundationdb/tests/api.rs @@ -0,0 +1,56 @@ +use foundationdb::api::{self, FdbApiBuilder}; +use foundationdb::options::NetworkOption; + +// The whole lifecycle in a single #[test]: `stop_network` is terminal for the +// process, so a second test in this binary would race it. +#[test] +fn test_network_lifecycle() { + // Integer options use the C API's full signed 64-bit domain. These values + // must reach the client without truncating to a negative 32-bit integer. + FdbApiBuilder::default() + .build() + .expect("failed to select fdb api") + .set_option(NetworkOption::TraceRollSize(3 * 1024 * 1024 * 1024)) + .expect("trace roll size above i32::MAX must be accepted") + .set_option(NetworkOption::TraceMaxLogsSize(i64::MAX)) + .expect("maximum signed 64-bit trace log size must be accepted"); + + // boot is safe and idempotent + let _guard1 = foundationdb::boot().expect("failed to boot fdb"); + let _guard2 = foundationdb::boot().expect("boot must be idempotent"); + + // re-selecting the same api version is Ok, a different one fails with 2201 + let builder = FdbApiBuilder::default() + .build() + .expect("re-selecting the same api version must be Ok"); + let err = FdbApiBuilder::default() + .set_runtime_version(300) + .build() + .map(drop) + .expect_err("selecting another api version must fail"); + assert_eq!(err.code(), 2201); + + // network options are rejected once the network is running + let err = builder + .set_option(NetworkOption::TraceEnable(String::new())) + .map(drop) + .expect_err("setting a network option after boot must fail"); + assert_eq!(err.code(), 2009); + + // the network event loop reported no error + assert!(api::network_run_error().is_none()); + + // explicit stop: Ok, idempotent, terminal + api::stop_network().expect("failed to stop the network"); + api::stop_network().expect("stop_network must be idempotent"); + let err = foundationdb::boot() + .map(drop) + .expect_err("boot after stop must fail"); + assert_eq!(err.code(), 2025); + let err = futures::executor::block_on(foundationdb::Database::new_compat(None)) + .map(drop) + .expect_err("creating a database after stop must fail"); + assert_eq!(err.code(), 2025); + // the guards drop here: they are inert, and the atexit hook then finds the + // network already stopped and does nothing +} diff --git a/bindings/rust/foundationdb/tests/api_ub.rs b/bindings/rust/foundationdb/tests/api_ub.rs new file mode 100644 index 00000000000..7530376ad9b --- /dev/null +++ b/bindings/rust/foundationdb/tests/api_ub.rs @@ -0,0 +1,22 @@ +use std::panic; + +#[test] +fn test_run() { + let old = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + let mut db = None; + let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { + // Run the foundationdb client API + let _drop_me = foundationdb::boot().expect("failed to initialize FoundationDB"); + db = Some(futures::executor::block_on(foundationdb::Database::new_compat(None)).unwrap()); + // Try to escape via unwind + panic!("UNWIND!") + })); + assert!(result.is_err()); + // The boot guard is inert: the network survives the unwind and the database + // is still usable (see issue #132). + let trx = db.unwrap().create_trx().unwrap(); + futures::executor::block_on(trx.get_read_version()) + .expect("the network must survive an unwind past the boot guard"); + panic::set_hook(old); +} diff --git a/bindings/rust/foundationdb/tests/atomic.rs b/bindings/rust/foundationdb/tests/atomic.rs new file mode 100644 index 00000000000..de34eae5a04 --- /dev/null +++ b/bindings/rust/foundationdb/tests/atomic.rs @@ -0,0 +1,61 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. +use byteorder::ByteOrder; +use foundationdb::*; +use futures::future::*; + +mod common; + +async fn atomic_add(db: &Database, key: &[u8], value: i64) -> FdbResult<()> { + let trx = db.create_trx()?; + + let val = { + let mut buf = [0u8; 8]; + byteorder::LE::write_i64(&mut buf, value); + buf + }; + trx.atomic_op(key, &val, options::MutationType::Add); + + trx.commit().await?; + Ok(()) +} + +#[tokio::test] +async fn test_atomic() -> FdbResult<()> { + const KEY: &[u8] = b"test-atomic"; + + let db = common::database().await?; + + println!("clear!"); + { + let trx = db.create_trx()?; + trx.clear(KEY); + trx.commit().await?; + } + + println!("concurrent!"); + { + let n = 1000usize; + + let fut_add = try_join_all((0..n).map(|_| atomic_add(&db, KEY, 1))); + let fut_sub = try_join_all((0..n).map(|_| atomic_add(&db, KEY, -1))); + + // Wait for all atomic operations + try_join(fut_add, fut_sub).await?; + } + + println!("check!"); + { + let trx = db.create_trx()?; + let value = trx.get(KEY, false).await?.expect("value should exists"); + let v: i64 = byteorder::LE::read_i64(&value); + if v != 0 { + panic!("expected 0, found {v}"); + } + } + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/common/mod.rs b/bindings/rust/foundationdb/tests/common/mod.rs new file mode 100644 index 00000000000..b5eea869276 --- /dev/null +++ b/bindings/rust/foundationdb/tests/common/mod.rs @@ -0,0 +1,20 @@ +use foundationdb as fdb; +use rand::RngExt; +use rand::distr::Alphanumeric; + +/// generate random string. Foundationdb watch only fires when value changed, so updating with same +/// value twice will not fire watches. To make examples work over multiple run, we use random +/// string as a value. +#[allow(unused)] +pub fn random_str(len: usize) -> String { + rand::rng() + .sample_iter(&Alphanumeric) + .take(len) + .map(char::from) + .collect() +} + +#[allow(unused)] +pub async fn database() -> fdb::FdbResult { + fdb::Database::new_compat(None).await +} diff --git a/bindings/rust/foundationdb/tests/conflict_ranges.rs b/bindings/rust/foundationdb/tests/conflict_ranges.rs new file mode 100644 index 00000000000..22fe9d92a27 --- /dev/null +++ b/bindings/rust/foundationdb/tests/conflict_ranges.rs @@ -0,0 +1,224 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! The read and write conflict range readers, against a live cluster. +//! +//! The `\xff\xff/transaction/{read,write}_conflict_range/` special keyspaces +//! exist from API version 630. + +#[allow(unused_imports)] +use foundationdb::*; +#[allow(unused_imports)] +use foundationdb_macros::cfg_api_versions; + +mod common; + +/// Whether `ranges` holds exactly `begin..end`. +#[allow(dead_code)] +fn contains(ranges: &[ConflictRange], begin: &[u8], end: &[u8]) -> bool { + ranges + .iter() + .any(|range| range.begin() == begin && range.end() == end) +} + +/// A point read registers `key..key\x00`, a range read the range itself. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn read_conflict_ranges_report_reads() -> FdbResult<()> { + const POINT: &[u8] = b"test-rcr-point"; + const RANGE_BEGIN: &[u8] = b"test-rcr-range-a"; + const RANGE_END: &[u8] = b"test-rcr-range-z"; + + let db = common::database().await?; + let trx = db.create_trx()?; + + trx.get(POINT, false).await?; + trx.get_range( + &RangeOption::from((RANGE_BEGIN, RANGE_END)), + 1, + false, // not a snapshot read: it must register a conflict range + ) + .await?; + + let ranges = trx.read_conflict_ranges().await?; + + let mut point_key = POINT.to_vec(); + point_key.push(0); + assert!( + contains(&ranges, POINT, &point_key), + "point read missing from {ranges:?}" + ); + assert!( + contains(&ranges, RANGE_BEGIN, RANGE_END), + "range read missing from {ranges:?}" + ); + + Ok(()) +} + +/// A snapshot read is not serializable and registers no read conflict range. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn snapshot_reads_register_no_read_conflict_range() -> FdbResult<()> { + const KEY: &[u8] = b"test-rcr-snapshot"; + + let db = common::database().await?; + let trx = db.create_trx()?; + + trx.get(KEY, true).await?; + + assert!(trx.read_conflict_ranges().await?.is_empty()); + + Ok(()) +} + +/// Writes register write conflict ranges, and only those. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn write_conflict_ranges_report_writes() -> FdbResult<()> { + const KEY: &[u8] = b"test-wcr-key"; + const CLEAR_BEGIN: &[u8] = b"test-wcr-cleared-a"; + const CLEAR_END: &[u8] = b"test-wcr-cleared-z"; + + let db = common::database().await?; + let trx = db.create_trx()?; + + trx.set(KEY, b"value"); + trx.clear_range(CLEAR_BEGIN, CLEAR_END); + + let ranges = trx.write_conflict_ranges().await?; + + let mut key_end = KEY.to_vec(); + key_end.push(0); + assert!( + contains(&ranges, KEY, &key_end), + "set missing from {ranges:?}" + ); + assert!( + contains(&ranges, CLEAR_BEGIN, CLEAR_END), + "cleared range missing from {ranges:?}" + ); + + // A write is not a read. + assert!(trx.read_conflict_ranges().await?.is_empty()); + + Ok(()) +} + +/// A range added without the associated read or write shows up in the keyspace +/// matching its type. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn add_conflict_range_shows_up() -> FdbResult<()> { + const READ_BEGIN: &[u8] = b"test-acr-read-a"; + const READ_END: &[u8] = b"test-acr-read-z"; + const WRITE_BEGIN: &[u8] = b"test-acr-write-a"; + const WRITE_END: &[u8] = b"test-acr-write-z"; + + let db = common::database().await?; + let trx = db.create_trx()?; + + trx.add_conflict_range(READ_BEGIN, READ_END, options::ConflictRangeType::Read)?; + trx.add_conflict_range(WRITE_BEGIN, WRITE_END, options::ConflictRangeType::Write)?; + + let read_ranges = trx.read_conflict_ranges().await?; + let write_ranges = trx.write_conflict_ranges().await?; + + assert!( + contains(&read_ranges, READ_BEGIN, READ_END), + "added read range missing from {read_ranges:?}" + ); + assert!( + contains(&write_ranges, WRITE_BEGIN, WRITE_END), + "added write range missing from {write_ranges:?}" + ); + assert!(!contains(&read_ranges, WRITE_BEGIN, WRITE_END)); + assert!(!contains(&write_ranges, READ_BEGIN, READ_END)); + + Ok(()) +} + +/// A fresh transaction has no conflict range at all. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn fresh_transaction_has_no_conflict_range() -> FdbResult<()> { + let db = common::database().await?; + let trx = db.create_trx()?; + + assert!(trx.read_conflict_ranges().await?.is_empty()); + assert!(trx.write_conflict_ranges().await?.is_empty()); + + Ok(()) +} + +/// Hundreds of disjoint ranges do not fit in one `get_range` batch, so the +/// reader has to paginate and to carry a begin marker whose end marker lands in +/// the next batch. A single-batch read truncates the result here. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn many_conflict_ranges_are_fully_paginated() -> FdbResult<()> { + const RANGES: usize = 500; + + let db = common::database().await?; + let trx = db.create_trx()?; + + for index in 0..RANGES { + let begin = format!("test-many-rcr/{index:05}/a"); + let end = format!("test-many-rcr/{index:05}/b"); + trx.add_conflict_range( + begin.as_bytes(), + end.as_bytes(), + options::ConflictRangeType::Read, + )?; + } + + let ranges = trx.read_conflict_ranges().await?; + + assert_eq!(ranges.len(), RANGES, "conflict ranges were truncated"); + for (index, range) in ranges.iter().enumerate() { + assert_eq!( + range.begin(), + format!("test-many-rcr/{index:05}/a").as_bytes() + ); + assert_eq!( + range.end(), + format!("test-many-rcr/{index:05}/b").as_bytes() + ); + } + + Ok(()) +} + +/// A failed transaction exposes the key that caused its commit conflict +/// before the native error handler resets it. +#[cfg_api_versions(min = 630)] +#[tokio::test] +async fn commit_error_reports_conflicting_key() -> FdbResult<()> { + const KEY: &[u8] = b"test-conflicting-key-direct"; + let db = common::database().await?; + let first = db.create_trx()?; + first.set_option(options::TransactionOption::ReportConflictingKeys)?; + first.get(KEY, false).await?; + + let second = db.create_trx()?; + second.set(KEY, b"other"); + second.commit().await.expect("second transaction commits"); + + first.set(KEY, b"first"); + let error = first + .commit() + .await + .expect_err("first transaction conflicts"); + assert_eq!(error.code(), 1020); + let ranges = error.conflicting_keys().await?; + assert!( + ranges + .iter() + .any(|range| KEY >= range.begin() && KEY < range.end()) + ); + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/database.rs b/bindings/rust/foundationdb/tests/database.rs new file mode 100644 index 00000000000..87db98a581b --- /dev/null +++ b/bindings/rust/foundationdb/tests/database.rs @@ -0,0 +1,36 @@ +use foundationdb_macros::cfg_api_versions; + +mod common; + +#[cfg_api_versions(min = 730)] +#[tokio::test] +async fn test_status() -> foundationdb::FdbResult<()> { + let db = common::database().await?; + let status = db.get_client_status().await?; + + let status = + std::str::from_utf8(status.as_ref()).expect("could not find any utf-8 bytes to read"); + assert!( + status.contains("Healthy"), + "Could not find healthy in '{status}'" + ); + + Ok(()) +} + +#[cfg_api_versions(min = 710)] +#[tokio::test] +async fn test_get_main_thread_busyness() -> foundationdb::FdbResult<()> { + let db = common::database().await?; + + let busyness = db + .get_main_thread_busyness() + .await + .expect("could not get busyness"); + assert!( + busyness == 0.0, + "{}", + format!("non-zero thread busyness: {busyness}") + ); + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/directory.rs b/bindings/rust/foundationdb/tests/directory.rs new file mode 100644 index 00000000000..09613a44fcb --- /dev/null +++ b/bindings/rust/foundationdb/tests/directory.rs @@ -0,0 +1,197 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use foundationdb::directory::{DirectoryError, DirectoryLayer}; + +use foundationdb::directory::Directory; + +use foundationdb::tuple::Subspace; +use foundationdb::*; + +mod common; + +#[tokio::test] +// testing basic features of the Directory, everything is tracked using with the BindingTester. +async fn test_directory() { + let db = common::database().await.expect("cannot open fdb"); + + // Scope the directory layer to a test-specific subspace: create() fails on + // an already existing path, so reruns need a fresh state, and clearing only + // our own subspace keeps the rest of the cluster untouched. + let test_root = Subspace::from("test-directory"); + let directory = DirectoryLayer::new( + test_root.subspace(&"node"), + test_root.subspace(&"content"), + false, + ); + + eprintln!("clearing the test subspace"); + let trx = db.create_trx().expect("cannot create txn"); + trx.clear_subspace_range(&test_root); + trx.commit().await.expect("could not clear keys"); + + eprintln!("creating directories"); + + test_create_then_open_then_delete(&db, &directory, vec![String::from("application")]) + .await + .expect("failed to run"); + + test_create_then_open_then_delete(&db, &directory, vec![String::from("1"), String::from("2")]) + .await + .expect("failed to run"); +} + +#[tokio::test] +async fn test_directory_rejects_occupied_bare_prefix() { + test_directory_rejects_occupied_raw_prefix("test-directory-bare-prefix", &[]).await; +} + +#[tokio::test] +async fn test_directory_rejects_occupied_ff_suffix() { + test_directory_rejects_occupied_raw_prefix("test-directory-ff-suffix", &[0xff]).await; +} + +#[tokio::test] +async fn test_directory_newer_minor_version_is_read_only() { + let db = common::database().await.expect("cannot open fdb"); + let test_root = Subspace::from("test-directory-newer-minor"); + let nodes = test_root.subspace(&"node"); + let directory = DirectoryLayer::new(nodes.clone(), test_root.subspace(&"content"), false); + let existing = vec![String::from("existing")]; + let missing = vec![String::from("missing")]; + + let trx = db.create_trx().expect("cannot create txn"); + trx.clear_subspace_range(&test_root); + let created = directory + .create(&trx, &existing, None, None) + .await + .expect("cannot create directory"); + let version_key = nodes.subspace(&nodes.bytes()).pack(&b"version".as_slice()); + let version: Vec<_> = [1_u32, 1, 0] + .into_iter() + .flat_map(u32::to_le_bytes) + .collect(); + trx.set(&version_key, &version); + trx.commit().await.expect("cannot commit directory fixture"); + + let trx = db.create_trx().expect("cannot create txn"); + let opened = directory + .open(&trx, &existing, None) + .await + .expect("newer minor version must allow reads"); + assert_eq!(created.bytes().unwrap(), opened.bytes().unwrap()); + assert!(directory.exists(&trx, &existing).await.unwrap()); + assert!(!directory.exists(&trx, &missing).await.unwrap()); + assert_eq!(directory.list(&trx, &[]).await.unwrap(), existing); + assert!( + directory + .create_or_open(&trx, &existing, None, None) + .await + .is_ok() + ); + + assert!(matches!( + directory.create(&trx, &missing, None, None).await, + Err(DirectoryError::Version(_)) + )); + assert!(matches!( + directory.move_to(&trx, &existing, &missing).await, + Err(DirectoryError::Version(_)) + )); + assert!(matches!( + directory.remove(&trx, &existing).await, + Err(DirectoryError::Version(_)) + )); +} + +async fn test_directory_rejects_occupied_raw_prefix(name: &str, suffix: &[u8]) { + let db = common::database().await.expect("cannot open fdb"); + let test_root = Subspace::from(name); + let content = test_root.subspace(&"content"); + let directory = DirectoryLayer::new(test_root.subspace(&"node"), content.clone(), false); + let path = vec![String::from("occupied")]; + let value = b"existing application data"; + + // Occupy every candidate in the initial allocator window so this does not + // depend on which random candidate the allocator chooses. + let keys: Vec<_> = (0..64_i64) + .map(|candidate| { + let mut key = content.pack(&candidate); + key.extend_from_slice(suffix); + key + }) + .collect(); + let trx = db.create_trx().expect("cannot create txn"); + trx.clear_subspace_range(&test_root); + for key in &keys { + trx.set(key, value); + } + trx.commit() + .await + .expect("cannot populate occupied prefixes"); + + let trx = db.create_trx().expect("cannot create txn"); + assert!(matches!( + directory.create(&trx, &path, None, None).await, + Err(DirectoryError::PrefixNotEmpty) + )); + drop(trx); + + let trx = db.create_trx().expect("cannot create txn"); + assert!( + !directory + .remove_if_exists(&trx, &path) + .await + .expect("cannot check rejected directory") + ); + trx.commit().await.expect("cannot commit removal check"); + + let trx = db.create_trx().expect("cannot create txn"); + for key in &keys { + let stored = trx + .get(key, false) + .await + .expect("cannot read occupied prefix") + .expect("existing key was removed"); + assert_eq!(&*stored, value); + } +} + +async fn test_create_then_open_then_delete( + db: &Database, + directory: &DirectoryLayer, + path: Vec, +) -> FdbResult<()> { + let trx = db.create_trx()?; + + eprintln!("creating {:?}", path); + let create_output = directory.create(&trx, &path, None, None).await; + assert!( + create_output.is_ok(), + "cannot create: {:?}", + create_output.err().unwrap() + ); + trx.commit().await.expect("cannot commit"); + let trx = db.create_trx()?; + + eprintln!("opening {:?}", path); + let open_output = directory.open(&trx, &path, None).await; + assert!( + open_output.is_ok(), + "cannot create: {:?}", + open_output.err().unwrap() + ); + + assert_eq!( + create_output.unwrap().bytes().unwrap(), + open_output.unwrap().bytes().unwrap() + ); + trx.commit().await.expect("cannot commit"); + + // removing folder + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/error.rs b/bindings/rust/foundationdb/tests/error.rs new file mode 100644 index 00000000000..e19d1238616 --- /dev/null +++ b/bindings/rust/foundationdb/tests/error.rs @@ -0,0 +1,68 @@ +use foundationdb::directory::DirectoryError; +use foundationdb::tuple::hca::HcaError; +use foundationdb::{FdbBindingError, FdbError}; +use std::fmt; + +#[test] +// This test is here because I'm always creating infinite recursion on Display and Debug impl 🤦 +// Exhibit A: https://github.com/foundationdb-rs/foundationdb-rs/pull/83 +// Exhibit B: https://github.com/foundationdb-rs/foundationdb-rs/issues/93 +fn test_debug_display_trait() { + let error = FdbBindingError::ReferenceToTransactionKept; + println!("{error}"); + println!("{error:?}"); +} + +/// A typical layer error wrapping an FdbError as its source, like a +/// thiserror enum with `#[source]`/`#[from]` would. +#[derive(Debug)] +struct LayerError { + source: FdbError, +} + +impl fmt::Display for LayerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "layer error: {}", self.source) + } +} + +impl std::error::Error for LayerError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} + +#[test] +fn get_fdb_error_walks_source_chain() { + let layer_err = LayerError { + source: FdbError::from_code(1020), + }; + let err = FdbBindingError::new_custom_error(Box::new(layer_err)); + assert_eq!(err.get_fdb_error().map(|e| e.code()), Some(1020)); +} + +#[test] +fn get_fdb_error_finds_nested_directory_hca() { + let err = FdbBindingError::from(DirectoryError::HcaError(HcaError::FdbError( + FdbError::from_code(1020), + ))); + assert_eq!(err.get_fdb_error().map(|e| e.code()), Some(1020)); +} + +#[test] +fn get_fdb_error_boxed_binding_error() { + let inner = FdbBindingError::from(FdbError::from_code(1020)); + let err = FdbBindingError::new_custom_error(Box::new(inner)); + assert_eq!(err.get_fdb_error().map(|e| e.code()), Some(1020)); +} + +#[test] +fn stringified_error_has_no_fdb_error() { + // Stringifying an error destroys the source chain: there is no FdbError + // to recover from a String, so such errors are never retried. + let layer_err = LayerError { + source: FdbError::from_code(1020), + }; + let err = FdbBindingError::CustomError(layer_err.to_string().into()); + assert!(err.get_fdb_error().is_none()); +} diff --git a/bindings/rust/foundationdb/tests/future.rs b/bindings/rust/foundationdb/tests/future.rs new file mode 100644 index 00000000000..300734a2557 --- /dev/null +++ b/bindings/rust/foundationdb/tests/future.rs @@ -0,0 +1,58 @@ +use foundationdb::*; +use futures::prelude::*; +use std::pin::Pin; +use std::task::{Context, Poll}; + +mod common; + +struct AbortingFuture { + inner: F, + polled: bool, +} + +impl Future for AbortingFuture +where + T: Future + Unpin, +{ + type Output = FdbResult; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + // poll once only + if !self.polled { + self.polled = true; + match Pin::new(&mut self.as_mut().inner).poll(cx) { + Poll::Pending => (), + _ => return Poll::Ready(Ok(false)), + } + } + + Poll::Ready(Ok(true)) + } +} + +#[tokio::test] +async fn test_future_discard() -> FdbResult<()> { + // dropping a future while it's in the pending state should not crash + let db = common::database().await?; + let mut hit_pending = false; + for _i in 0..=1000 { + let hit_pending_step = db + .transact_boxed_local( + (), + |trx, ()| { + AbortingFuture { + inner: trx.get(b"key", false), + polled: false, + } + .boxed_local() + }, + TransactOption::default(), + ) + .await?; + hit_pending = hit_pending || hit_pending_step; + } + + assert!(hit_pending); + + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/get.rs b/bindings/rust/foundationdb/tests/get.rs new file mode 100644 index 00000000000..310e8f3c259 --- /dev/null +++ b/bindings/rust/foundationdb/tests/get.rs @@ -0,0 +1,380 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use foundationdb::*; +use foundationdb_macros::cfg_api_versions; +use futures::future::*; +use std::ops::Deref; +use std::sync::{Arc, atomic::*}; + +mod common; + +// These tests run in parallel without any explicit boot: the first +// `common::database()` call starts the network lazily (see issue #132). + +#[tokio::test] +async fn test_set_get() -> FdbResult<()> { + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.set(b"hello", b"world"); + trx.commit().await?; + + let trx = db.create_trx()?; + let value = trx.get(b"hello", false).await?.unwrap(); + assert_eq!(value.deref(), b"world"); + + trx.clear(b"hello"); + trx.commit().await?; + + let trx = db.create_trx()?; + assert!(trx.get(b"hello", false).await?.is_none()); + + Ok(()) +} + +#[tokio::test] +async fn test_get_multi() -> FdbResult<()> { + let db = common::database().await?; + + let trx = db.create_trx()?; + let keys: &[&[u8]] = &[b"hello", b"world", b"foo", b"bar"]; + let _results = try_join_all(keys.iter().map(|k| trx.get(k, false))).await?; + + Ok(()) +} + +#[tokio::test] +async fn test_set_conflict() -> FdbResult<()> { + let key = b"test_set_conflict"; + let db = common::database().await?; + + let trx1 = db.create_trx()?; + let trx2 = db.create_trx()?; + + // try to read value to set conflict range + let _ = trx2.get(key, false).await?; + + // commit first transaction to create conflict + trx1.set(key, common::random_str(10).as_bytes()); + trx1.commit().await?; + + // commit seconds transaction, which will cause conflict + trx2.set(key, common::random_str(10).as_bytes()); + let err = trx2.commit().await.unwrap_err(); + assert_eq!( + err.message(), + "Transaction not committed due to conflict with another transaction" + ); + assert_eq!( + format!("{err}"), + "Transaction not committed due to conflict with another transaction" + ); + assert_eq!( + format!("{err:?}"), + "TransactionCommitError(Transaction not committed due to conflict with another transaction)" + ); + assert!(err.is_retryable()); + assert!(err.is_retryable_not_committed()); + + Ok(()) +} + +#[tokio::test] +async fn test_set_conflict_snapshot() -> FdbResult<()> { + let key = b"test_set_conflict_snapshot"; + let db = common::database().await?; + + let trx1 = db.create_trx()?; + let trx2 = db.create_trx()?; + + // snapshot read does not set conflict range, so both transaction will be + // committed. + let _ = trx2.get(key, true).await?; + + // commit first transaction + trx1.set(key, common::random_str(10).as_bytes()); + trx1.commit().await?; + + // commit seconds transaction, which will *not* cause conflict because of + // snapshot read + trx2.set(key, common::random_str(10).as_bytes()); + trx2.commit().await?; + + Ok(()) +} + +// Makes the key dirty. It will abort transactions which performs non-snapshot read on the `key`. +async fn make_dirty(db: &Database, key: &[u8]) -> FdbResult<()> { + let trx = db.create_trx()?; + trx.set(key, b""); + trx.commit().await?; + + Ok(()) +} + +#[tokio::test] +async fn test_transact() -> FdbResult<()> { + const KEY: &[u8] = b"test_transact"; + const RETRY_COUNT: usize = 5; + async fn async_body( + db: &Database, + trx: &Transaction, + try_count0: Arc, + ) -> FdbResult<()> { + // increment try counter + try_count0.fetch_add(1, Ordering::SeqCst); + + trx.set_option(options::TransactionOption::RetryLimit(RETRY_COUNT as i64)) + .expect("failed to set retry limit"); + + // update conflict range + trx.get(KEY, false).await?; + + // make current transaction invalid by making conflict + make_dirty(db, KEY).await?; + + trx.set(KEY, common::random_str(10).as_bytes()); + + // `Database::transact` will handle commit by itself, so returns without commit + Ok(()) + } + + let try_count = Arc::new(AtomicUsize::new(0)); + let db = common::database().await?; + let res = db + .transact_boxed( + &db, + |trx, db| async_body(db, trx, try_count.clone()).boxed(), + TransactOption::default(), + ) + .await; + assert!(res.is_err(), "should not be able to commit"); + + // `TransactionOption::RetryCount` does not count first try, so `try_count` should be equal to + // `RETRY_COUNT+1` + assert_eq!(try_count.load(Ordering::SeqCst), RETRY_COUNT + 1); + + Ok(()) +} + +#[tokio::test] +async fn test_transact_limit() -> FdbResult<()> { + const KEY: &[u8] = b"test_transact_limit"; + async fn async_body( + db: &Database, + trx: &Transaction, + try_count0: Arc, + ) -> FdbResult<()> { + // increment try counter + try_count0.fetch_add(1, Ordering::SeqCst); + + // update conflict range + trx.get(KEY, false).await?; + + // make current transaction invalid by making conflict + make_dirty(db, KEY).await?; + + trx.set(KEY, common::random_str(10).as_bytes()); + + // `Database::transact` will handle commit by itself, so returns without commit + Ok(()) + } + + let try_count = Arc::new(AtomicUsize::new(0)); + let db = common::database().await?; + let res = db + .transact_boxed( + &db, + |trx, db| async_body(db, trx, try_count.clone()).boxed(), + TransactOption { + retry_limit: Some(5), + ..TransactOption::default() + }, + ) + .await; + assert!(res.is_err(), "should not be able to commit"); + + assert_eq!(try_count.load(Ordering::SeqCst), 5); + + Ok(()) +} + +#[tokio::test] +async fn test_transact_timeout() -> FdbResult<()> { + const KEY: &[u8] = b"test_transact_timeout"; + async fn async_body( + db: &Database, + trx: &Transaction, + try_count0: Arc, + ) -> FdbResult<()> { + // increment try counter + try_count0.fetch_add(1, Ordering::SeqCst); + + // update conflict range + trx.get(KEY, false).await?; + + // make current transaction invalid by making conflict + make_dirty(db, KEY).await?; + + trx.set(KEY, common::random_str(10).as_bytes()); + + // `Database::transact` will handle commit by itself, so returns without commit + Ok(()) + } + + let try_count = Arc::new(AtomicUsize::new(0)); + let db = common::database().await?; + let res = db + .transact_boxed( + &db, + |trx, db| async_body(db, trx, try_count.clone()).boxed(), + TransactOption { + time_out: Some(std::time::Duration::from_millis(250)), + ..TransactOption::default() + }, + ) + .await; + assert!(res.is_err(), "should not be able to commit"); + + Ok(()) +} + +#[tokio::test] +async fn test_versionstamp() -> FdbResult<()> { + const KEY: &[u8] = b"test_versionstamp"; + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.set(KEY, common::random_str(10).as_bytes()); + let f_version = trx.get_versionstamp(); + trx.commit().await?; + f_version.await?; + + Ok(()) +} + +#[tokio::test] +async fn test_read_version() -> FdbResult<()> { + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.get_read_version().await?; + + Ok(()) +} + +#[tokio::test] +async fn test_set_read_version() -> FdbResult<()> { + const KEY: &[u8] = b"test_set_read_version"; + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.set_read_version(0); + assert!(trx.get(KEY, false).await.is_err()); + + Ok(()) +} + +#[tokio::test] +async fn test_get_addresses_for_key() -> FdbResult<()> { + const KEY: &[u8] = b"test_get_addresses_for_key"; + + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.clear(KEY); + trx.commit().await?; + + let trx = db.create_trx()?; + let addrs = trx.get_addresses_for_key(KEY).await?; + let mut it = addrs.iter(); + let addr0 = it.next().unwrap(); + eprintln!("{}", addr0.to_str().unwrap()); + assert!(it.next().is_none()); + + Ok(()) +} + +#[cfg_api_versions(min = 610)] +#[tokio::test] +async fn test_metadata_version() -> FdbResult<()> { + let db = common::database().await?; + + let trx = db.create_trx()?; + trx.set_option(options::TransactionOption::AccessSystemKeys)?; + trx.update_metadata_version(); + let commit_result = trx.commit().await.expect("could not commit"); + let commit_version = commit_result.committed_version()?; + assert!(commit_version > 0, "transaction was read-only(-1)"); + + // second time, we should have the previous `commit_version` + let trx = db.create_trx()?; + trx.set_option(options::TransactionOption::ReadSystemKeys)?; + let metadata_version = trx + .get_metadata_version(false) + .await? + .expect("metadataVersion should be set by the previous transaction"); + eprintln!("commit_version: {commit_version}, metadata_version: {metadata_version}"); + assert_eq!(commit_version, metadata_version); + + Ok(()) +} + +#[tokio::test] +async fn test_set_raw_option() -> FdbResult<()> { + const KEY: &[u8] = b"test_set_raw_option_async"; + const RETRY_COUNT: usize = 5; + async fn async_body( + db: &Database, + trx: &Transaction, + try_count0: Arc, + ) -> FdbResult<()> { + // increment try counter + try_count0.fetch_add(1, Ordering::SeqCst); + let option = options::TransactionOption::RetryLimit(RETRY_COUNT as i64); + trx.set_raw_option(option.code(), Some(RETRY_COUNT.to_le_bytes().to_vec())) + .expect("failed to set retry limit"); + + // update conflict range + trx.get(KEY, false).await?; + + // make current transaction invalid by making conflict + make_dirty(db, KEY).await?; + + trx.set(KEY, common::random_str(10).as_bytes()); + + // `Database::transact` will handle commit by itself, so returns without commit + Ok(()) + } + + let try_count = Arc::new(AtomicUsize::new(0)); + let db = common::database().await?; + let res = db + .transact_boxed( + &db, + |trx, db| async_body(db, trx, try_count.clone()).boxed(), + TransactOption::default(), + ) + .await; + assert!(res.is_err(), "should not be able to commit"); + + // `TransactionOption::RetryCount` does not count first try, so `try_count` should be equal to + // `RETRY_COUNT+1` + assert_eq!(try_count.load(Ordering::SeqCst), RETRY_COUNT + 1); + + Ok(()) +} + +#[tokio::test] +async fn test_fails_to_set_unknown_raw_option() -> FdbResult<()> { + let db = common::database().await?; + let trx = db.create_trx()?; + let res = trx.set_raw_option(0, None); + assert!(res.is_err(), "should not be able to set option"); + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/hca.rs b/bindings/rust/foundationdb/tests/hca.rs new file mode 100644 index 00000000000..e012f33c782 --- /dev/null +++ b/bindings/rust/foundationdb/tests/hca.rs @@ -0,0 +1,135 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use foundationdb::tuple::{Subspace, hca::HighContentionAllocator}; +use foundationdb::{FdbResult, TransactOption}; +use futures::prelude::*; +use std::collections::HashSet; +use std::iter::FromIterator; +use std::sync::Barrier; + +mod common; + +#[tokio::test] +async fn test_hca_many_sequential_allocations() -> FdbResult<()> { + const N: usize = 1000; + // Not a byte-prefix of the concurrent test's subspace: both tests run in + // parallel and clear their own subspace range at startup. + const KEY: &[u8] = b"test-hca-seq"; + + let db = common::database().await?; + + { + let tx = db.create_trx()?; + tx.clear_subspace_range(&Subspace::from_bytes(KEY)); + tx.commit().await?; + } + + let hca = HighContentionAllocator::new(Subspace::from_bytes(KEY)); + + let mut all_ints = Vec::new(); + + for _ in 0..N { + let tx = db.create_trx()?; + + let next_int: i64 = hca + .allocate(&tx) + .await + .expect("could not allocate with HCA"); + all_ints.push(next_int); + + tx.commit().await?; + } + + check_hca_result_uniqueness(&all_ints); + + eprintln!("ran test {all_ints:?}"); + + Ok(()) +} + +#[tokio::test] +async fn test_hca_concurrent_allocations() -> FdbResult<()> { + const N: usize = 1000; + const KEY: &[u8] = b"test-hca-conc"; + + let db = common::database().await?; + + { + let tx = db.create_trx()?; + tx.clear_subspace_range(&Subspace::from_bytes(KEY)); + tx.commit().await?; + } + + let hca = HighContentionAllocator::new(Subspace::from_bytes(KEY)); + + let all_ints: Vec = future::try_join_all((0..N).map(|_| { + db.transact_boxed( + &hca, + move |tx, hca| hca.allocate(tx).boxed(), + TransactOption::default(), + ) + })) + .await + .unwrap(); + check_hca_result_uniqueness(&all_ints); + + eprintln!("ran test {all_ints:?}"); + + Ok(()) +} + +#[tokio::test] +async fn test_hca_independent_allocators_share_transaction() -> FdbResult<()> { + const THREADS: usize = 8; + const ALLOCATIONS_PER_THREAD: usize = 64; + let subspace = Subspace::from_bytes(b"test-hca-shared-trx"); + let db = common::database().await?; + let trx = db.create_trx()?; + trx.clear_subspace_range(&subspace); + trx.commit().await?; + + let trx = db.create_trx()?; + let barrier = Barrier::new(THREADS); + // Cooperative tasks cannot interleave the allocator's synchronous read/set + // sequence. Separate threads and allocator instances exercise that boundary. + let all_ints = std::thread::scope(|scope| { + let workers: Vec<_> = (0..THREADS) + .map(|_| { + scope.spawn(|| { + let allocator = HighContentionAllocator::new(subspace.clone()); + barrier.wait(); + (0..ALLOCATIONS_PER_THREAD) + .map(|_| { + futures::executor::block_on(allocator.allocate(&trx)) + .expect("cannot allocate on shared transaction") + }) + .collect::>() + }) + }) + .collect(); + workers + .into_iter() + .flat_map(|worker| worker.join().expect("allocator thread panicked")) + .collect::>() + }); + check_hca_result_uniqueness(&all_ints); + trx.commit().await?; + Ok(()) +} + +fn check_hca_result_uniqueness(results: &[i64]) { + let result_set: HashSet = HashSet::from_iter(results.to_owned()); + + if results.len() != result_set.len() { + panic!( + "Set size does not much, got duplicates from HCA. Set: {:?}, List: {:?}", + result_set.len(), + results.len(), + ); + } +} diff --git a/bindings/rust/foundationdb/tests/range.rs b/bindings/rust/foundationdb/tests/range.rs new file mode 100644 index 00000000000..75c3b21cf7e --- /dev/null +++ b/bindings/rust/foundationdb/tests/range.rs @@ -0,0 +1,512 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +#[cfg_api_versions(min = 710)] +use crate::tuple::Subspace; + +use foundationdb::*; +use foundationdb_macros::cfg_api_versions; +use foundationdb_sys::if_cfg_api_versions; +use futures::future; +use futures::prelude::*; + +use std::borrow::Cow; + +mod common; + +// Kept as a single sequential #[test] on purpose: test_range_option_async and +// test_get_estimate_range share the "test-rangeoption-" prefix, and the two +// mapped-range tests clear the same data/index subspaces. +#[test] +fn test_range() { + let _guard = foundationdb::boot().expect("failed to initialize FoundationDB"); + futures::executor::block_on(test_get_range_async()).expect("failed to run"); + futures::executor::block_on(test_range_option_async()).expect("failed to run"); + futures::executor::block_on(test_get_ranges_async()).expect("failed to run"); + if_cfg_api_versions!(min = 630 => + futures::executor::block_on(test_get_estimate_range()).expect("failed to run") + ); + if_cfg_api_versions!(min = 700 => + futures::executor::block_on(test_get_range_split_points()).expect("failed to run") + ); + if_cfg_api_versions!(min = 710 => + futures::executor::block_on(test_mapped_value()).expect("failed to run"); + futures::executor::block_on(test_mapped_values()).expect("failed to run") + ) +} + +async fn test_get_range_async() -> FdbResult<()> { + const N: usize = 10000; + + let db = common::database().await?; + + { + let trx = db.create_trx()?; + let key_begin = "test-range-"; + let key_end = "test-range."; + + eprintln!("clearing..."); + trx.clear_range(key_begin.as_bytes(), key_end.as_bytes()); + + eprintln!("inserting..."); + for _ in 0..N { + let key = format!("{}-{}", key_begin, common::random_str(10)); + let value = common::random_str(10); + trx.set(key.as_bytes(), value.as_bytes()); + } + + eprintln!("counting..."); + let begin = KeySelector::first_greater_or_equal(Cow::Borrowed(key_begin.as_bytes())); + let end = KeySelector::first_greater_than(Cow::Borrowed(key_end.as_bytes())); + let opt = RangeOption::from((begin, end)); + + let range = trx.get_range(&opt, 1, false).await?; + assert!(!range.is_empty()); + assert!(range.more()); + let len = range.len(); + let mut i = 0; + for kv in &range { + assert!(!kv.key().is_empty()); + assert!(!kv.value().is_empty()); + i += 1; + } + assert_eq!(i, len); + + let refs_asc = (&range).into_iter().collect::>(); + assert_eq!( + refs_asc, + (&range).into_iter().rev().rev().collect::>() + ); + + let owned_asc = trx + .get_range(&opt, 1, false) + .await? + .into_iter() + .collect::>(); + assert_eq!(owned_asc, range.into_iter().rev().rev().collect::>()); + } + + Ok(()) +} + +async fn test_get_ranges_async() -> FdbResult<()> { + const N: usize = 10000; + + let db = common::database().await?; + + { + let trx = db.create_trx()?; + let key_begin = "test-ranges-"; + let key_end = "test-ranges."; + + eprintln!("clearing..."); + trx.clear_range(key_begin.as_bytes(), key_end.as_bytes()); + + eprintln!("inserting..."); + for _ in 0..N { + let key = format!("{}-{}", key_begin, common::random_str(10)); + let value = common::random_str(10); + trx.set(key.as_bytes(), value.as_bytes()); + } + + eprintln!("counting..."); + let begin = KeySelector::first_greater_or_equal(Cow::Borrowed(key_begin.as_bytes())); + let end = KeySelector::first_greater_than(Cow::Borrowed(key_end.as_bytes())); + let opt = RangeOption::from((begin, end)); + + let count = trx + .get_ranges(opt.clone(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?; + + assert_eq!(count, N); + eprintln!("count: {count:?}"); + + let unlimited = RangeOption { + limit: Some(0), + ..opt + }; + for reverse in [false, true] { + let opt = RangeOption { + reverse, + ..unlimited.clone() + }; + let (pages, count) = trx + .get_ranges(opt, false) + .try_fold((0usize, 0usize), |(pages, count), kvs| { + future::ok((pages + 1, count + kvs.len())) + }) + .await?; + assert!(pages > 1, "expected a paginated range scan"); + assert_eq!(count, N); + } + } + + Ok(()) +} + +async fn test_range_option_async() -> FdbResult<()> { + let db = common::database().await?; + + { + let trx = db.create_trx()?; + let key_begin = "test-rangeoption-"; + let key_end = "test-rangeoption."; + let k = |i: u32| format!("{key_begin}-{i:010}"); + + eprintln!("clearing..."); + trx.clear_range(key_begin.as_bytes(), key_end.as_bytes()); + + eprintln!("inserting..."); + for i in 0..10000 { + let value = common::random_str(10); + trx.set(k(i).as_bytes(), value.as_bytes()); + } + assert_eq!( + trx.get_ranges( + (KeySelector::first_greater_or_equal(k(100).into_bytes()) + ..KeySelector::first_greater_or_equal(k(5000).as_bytes())) + .into(), + false + ) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + assert_eq!( + trx.get_ranges( + ( + KeySelector::first_greater_or_equal(k(100).into_bytes()), + KeySelector::first_greater_or_equal(k(5000).as_bytes()) + ) + .into(), + false + ) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + assert_eq!( + trx.get_ranges((k(100).into_bytes()..k(5000).into_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + assert_eq!( + trx.get_ranges((k(100).into_bytes(), k(5000).into_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + assert_eq!( + trx.get_ranges((k(100).as_bytes()..k(5000).as_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + assert_eq!( + trx.get_ranges((k(100).as_bytes(), k(5000).as_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4900 + ); + + assert_eq!( + trx.get_ranges( + (KeySelector::first_greater_or_equal(k(100).into_bytes()) + ..KeySelector::first_greater_than(k(5000).as_bytes())) + .into(), + false + ) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4901 + ); + assert_eq!( + trx.get_ranges((k(100).into_bytes()..=k(5000).into_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4901 + ); + assert_eq!( + trx.get_ranges((k(100).as_bytes()..=k(5000).as_bytes()).into(), false) + .try_fold(0usize, |count, kvs| future::ok(count + kvs.as_ref().len())) + .await?, + 4901 + ); + } + + Ok(()) +} + +#[cfg_api_versions(min = 630)] +async fn test_get_estimate_range() -> FdbResult<()> { + const N: u32 = 10000; + + let db = common::database().await?; + let trx = db.create_trx()?; + let key_begin = "test-rangeoption-"; + let key_end = "test-rangeoption."; + let k = |i: u32| format!("{key_begin}-{i:010}"); + + eprintln!("clearing..."); + trx.clear_range(key_begin.as_bytes(), key_end.as_bytes()); + + eprintln!("inserting..."); + for i in 0..N { + let value = common::random_str(10); + trx.set(k(i).as_bytes(), value.as_bytes()); + } + trx.commit().await?; + + let trx = db.create_trx()?; + let estimate = trx + .get_estimated_range_size_bytes(key_begin.as_bytes(), key_end.as_bytes()) + .await?; + eprintln!("get an estimate of {estimate} bytes"); + assert!(estimate > 0); + + Ok(()) +} + +#[cfg_api_versions(min = 700)] +async fn test_get_range_split_points() -> FdbResult<()> { + const N: u32 = 10000; + + let db = common::database().await?; + let trx = db.create_trx()?; + let key_begin = "test-split-point-"; + let key_end = "test-split-point."; + let k = |i: u32| format!("{key_begin}-{i:010}"); + + eprintln!("clearing..."); + trx.clear_range(key_begin.as_bytes(), key_end.as_bytes()); + + eprintln!("inserting..."); + for i in 0..N { + let value = common::random_str(10); + trx.set(k(i).as_bytes(), value.as_bytes()); + } + trx.commit().await?; + + let trx = db.create_trx()?; + let splits = trx + .get_range_split_points(key_begin.as_bytes(), key_end.as_bytes(), 100) + .await?; + assert!(!splits.is_empty()); + for split in splits { + eprintln!("split point: {:?}", split.key()); + } + Ok(()) +} + +#[cfg_api_versions(min = 710)] +async fn test_mapped_value() -> FdbResult<()> { + use foundationdb::tuple::{Subspace, pack}; + + let db = common::database().await?; + + let data_subspace = Subspace::all().subspace(&("test-range-data")); + let index_subspace = Subspace::all().subspace(&("test-range-index")); + let number_of_records: i32 = 20; + + clear_mapped_data(&db, &data_subspace, &index_subspace).await; + + let blue_counter = + setup_mapped_data(&db, &data_subspace, &index_subspace, number_of_records).await?; + + let t = db.create_trx()?; + let range_option = RangeOption::from(&index_subspace.subspace(&("blue"))); + + // The mapper is a Tuple that allow to transform keys. + // This one is allowing fdb to convert a key like `("test-range-index", "blue", PRIMARY_KEY)` + // to generate a scan in the range `("test-range-data", PRIMARY_KEY, ...)` + // More info can be found here: https://github.com/apple/foundationdb/wiki/Everything-about-GetMappedRange + let mapper = pack(&("test-range-data", "{K[2]}", "{...}")); + + let mapped_key_values = t + .get_mapped_range(&range_option, &mapper, 1024, false) + .await?; + + // Neighboring records surround each interior child range, so a shifted + // selector would include the previous record and omit the last child key. + for mapped_key_value in &mapped_key_values { + let child_range = RangeOption::from(( + mapped_key_value.begin_selector(), + mapped_key_value.end_selector(), + )); + let child_values = t.get_range(&child_range, 1, false).await?; + assert!(!child_values.more()); + assert_eq!(child_values.as_ref(), mapped_key_value.key_values()); + } + + verify_mapped_values(blue_counter, vec![mapped_key_values]); + + Ok(()) +} + +#[cfg_api_versions(min = 710)] +async fn clear_mapped_data(db: &Database, data_subspace: &Subspace, index_subspace: &Subspace) { + let t = db.create_trx().expect("could not create transaction"); + t.clear_subspace_range(data_subspace); + t.clear_subspace_range(index_subspace); + t.commit().await.expect("could not commit"); +} + +#[cfg_api_versions(min = 710)] +fn verify_mapped_values( + blue_counter: i32, + vec_mapped_key_values: Vec, +) { + use foundationdb::tuple::{Element, unpack}; + + let mut value_counter = 0; + + for mapped_key_values in vec_mapped_key_values { + for mapped_key_value in mapped_key_values { + // checking the parent key that generated the scan + let parent_key: Vec = + unpack(mapped_key_value.parent_key()).expect("could not unpack index key"); + assert!(parent_key.starts_with(&[ + Element::String(Cow::from("test-range-index")), + Element::String(Cow::from("blue")) + ])); + + let mapped_values = mapped_key_value.key_values(); + assert_eq!( + mapped_values.len(), + 2, + "bad length, expecting 2, got {}", + mapped_values.len() + ); + + for kv in mapped_values { + let key: Vec = unpack(kv.key()).expect("could not unpack key"); + assert!(key.starts_with(&[Element::String(Cow::from("test-range-data"))])); + } + value_counter += 1; + } + } + assert_eq!( + value_counter, blue_counter, + "found {value_counter} elements instead of {blue_counter}" + ); +} + +#[cfg_api_versions(min = 710)] +async fn test_mapped_values() -> FdbResult<()> { + use foundationdb::tuple::{Subspace, pack}; + + let db = common::database().await?; + + let data_subspace = Subspace::all().subspace(&("test-range-data")); + let index_subspace = Subspace::all().subspace(&("test-range-index")); + let number_of_records: i32 = 10_000; + + clear_mapped_data(&db, &data_subspace, &index_subspace).await; + + let blue_counter = + setup_mapped_data(&db, &data_subspace, &index_subspace, number_of_records).await?; + + let t = db.create_trx()?; + let range_option = RangeOption::from(&index_subspace.subspace(&("blue"))); + + // The mapper is a Tuple that allow to transform keys. + // This one is allowing fdb to convert a key like `("test-range-index", "blue", PRIMARY_KEY)` + // to generate a scan in the range `("test-range-data", PRIMARY_KEY, ...)` + // More info can be found here: https://github.com/apple/foundationdb/wiki/Everything-about-GetMappedRange + let mapper = pack(&("test-range-data", "{K[2]}", "{...}")); + + let key_values = t + .get_mapped_ranges(range_option.clone(), &mapper, false) + .try_collect::>() + .await?; + + dbg!(t.get_approximate_size().await?); + + verify_mapped_values(blue_counter, key_values); + + let unlimited = RangeOption { + limit: Some(0), + ..range_option + }; + let (pages, parents) = t + .get_mapped_ranges(unlimited, &mapper, false) + .try_fold((0usize, 0usize), |(pages, parents), values| { + future::ok((pages + 1, parents + values.len())) + }) + .await?; + assert!(pages > 1, "expected a paginated mapped range scan"); + assert_eq!(parents, blue_counter as usize); + + Ok(()) +} + +#[cfg_api_versions(min = 710)] +async fn setup_mapped_data( + db: &Database, + data_subspace: &Subspace, + index_subspace: &Subspace, + number_of_records: i32, +) -> Result { + use foundationdb::tuple::pack; + + let mut written_records = 0; + let mut blue_counter = 0; + + loop { + let trx = db.create_trx()?; + eprintln!( + "Creating a new transaction, {} records left to write", + number_of_records - written_records + ); + loop { + let primary_key = written_records; + let eye_color = match primary_key % 3 { + 0 => { + blue_counter += 1; + "blue" + } + 1 => "brown", + 2 => "green", + _ => unreachable!(), + }; + + // write into the data subspace + trx.set( + &data_subspace.pack(&(primary_key, "eye_color", eye_color)), + eye_color.as_bytes(), + ); + // write another key next to it + trx.set( + &data_subspace.pack(&(primary_key, "some_data")), + &pack(&("fdb-rs")), + ); + // write into the index subspace + trx.set(&index_subspace.pack(&(eye_color, primary_key)), &[]); + + written_records += 1; + if written_records == number_of_records { + eprintln!("committing batch, because we have all records"); + break; + } + + let size_trx = trx.get_approximate_size().await?; + if size_trx >= 100_000 { + eprintln!("committing batch, because we have already written ~{size_trx}b"); + break; + } + } + trx.commit().await.expect("could not commit"); + if written_records == number_of_records { + eprintln!("records are now ready"); + break; + } + } + + Ok(blue_counter) +} diff --git a/bindings/rust/foundationdb/tests/run_retry.rs b/bindings/rust/foundationdb/tests/run_retry.rs new file mode 100644 index 00000000000..005da0f8276 --- /dev/null +++ b/bindings/rust/foundationdb/tests/run_retry.rs @@ -0,0 +1,288 @@ +// Copyright 2026 foundationdb-rs developers +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Retry behavior of `Database::run` for closure errors that wrap an +//! FdbError or are fatal (#479). + +use std::fmt; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use foundationdb::{FdbBindingError, FdbError, options}; + +mod common; + +/// A layer error keeping the FdbError as its source, like a thiserror enum +/// with `#[source]`/`#[from]` would. +#[derive(Debug)] +struct WrappedFdbError { + source: FdbError, +} + +impl fmt::Display for WrappedFdbError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "layer error: {}", self.source) + } +} + +impl std::error::Error for WrappedFdbError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} + +/// A typed layer error preserving the native error as its source. +#[derive(Debug)] +enum RetryTestError { + /// A domain error with no FdbError anywhere in its chain. + InvalidDocument, + Fdb(FdbError), + Binding(FdbBindingError), +} + +impl fmt::Display for RetryTestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::error::Error for RetryTestError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Fdb(e) => Some(e), + Self::Binding(e) => Some(e), + Self::InvalidDocument => None, + } + } +} + +impl From for RetryTestError { + fn from(e: FdbError) -> Self { + Self::Fdb(e) + } +} + +impl From for RetryTestError { + fn from(e: FdbBindingError) -> Self { + Self::Binding(e) + } +} + +/// The end-to-end regression for #479: a retryable FdbError wrapped inside a +/// layer error and boxed into CustomError is retried (port of the Go +/// binding's TestErrorWrapping). +#[tokio::test] +async fn run_retries_wrapped_closure_error() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + + let result = db + .run(|trx, _| async move { + if attempt_ref.fetch_add(1, Ordering::SeqCst) == 0 { + return Err(FdbBindingError::new_custom_error(Box::new( + WrappedFdbError { + source: FdbError::from_code(1020), + }, + ))); + } + trx.set(b"run_retry_wrapped", b"ok"); + Ok(()) + }) + .await; + + assert!(result.is_ok(), "wrapped retryable error must be retried"); + assert_eq!(attempt.load(Ordering::SeqCst), 2); +} + +/// Same regression through a typed closure error: the default source() walk +/// makes the wrapped FdbError retryable and the caller gets the typed error +/// back on fatal paths. +#[tokio::test] +async fn run_retries_typed_wrapped_error() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + + let result = db + .run(|trx, _| async move { + if attempt_ref.fetch_add(1, Ordering::SeqCst) == 0 { + return Err(RetryTestError::from(FdbError::from_code(1020))); + } + trx.set(b"run_retry_typed", b"ok"); + Ok(()) + }) + .await; + + assert!(result.is_ok(), "typed wrapped error must be retried"); + assert_eq!(attempt.load(Ordering::SeqCst), 2); +} + +/// Native RetryLimit exhausts the retries while preserving the typed closure +/// error and its source, rather than replacing it with the on_error result. +#[tokio::test] +async fn run_wrapped_error_honors_retry_limit() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + let retry_limit = 2; + + let result: Result<(), RetryTestError> = db + .run(|trx, _| async move { + trx.set_option(options::TransactionOption::RetryLimit(retry_limit))?; + attempt_ref.fetch_add(1, Ordering::SeqCst); + Err(RetryTestError::from(FdbError::from_code(1020))) + }) + .await; + + assert!(matches!(result, Err(RetryTestError::Fdb(e)) if e.code() == 1020)); + assert_eq!(attempt.load(Ordering::SeqCst) as i64, retry_limit + 1); +} + +/// An error with no FdbError in its chain is fatal: the closure runs exactly +/// once and the caller gets the original error back. +#[tokio::test] +async fn run_typed_error_fatal_returns_original() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + + let result: Result<(), RetryTestError> = db + .run(|_trx, _| async move { + attempt_ref.fetch_add(1, Ordering::SeqCst); + Err(RetryTestError::InvalidDocument) + }) + .await; + + assert!( + matches!(result, Err(RetryTestError::InvalidDocument)), + "fatal error must be returned as-is, got {result:?}" + ); + assert_eq!(attempt.load(Ordering::SeqCst), 1); +} + +/// `MaybeCommitted` is computed from the closure error itself: an error +/// wrapping `commit_unknown_result` (1021) tells the next attempt that the +/// previous one may have committed. +#[tokio::test] +async fn run_reports_maybe_committed_from_the_closure_error() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + let seen = AtomicBool::new(false); + let seen_ref = &seen; + + let result: Result<(), RetryTestError> = db + .run(|trx, maybe_committed| async move { + if attempt_ref.fetch_add(1, Ordering::SeqCst) == 0 { + // commit_unknown_result: retryable and maybe committed. + return Err(RetryTestError::from(FdbError::from_code(1021))); + } + seen_ref.store(maybe_committed.into(), Ordering::SeqCst); + trx.set(b"run_retry_maybe_committed", b"ok"); + Ok(()) + }) + .await; + + assert!(result.is_ok(), "1021 must be retried: {result:?}"); + assert_eq!(attempt.load(Ordering::SeqCst), 2); + assert!( + seen.load(Ordering::SeqCst), + "the retried closure must see maybe_committed" + ); +} + +/// A later `transaction_too_old` cannot resolve an earlier uncertain commit. +#[tokio::test] +async fn run_keeps_maybe_committed_after_later_closure_errors() { + let db = common::database().await.expect("failed to open database"); + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + + let result: Result<(), RetryTestError> = db + .run(|trx, maybe_committed| async move { + let attempt = attempt_ref.fetch_add(1, Ordering::SeqCst); + assert_eq!(bool::from(maybe_committed), attempt != 0); + match attempt { + 0 => Err(RetryTestError::from(FdbError::from_code(1021))), + 1 => Err(RetryTestError::from(FdbError::from_code(1007))), + _ => { + trx.set(b"run_retry_sticky_closure", b"ok"); + Ok(()) + } + } + }) + .await; + + assert!(result.is_ok(), "both errors must be retried: {result:?}"); + assert_eq!(attempt.load(Ordering::SeqCst), 3); +} + +/// A commit conflict cannot resolve an earlier uncertain commit either. +#[tokio::test] +async fn run_keeps_maybe_committed_after_commit_conflicts() { + let db = common::database().await.expect("failed to open database"); + let db_ref = &db; + let attempt = AtomicU8::new(0); + let attempt_ref = &attempt; + let key = b"run_retry_sticky_commit"; + + let result: Result<(), RetryTestError> = db + .run(|trx, maybe_committed| async move { + let attempt = attempt_ref.fetch_add(1, Ordering::SeqCst); + assert_eq!(bool::from(maybe_committed), attempt != 0); + if attempt == 0 { + return Err(RetryTestError::from(FdbError::from_code(1021))); + } + if attempt == 1 { + trx.get(key, false).await?; + // Commit a write after the outer transaction's read to force + // its commit, rather than its closure, to fail with a conflict. + db_ref + .run(|other, _| async move { + other.set(key, b"conflict"); + Ok::<_, FdbBindingError>(()) + }) + .await?; + } + trx.set(key, b"ok"); + Ok(()) + }) + .await; + + assert!( + result.is_ok(), + "the commit conflict must be retried: {result:?}" + ); + assert_eq!(attempt.load(Ordering::SeqCst), 3); +} + +/// A retained clone must not permit commit or reset while user code can still +/// use the transaction. Exercise both the success and native-retry paths. +#[tokio::test] +async fn run_rejects_retained_transaction_clones() { + let db = common::database().await.expect("failed to open database"); + for retry in [false, true] { + let retained = std::sync::Mutex::new(None); + let result: Result<(), FdbBindingError> = db + .run(|trx, _| { + *retained.lock().expect("retained transaction mutex") = Some(trx.clone()); + async move { + trx.set(b"run_retained_transaction", b"uncommitted"); + if retry { + Err(FdbBindingError::from(FdbError::from_code(1020))) + } else { + Ok(()) + } + } + }) + .await; + assert!(matches!( + result, + Err(FdbBindingError::ReferenceToTransactionKept) + )); + } +} diff --git a/bindings/rust/foundationdb/tests/tokio.rs b/bindings/rust/foundationdb/tests/tokio.rs new file mode 100644 index 00000000000..09b5c23dce5 --- /dev/null +++ b/bindings/rust/foundationdb/tests/tokio.rs @@ -0,0 +1,206 @@ +extern crate core; + +use foundationdb::future::FdbValues; +use foundationdb::options::ConflictRangeType; +use foundationdb::tuple::{Subspace, pack}; +use foundationdb::*; +use futures::prelude::*; +use std::fmt::{Display, Formatter}; +use std::sync::Arc; +use std::sync::atomic::{AtomicI16, Ordering}; +use std::time::Duration; +use tokio::time::timeout; + +mod common; + +#[derive(Debug)] +enum CustomError { + MyError1, +} + +impl Display for CustomError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "MyError1") + } +} + +impl std::error::Error for CustomError {} + +#[tokio::test(flavor = "multi_thread")] +async fn do_run_with_custom_error() { + let db = Arc::new( + foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"), + ); + + db.perform_no_op().await.expect("could not perform no_op"); + + let result: Result<(), FdbBindingError> = db + .run(|_trx, _maybe_committed| async move { + let toto: Box = Box::new(CustomError::MyError1); + Err(FdbBindingError::new_custom_error(toto)) + }) + .await; + + match result { + Ok(_) => panic!("expecting an error, got an Ok from db.run"), + Err(err) => match err { + FdbBindingError::CustomError(e) => match e.downcast_ref::().unwrap() { + // no-op + CustomError::MyError1 => {} + }, + FdbBindingError::ReferenceToTransactionKept => {} + _ => panic!("expecting an Custom Error"), + }, + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn do_transact() { + let db = Arc::new( + foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"), + ); + + if (timeout(Duration::from_secs(1), db.perform_no_op()).await).is_err() { + panic!("database is unavailable"); + } + + let adb = db; + tokio::spawn(async move { + async fn txnfn(_txn: &Transaction) -> FdbResult<()> { + Ok(()) + } + + adb.transact_boxed( + (), + |txn: &Transaction, ()| txnfn(txn).boxed(), + TransactOption::default(), + ) + .await + .expect("failed to transact") + }); +} + +#[tokio::test(flavor = "multi_thread")] +async fn do_trx() { + let db = Arc::new( + foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"), + ); + + let adb = db; + tokio::spawn(async move { + adb.create_trx() + .expect("failed to create trx") + .commit() + .await + .expect("failed to commit"); + }); +} + +#[tokio::test(flavor = "multi_thread")] +async fn do_run() { + let limit_retry = 16; + let db = Arc::new( + foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"), + ); + + let counter = AtomicI16::new(0); + let counter_ref = &counter; + let db_arc = &db; + + let result = db + .run(|trx, _maybe_committed| async move { + let read_subspace = Subspace::all().subspace(&("do_run")); + let conflict_transaction = db_arc.create_trx().expect("could not create a transaction"); + + let local_counter = counter_ref.fetch_add(1, Ordering::SeqCst); + + // virtually reading some random data in the subspace "do_run" + trx.add_conflict_range( + &read_subspace.range().0, + &read_subspace.range().1, + ConflictRangeType::Write, + )?; + + let _keys: Vec = trx + .get_ranges(read_subspace.range().into(), false) + .try_collect() + .await?; + + trx.set(&read_subspace.pack(&(1)), &pack(&("42"))); + + if local_counter < limit_retry { + // writing with another transaction in the subspace "do_run" + conflict_transaction.set(&read_subspace.pack(&(2)), &pack(&("42"))); + conflict_transaction + .commit() + .await + .expect("cannot commit the conflict transaction"); + } + + // trying to commit + Ok::<_, FdbBindingError>(()) + }) + .await; + + assert!(result.is_ok()); + assert_eq!(limit_retry + 1, counter.load(Ordering::SeqCst)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn do_run_with_transaction_limits() { + let limit_retry = 16; + let db = Arc::new( + foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"), + ); + + let db_arc = &db; + let counter = AtomicI16::new(0); + let counter_ref = &counter; + + let result = db + .run(|trx, _maybe_committed| async move { + // dedicated subspace: do_run() uses "do_run" and both tests run in parallel + let read_subspace = Subspace::all().subspace(&("do_run_limits")); + trx.set_option(foundationdb::options::TransactionOption::RetryLimit(16))?; + + let conflict_transaction = db_arc.create_trx().expect("could not create a transaction"); + counter_ref.fetch_add(1, Ordering::SeqCst); + + // virtually reading some random data in the subspace "do_run_limits" + let _keys: Vec = trx + .get_ranges(read_subspace.range().into(), false) + .try_collect() + .await?; + + trx.set( + &Subspace::all().pack(&("do_run_limits", "1")), + &pack(&("42")), + ); + + // writing with another transaction in the subspace "do_run_limits" + conflict_transaction.set( + &Subspace::all().pack(&("do_run_limits", "2")), + &pack(&("42")), + ); + conflict_transaction + .commit() + .await + .expect("cannot commit the conflict transaction"); + + Ok::<_, FdbBindingError>(()) + }) + .await; + + assert!(result.is_err()); + assert_eq!(limit_retry + 1, counter.load(Ordering::SeqCst)); +} diff --git a/bindings/rust/foundationdb/tests/trace.rs b/bindings/rust/foundationdb/tests/trace.rs new file mode 100644 index 00000000000..68512592f9a --- /dev/null +++ b/bindings/rust/foundationdb/tests/trace.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "trace")] +use foundationdb::{FdbBindingError, FdbError}; +#[cfg(feature = "trace")] +use std::error::Error; +#[cfg(feature = "trace")] +use std::fmt::Formatter; +#[cfg(feature = "trace")] +use std::sync::atomic::AtomicU8; + +#[cfg(feature = "trace")] +#[tokio::test] +async fn test_trace() { + use tracing_subscriber::fmt::format::FmtSpan; + + let test_writer = tracing_subscriber::fmt::TestWriter::new(); + + let _e = tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .with_span_events(FmtSpan::FULL) + .with_writer(test_writer) + .try_init(); + + test_traces_on_run().await; +} + +#[cfg(feature = "trace")] +#[tracing::instrument(skip_all)] +async fn test_traces_on_run() { + let db = foundationdb::Database::new_compat(None) + .await + .expect("failed to open fdb"); + + db.run(|trx, _b| async move { + trx.set("tracing".as_ref(), "ok".as_ref()); + Ok::<_, FdbBindingError>(()) + }) + .await + .expect("could not run transaction"); + + // Test closure error + #[derive(Debug)] + struct CustomError; + + impl std::fmt::Display for CustomError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } + } + impl Error for CustomError {} + + let result = db + .run(|_trx, _b| async move { + Err::<(), FdbBindingError>(FdbBindingError::CustomError(Box::new(CustomError))) + }) + .await; + + assert!(result.is_err()); + + // test transaction kept error + let result = db + .run(|trx, _b| async move { + let trx_clone = trx.clone(); + Ok(trx_clone) + }) + .await; + assert!(result.is_err()); + if let Err(e) = result { + assert!(matches!(e, FdbBindingError::ReferenceToTransactionKept)); + } + + // Test retryable errors + let attempt: AtomicU8 = AtomicU8::new(0); + let result = db + .run(|_trx, _| async { + let new_val = &attempt.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + if *new_val < 2 { + return Err(FdbError::from_code(1020).into()); + } + Ok::<_, FdbBindingError>(()) + }) + .await; + assert!(result.is_ok()); + + // cleaning up + db.run(|trx, _b| async move { + trx.clear("tracing".as_ref()); + Ok::<_, FdbBindingError>(()) + }) + .await + .expect("could not run transaction"); +} diff --git a/bindings/rust/foundationdb/tests/tuple.rs b/bindings/rust/foundationdb/tests/tuple.rs new file mode 100644 index 00000000000..565f41c5d18 --- /dev/null +++ b/bindings/rust/foundationdb/tests/tuple.rs @@ -0,0 +1,336 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use foundationdb::{ + Database, FdbBindingError, FdbError, + options::MutationType, + tuple::{Subspace, Versionstamp}, +}; +use futures::StreamExt; +use std::sync::{Arc, Barrier, Mutex}; +use std::thread; + +mod common; + +#[tokio::test] +// testing subspace with versionstamps. +async fn test_tuples() { + let db = common::database().await.expect("cannot open fdb"); + + // Clear only this test's subspace: versionstamped keys accumulate across + // runs, and the rest of the cluster is none of our business. + eprintln!("clearing the test subspace"); + let trx = db.create_trx().expect("cannot create txn"); + trx.clear_subspace_range(&Subspace::from("test-tuple")); + trx.commit().await.expect("could not clear keys"); + + eprintln!("creating directories"); + test_subspace_with_versionstamp(&db).await; +} + +async fn test_subspace_with_versionstamp(db: &Database) { + let trx = db.create_trx().expect("cannot create txn"); + + // In this example we will create a subspace starting with a versionstamp. + let subspace = Subspace::from("test-tuple"); + let subspace = subspace.subspace(&Versionstamp::incomplete(0)); + let key = subspace.pack_with_versionstamp(&"key"); + + eprintln!("writing key {key:?}"); + trx.atomic_op(&key, b"hello", MutationType::SetVersionstampedKey); + + // we want to get the versionstamp back to be able to read the subspace. + let versionstamp = trx.get_versionstamp(); + + trx.commit().await.expect("cannot commit"); + + let versionstamp = versionstamp + .await + .expect("cannot get versionstamp after commit"); + let versionstamp = Versionstamp::complete( + (*versionstamp) + .try_into() + .expect("versionstamp is the wrong size"), + 0, + ); + // Now that we have the versionstamp we can rebuild the subspace and read the key. + let trx = db.create_trx().expect("cannot create txn"); + + // In this example we will create a subspace starting with a versionstamp. + let subspace = Subspace::from("test-tuple"); + let subspace = subspace.subspace(&versionstamp); + + { + // we can read the whole subspace to get the chance to unpack the key too. + let mut elements = trx.get_ranges_keyvalues(subspace.range().into(), false); + while let Some(key_value) = elements.next().await { + let key_value = key_value.expect("cannot read keyvalue"); + let key = subspace + .unpack::(key_value.key()) + .expect("cannot unpack key"); + assert_eq!(key, "key"); + assert_eq!(key_value.value(), b"hello"); + } + } + trx.commit().await.expect("cannot commit"); + + // Now that the subspace already exists, we can add more versionstamped keys in there. + let trx = db.create_trx().expect("cannot create txn"); + let key = subspace.pack_with_versionstamp(&Versionstamp::incomplete(0)); + trx.atomic_op(&key, b"hello2", MutationType::SetVersionstampedKey); + let key_versionstamp = trx.get_versionstamp(); + trx.commit().await.expect("cannot commit"); + let key_versionstamp = key_versionstamp + .await + .expect("cannot get versionstamp after commit"); + let key_versionstamp = Versionstamp::complete( + (*key_versionstamp) + .try_into() + .expect("versionstamp is the wrong size"), + 0, + ); + // we can read the key back by re-packing with the subspace: + let trx = db.create_trx().expect("cannot create txn"); + let key = subspace.pack(&key_versionstamp); + let value = trx.get(&key, false).await.expect("cannot read key"); + assert_eq!(value.as_deref(), Some(b"hello2".as_ref())); + trx.commit().await.expect("cannot commit"); +} + +#[tokio::test] +async fn user_version_allocator_is_per_transaction_and_shared_by_runner_clones() +-> Result<(), FdbBindingError> { + let db = common::database().await?; + + let first = db.create_trx()?; + let second = db.create_trx()?; + assert_eq!(first.allocate_user_version()?, 0); + assert_eq!(second.allocate_user_version()?, 0); + assert_eq!(first.allocate_user_version()?, 1); + + let versions = db + .run(|trx, _| async move { + let other_handle = trx.clone(); + let versions = [ + trx.allocate_user_version()?, + other_handle.allocate_user_version()?, + trx.allocate_user_version()?, + ]; + drop(other_handle); + Ok::<_, FdbBindingError>(versions) + }) + .await?; + assert_eq!(versions, [0, 1, 2]); + + Ok(()) +} + +#[tokio::test] +async fn user_version_allocator_is_concurrent_and_exhaustible() -> Result<(), FdbBindingError> { + const THREADS: usize = 8; + const VERSIONS_PER_THREAD: usize = 8_192; + + let db = common::database().await?; + let trx = Arc::new(db.create_trx()?); + let start = Arc::new(Barrier::new(THREADS)); + let mut threads = Vec::with_capacity(THREADS); + for _ in 0..THREADS { + let trx = trx.clone(); + let start = start.clone(); + threads.push(thread::spawn(move || { + start.wait(); + (0..VERSIONS_PER_THREAD) + .map(|_| { + trx.allocate_user_version() + .expect("allocator must not exhaust early") + }) + .collect::>() + })); + } + + let mut versions = threads + .into_iter() + .flat_map(|thread| thread.join().expect("allocator thread panicked")) + .collect::>(); + versions.sort_unstable(); + assert_eq!(versions, (0..=u16::MAX).collect::>()); + assert!(matches!( + trx.allocate_user_version(), + Err(FdbBindingError::UserVersionExhausted) + )); + assert!(matches!( + trx.allocate_user_version(), + Err(FdbBindingError::UserVersionExhausted) + )); + + let mut trx = Arc::try_unwrap(trx).expect("allocator references must be released"); + trx.reset(); + assert_eq!(trx.allocate_user_version()?, 0); + + Ok(()) +} + +#[tokio::test] +async fn user_version_allocator_restarts_with_transaction_attempts() -> Result<(), FdbBindingError> +{ + let db = common::database().await?; + + let mut trx = db.create_trx()?; + assert_eq!(trx.allocate_user_version()?, 0); + trx.reset(); + assert_eq!(trx.allocate_user_version()?, 0); + + let trx = trx.on_error(FdbError::from_code(1020)).await?; + assert_eq!(trx.allocate_user_version()?, 0); + + let trx = trx.cancel().reset(); + assert_eq!(trx.allocate_user_version()?, 0); + + let trx = trx.commit().await.map_err(FdbError::from)?.reset(); + assert_eq!(trx.allocate_user_version()?, 0); + + Ok(()) +} + +#[tokio::test] +async fn user_version_allocator_restarts_after_commit_error_paths() -> Result<(), FdbBindingError> { + let db = common::database().await?; + + let trx = commit_error_after_allocation(&db, b"test-user-version-reset-error").await; + let trx = trx.reset(); + assert_eq!(trx.allocate_user_version()?, 0); + + let trx = commit_error_after_allocation(&db, b"test-user-version-on-error").await; + let trx = trx.on_error().await?; + assert_eq!(trx.allocate_user_version()?, 0); + + Ok(()) +} + +async fn commit_error_after_allocation( + db: &Database, + key: &[u8], +) -> foundationdb::TransactionCommitError { + let trx = db.create_trx().expect("cannot create transaction"); + assert_eq!( + trx.allocate_user_version() + .expect("cannot allocate user version"), + 0 + ); + trx.get(key, false).await.expect("cannot read conflict key"); + + let conflicting = db + .create_trx() + .expect("cannot create conflicting transaction"); + conflicting.set(key, b"conflicting value"); + conflicting + .commit() + .await + .expect("cannot commit conflicting transaction"); + + trx.set(key, b"original value"); + trx.commit().await.expect_err("transaction must conflict") +} + +#[tokio::test] +async fn user_version_allocator_runner_retry_restarts_and_keeps_clones_shared() +-> Result<(), FdbBindingError> { + let db = common::database().await?; + let attempts = Arc::new(Mutex::new(Vec::new())); + + db.run(|trx, _| { + let attempts = attempts.clone(); + async move { + let clone = trx.clone(); + let versions = [trx.allocate_user_version()?, clone.allocate_user_version()?]; + drop(clone); + + let mut attempts = attempts.lock().expect("attempt list mutex poisoned"); + let retry = attempts.is_empty(); + attempts.push(versions); + if retry { + Err(FdbBindingError::from(FdbError::from_code(1020))) + } else { + Ok(()) + } + } + }) + .await?; + + assert_eq!( + *attempts.lock().expect("attempt list mutex poisoned"), + [[0, 1], [0, 1]] + ); + Ok(()) +} + +#[tokio::test] +async fn explicit_versionstamps_remain_independent_from_allocation() -> Result<(), FdbBindingError> +{ + let db = common::database().await?; + let trx = db.create_trx()?; + + let explicit = Versionstamp::incomplete(0); + assert_eq!(explicit.user_version(), 0); + assert_eq!(trx.allocate_user_version()?, 0); + assert_eq!(Versionstamp::incomplete(42).user_version(), 42); + + Ok(()) +} + +#[tokio::test] +async fn allocated_versionstamps_create_distinct_keys_in_one_transaction() +-> Result<(), FdbBindingError> { + let db = common::database().await?; + let subspace = Subspace::from("test-user-version-allocator"); + + let clear = db.create_trx()?; + clear.clear_subspace_range(&subspace); + clear.commit().await.map_err(FdbError::from)?; + + let trx = db.create_trx()?; + let a = &trx; + let b = &trx; + for (handle, value) in [ + (a, b"a".as_slice()), + (b, b"b".as_slice()), + (a, b"c".as_slice()), + ] { + let user_version = handle.allocate_user_version()?; + let key = subspace.pack_with_versionstamp(&Versionstamp::incomplete(user_version)); + handle.atomic_op(&key, value, MutationType::SetVersionstampedKey); + } + let transaction_version = trx.get_versionstamp(); + trx.commit().await.map_err(FdbError::from)?; + let transaction_version: [u8; 10] = (*transaction_version.await?) + .try_into() + .expect("versionstamp is the wrong size"); + + let reader = db.create_trx()?; + for (user_version, value) in [ + (0, b"a".as_slice()), + (1, b"b".as_slice()), + (2, b"c".as_slice()), + ] { + let versionstamp = Versionstamp::complete(transaction_version, user_version); + assert_eq!( + versionstamp.transaction_version(), + transaction_version.as_slice() + ); + let key = subspace.pack(&versionstamp); + assert_eq!(reader.get(&key, false).await?.as_deref(), Some(value)); + } + let mut entries = reader.get_ranges_keyvalues(subspace.range().into(), false); + let mut count = 0; + while let Some(entry) = entries.next().await { + entry?; + count += 1; + } + assert_eq!(count, 3); + + Ok(()) +} diff --git a/bindings/rust/foundationdb/tests/versionstamp.rs b/bindings/rust/foundationdb/tests/versionstamp.rs new file mode 100644 index 00000000000..bb6a5632c91 --- /dev/null +++ b/bindings/rust/foundationdb/tests/versionstamp.rs @@ -0,0 +1,134 @@ +use foundationdb::{ + Database, + api::FdbApiBuilder, + options::{MutationType, TransactionOption}, + tuple::{self, Subspace, Versionstamp}, +}; +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::process::Command; + +#[test] +fn versionstamped_mutations_follow_selected_runtime_api() { + const CHILD_API: &str = "FDB_TEST_VERSIONSTAMP_RUNTIME_API"; + let Ok(version) = std::env::var(CHILD_API) else { + // The C API selection is process-global and irreversible. Exercise old + // runtime semantics even when this binary was built with current headers. + for version in [510, 520, 740] { + if version > foundationdb_sys::FDB_API_VERSION { + continue; + } + let status = Command::new(std::env::current_exe().expect("test executable")) + .args([ + "--exact", + "versionstamped_mutations_follow_selected_runtime_api", + "--nocapture", + ]) + .env(CHILD_API, version.to_string()) + .status() + .expect("start test with independent API selection"); + assert!(status.success(), "runtime API {version} failed"); + } + return; + }; + let version = version.parse::().expect("integer runtime API"); + let _network = FdbApiBuilder::default() + .set_runtime_version(version) + .build() + .expect("select runtime API") + .boot() + .expect("start network"); + futures::executor::block_on(check_versionstamped_mutations(version)); +} + +async fn check_versionstamped_mutations(version: i32) { + let db = Database::new_compat(None).await.expect("open database"); + let subspace = Subspace::from(("test-versionstamp-runtime", version)); + let trx = db.create_trx().expect("create cleanup transaction"); + trx.set_option(TransactionOption::Timeout(10_000)) + .expect("set timeout"); + trx.clear_subspace_range(&subspace); + trx.commit().await.expect("clear test keys"); + + let stamped_key = subspace.pack_with_versionstamp(&("key", Versionstamp::incomplete(0))); + let value_key = subspace.pack(&"value"); + let stamped_value = tuple::pack_with_versionstamp(&("value", Versionstamp::incomplete(1))); + let counter_key = subspace.pack(&"counter"); + let trx = db.create_trx().expect("create mutation transaction"); + trx.set_option(TransactionOption::Timeout(10_000)) + .expect("set timeout"); + if version < 520 { + for (key, value, mutation) in [ + ( + stamped_key.as_slice(), + b"stored".as_slice(), + MutationType::SetVersionstampedKey, + ), + ( + value_key.as_slice(), + stamped_value.as_slice(), + MutationType::SetVersionstampedValue, + ), + ] { + let panic = catch_unwind(AssertUnwindSafe(|| trx.atomic_op(key, value, mutation))) + .expect_err("pre-520 versionstamped mutations must be rejected"); + let message = panic + .downcast_ref::() + .map(String::as_str) + .or_else(|| panic.downcast_ref::<&str>().copied()); + assert_eq!( + message, + Some("versionstamped mutations require runtime API 520 or later") + ); + } + // Unsupported versionstamps do not prevent API 510 atomic operations. + trx.atomic_op(&counter_key, &1_i64.to_le_bytes(), MutationType::Add); + trx.commit().await.expect("commit API 510 atomic add"); + let read = db.create_trx().expect("create read transaction"); + read.set_option(TransactionOption::Timeout(10_000)) + .expect("set timeout"); + assert_eq!( + read.get(&counter_key, false) + .await + .expect("read counter") + .as_deref(), + Some(1_i64.to_le_bytes().as_slice()) + ); + assert!( + read.get(&value_key, false) + .await + .expect("read rejected mutation") + .is_none() + ); + return; + } + + trx.atomic_op(&stamped_key, b"stored", MutationType::SetVersionstampedKey); + trx.atomic_op( + &value_key, + &stamped_value, + MutationType::SetVersionstampedValue, + ); + let committed_version = trx.get_versionstamp(); + trx.commit().await.expect("commit versionstamped mutations"); + let committed_version = committed_version.await.expect("get commit versionstamp"); + let transaction_version: [u8; 10] = (*committed_version).try_into().expect("ten-byte version"); + let expected_key = subspace.pack(&("key", Versionstamp::complete(transaction_version, 0))); + let expected_value = tuple::pack(&("value", Versionstamp::complete(transaction_version, 1))); + let read = db.create_trx().expect("create read transaction"); + read.set_option(TransactionOption::Timeout(10_000)) + .expect("set timeout"); + assert_eq!( + read.get(&expected_key, false) + .await + .expect("read versionstamped key") + .as_deref(), + Some(b"stored".as_slice()) + ); + assert_eq!( + read.get(&value_key, false) + .await + .expect("read versionstamped value") + .as_deref(), + Some(expected_value.as_slice()) + ); +} diff --git a/bindings/rust/foundationdb/tests/watch.rs b/bindings/rust/foundationdb/tests/watch.rs new file mode 100644 index 00000000000..8fb448c73bb --- /dev/null +++ b/bindings/rust/foundationdb/tests/watch.rs @@ -0,0 +1,50 @@ +// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use foundationdb::*; + +mod common; + +#[tokio::test] +async fn test_watch() -> FdbResult<()> { + const KEY: &[u8] = b"test-watch"; + + let db = common::database().await?; + + eprintln!("setting watch"); + let trx = db.create_trx()?; + let watch = trx.watch(KEY); + trx.commit().await?; + eprintln!("watch committed"); + + eprintln!("writing value"); + let trx = db.create_trx()?; + let value = common::random_str(10); + trx.set(KEY, value.as_bytes()); + trx.commit().await?; + eprintln!("write committed"); + + watch.await?; + + Ok(()) +} + +#[tokio::test] +async fn test_watch_without_commit() -> FdbResult<()> { + const KEY: &[u8] = b"test-watch-2"; + + let db = common::database().await?; + + eprintln!("setting watch"); + let trx = db.create_trx()?; + let watch = trx.watch(KEY); + + drop(trx); + assert!(watch.await.is_err()); + + Ok(()) +} diff --git a/bindings/rust/rust-toolchain.toml b/bindings/rust/rust-toolchain.toml new file mode 100644 index 00000000000..f1db5f079b8 --- /dev/null +++ b/bindings/rust/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.85.1" +components = ["cargo", "clippy", "rustc", "rustfmt"] diff --git a/bindings/rust/scripts/run_bindingtester.sh b/bindings/rust/scripts/run_bindingtester.sh new file mode 100755 index 00000000000..7e27a1f9d32 --- /dev/null +++ b/bindings/rust/scripts/run_bindingtester.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# == 0 ]]; then + echo "Usage: $0 [iterations] [bindingtester options...]" >&2 + exit 2 +fi + +build_dir=$(cd "$1" && pwd -P) +shift +iterations=${1:-1} +if [[ $# -gt 0 ]]; then + shift +fi +if [[ ! $iterations =~ ^[1-9][0-9]*$ ]]; then + echo "iterations must be a positive integer" >&2 + exit 2 +fi + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) +bindings_dir=$(cd "${script_dir}/../.." && pwd -P) +bindingtester="${build_dir}/bindings/bindingtester/bindingtester.py" +fdb_api_version=740 +export FDB_RUST_BINDINGTESTER="${build_dir}/bindings/rust/bin/bindingtester" +export PYTHONPATH="${build_dir}/bindings/python${PYTHONPATH:+:${PYTHONPATH}}" +export LD_LIBRARY_PATH="${build_dir}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" +export DYLD_LIBRARY_PATH="${build_dir}/lib${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}" + +if [[ ! -x $FDB_RUST_BINDINGTESTER || ! -f ${build_dir}/bindings/python/fdb/fdboptions.py ]]; then + echo "Build fdb_rust_tester and python_binding first (see setup_bindingtester.sh)." >&2 + exit 1 +fi + +# The Python tester imports its adjacent fdb package, so stage it beside the +# generated Python binding rather than running it from the source tree. +if [[ $bindings_dir != "${build_dir}/bindings" ]]; then + mkdir -p "${build_dir}/bindings/bindingtester" "${build_dir}/bindings/python/tests" + cp -R "${bindings_dir}/bindingtester/." "${build_dir}/bindings/bindingtester/" + cp -R "${bindings_dir}/python/tests/." "${build_dir}/bindings/python/tests/" +fi + +extra_options=("$@") +run_test() { + python3 "$bindingtester" "${extra_options[@]}" "$@" +} + +# These are faulty seeds, for now, it is good to check them all the time to avoid regression. +run_test --num-ops 1000 --api-version $fdb_api_version --test-name api --compare python rust --seed 3534790651 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name api --compare python rust --seed 3864917676 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name api --concurrency 5 rust --seed 3153055325 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 2095856910 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 2483220251 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 241550211 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 1508488514 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 4203526853 + +# https://github.com/foundationdb-rs/foundationdb-rs/issues/38 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 4142326254 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 4275610547 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 1951034301 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 1700644945 + +# https://github.com/foundationdb-rs/foundationdb-rs/issues/42 +run_test --num-ops 1000 --api-version $fdb_api_version --test-name directory --concurrency 1 rust --no-directory-snapshot-ops --compare python --seed 584458794 + +# Database-level directory operations must retry concurrent commit conflicts. +run_test --num-ops 100 --api-version 610 --test-name directory_hca --concurrency 5 rust --seed 3179331380 + +# The shared runner selects the newest supported API for scripted cases and +# samples supported runtime versions for randomized cases. +BINDINGTESTS=rust DISPLAYERROR=1 BREAKONERROR=1 \ + bash "${build_dir}/bindings/bindingtester/run_binding_tester.sh" \ + "$iterations" "${build_dir}/bindings/rust/bindingtester-errors.log" "${extra_options[@]}" diff --git a/bindings/rust/scripts/setup_bindingtester.sh b/bindings/rust/scripts/setup_bindingtester.sh new file mode 100755 index 00000000000..74de155079f --- /dev/null +++ b/bindings/rust/scripts/setup_bindingtester.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# != 1 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +# The configured build must enable BUILD_RUST_BINDING and BUILD_PYTHON_BINDING. +cmake --build "$1" --target fdb_rust_tester python_binding diff --git a/bindings/rust/tests/api_version_libraries.py b/bindings/rust/tests/api_version_libraries.py new file mode 100644 index 00000000000..94e4335d019 --- /dev/null +++ b/bindings/rust/tests/api_version_libraries.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +"""Exercise independent Rust API-selection state against one shared C client.""" + +import argparse +import contextlib +import ctypes +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import threading + + +def load_client(path): + client = ctypes.CDLL(str(path), mode=ctypes.RTLD_LOCAL) + client.fdb_select_api_version_impl.argtypes = [ctypes.c_int, ctypes.c_int] + client.fdb_select_api_version_impl.restype = ctypes.c_int + client.fdb_get_selected_api_versions.argtypes = [ + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ] + client.fdb_get_selected_api_versions.restype = None + for name in ("fdb_setup_network", "fdb_run_network", "fdb_stop_network"): + function = getattr(client, name) + function.argtypes = [] + function.restype = ctypes.c_int + client.fdb_create_database.argtypes = [ + ctypes.c_char_p, + ctypes.POINTER(ctypes.c_void_p), + ] + client.fdb_create_database.restype = ctypes.c_int + client.fdb_database_destroy.argtypes = [ctypes.c_void_p] + client.fdb_database_destroy.restype = None + client.fdb_get_error.argtypes = [ctypes.c_int] + client.fdb_get_error.restype = ctypes.c_char_p + return client + + +def load_rust(path): + library = ctypes.CDLL(str(path), mode=ctypes.RTLD_LOCAL) + library.test_select_api.argtypes = [ctypes.c_int] + library.test_select_api.restype = ctypes.c_int + library.test_versionstamped_mutations.argtypes = [ctypes.c_void_p] + library.test_versionstamped_mutations.restype = ctypes.c_int + return library + + +def require(condition, message): + if not condition: + raise AssertionError(message) + + +def check(client, error, operation): + if error: + description = client.fdb_get_error(error).decode("utf-8") + raise RuntimeError(f"{operation}: {error} ({description})") + + +def selected_versions(client): + runtime = ctypes.c_int(-1) + header = ctypes.c_int(-1) + client.fdb_get_selected_api_versions(ctypes.byref(runtime), ctypes.byref(header)) + return runtime.value, header.value + + +def concurrent_selections(*operations): + barrier = threading.Barrier(len(operations), timeout=10) + results = [None] * len(operations) + errors = [None] * len(operations) + + def select(index, operation): + try: + barrier.wait() + # CDLL releases the GIL while each independent library enters C. + results[index] = operation() + except Exception as error: + errors[index] = error + + threads = [ + threading.Thread(target=select, args=(index, operation), daemon=True) + for index, operation in enumerate(operations) + ] + for thread in threads: + thread.start() + for thread in threads: + thread.join(timeout=10) + require(not thread.is_alive(), "API selection did not finish within 10 seconds") + for error in errors: + if error is not None: + raise RuntimeError("concurrent API selection failed") from error + return results + + +@contextlib.contextmanager +def running_database(client, cluster_file): + check(client, client.fdb_setup_network(), "set up network") + network_result = [] + network = threading.Thread( + target=lambda: network_result.append(client.fdb_run_network()), daemon=True + ) + network.start() + database = ctypes.c_void_p() + try: + cluster_path = str(cluster_file).encode() if cluster_file else None + check( + client, + client.fdb_create_database(cluster_path, ctypes.byref(database)), + "create database", + ) + yield database + finally: + if database.value: + client.fdb_database_destroy(database) + stop_error = client.fdb_stop_network() + network.join(timeout=10) + require(not network.is_alive(), "network thread did not stop within 10 seconds") + check(client, stop_error, "stop network") + require(len(network_result) == 1, "network thread did not return a result") + check(client, network_result[0], "run network") + + +def run_case(args): + client = load_client(args.client_library) + require(selected_versions(client) == (0, 0), "API was selected before the test") + with tempfile.TemporaryDirectory(prefix="fdb-rust-api-libraries-") as directory: + first_path = Path(directory) / ("first" + args.rust_library.suffix) + second_path = Path(directory) / ("second" + args.rust_library.suffix) + shutil.copyfile(args.rust_library, first_path) + shutil.copyfile(args.rust_library, second_path) + require( + not first_path.samefile(second_path), + "Rust fixtures must be distinct files so dlopen does not reuse one image", + ) + first = load_rust(first_path) + if args.case == "incompatible-header": + check( + client, + client.fdb_select_api_version_impl(730, 730), + "select native API", + ) + require( + first.test_select_api(730) == 2201, "accepted a mismatched header API" + ) + require(selected_versions(client) == (730, 730), "changed the native API") + return + + second = load_rust(second_path) + if args.case == "concurrent-matching": + results = concurrent_selections( + lambda: first.test_select_api(740), + lambda: second.test_select_api(740), + ) + require(results == [0, 0], f"matching API selections failed: {results}") + expected_versions = (740, 740) + elif args.case == "concurrent-runtime-mismatch": + results = concurrent_selections( + lambda: first.test_select_api(740), + lambda: second.test_select_api(730), + ) + require( + sorted(results) == [0, 2201], + f"conflicting runtime selections must have one winner: {results}", + ) + expected_versions = ((740, 730)[results.index(0)], 740) + elif args.case == "concurrent-header-mismatch": + results = concurrent_selections( + lambda: first.test_select_api(730), + lambda: client.fdb_select_api_version_impl(730, 730), + ) + require( + sorted(results) == [0, 2201], + f"conflicting header selections must have one winner: {results}", + ) + expected_versions = (730, (740, 730)[results.index(0)]) + require( + selected_versions(client) == expected_versions, "changed the API pair" + ) + if expected_versions[1] == 730: + for library in (first, second): + require( + library.test_select_api(730) == 2201, + "accepted the incompatible winning header API", + ) + return + else: + check( + client, first.test_select_api(740), "select API in first Rust library" + ) + require( + selected_versions(client) == (740, 740), + "first library used another client", + ) + require( + second.test_select_api(730) == 2201, "accepted a mismatched runtime API" + ) + expected_versions = (740, 740) + + require( + selected_versions(client) == expected_versions, "wrong API pair selected" + ) + for name, library in (("first", first), ("second", second)): + check( + client, + library.test_select_api(expected_versions[0]), + f"adopt or repeat winning API in {name} Rust library", + ) + require( + selected_versions(client) == expected_versions, "changed the selected API" + ) + with running_database(client, args.cluster_file) as database: + for name, library in (("first", first), ("second", second)): + check( + client, + library.test_versionstamped_mutations(database), + f"versionstamp mutations in {name} Rust library", + ) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--client-library", required=True, type=Path) + parser.add_argument("--rust-library", required=True, type=Path) + parser.add_argument("--cluster-file", type=Path) + cases = ( + "shared-selection", + "incompatible-header", + "concurrent-matching", + "concurrent-runtime-mismatch", + "concurrent-header-mismatch", + ) + parser.add_argument("--case", choices=cases, help=argparse.SUPPRESS) + args = parser.parse_args() + if args.case: + run_case(args) + print(f"Rust API library regression passed: {args.case}", flush=True) + return + + # API selection is irreversible, so each compatibility boundary needs a + # fresh process. An abort from either Rust library must fail the parent. + for case in cases: + command = [ + sys.executable, + str(Path(__file__).resolve()), + "--client-library", + str(args.client_library.resolve()), + "--rust-library", + str(args.rust_library.resolve()), + "--case", + case, + ] + if args.cluster_file: + command.extend(("--cluster-file", str(args.cluster_file.resolve()))) + subprocess.run(command, check=True, timeout=60) + + +if __name__ == "__main__": + main() diff --git a/bindings/rust/tests/current_headers.cmake b/bindings/rust/tests/current_headers.cmake new file mode 100644 index 00000000000..151d598c893 --- /dev/null +++ b/bindings/rust/tests/current_headers.cmake @@ -0,0 +1,25 @@ +# Generate the canonical C header inputs for compile-only Rust CI without +# configuring or linking the FoundationDB server. +if(NOT DEFINED OUTPUT_DIR) + message(FATAL_ERROR "OUTPUT_DIR is required") +endif() +get_filename_component(fdb_source_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +include(${fdb_source_dir}/flow/ApiVersions.cmake) +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +file(MAKE_DIRECTORY ${OUTPUT_DIR}) +file(COPY + ${fdb_source_dir}/bindings/c/foundationdb/fdb_c.h + ${fdb_source_dir}/bindings/c/foundationdb/fdb_c_types.h + ${fdb_source_dir}/bindings/c/foundationdb/CWorkload.h + ${fdb_source_dir}/fdbclient/vexillographer/fdb.options + DESTINATION ${OUTPUT_DIR}) +configure_file( + ${fdb_source_dir}/bindings/c/foundationdb/fdb_c_apiversion.h.cmake + ${OUTPUT_DIR}/fdb_c_apiversion.g.h @ONLY) +execute_process( + COMMAND ${Python3_EXECUTABLE} + ${fdb_source_dir}/fdbclient/vexillographer/vexillographer.py + ${fdb_source_dir}/fdbclient/vexillographer/fdb.options c + ${OUTPUT_DIR}/fdb_c_options.g.h + COMMAND_ERROR_IS_FATAL ANY) diff --git a/cmake/AddFdbTest.cmake b/cmake/AddFdbTest.cmake index 0dbe8b4b394..80b35a59b25 100644 --- a/cmake/AddFdbTest.cmake +++ b/cmake/AddFdbTest.cmake @@ -437,6 +437,18 @@ function(prepare_binding_test_files build_directory target_name target_dependenc COMMENT "Copy generated.go for bindingtester") endif() + if(WITH_RUST_BINDING) + add_dependencies(${target_name} fdb_rust_tester) + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${build_directory}/tests/rust/bin + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/bindings/rust/bin/bindingtester + ${build_directory}/tests/rust/bin/bindingtester + COMMENT "Copy Rust tester for bindingtester") + endif() + if(WITH_SWIFT_BINDING) add_dependencies(${target_name} stacktester_swift) add_custom_command( diff --git a/cmake/FDBComponents.cmake b/cmake/FDBComponents.cmake index a8daf9b3531..f58e716b71c 100644 --- a/cmake/FDBComponents.cmake +++ b/cmake/FDBComponents.cmake @@ -157,6 +157,26 @@ else() endif() endif() +################################################################################ +# Rust +################################################################################ + +# Rust remains opt-in while the imported binding is integrated with the build +# and binding tester. Other components do not require a Rust toolchain. +option(BUILD_RUST_BINDING "build rust binding" OFF) +set(WITH_RUST_BINDING OFF) +if(BUILD_RUST_BINDING AND NOT OPEN_FOR_IDE) + if(NOT WITH_C_BINDING) + message(FATAL_ERROR "Rust binding depends on the C binding") + endif() + if(WIN32 OR CMAKE_CROSSCOMPILING OR USE_SANITIZER) + message(FATAL_ERROR "The Rust CMake build currently supports native, non-sanitized Unix builds only") + endif() + find_program(CARGO_EXECUTABLE cargo REQUIRED) + find_program(RUSTC_EXECUTABLE rustc REQUIRED) + set(WITH_RUST_BINDING ON) +endif() + ################################################################################ # Swift ################################################################################ @@ -372,6 +392,7 @@ function(print_components) message(STATUS "Build Python Bindings: ${WITH_PYTHON_BINDING}") message(STATUS "Build Java Bindings: ${WITH_JAVA_BINDING}") message(STATUS "Build Go bindings: ${WITH_GO_BINDING}") + message(STATUS "Build Rust bindings: ${WITH_RUST_BINDING}") message(STATUS "Build Swift bindings: ${WITH_SWIFT_BINDING}") message(STATUS "Build Ruby bindings: ${WITH_RUBY_BINDING}") message(STATUS "Build Swift (depends on Swift): ${WITH_SWIFT}") diff --git a/contrib/local_cluster/binding_test.py b/contrib/local_cluster/binding_test.py index f50416ba1f1..c996cca586d 100755 --- a/contrib/local_cluster/binding_test.py +++ b/contrib/local_cluster/binding_test.py @@ -187,7 +187,7 @@ async def _test_coroutine( env=self._env, ).run() try: - await asyncio.wait_for(process.wait(), timeout=self._timeout) + return_code = await asyncio.wait_for(process.wait(), timeout=self._timeout) finally: stdout = (await process.stdout.read(-1)).decode("utf-8") stderr = (await process.stderr.read(-1)).decode("utf-8") @@ -199,6 +199,10 @@ async def _test_coroutine( logger.warning("API Test stderr:\n{}".format(stderr)) else: logger.info("API Test stderr: [Empty]") + if return_code != 0: + raise RuntimeError( + f"Binding tester [{api_language}] {test_name} exited with {return_code}" + ) async def _run_test( self, @@ -267,13 +271,23 @@ async def run_directory_hca_test(self, test: str): API_LANGUAGES = [ - "python3", + "python", "java", "java_async", "go", "flow", "swift", ] +if os.access( + os.environ.get( + "FDB_RUST_BINDINGTESTER", + os.path.join(BINARY_DIR, "tests", "rust", "bin", "bindingtester"), + ), + os.X_OK, +): + API_LANGUAGES.append("rust") +if os.environ.get("BINDINGTESTS"): + API_LANGUAGES = os.environ["BINDINGTESTS"].split() def _log_cluster_lines_with_severity( @@ -310,7 +324,7 @@ def _generate_test_list(test_set: TestSet, api_languages: List[str] = API_LANGUA result = [] for test in tests: for api_language in api_languages: - result.append(lambda lang=api_language: test(lang)) + result.append(lambda lang=api_language, run=test: run(lang)) return result diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index 8971175bae1..78460d0a700 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -144,7 +144,7 @@ Prior to including ``fdb_c.h``, you must define the ``FDB_API_VERSION`` macro. T Passing a version less than ``FDB_API_VERSION`` will cause the API to behave as it did in the older version. - It is an error to call this function after it has returned successfully. It is not thread safe, and if called from more than one thread simultaneously its behavior is undefined. + Concurrent calls are serialized. Once a call succeeds, every later call returns ``api_version_already_set``, even when requesting the same version. API selection must still complete before other threads use the FoundationDB API. .. note:: This is actually implemented as a macro. If you are accessing this API via ``dlopen()`` or an FFI, you will need to use :func:`fdb_select_api_version_impl()`. @@ -154,7 +154,7 @@ Prior to including ``fdb_c.h``, you must define the ``FDB_API_VERSION`` macro. T This is the actual entry point called by the :func:`fdb_select_api_version` macro. It should never be called directly from C, but if you are accessing this API via ``dlopen()`` or an FFI, you will need to use it. ``fdb_select_api_version(v)`` is equivalent to ``fdb_select_api_version_impl(v, FDB_API_VERSION)``. - It is an error to call this function after it has returned successfully. It is not thread safe, and if called from more than one thread simultaneously its behavior is undefined. + Concurrent calls are serialized. Once a call succeeds, every later call returns ``api_version_already_set``, even when requesting the same runtime and header versions. API selection must still complete before other threads use the FoundationDB API. ``runtime_version`` The version of run-time behavior the API is requested to provide. Must be less than or equal to ``header_version``, and should almost always be equal. diff --git a/documentation/sphinx/source/api-reference.rst b/documentation/sphinx/source/api-reference.rst index c0064b401b5..5cd1dc0e08c 100644 --- a/documentation/sphinx/source/api-reference.rst +++ b/documentation/sphinx/source/api-reference.rst @@ -14,6 +14,7 @@ The following documents give detailed descriptions of the API for each language: api-ruby Java API Go API + Rust API api-c api-error-codes special-keys