├── .asf.yaml ├── .devcontainer ├── Dockerfile ├── add_erlang_bindings.patch ├── create_cluster_file.bash ├── devcontainer.json └── docker-compose.yaml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── BINDING_TESTER.md ├── LICENSE ├── Makefile ├── README.md ├── c_src ├── atom_names.h ├── atoms.c ├── atoms.h ├── fdb.h ├── main.c ├── resources.c ├── resources.h ├── util.c └── util.h ├── devcontainer.config ├── include └── erlfdb.hrl ├── notes └── directory-layer-design.md ├── plugins └── rebar_gdb_plugin.erl ├── priv └── monitor.py ├── rebar.config ├── rebar.config.script ├── rebar.lock ├── src ├── erlfdb.app.src ├── erlfdb.erl ├── erlfdb_directory.erl ├── erlfdb_float.erl ├── erlfdb_hca.erl ├── erlfdb_key.erl ├── erlfdb_nif.erl ├── erlfdb_subspace.erl ├── erlfdb_tuple.erl └── erlfdb_util.erl └── test ├── erlfdb_01_basic_test.erl ├── erlfdb_02_anon_fdbserver_test.erl ├── erlfdb_03_transaction_options_test.erl ├── erlfdb_04_snapshot_test.erl ├── erlfdb_05_get_next_tx_id_test.erl ├── erlfdb_06_get_addresses_test.erl └── tester.es /.asf.yaml: -------------------------------------------------------------------------------- 1 | github: 2 | description: "Erlang API for FoundationDB" 3 | homepage: https://www.foundationdb.org 4 | labels: 5 | - couchdb 6 | - erlang 7 | - foundationdb 8 | features: 9 | issues: true 10 | enabled_merge_buttons: 11 | squash: true 12 | rebase: true 13 | merge: true 14 | 15 | notifications: 16 | commits: commits@couchdb.apache.org 17 | issues: notifications@couchdb.apache.org 18 | pullrequests: notifications@couchdb.apache.org 19 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG FDB_VERSION 2 | ARG ERLANG_VERSION 3 | 4 | # Grab fdbcli and client library from same image as server 5 | FROM foundationdb/foundationdb:${FDB_VERSION} as fdb 6 | 7 | # Debian image with Erlang installed 8 | FROM erlang:${ERLANG_VERSION} 9 | 10 | # The FROM directive above sweeps out the ARGs so we need to re-declare here 11 | # in order to use it again to download the FDB client package 12 | ARG FDB_VERSION 13 | 14 | # Install the FDB client used underneath erlfdb 15 | RUN set -ex; \ 16 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/foundationdb-clients_${FDB_VERSION}-1_amd64.deb; \ 17 | mkdir /var/lib/foundationdb; \ 18 | dpkg -i foundationdb-clients_${FDB_VERSION}-1_amd64.deb; \ 19 | rm foundationdb-clients_${FDB_VERSION}-1_amd64.deb 20 | 21 | 22 | # Clone FoundationDB repo to retrieve bindings tester package and 23 | # patch it to support erlfdb 24 | COPY add_erlang_bindings.patch /tmp/ 25 | RUN set -ex; \ 26 | git clone --branch ${FDB_VERSION} --depth 1 https://github.com/apple/foundationdb /usr/src/foundationdb; \ 27 | cd /usr/src/foundationdb; \ 28 | git apply /tmp/add_erlang_bindings.patch 29 | 30 | # `dig` is used by the script that creates the FDB cluster file 31 | RUN set -ex; \ 32 | apt-get update; \ 33 | apt-get install -y --no-install-recommends \ 34 | dnsutils \ 35 | python3-setuptools \ 36 | python3-pip; \ 37 | rm -rf /var/lib/apt/lists/* 38 | 39 | # FDB bindings tester uses the Python bindings 40 | RUN pip3 install foundationdb==${FDB_VERSION} 41 | 42 | COPY create_cluster_file.bash /usr/local/bin/ 43 | 44 | CMD sleep infinity 45 | -------------------------------------------------------------------------------- /.devcontainer/add_erlang_bindings.patch: -------------------------------------------------------------------------------- 1 | diff --git a/bindings/bindingtester/__init__.py b/bindings/bindingtester/__init__.py 2 | index 75454625c..fa20d37ab 100644 3 | --- a/bindings/bindingtester/__init__.py 4 | +++ b/bindings/bindingtester/__init__.py 5 | @@ -22,7 +22,6 @@ import math 6 | import sys 7 | import os 8 | 9 | -sys.path[:0] = [os.path.join(os.path.dirname(__file__), '..', '..', 'bindings', 'python')] 10 | 11 | import util 12 | 13 | diff --git a/bindings/bindingtester/known_testers.py b/bindings/bindingtester/known_testers.py 14 | index 2c5211a3d..30c1fafdc 100644 15 | --- a/bindings/bindingtester/known_testers.py 16 | +++ b/bindings/bindingtester/known_testers.py 17 | @@ -57,6 +57,7 @@ _java_cmd = 'java -ea -cp %s:%s com.apple.foundationdb.test.' % ( 18 | 19 | # We could set min_api_version lower on some of these if the testers were updated to support them 20 | testers = { 21 | + 'erlang': Tester('erlang', '/usr/src/erlfdb/test/tester.es', 2040, 610, MAX_API_VERSION, types=ALL_TYPES), 22 | 'python': Tester('python', 'python ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES), 23 | 'python3': Tester('python3', 'python3 ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES), 24 | 'ruby': Tester('ruby', _absolute_path('ruby/tests/tester.rb'), 2040, 23, MAX_API_VERSION), 25 | -------------------------------------------------------------------------------- /.devcontainer/create_cluster_file.bash: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # 4 | # create_cluster_file.bash 5 | # 6 | # This source file is part of the FoundationDB open source project 7 | # 8 | # Copyright 2013-2018 Apple Inc. and the FoundationDB project authors 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | # This script creates a cluster file for a server or client. 24 | # This takes the cluster file path from the FDB_CLUSTER_FILE 25 | # environment variable, with a default of /etc/foundationdb/fdb.cluster 26 | # 27 | # The name of the coordinator must be defined in the FDB_COORDINATOR environment 28 | # variable, and it must be a name that can be resolved through DNS. 29 | 30 | function create_cluster_file() { 31 | FDB_CLUSTER_FILE=${FDB_CLUSTER_FILE:-/etc/foundationdb/fdb.cluster} 32 | mkdir -p $(dirname $FDB_CLUSTER_FILE) 33 | 34 | if [[ -n "$FDB_CLUSTER_FILE_CONTENTS" ]]; then 35 | echo "$FDB_CLUSTER_FILE_CONTENTS" > $FDB_CLUSTER_FILE 36 | elif [[ -n $FDB_COORDINATOR ]]; then 37 | coordinator_ip=$(dig +short $FDB_COORDINATOR) 38 | if [[ -z "$coordinator_ip" ]]; then 39 | echo "Failed to look up coordinator address for $FDB_COORDINATOR" 1>&2 40 | exit 1 41 | fi 42 | coordinator_port=${FDB_COORDINATOR_PORT:-4500} 43 | echo "docker:docker@$coordinator_ip:$coordinator_port" > $FDB_CLUSTER_FILE 44 | else 45 | echo "FDB_COORDINATOR environment variable not defined" 1>&2 46 | exit 1 47 | fi 48 | } 49 | 50 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 51 | create_cluster_file "$@" 52 | fi -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerComposeFile": "docker-compose.yaml", 3 | "service": "erlfdb", 4 | "workspaceFolder": "/usr/src/erlfdb", 5 | 6 | // Create the fdb.cluster file, resolving the FDB service name to an IP 7 | "onCreateCommand": ["bash", "/usr/local/bin/create_cluster_file.bash"], 8 | 9 | // Initialize a new database. If the erlfdb container is being re-created, 10 | // a database may already exist. In that case an error message will be 11 | // printed in the logs and the existing database will be reused. 12 | "postCreateCommand": "fdbcli --exec 'configure new single ssd'", 13 | 14 | "extensions": [ 15 | "erlang-ls.erlang-ls" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | erlfdb: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | args: 7 | ERLANG_VERSION: "24" 8 | 9 | # This should always match the value in fdb.image 10 | FDB_VERSION: "6.3.23" 11 | 12 | environment: 13 | # This needs to match the name of the FoundationDB service below 14 | FDB_COORDINATOR: fdb 15 | 16 | # This profile ensures we use the FDB server in the sibling container 17 | # for the EUnit test suite 18 | REBAR_PROFILE: devcontainer 19 | 20 | volumes: 21 | # Mounts the project folder to '/usr/src/erlfdb'. The target path inside 22 | # the container should match what your application expects. In this case, 23 | # the compose file is in a sub-folder, so you will mount '..'. You would 24 | # then reference this path as the 'workspaceFolder' in 25 | # '.devcontainer/devcontainer.json' so VS Code starts here. 26 | - ..:/usr/src/erlfdb:cached 27 | 28 | network_mode: service:fdb 29 | 30 | fdb: 31 | image: foundationdb/foundationdb:6.3.23 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | 13 | otp_fdb_matrix: 14 | strategy: 15 | matrix: 16 | otp-version: ['22', '23', '24'] 17 | fdb-version: ['6.3.22'] 18 | runs-on: ubuntu-latest 19 | env: 20 | FDB_VERSION: ${{ matrix.fdb-version }} 21 | # Set to 1 for verbose rebar3 logging 22 | DEBUG: 0 23 | # Set to 1 for even more verbose rebar3 logging 24 | DIAGNOSTIC: 0 25 | steps: 26 | - name: Check out repository code 27 | uses: actions/checkout@v2 28 | with: 29 | persist-credentials: false 30 | submodules: recursive 31 | - name: Setup Erlang 32 | uses: ./.github/actions/setup-beam 33 | with: 34 | otp-version: ${{ matrix.otp-version }} 35 | rebar3-version: '3.17' 36 | - name: Install FoundationDB 37 | run: | 38 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/foundationdb-clients_${FDB_VERSION}-1_amd64.deb 39 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/foundationdb-server_${FDB_VERSION}-1_amd64.deb 40 | sudo dpkg -i foundationdb-clients_${FDB_VERSION}-1_amd64.deb 41 | sudo dpkg -i foundationdb-server_${FDB_VERSION}-1_amd64.deb 42 | - name: Compile 43 | run: rebar3 compile 44 | - name: EUnit tests 45 | run: rebar3 eunit 46 | - name: Setup tmate session on job failure 47 | uses: ./.github/actions/tmate 48 | if: ${{ failure() }} 49 | with: 50 | limit-access-to-actor: true 51 | 52 | os_fdb_matrix: 53 | strategy: 54 | matrix: 55 | # erlef/setup-beam action does not support macos yet 56 | os: [ubuntu-latest, windows-latest] 57 | fdb-version: ['6.2.30', '6.3.22'] 58 | # Windows builds are not being published beyond 6.3.9 right now 59 | exclude: 60 | - os: windows-latest 61 | fdb-version: '6.3.22' 62 | include: 63 | - os: windows-latest 64 | fdb-version: '6.3.9' 65 | runs-on: ${{ matrix.os }} 66 | env: 67 | FDB_VERSION: ${{ matrix.fdb-version }} 68 | # This profile uses the FDB server started in the "Install FoundationDB" step 69 | # instead of starting another one (the code that manages the "local" FDB in the 70 | # test suite is not designed with x-platform compatibility in mind) 71 | REBAR_PROFILE: devcontainer 72 | # Set to 1 for verbose rebar3 logging 73 | DEBUG: 0 74 | # Set to 1 for even more verbose rebar3 logging 75 | DIAGNOSTIC: 0 76 | steps: 77 | - name: Check out repository code 78 | uses: actions/checkout@v2 79 | with: 80 | persist-credentials: false 81 | submodules: recursive 82 | - name: Setup Erlang 83 | uses: ./.github/actions/setup-beam 84 | with: 85 | otp-version: '24' 86 | rebar3-version: '3.17' 87 | - name: Setup MSVC toolchain 88 | if: ${{ matrix.os == 'windows-latest' }} 89 | uses: ./.github/actions/msvc-dev-cmd 90 | - name: Install FoundationDB (Ubuntu) 91 | if: ${{ matrix.os == 'ubuntu-latest' }} 92 | run: | 93 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/foundationdb-clients_${FDB_VERSION}-1_amd64.deb 94 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/foundationdb-server_${FDB_VERSION}-1_amd64.deb 95 | sudo dpkg -i foundationdb-clients_${FDB_VERSION}-1_amd64.deb 96 | sudo dpkg -i foundationdb-server_${FDB_VERSION}-1_amd64.deb 97 | - name: Install FoundationDB (Windows) 98 | if: ${{ matrix.os == 'windows-latest' }} 99 | run: | 100 | Set-PSDebug -Trace 1 101 | Invoke-WebRequest -Uri https://github.com/apple/foundationdb/releases/download/$env:FDB_VERSION/foundationdb-$env:FDB_VERSION-x64.msi -OutFile foundationdb-$env:FDB_VERSION-x64.msi 102 | Start-Process -Wait -FilePath msiexec -ArgumentList /i, foundationdb-$env:FDB_VERSION-x64.msi, /passive 103 | echo "C:\Program Files\foundationdb\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 104 | - name: Install FoundationDB (macOS) 105 | if: ${{ matrix.os == 'macos-latest' }} 106 | run: | 107 | wget https://github.com/apple/foundationdb/releases/download/${FDB_VERSION}/FoundationDB-${FDB_VERSION}.pkg 108 | sudo installer -allowUntrusted -verboseR -pkg FoundationDB-${FDB_VERSION}.pkg -target / 109 | - name: Compile 110 | run: rebar3 compile 111 | - name: EUnit tests 112 | run: rebar3 eunit 113 | - name: Setup tmate session on job failure 114 | uses: ./.github/actions/tmate 115 | if: ${{ failure() }} 116 | with: 117 | limit-access-to-actor: true 118 | 119 | binding_tester: 120 | runs-on: ubuntu-latest 121 | strategy: 122 | matrix: 123 | test-name: [api, directory, directory_hca, tuple] 124 | api-version: [610, 620, 630] 125 | include: 126 | # The `scripted` test only supports the latest API version 127 | - test-name: scripted 128 | api-version: 630 129 | container: 130 | image: apache/couchdbci-debian:erlfdb-erlang-24.1.5.0-fdb-6.3.18-1 131 | services: 132 | foundationdb: 133 | image: foundationdb/foundationdb:6.3.18 134 | env: 135 | # This profile just ensures we use the FDB server in the service container 136 | REBAR_PROFILE: devcontainer 137 | steps: 138 | - name: Create FDB cluster file 139 | env: 140 | # This needs to match the name of the service above so the script can do 141 | # a DNS lookup to write down the coordinator IP in the fdb.cluster file 142 | FDB_COORDINATOR: foundationdb 143 | shell: bash 144 | run: /usr/local/bin/create_cluster_file.bash 145 | - name: Initialize FDB database 146 | run: fdbcli --exec "configure new single ssd" 147 | - name: Check out repository code 148 | uses: actions/checkout@v2 149 | with: 150 | persist-credentials: false 151 | - name: Compile erlfdb 152 | run: rebar3 compile 153 | - name: Execute unit tests 154 | run: rebar3 eunit 155 | - name: Execute binding test 156 | env: 157 | TEST_NAME: ${{ matrix.test-name }} 158 | API_VERSION: ${{ matrix.api-version }} 159 | COVER_ENABLED: true 160 | ERL_LIBS: _build/devcontainer+test/lib/erlfdb/ 161 | run: | 162 | mkdir -p /usr/src/erlfdb/test/ 163 | ln -s $GITHUB_WORKSPACE/test/tester.es /usr/src/erlfdb/test/tester.es 164 | /usr/src/foundationdb/bindings/bindingtester/bindingtester.py erlang \ 165 | --test-name $TEST_NAME \ 166 | --api-version $API_VERSION \ 167 | --instruction-prefix $TEST_NAME \ 168 | --num-ops 10000 169 | - name: Upload results to Coveralls 170 | env: 171 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 172 | COVERALLS_FLAG_NAME: bindingtest-${{ matrix.test-name }}-${{ matrix.api-version }} 173 | run: rebar3 as test coveralls send 174 | 175 | finalize_coverage_report: 176 | needs: [binding_tester] 177 | runs-on: ubuntu-latest 178 | steps: 179 | - name: Finalize Coveralls report 180 | run: | 181 | curl https://coveralls.io/webhook \ 182 | --header 'Content-Type: application/json' \ 183 | --data '{ 184 | "repo_name": "${{ github.repository }}", 185 | "repo_token": "${{ secrets.GITHUB_TOKEN }}", 186 | "payload": { 187 | "build_num": ${{ github.run_number }}, 188 | "status": "done" 189 | } 190 | }' 191 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Unit testing 2 | .erlfdb/ 3 | .eunit/ 4 | 5 | compile_commands.json 6 | erln8.config 7 | 8 | # Build artifacts 9 | c_src/*.d 10 | c_src/*.o 11 | priv/erlfdb_nif.* 12 | _build/ 13 | 14 | # Artifacts when an umbrella app (e.g. CouchDB) builds erlfdb using rebar2 15 | .rebar/ 16 | ebin/ 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule ".github/actions/setup-beam"] 2 | path = .github/actions/setup-beam 3 | url = https://github.com/erlef/setup-beam 4 | [submodule ".github/actions/msvc-dev-cmd"] 5 | path = .github/actions/msvc-dev-cmd 6 | url = https://github.com/ilammy/msvc-dev-cmd 7 | [submodule ".github/actions/tmate"] 8 | path = .github/actions/tmate 9 | url = https://github.com/mxschmitt/action-tmate 10 | -------------------------------------------------------------------------------- /BINDING_TESTER.md: -------------------------------------------------------------------------------- 1 | Running the bindingstester 2 | === 3 | 4 | # Easy Button: devcontainer 5 | 6 | The image build takes care of setting up an environment where the Erlang 7 | bindings can be tested. It clones the FDB repo and patches the necessary 8 | files to register Erlang as a known binding. Assuming erlfdb has been built 9 | using `make`; the bindings tests be run directly via 10 | 11 | ```bash 12 | ERL_LIBS=/usr/src/erlfdb/_build/test/lib/erlfdb/ /usr/src/foundationdb/bindings/bindingtester/bindingtester.py erlang 13 | ``` 14 | 15 | # Manual Approach 16 | 17 | This assumes that all FoundationDB dependencies are installed properly. See 18 | the FoundationDB documentation for information on the dependencies. 19 | 20 | 1. Download and build FoundationDB 21 | --- 22 | 23 | ```bash 24 | $ git clone https://github.com/apple/foundationdb 25 | $ cd foundationdb 26 | $ # Optionally checkout a specific release branch 27 | $ # git checkout -b release-6.1 origin/release-6.1 28 | $ mkdir _build 29 | $ cd _build 30 | $ cmake .. 31 | $ make -j4 32 | ``` 33 | 34 | 2. Tweak `bindingtester` to be able to run our Erlang client 35 | --- 36 | 37 | ```bash 38 | $ cd ../bindings/bindingtester 39 | $ vi __init__.py # Comment out the sys.path injection on line 25 40 | $ vi known_testers.py # Add the following line to the `testers` hash 41 | ``` 42 | 43 | ```python 44 | 'erlang': Tester('erlang', '/Users/davisp/github/labs-cloudant/couchdb-erlfdb/test/tester.es', 2040, MAX_API_VERSION, MAX_API_VERSION, types=ALL_TYPES), 45 | ``` 46 | 47 | 3. Start a temporary fdbserver instance 48 | --- 49 | 50 | *This should be done in a second shell* 51 | 52 | ```bash 53 | $ mkdir ~/tmp/fdbtest 54 | $ cd ~/tmp/fdbtest 55 | $ echo "foo:bar@127.0.0.1:4689" > fdb.cluster 56 | $ /Users/davisp/github/davisp/foundationdb/_build/bin/fdbserver \ 57 | -p 127.0.0.1:4689 \ 58 | -C fdb.cluster \ 59 | -d . \ 60 | -L . 61 | ``` 62 | 63 | 4. Configure the temporary fdbserver instance 64 | --- 65 | 66 | *This only needs to be done once after step 3* 67 | 68 | ```bash 69 | $ cd ~/tmp/fdbtest 70 | $ /Users/davisp/github/davisp/foundationdb/_build/bin/fdbcli 71 | Database created 72 | ``` 73 | 74 | 5. Start the binding test 75 | --- 76 | 77 | *Notice that the ERL_LIBS=... command is one long single line.* 78 | 79 | ```bash 80 | $ cd /Users/davisp/github/davisp/foundationdb/bindings/bindingtester 81 | $ ERL_LIBS=/Users/davisp/github/labs-cloudant/couchdb-erlfdb/_build/test/lib/erlfdb/ PYTHONPATH=/Users/davisp/github/davisp/foundationdb/_build/bindings/python/ ./bindingtester.py --cluster-file /Users/davisp/tmp/fdbtest/fdb.cluster erlang 82 | ``` 83 | 84 | # Testing Notes 85 | 86 | By default, `bindingtester.py` runs the `scripted.py` test which is a deterministic set of tests. To really try and soak test the bindings you should add the following command line parameters: 87 | 88 | `--test-name api --num-ops 10000` 89 | 90 | The `api` test is a large randomly generated set of test instructions and should exercise a number of combinations of parameters exercising large portions of the bindings. 91 | 92 | If you do encounter an error in tests you can add these options to try and debug the issue: 93 | 94 | `--seed $SEED --bisect` 95 | 96 | Where `$SEED` is printed in the output of the failed test run. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: check 2 | 3 | build: 4 | @rebar3 compile 5 | 6 | check: build 7 | @rebar3 eunit 8 | 9 | clean: 10 | @rebar3 clean 11 | @rm -rf _build 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An Erlang Binding to FoundationDB 2 | === 3 | 4 | [![CI](https://github.com/apache/couchdb-erlfdb/actions/workflows/ci.yml/badge.svg)](https://github.com/apache/couchdb-erlfdb/actions/workflows/ci.yml)[![Coverage](https://coveralls.io/repos/github/apache/couchdb-erlfdb/badge.svg?branch=main)](https://coveralls.io/github/apache/couchdb-erlfdb?branch=main) 5 | 6 | This project is a NIF wrapper for the FoundationDB C API. Documentation on 7 | the main API can be found [here][fdb_docs]. 8 | 9 | This project also provides a conforming implementation of the [Tuple] and 10 | [Directory] layers. 11 | 12 | [fdb_docs]: https://apple.github.io/foundationdb/api-c.html 13 | [Tuple]: https://github.com/apple/foundationdb/blob/master/design/tuple.md 14 | [Directory]: https://apple.github.io/foundationdb/developer-guide.html#directories 15 | 16 | 17 | Building 18 | --- 19 | 20 | Assuming you have installed the FoundationDB C API library, building erlfdb 21 | is as simple as: 22 | 23 | $ make 24 | 25 | Alternatively, adding erlfdb as a rebar dependency should Just Work ®. 26 | 27 | 28 | Documentation for installing FoundationDB can be found [here for macOS] 29 | or [here for Linux]. 30 | 31 | [here for macOS]: https://apple.github.io/foundationdb/getting-started-mac.html 32 | [here for Linux]: https://apple.github.io/foundationdb/getting-started-linux.html 33 | 34 | 35 | Quick Example 36 | --- 37 | 38 | A simple example showing how to open a database and read and write keys: 39 | 40 | ```erlang 41 | 42 | Eshell V9.3.3.6 (abort with ^G) 43 | 1> Db = erlfdb:open(<<"/usr/local/etc/foundationdb/fdb.cluster">>). 44 | {erlfdb_database,#Ref<0.2859661758.3941466120.85406>} 45 | 2> ok = erlfdb:set(Db, <<"foo">>, <<"bar">>). 46 | ok 47 | 3> erlfdb:get(Db, <<"foo">>). 48 | <<"bar">> 49 | 4> erlfdb:get(Db, <<"bar">>). 50 | not_found 51 | ``` 52 | 53 | Binding Tester 54 | --- 55 | 56 | FoundationDB has a custom binding tester that can be used to test whether 57 | changes have broken compatibility. See the [BINDING_TESTER](BINDING_TESTER.md) 58 | documentation for instructions on building and running that system. -------------------------------------------------------------------------------- /c_src/atom_names.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // General 14 | ATOM_MAP(ok); 15 | ATOM_MAP(error); 16 | 17 | ATOM_MAP(true); 18 | ATOM_MAP(false); 19 | 20 | ATOM_MAP(ready); 21 | 22 | ATOM_MAP(lt); 23 | ATOM_MAP(lteq); 24 | ATOM_MAP(gt); 25 | ATOM_MAP(gteq); 26 | 27 | ATOM_MAP(not_found); 28 | 29 | ATOM_MAP(erlfdb_error); 30 | ATOM_MAP(erlfdb_future); 31 | ATOM_MAP(erlfdb_database); 32 | ATOM_MAP(erlfdb_transaction); 33 | 34 | ATOM_MAP(invalid_future_type); 35 | 36 | ATOM_MAP(writes_not_allowed); 37 | 38 | // Network Options 39 | ATOM_MAP(local_address); 40 | ATOM_MAP(cluster_file); 41 | ATOM_MAP(trace_enable); 42 | ATOM_MAP(trace_format); 43 | ATOM_MAP(trace_roll_size); 44 | ATOM_MAP(trace_max_logs_size); 45 | ATOM_MAP(trace_log_group); 46 | ATOM_MAP(knob); 47 | ATOM_MAP(tls_plugin); 48 | ATOM_MAP(tls_cert_bytes); 49 | ATOM_MAP(tls_cert_path); 50 | ATOM_MAP(tls_key_bytes); 51 | ATOM_MAP(tls_key_path); 52 | ATOM_MAP(tls_verify_peers); 53 | ATOM_MAP(client_buggify_enable); 54 | ATOM_MAP(client_buggify_disable); 55 | ATOM_MAP(client_buggify_section_activated_probability); 56 | ATOM_MAP(client_buggify_section_fired_probability); 57 | ATOM_MAP(tls_ca_bytes); 58 | ATOM_MAP(tls_ca_path); 59 | ATOM_MAP(tls_password); 60 | ATOM_MAP(disable_multi_version_client_api); 61 | ATOM_MAP(callbacks_on_external_threads); 62 | ATOM_MAP(external_client_library); 63 | ATOM_MAP(external_client_directory); 64 | ATOM_MAP(disable_local_client); 65 | ATOM_MAP(disable_client_statistics_logging); 66 | ATOM_MAP(enable_slow_task_profiling); 67 | ATOM_MAP(enable_run_loop_profiling); 68 | 69 | 70 | // Database Options 71 | ATOM_MAP(location_cache_size); 72 | ATOM_MAP(max_watches); 73 | ATOM_MAP(machine_id); 74 | ATOM_MAP(datacenter_id); 75 | 76 | 77 | // Transaction Options 78 | ATOM_MAP(causal_write_risky); 79 | ATOM_MAP(causal_read_risky); 80 | ATOM_MAP(causal_read_disable); 81 | ATOM_MAP(next_write_no_write_conflict_range); 82 | ATOM_MAP(read_your_writes_enable); 83 | ATOM_MAP(read_your_writes_disable); 84 | ATOM_MAP(read_ahead_disable); 85 | ATOM_MAP(durability_datacenter); 86 | ATOM_MAP(durability_risky); 87 | ATOM_MAP(durability_dev_null_is_web_scale); 88 | ATOM_MAP(priority_system_immediate); 89 | ATOM_MAP(priority_batch); 90 | ATOM_MAP(initialize_new_database); 91 | ATOM_MAP(access_system_keys); 92 | ATOM_MAP(read_system_keys); 93 | ATOM_MAP(debug_retry_logging); 94 | ATOM_MAP(transaction_logging_enable); 95 | ATOM_MAP(debug_transaction_identifier); 96 | ATOM_MAP(transaction_logging_max_field_length); 97 | ATOM_MAP(log_transaction); 98 | ATOM_MAP(timeout); 99 | ATOM_MAP(retry_limit); 100 | ATOM_MAP(max_retry_delay); 101 | ATOM_MAP(snapshot_ryw_enable); 102 | ATOM_MAP(snapshot_ryw_disable); 103 | ATOM_MAP(lock_aware); 104 | ATOM_MAP(used_during_commit_protection_disable); 105 | ATOM_MAP(read_lock_aware); 106 | ATOM_MAP(size_limit); 107 | ATOM_MAP(allow_writes); 108 | ATOM_MAP(disallow_writes); 109 | ATOM_MAP(include_port_in_address); 110 | ATOM_MAP(use_provisional_proxies); 111 | ATOM_MAP(report_conflicting_keys); 112 | 113 | 114 | // Streaming mode 115 | ATOM_MAP(want_all); 116 | ATOM_MAP(iterator); 117 | ATOM_MAP(exact); 118 | ATOM_MAP(small); 119 | ATOM_MAP(medium); 120 | ATOM_MAP(large); 121 | ATOM_MAP(serial); 122 | 123 | 124 | // Atomic Mutation Types 125 | ATOM_MAP(add); 126 | ATOM_MAP(bit_and); 127 | ATOM_MAP(bit_or); 128 | ATOM_MAP(bit_xor); 129 | ATOM_MAP(append_if_fits); 130 | ATOM_MAP(max); 131 | ATOM_MAP(min); 132 | ATOM_MAP(byte_min); 133 | ATOM_MAP(byte_max); 134 | ATOM_MAP(set_versionstamped_key); 135 | ATOM_MAP(set_versionstamped_value); 136 | 137 | 138 | // Conflict Range Types 139 | ATOM_MAP(read); 140 | ATOM_MAP(write); 141 | 142 | 143 | // Error Predicates 144 | ATOM_MAP(retryable); 145 | ATOM_MAP(maybe_committed); 146 | ATOM_MAP(retryable_not_committed); 147 | -------------------------------------------------------------------------------- /c_src/atoms.c: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #include "atoms.h" 14 | 15 | 16 | #define ATOM_MAP(NAME) ERL_NIF_TERM ATOM_##NAME 17 | #include "atom_names.h" 18 | #undef ATOM_MAP 19 | 20 | 21 | #define ATOM_MAP(NAME) ATOM_##NAME = enif_make_atom(env, #NAME) 22 | void 23 | erlfdb_init_atoms(ErlNifEnv* env) 24 | { 25 | #include "atom_names.h" 26 | } 27 | #undef ATOM_MAP 28 | -------------------------------------------------------------------------------- /c_src/atoms.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | 14 | #ifndef ERLFDB_ATOMS_H 15 | #define ERLFDB_ATOMS_H 16 | 17 | #include "erl_nif.h" 18 | 19 | 20 | #define ATOM_MAP(NAME) extern ERL_NIF_TERM ATOM_##NAME; 21 | #include "atom_names.h" 22 | #undef ATOM_MAP 23 | 24 | 25 | void erlfdb_init_atoms(ErlNifEnv* env); 26 | 27 | 28 | #endif // Included atoms.h 29 | -------------------------------------------------------------------------------- /c_src/fdb.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #ifndef ERLFDB_FDB_H 14 | #define ERLFDB_FDB_H 15 | 16 | #ifndef FDB_API_VERSION 17 | #define FDB_API_VERSION 620 18 | #endif // Allow command-line override of default FDB_API_VERSION 19 | 20 | #include 21 | 22 | #endif // Included fdb.h 23 | -------------------------------------------------------------------------------- /c_src/resources.c: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #include "resources.h" 14 | 15 | 16 | ErlNifResourceType* ErlFDBFutureRes; 17 | ErlNifResourceType* ErlFDBDatabaseRes; 18 | ErlNifResourceType* ErlFDBTransactionRes; 19 | 20 | 21 | int 22 | erlfdb_init_resources(ErlNifEnv* env) 23 | { 24 | 25 | ErlFDBFutureRes = enif_open_resource_type( 26 | env, 27 | NULL, 28 | "erlfdb_future", 29 | erlfdb_future_dtor, 30 | ERL_NIF_RT_CREATE, 31 | NULL 32 | ); 33 | if(ErlFDBFutureRes == NULL) { 34 | return 0; 35 | } 36 | 37 | ErlFDBDatabaseRes = enif_open_resource_type( 38 | env, 39 | NULL, 40 | "erlfdb_database", 41 | erlfdb_database_dtor, 42 | ERL_NIF_RT_CREATE, 43 | NULL 44 | ); 45 | if(ErlFDBDatabaseRes == NULL) { 46 | return 0; 47 | } 48 | 49 | ErlFDBTransactionRes = enif_open_resource_type( 50 | env, 51 | NULL, 52 | "erlfdb_transaction", 53 | erlfdb_transaction_dtor, 54 | ERL_NIF_RT_CREATE, 55 | NULL 56 | ); 57 | if(ErlFDBTransactionRes == NULL) { 58 | return 0; 59 | } 60 | 61 | 62 | return 1; 63 | } 64 | 65 | void 66 | erlfdb_future_dtor(ErlNifEnv* env, void* obj) 67 | { 68 | ErlFDBFuture* f = (ErlFDBFuture*) obj; 69 | 70 | if(f->future != NULL) { 71 | fdb_future_destroy(f->future); 72 | } 73 | 74 | if(f->msg_env != NULL) { 75 | enif_free_env(f->msg_env); 76 | } 77 | 78 | if(f->lock != NULL) { 79 | enif_mutex_destroy(f->lock); 80 | } 81 | } 82 | 83 | 84 | void 85 | erlfdb_database_dtor(ErlNifEnv* env, void* obj) 86 | { 87 | ErlFDBDatabase* d = (ErlFDBDatabase*) obj; 88 | 89 | if(d->database != NULL) { 90 | fdb_database_destroy(d->database); 91 | } 92 | } 93 | 94 | 95 | void 96 | erlfdb_transaction_dtor(ErlNifEnv* env, void* obj) 97 | { 98 | ErlFDBTransaction* t = (ErlFDBTransaction*) obj; 99 | 100 | if(t->transaction != NULL) { 101 | fdb_transaction_destroy(t->transaction); 102 | } 103 | } 104 | 105 | 106 | int 107 | erlfdb_transaction_is_owner(ErlNifEnv* env, ErlFDBTransaction* t) 108 | { 109 | ErlNifPid pid; 110 | ERL_NIF_TERM self; 111 | 112 | enif_self(env, &pid); 113 | self = enif_make_pid(env, &pid); 114 | 115 | return enif_compare(t->owner, self) == 0; 116 | } 117 | -------------------------------------------------------------------------------- /c_src/resources.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #ifndef ERLFDB_RESOURCES_H 14 | #define ERLFDB_RESOURCES_H 15 | 16 | #include 17 | 18 | #include "erl_nif.h" 19 | #include "fdb.h" 20 | 21 | 22 | extern ErlNifResourceType* ErlFDBFutureRes; 23 | extern ErlNifResourceType* ErlFDBDatabaseRes; 24 | extern ErlNifResourceType* ErlFDBTransactionRes; 25 | 26 | 27 | typedef enum _ErlFDBFutureType 28 | { 29 | ErlFDB_FT_NONE = 0, 30 | ErlFDB_FT_VOID, 31 | ErlFDB_FT_INT64, 32 | ErlFDB_FT_KEY, 33 | ErlFDB_FT_VALUE, 34 | ErlFDB_FT_STRING_ARRAY, 35 | ErlFDB_FT_KEYVALUE_ARRAY 36 | } ErlFDBFutureType; 37 | 38 | 39 | typedef struct _ErlFDBFuture 40 | { 41 | FDBFuture* future; 42 | ErlFDBFutureType ftype; 43 | ErlNifPid pid; 44 | ErlNifEnv* pid_env; 45 | ErlNifEnv* msg_env; 46 | ERL_NIF_TERM msg_ref; 47 | ErlNifMutex* lock; 48 | bool cancelled; 49 | } ErlFDBFuture; 50 | 51 | 52 | typedef struct _ErlFDBDatabase 53 | { 54 | FDBDatabase* database; 55 | } ErlFDBDatabase; 56 | 57 | 58 | typedef struct _ErlFDBTransaction 59 | { 60 | FDBTransaction* transaction; 61 | ERL_NIF_TERM owner; 62 | unsigned int txid; 63 | bool read_only; 64 | bool writes_allowed; 65 | bool has_watches; 66 | } ErlFDBTransaction; 67 | 68 | 69 | int erlfdb_init_resources(ErlNifEnv* env); 70 | void erlfdb_future_dtor(ErlNifEnv* env, void* obj); 71 | void erlfdb_database_dtor(ErlNifEnv* env, void* obj); 72 | void erlfdb_transaction_dtor(ErlNifEnv* env, void* obj); 73 | 74 | 75 | int erlfdb_transaction_is_owner(ErlNifEnv* env, ErlFDBTransaction* t); 76 | 77 | 78 | #endif // Included resources.h 79 | -------------------------------------------------------------------------------- /c_src/util.c: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #include 14 | 15 | #include "atoms.h" 16 | #include "util.h" 17 | 18 | 19 | ERL_NIF_TERM 20 | erlfdb_erlang_error(ErlNifEnv* env, fdb_error_t err) 21 | { 22 | ERL_NIF_TERM code = enif_make_int(env, err); 23 | ERL_NIF_TERM tuple = T2(env, ATOM_erlfdb_error, code); 24 | return enif_raise_exception(env, tuple); 25 | } 26 | 27 | 28 | int 29 | erlfdb_get_boolean(ERL_NIF_TERM term, fdb_bool_t* ret) 30 | { 31 | if(IS_ATOM(term, true)) { 32 | *ret = 1; 33 | } else if(IS_ATOM(term, false)) { 34 | *ret = 0; 35 | } else { 36 | return 0; 37 | } 38 | 39 | return 1; 40 | } 41 | 42 | 43 | int 44 | erlfdb_get_key_selector( 45 | ErlNifEnv* env, 46 | ERL_NIF_TERM selector, 47 | ErlNifBinary* bin, 48 | fdb_bool_t* or_equal, 49 | int* offset 50 | ) 51 | { 52 | const ERL_NIF_TERM* tuple; 53 | int arity; 54 | int number_or_equal; 55 | int add_offset; 56 | 57 | if(!enif_get_tuple(env, selector, &arity, &tuple)) { 58 | return 0; 59 | } 60 | 61 | if(arity != 2 && arity != 3) { 62 | return 0; 63 | } 64 | 65 | if(!enif_inspect_binary(env, tuple[0], bin)) { 66 | return 0; 67 | } 68 | 69 | *offset = 0; 70 | 71 | if(arity >= 2) { 72 | if(IS_ATOM(tuple[1], lt)) { 73 | *or_equal = 0; 74 | *offset = 0; 75 | } else if(IS_ATOM(tuple[1], lteq)) { 76 | *or_equal = 1; 77 | *offset = 0; 78 | } else if(IS_ATOM(tuple[1], gt)) { 79 | *or_equal = 1; 80 | *offset = 1; 81 | } else if(IS_ATOM(tuple[1], gteq)) { 82 | *or_equal = 0; 83 | *offset = 1; 84 | } else if(IS_ATOM(tuple[1], true)) { 85 | *or_equal = 1; 86 | } else if(IS_ATOM(tuple[1], false)) { 87 | *or_equal = 0; 88 | } else if(enif_get_int(env, tuple[1], &number_or_equal)) { 89 | if(number_or_equal) { 90 | *or_equal = 1; 91 | } else { 92 | *or_equal = 0; 93 | } 94 | } else { 95 | return 0; 96 | } 97 | } 98 | 99 | if(arity == 2) { 100 | return 1; 101 | } 102 | 103 | if(arity == 3) { 104 | if(!enif_get_int(env, tuple[2], &add_offset)) { 105 | return 0; 106 | } 107 | 108 | *offset += add_offset; 109 | return 1; 110 | } 111 | 112 | // Technically this is dead code, but keeping 113 | // it here in case the arity conditional earlier 114 | // in this function is ever changed. 115 | return 0; 116 | } -------------------------------------------------------------------------------- /c_src/util.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | #ifndef ERLFDB_UTIL_H 14 | #define ERLFDB_UTIL_H 15 | 16 | #include "erl_nif.h" 17 | #include "fdb.h" 18 | 19 | #define ERLFDB_MAX_ATOM_LENGTH 255 20 | 21 | #define T2(e, a, b) enif_make_tuple2(e, a, b) 22 | #define T3(e, a, b, c) enif_make_tuple3(e, a, b, c) 23 | #define T4(e, a, b, c, d) enif_make_tuple4(e, a, b, c, d) 24 | 25 | #define IS_ATOM(term, atom) (enif_compare(term, ATOM_##atom) == 0) 26 | 27 | 28 | ERL_NIF_TERM erlfdb_erlang_error(ErlNifEnv* env, fdb_error_t err); 29 | 30 | int erlfdb_get_boolean(ERL_NIF_TERM term, fdb_bool_t* ret); 31 | int erlfdb_get_key_selector( 32 | ErlNifEnv* env, 33 | ERL_NIF_TERM selector, 34 | ErlNifBinary* bin, 35 | fdb_bool_t* or_equal, 36 | int* offset 37 | ); 38 | 39 | #endif // Included util.h 40 | -------------------------------------------------------------------------------- /devcontainer.config: -------------------------------------------------------------------------------- 1 | [ 2 | {erlfdb, [ 3 | {test_cluster_file, system_default} 4 | ]} 5 | ]. 6 | -------------------------------------------------------------------------------- /include/erlfdb.hrl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -define(ERLFDB_ERROR(Reason), erlang:error({?MODULE, Reason})). 14 | 15 | -define(ERLFDB_PACK(Tuple), erlfdb_tuple:pack(Tuple)). 16 | -define(ERLFDB_PACK(Prefix, Tuple), erlfdb_tuple:pack(Tuple, Prefix)). 17 | 18 | -define(ERLFDB_RANGE(Prefix), 19 | erlfdb_subspace:range(erlfdb_subspace:create({}, Prefix)) 20 | ). 21 | -define(ERLFDB_RANGE(Prefix, Term), 22 | erlfdb_subspace:range(erlfdb_subspace:create({Term}, Prefix)) 23 | ). 24 | 25 | -define(ERLFDB_EXTEND(Prefix, Term), erlfdb_tuple:pack({Term}, Prefix)). 26 | 27 | -define(ERLFDB_EXTRACT(Prefix, Packed), 28 | (fun() -> 29 | __PrefixLen = size(Prefix), 30 | <> = Packed, 31 | erlfdb_tuple:unpack(__Tail) 32 | end)() 33 | ). 34 | 35 | % Most of the retriable FDB errors. The list of errors can be generated with 36 | % something like [erlfdb:get_error_string(C) || C <- lists:seq(1, 5000), 37 | % erlfdb:error_predicate(retryable, C)]. 38 | % 39 | -define(ERLFDB_TIMED_OUT, 1004). 40 | -define(ERLFDB_TRANSACTION_TOO_OLD, 1007). 41 | -define(ERLFDB_FUTURE_VERSION, 1009). 42 | -define(ERLFDB_NOT_COMMITTED, 1020). 43 | -define(ERLFDB_COMMIT_UNKNOWN_RESULT, 1021). 44 | -define(ERLFDB_TRANSACTION_CANCELLED, 1025). 45 | -define(ERLFDB_TRANSACTION_TIMED_OUT, 1031). 46 | -define(ERLFDB_PROCESS_BEHIND, 1037). 47 | -define(ERLFDB_DATABASE_LOCKED, 1038). 48 | -define(ERLFDB_CLUSTER_VERSION_CHANGED, 1039). 49 | -define(ERLFDB_PROXY_MEMORY_LIMIT_EXCEEDED, 1042). 50 | -define(ERLFDB_BATCH_TRANSACTION_THROTTLED, 1051). 51 | -define(ERLFDB_TAG_THROTTLED, 1213). 52 | -define(ERLFDB_TRANSACTION_TOO_LARGE, 2101). 53 | 54 | % The list is the exact result from calling `error_predicate/2` and does not 55 | % include ?TRANSACTION_TIMED_OUT. In some cases it may make sense to also 56 | % consider that error as retryable. 57 | % 58 | %% erlfmt-ignore 59 | -define(ERLFDB_IS_RETRYABLE(Code), ( 60 | (Code == ?ERLFDB_TRANSACTION_TOO_OLD) orelse 61 | (Code == ?ERLFDB_FUTURE_VERSION) orelse 62 | (Code == ?ERLFDB_NOT_COMMITTED) orelse 63 | (Code == ?ERLFDB_COMMIT_UNKNOWN_RESULT) orelse 64 | (Code == ?ERLFDB_PROCESS_BEHIND) orelse 65 | (Code == ?ERLFDB_DATABASE_LOCKED) orelse 66 | (Code == ?ERLFDB_CLUSTER_VERSION_CHANGED) orelse 67 | (Code == ?ERLFDB_PROXY_MEMORY_LIMIT_EXCEEDED) orelse 68 | (Code == ?ERLFDB_BATCH_TRANSACTION_THROTTLED) orelse 69 | (Code == ?ERLFDB_TAG_THROTTLED) 70 | )). 71 | -------------------------------------------------------------------------------- /notes/directory-layer-design.md: -------------------------------------------------------------------------------- 1 | Directory Layer Schema-ish 2 | ========================== 3 | 4 | 5 | `'\x16...'` to `'\x1D...'` - Keys that represent contents of the directory 6 | tree. This is basically "all possible positive integers". The prefix 7 | of each key is a directory node down below. 8 | 9 | 10 | `'\xFE'` - Prefix for all keys related to the directory tree node 11 | hierarchy. 12 | 13 | 14 | `'\xFE\x01version\x00'` - Version of the directory tree formatted as: 15 | <> 16 | 17 | 18 | `'\xFE\x01\xFE\x00'` - the root node of the tree 19 | 20 | 21 | `'\xFE\x01\xFE\x00\x01layer\x00'` - the "layer" of the given node. This 22 | is set at node creation time and never mutated by the directory layer. 23 | If a layer is provided when opening nodes it checks to see that the 24 | layer matches nodes that are read. When there's a mismatch an error 25 | is thrown. I believe the purpose of this is so that users of the 26 | DirectoryLayer can assert a primitive "ownership" over part of the 27 | directory tree with some guarantee that they won't accidentally trample 28 | over each other. However, if "layer" is the binary string "partition" 29 | then this becomes a whole new thing which I'll discuss below. 30 | 31 | 32 | `'\xFE\x01\xFE\x00\x16' - The prefix for all child nodes of the root node. The 33 | pattern here is basically `'\xFE'` + erlfdb_tupe:pack({node.key(), 0}) 34 | although its using erlfdb_subspaces under the hood. I may ixnay subspaces 35 | in the directory layer because the syntax really isn't all that useful 36 | in Erlang. 37 | 38 | 39 | `'\xFE\x01\xFE\x00\x16\x02foo\x00'` -> `'\x17\x05'` - A child node named "foo" 40 | which has a node id of `\x17\x05`. 41 | 42 | 43 | `'\xFE\x17\x05\x16\x02bar\x00'` -> `'\x17\x19'` - A child of "foo" named 44 | "bar" with node id `'\x17\x19'`. The tree is made by recursively 45 | following these paths. 46 | 47 | 48 | `'\xFE\x17\x19\x01layer\x00'` -> `'partition'` A node id has its layer value 49 | set to partition which means that this node is "DirectoryPartition" 50 | which basically means its node id is pasted onto the front of 51 | every subtree node. This can be useful to do a range scan against an 52 | entire subtree. However, directories can not be moved across partition 53 | boundaries (because that would require changing all of their key 54 | prefixes). Also, it makes key lengths slightly longer by the length 55 | of the short node id string. 56 | 57 | Partitions are roughly equivalent to a nested directory tree re-rooted 58 | outside the main directory tree. All of the contents and node keys 59 | share the same prefix based on the node id of the partition. 60 | 61 | 62 | Some Names 63 | ========== 64 | 65 | A warning to future readers, this list of names is used fairly consistently 66 | by the `erlfdb` DirectoryLayer, however they are not all used in the 67 | Python implementation. So any comparison between the two requires some 68 | fairly decent knowledge of how things work. This is mostly because the 69 | Python implementation doesn't actually bother naming things and instead has 70 | a number of long function invocations that gloss over where we might need 71 | to assign something to a variable in Erlang. 72 | 73 | * directory name = human readable string 74 | * node_name = shortened binary 75 | * root version = node_prefix + {"version"} 76 | * root node = node_prefix + {node_prefix} 77 | * partition id = node_name + 16#FE 78 | * partition version = node_name + {"version"} 79 | * partition node = partition_id + {partition_id} 80 | * node_id = node_prefix + {node_name} 81 | * node_layer_id = node_prefix + {node_name, "layer"} 82 | * node_entry_id = node_prefix + {node_name, SUBDIRS, directory name} 83 | 84 | 85 | 86 | Algorithms 87 | ========== 88 | 89 | These are all based on the assumption that my schema is at least relatively 90 | close to reality and also by staring at the Python implementation. However, 91 | the Python implementation makes extensive use of OO inheritance as well 92 | as syntactical sugar via magic methods. These algorithms are my general 93 | reinterpretation of the Python approach in Erlang terms. 94 | 95 | Some common assumptions here: 96 | 97 | * Path is a tuple of UTF-8 encoded binaries. 98 | 99 | 100 | Find Node at Path 101 | ----------------- 102 | 103 | * Get the root node (i.e., `\xFE\x01\xFE\x00`) (though this will 104 | obviously be parameterizable). 105 | * curr_node = RootNode 106 | * For part in path: 107 | - node_id = erlfdb:get(Tx, Root + to_string(Path)) 108 | - if node_id == not_found: return not_found 109 | - curr_node = `'\xFE' + node_id` 110 | * return curr_node 111 | 112 | 113 | Open Directory at Path 114 | ---------------------- 115 | 116 | * Find node at path 117 | * If not found, return not_found or throw an error or w/e 118 | * Read layer value 119 | * Optionally pre-fetch child keys. Would be more efficient as 120 | a single range scan, but super wide trees would be ungood 121 | * if layer != "partition": Return value representing this directory 122 | - root reference 123 | - path referecne 124 | - layer 125 | - children 126 | * else: Return record representing this partition 127 | - previous root 128 | - new root 129 | - path to partition root 130 | 131 | 132 | Create New Directory at Path 133 | ---------------------------- 134 | 135 | * Find directory at Path[:-1] 136 | * If directory[Path[-1]] exists: return exists 137 | * Allocate new node id via HCA 138 | * Write `'\xFE' + node_id + '\x01layer\x00' = whatever was passed 139 | * return samesies as for open path 140 | 141 | 142 | Remove A Directory at Path 143 | -------------------------- 144 | 145 | * Find node at Path 146 | * del_children(node_id): 147 | * for name, child_id in children(node_id): 148 | * del_children(child_id) 149 | * clear_range for all content related to child_id 150 | * clear_range for this node's layer and children nodes 151 | * delete node_id from Path[:-1] 152 | 153 | 154 | Move a Directory from PathA to PathB 155 | ------------------------------------ 156 | 157 | * Bunch of error checking and partition handling 158 | * Insert PathA node_id to PathB[:-1]'s list of children 159 | * Then remove PathA's node_id from PathA[:-1] 160 | 161 | 162 | List Directory at Path 163 | ---------------------- 164 | 165 | * Find Directory at Path 166 | * Return children list. Mebbe after a range read if they 167 | haven't been pre-loaded. 168 | 169 | 170 | Directory Exists 171 | ---------------- 172 | 173 | * Find directory, see if it throws an error 174 | 175 | 176 | Tests 177 | ----- 178 | 179 | * Opening a directory creates parent directories? 180 | * Creating parent directories does not set a label? 181 | * Opening nested directories does not check intermediate labels? 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /plugins/rebar_gdb_plugin.erl: -------------------------------------------------------------------------------- 1 | -module(rebar_gdb_plugin). 2 | -compile(export_all). 3 | 4 | pre_eunit(_Config, _AppFile) -> 5 | case os:getenv("USE_GDB") of 6 | false -> 7 | ok; 8 | _ -> 9 | Prompt = io_lib:format("GDB Attach to: ~s~n", [os:getpid()]), 10 | io:get_line(Prompt) 11 | end, 12 | ok. 13 | 14 | -------------------------------------------------------------------------------- /priv/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import time 6 | 7 | 8 | def main(): 9 | parent_pid = int(sys.argv[1]) 10 | target_pid = int(sys.argv[2]) 11 | while True: 12 | try: 13 | os.kill(parent_pid, 0) 14 | time.sleep(1.0) 15 | except OSError: 16 | try: 17 | os.kill(target_pid, 9) 18 | except: 19 | pass 20 | exit(0) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {plugins, [ 2 | coveralls, 3 | pc 4 | ]}. 5 | 6 | {project_plugins, [ 7 | erlfmt 8 | ]}. 9 | 10 | {provider_hooks, [ 11 | {pre, [ 12 | {compile, {pc, compile}}, 13 | {clean, {pc, clean}} 14 | ]} 15 | ]}. 16 | 17 | {port_specs, [ 18 | {"priv/erlfdb_nif.so", ["c_src/*.c"]} 19 | ]}. 20 | 21 | % port_env compiler / linker flags dynamically generated in rebar.config.script 22 | 23 | {profiles, [ 24 | {devcontainer, [ 25 | {eunit_opts, [ 26 | {sys_config, "devcontainer.config"} 27 | ]} 28 | ]} 29 | ]}. 30 | 31 | {eunit_opts, [ 32 | debug_info, 33 | verbose 34 | ]}. 35 | 36 | {cover_enabled, true}. 37 | {cover_export_enabled, true}. 38 | {coveralls_coverdata, "_build/devcontainer+test/cover/*.coverdata"}. 39 | {coveralls_service_name, "github"}. 40 | {coveralls_parallel, true}. 41 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | % Hacky means to extract API version from fdbcli protocol version output 2 | % See https://github.com/apple/foundationdb/blob/master/flow/ProtocolVersion.h 3 | MaxAPIVersion = 4 | begin 5 | VsnInfo = os:cmd("fdbcli --version"), 6 | {match, [ProtocolStr]} = re:run(VsnInfo, "protocol ([a-f0-9]*)", [{capture, [1], list}]), 7 | ProtocolVsn = list_to_integer(ProtocolStr, 16), 8 | APIVersionBytes = (ProtocolVsn band 16#0000000FFF00000) bsr 20, 9 | integer_to_list(APIVersionBytes, 16) 10 | end. 11 | 12 | % https://github.com/markusn/coveralls-erl#example-usage-rebar3-and-github-actions 13 | CoverallConfig = 14 | case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of 15 | {"true", Token} when is_list(Token) -> 16 | CONFIG1 = [{coveralls_repo_token, Token}, 17 | {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, 18 | {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}, 19 | {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, 20 | {coveralls_flag_name, os:getenv("COVERALLS_FLAG_NAME")} | CONFIG], 21 | case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" 22 | andalso string:tokens(os:getenv("GITHUB_REF"), "/") of 23 | [_, "pull", PRNO, _] -> 24 | [{coveralls_service_pull_request, PRNO} | CONFIG1]; 25 | _ -> 26 | CONFIG1 27 | end; 28 | _ -> 29 | CONFIG 30 | end. 31 | 32 | [{port_env, [ 33 | { 34 | "(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", 35 | "CFLAGS", 36 | "$CFLAGS -I/usr/local/include -Ic_src/ -g -Wall -Werror -DFDB_API_VERSION=" ++ MaxAPIVersion 37 | }, 38 | { 39 | "win32", 40 | "CFLAGS", 41 | "$CFLAGS /I\"c:/Program Files/foundationdb/include\" /O2 /DNDEBUG /DFDB_API_VERSION=" ++ MaxAPIVersion 42 | }, 43 | 44 | { 45 | "(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", 46 | "LDFLAGS", 47 | "$LDFLAGS -L/usr/local/lib -lfdb_c" 48 | }, 49 | { 50 | "win32", 51 | "LDFLAGS", 52 | "$LDFLAGS /LIBPATH:\"c:/Program Files/foundationdb/lib/foundationdb\" fdb_c.lib" 53 | } 54 | ]}] ++ CoverallConfig. 55 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/erlfdb.app.src: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | {application, erlfdb, [ 14 | {description, "Erlang client for FoundationDB"}, 15 | {vsn, "0.0.0"}, 16 | {registered, []}, 17 | {applications, [kernel, stdlib]}, 18 | {maintainers, ["Paul J. Davis"]}, 19 | {links, [{"GitHub", "https://github.com/cloudant-labs/couchdb-erlfdb"}]}, 20 | {env, [ 21 | {api_version, 620}, 22 | {network_options, []} 23 | ]} 24 | ]}. 25 | -------------------------------------------------------------------------------- /src/erlfdb.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb). 14 | 15 | -compile({no_auto_import, [get/1]}). 16 | 17 | -export([ 18 | open/0, 19 | open/1, 20 | 21 | create_transaction/1, 22 | transactional/2, 23 | snapshot/1, 24 | 25 | % Db/Tx configuration 26 | set_option/2, 27 | set_option/3, 28 | 29 | % Lifecycle Management 30 | commit/1, 31 | reset/1, 32 | cancel/1, 33 | cancel/2, 34 | 35 | % Future Specific functions 36 | is_ready/1, 37 | get/1, 38 | get_error/1, 39 | block_until_ready/1, 40 | wait/1, 41 | wait/2, 42 | wait_for_any/1, 43 | wait_for_any/2, 44 | wait_for_all/1, 45 | wait_for_all/2, 46 | 47 | % Data retrieval 48 | get/2, 49 | get_ss/2, 50 | 51 | get_key/2, 52 | get_key_ss/2, 53 | 54 | get_range/3, 55 | get_range/4, 56 | 57 | get_range_startswith/2, 58 | get_range_startswith/3, 59 | 60 | fold_range/5, 61 | fold_range/6, 62 | 63 | fold_range_future/4, 64 | fold_range_wait/4, 65 | 66 | % Data modifications 67 | set/3, 68 | clear/2, 69 | clear_range/3, 70 | clear_range_startswith/2, 71 | 72 | % Atomic operations 73 | add/3, 74 | bit_and/3, 75 | bit_or/3, 76 | bit_xor/3, 77 | min/3, 78 | max/3, 79 | byte_min/3, 80 | byte_max/3, 81 | set_versionstamped_key/3, 82 | set_versionstamped_value/3, 83 | atomic_op/4, 84 | 85 | % Watches 86 | watch/2, 87 | get_and_watch/2, 88 | set_and_watch/3, 89 | clear_and_watch/2, 90 | 91 | % Conflict ranges 92 | add_read_conflict_key/2, 93 | add_read_conflict_range/3, 94 | add_write_conflict_key/2, 95 | add_write_conflict_range/3, 96 | add_conflict_range/4, 97 | 98 | % Transaction versioning 99 | set_read_version/2, 100 | get_read_version/1, 101 | get_committed_version/1, 102 | get_versionstamp/1, 103 | 104 | % Transaction size info 105 | get_approximate_size/1, 106 | 107 | % Transaction status 108 | get_next_tx_id/1, 109 | is_read_only/1, 110 | has_watches/1, 111 | get_writes_allowed/1, 112 | 113 | % Locality and Statistics 114 | get_addresses_for_key/2, 115 | get_estimated_range_size/3, 116 | 117 | % Get conflict information 118 | get_conflicting_keys/1, 119 | 120 | % Misc 121 | on_error/2, 122 | error_predicate/2, 123 | get_last_error/0, 124 | get_error_string/1 125 | ]). 126 | 127 | -define(IS_FUTURE, {erlfdb_future, _, _}). 128 | -define(IS_FOLD_FUTURE, {fold_info, _, _}). 129 | -define(IS_DB, {erlfdb_database, _}). 130 | -define(IS_TX, {erlfdb_transaction, _}). 131 | -define(IS_SS, {erlfdb_snapshot, _}). 132 | -define(GET_TX(SS), element(2, SS)). 133 | -define(ERLFDB_ERROR, '$erlfdb_error'). 134 | 135 | -record(fold_st, { 136 | start_key, 137 | end_key, 138 | limit, 139 | target_bytes, 140 | streaming_mode, 141 | iteration, 142 | snapshot, 143 | reverse 144 | }). 145 | 146 | open() -> 147 | open(<<>>). 148 | 149 | open(ClusterFile) -> 150 | erlfdb_nif:create_database(ClusterFile). 151 | 152 | create_transaction(?IS_DB = Db) -> 153 | erlfdb_nif:database_create_transaction(Db). 154 | 155 | transactional(?IS_DB = Db, UserFun) when is_function(UserFun, 1) -> 156 | clear_erlfdb_error(), 157 | Tx = create_transaction(Db), 158 | do_transaction(Tx, UserFun); 159 | transactional(?IS_TX = Tx, UserFun) when is_function(UserFun, 1) -> 160 | UserFun(Tx); 161 | transactional(?IS_SS = SS, UserFun) when is_function(UserFun, 1) -> 162 | UserFun(SS). 163 | 164 | snapshot(?IS_TX = Tx) -> 165 | {erlfdb_snapshot, Tx}; 166 | snapshot(?IS_SS = SS) -> 167 | SS. 168 | 169 | set_option(DbOrTx, Option) -> 170 | set_option(DbOrTx, Option, <<>>). 171 | 172 | set_option(?IS_DB = Db, DbOption, Value) -> 173 | erlfdb_nif:database_set_option(Db, DbOption, Value); 174 | set_option(?IS_TX = Tx, TxOption, Value) -> 175 | erlfdb_nif:transaction_set_option(Tx, TxOption, Value). 176 | 177 | commit(?IS_TX = Tx) -> 178 | erlfdb_nif:transaction_commit(Tx). 179 | 180 | reset(?IS_TX = Tx) -> 181 | ok = erlfdb_nif:transaction_reset(Tx). 182 | 183 | cancel(?IS_FOLD_FUTURE = FoldInfo) -> 184 | cancel(FoldInfo, []); 185 | cancel(?IS_FUTURE = Future) -> 186 | cancel(Future, []); 187 | cancel(?IS_TX = Tx) -> 188 | ok = erlfdb_nif:transaction_cancel(Tx). 189 | 190 | cancel(?IS_FOLD_FUTURE = FoldInfo, Options) -> 191 | {fold_info, _St, Future} = FoldInfo, 192 | cancel(Future, Options); 193 | cancel(?IS_FUTURE = Future, Options) -> 194 | ok = erlfdb_nif:future_cancel(Future), 195 | case erlfdb_util:get(Options, flush, false) of 196 | true -> flush_future_message(Future); 197 | false -> ok 198 | end. 199 | 200 | is_ready(?IS_FUTURE = Future) -> 201 | erlfdb_nif:future_is_ready(Future). 202 | 203 | get_error(?IS_FUTURE = Future) -> 204 | erlfdb_nif:future_get_error(Future). 205 | 206 | get(?IS_FUTURE = Future) -> 207 | erlfdb_nif:future_get(Future). 208 | 209 | block_until_ready(?IS_FUTURE = Future) -> 210 | {erlfdb_future, MsgRef, _FRef} = Future, 211 | receive 212 | {MsgRef, ready} -> ok 213 | end. 214 | 215 | wait(?IS_FUTURE = Future) -> 216 | wait(Future, []); 217 | wait(Ready) -> 218 | Ready. 219 | 220 | wait(?IS_FUTURE = Future, Options) -> 221 | case is_ready(Future) of 222 | true -> 223 | Result = get(Future), 224 | % Flush ready message if already sent 225 | flush_future_message(Future), 226 | Result; 227 | false -> 228 | Timeout = erlfdb_util:get(Options, timeout, infinity), 229 | {erlfdb_future, MsgRef, _Res} = Future, 230 | receive 231 | {MsgRef, ready} -> get(Future) 232 | after Timeout -> 233 | erlang:error({timeout, Future}) 234 | end 235 | end; 236 | wait(Ready, _) -> 237 | Ready. 238 | 239 | wait_for_any(Futures) -> 240 | wait_for_any(Futures, []). 241 | 242 | wait_for_any(Futures, Options) -> 243 | wait_for_any(Futures, Options, []). 244 | 245 | wait_for_any(Futures, Options, ResendQ) -> 246 | Timeout = erlfdb_util:get(Options, timeout, infinity), 247 | receive 248 | {MsgRef, ready} = Msg -> 249 | case lists:keyfind(MsgRef, 2, Futures) of 250 | ?IS_FUTURE = Future -> 251 | lists:foreach( 252 | fun(M) -> 253 | self() ! M 254 | end, 255 | ResendQ 256 | ), 257 | Future; 258 | _ -> 259 | wait_for_any(Futures, Options, [Msg | ResendQ]) 260 | end 261 | after Timeout -> 262 | lists:foreach( 263 | fun(M) -> 264 | self() ! M 265 | end, 266 | ResendQ 267 | ), 268 | erlang:error({timeout, Futures}) 269 | end. 270 | 271 | wait_for_all(Futures) -> 272 | wait_for_all(Futures, []). 273 | 274 | wait_for_all(Futures, Options) -> 275 | % Same as wait for all. We might want to 276 | % handle timeouts here so we have a single 277 | % timeout for all future waiting. 278 | lists:map( 279 | fun(Future) -> 280 | wait(Future, Options) 281 | end, 282 | Futures 283 | ). 284 | 285 | get(?IS_DB = Db, Key) -> 286 | transactional(Db, fun(Tx) -> 287 | wait(get(Tx, Key)) 288 | end); 289 | get(?IS_TX = Tx, Key) -> 290 | erlfdb_nif:transaction_get(Tx, Key, false); 291 | get(?IS_SS = SS, Key) -> 292 | get_ss(?GET_TX(SS), Key). 293 | 294 | get_ss(?IS_TX = Tx, Key) -> 295 | erlfdb_nif:transaction_get(Tx, Key, true); 296 | get_ss(?IS_SS = SS, Key) -> 297 | get_ss(?GET_TX(SS), Key). 298 | 299 | get_key(?IS_DB = Db, Key) -> 300 | transactional(Db, fun(Tx) -> 301 | wait(get_key(Tx, Key)) 302 | end); 303 | get_key(?IS_TX = Tx, Key) -> 304 | erlfdb_nif:transaction_get_key(Tx, Key, false); 305 | get_key(?IS_SS = SS, Key) -> 306 | get_key_ss(?GET_TX(SS), Key). 307 | 308 | get_key_ss(?IS_TX = Tx, Key) -> 309 | erlfdb_nif:transaction_get_key(Tx, Key, true). 310 | 311 | get_range(DbOrTx, StartKey, EndKey) -> 312 | get_range(DbOrTx, StartKey, EndKey, []). 313 | 314 | get_range(?IS_DB = Db, StartKey, EndKey, Options) -> 315 | transactional(Db, fun(Tx) -> 316 | get_range(Tx, StartKey, EndKey, Options) 317 | end); 318 | get_range(?IS_TX = Tx, StartKey, EndKey, Options) -> 319 | Fun = fun(Rows, Acc) -> [Rows | Acc] end, 320 | Chunks = fold_range_int(Tx, StartKey, EndKey, Fun, [], Options), 321 | lists:flatten(lists:reverse(Chunks)); 322 | get_range(?IS_SS = SS, StartKey, EndKey, Options) -> 323 | get_range(?GET_TX(SS), StartKey, EndKey, [{snapshot, true} | Options]). 324 | 325 | get_range_startswith(DbOrTx, Prefix) -> 326 | get_range_startswith(DbOrTx, Prefix, []). 327 | 328 | get_range_startswith(DbOrTx, Prefix, Options) -> 329 | StartKey = Prefix, 330 | EndKey = erlfdb_key:strinc(Prefix), 331 | get_range(DbOrTx, StartKey, EndKey, Options). 332 | 333 | fold_range(DbOrTx, StartKey, EndKey, Fun, Acc) -> 334 | fold_range(DbOrTx, StartKey, EndKey, Fun, Acc, []). 335 | 336 | fold_range(?IS_DB = Db, StartKey, EndKey, Fun, Acc, Options) -> 337 | transactional(Db, fun(Tx) -> 338 | fold_range(Tx, StartKey, EndKey, Fun, Acc, Options) 339 | end); 340 | fold_range(?IS_TX = Tx, StartKey, EndKey, Fun, Acc, Options) -> 341 | fold_range_int( 342 | Tx, 343 | StartKey, 344 | EndKey, 345 | fun(Rows, InnerAcc) -> 346 | lists:foldl(Fun, InnerAcc, Rows) 347 | end, 348 | Acc, 349 | Options 350 | ); 351 | fold_range(?IS_SS = SS, StartKey, EndKey, Fun, Acc, Options) -> 352 | SSOptions = [{snapshot, true} | Options], 353 | fold_range(?GET_TX(SS), StartKey, EndKey, Fun, Acc, SSOptions). 354 | 355 | fold_range_future(?IS_TX = Tx, StartKey, EndKey, Options) -> 356 | St = options_to_fold_st(StartKey, EndKey, Options), 357 | fold_range_future_int(Tx, St); 358 | fold_range_future(?IS_SS = SS, StartKey, EndKey, Options) -> 359 | SSOptions = [{snapshot, true} | Options], 360 | fold_range_future(?GET_TX(SS), StartKey, EndKey, SSOptions). 361 | 362 | fold_range_wait(?IS_TX = Tx, ?IS_FOLD_FUTURE = FI, Fun, Acc) -> 363 | fold_range_int( 364 | Tx, 365 | FI, 366 | fun(Rows, InnerAcc) -> 367 | lists:foldl(Fun, InnerAcc, Rows) 368 | end, 369 | Acc 370 | ); 371 | fold_range_wait(?IS_SS = SS, ?IS_FOLD_FUTURE = FI, Fun, Acc) -> 372 | fold_range_wait(?GET_TX(SS), FI, Fun, Acc). 373 | 374 | set(?IS_DB = Db, Key, Value) -> 375 | transactional(Db, fun(Tx) -> 376 | set(Tx, Key, Value) 377 | end); 378 | set(?IS_TX = Tx, Key, Value) -> 379 | erlfdb_nif:transaction_set(Tx, Key, Value); 380 | set(?IS_SS = SS, Key, Value) -> 381 | set(?GET_TX(SS), Key, Value). 382 | 383 | clear(?IS_DB = Db, Key) -> 384 | transactional(Db, fun(Tx) -> 385 | clear(Tx, Key) 386 | end); 387 | clear(?IS_TX = Tx, Key) -> 388 | erlfdb_nif:transaction_clear(Tx, Key); 389 | clear(?IS_SS = SS, Key) -> 390 | clear(?GET_TX(SS), Key). 391 | 392 | clear_range(?IS_DB = Db, StartKey, EndKey) -> 393 | transactional(Db, fun(Tx) -> 394 | clear_range(Tx, StartKey, EndKey) 395 | end); 396 | clear_range(?IS_TX = Tx, StartKey, EndKey) -> 397 | erlfdb_nif:transaction_clear_range(Tx, StartKey, EndKey); 398 | clear_range(?IS_SS = SS, StartKey, EndKey) -> 399 | clear_range(?GET_TX(SS), StartKey, EndKey). 400 | 401 | clear_range_startswith(?IS_DB = Db, Prefix) -> 402 | transactional(Db, fun(Tx) -> 403 | clear_range_startswith(Tx, Prefix) 404 | end); 405 | clear_range_startswith(?IS_TX = Tx, Prefix) -> 406 | EndKey = erlfdb_key:strinc(Prefix), 407 | erlfdb_nif:transaction_clear_range(Tx, Prefix, EndKey); 408 | clear_range_startswith(?IS_SS = SS, Prefix) -> 409 | clear_range_startswith(?GET_TX(SS), Prefix). 410 | 411 | add(DbOrTx, Key, Param) -> 412 | atomic_op(DbOrTx, Key, Param, add). 413 | 414 | bit_and(DbOrTx, Key, Param) -> 415 | atomic_op(DbOrTx, Key, Param, bit_and). 416 | 417 | bit_or(DbOrTx, Key, Param) -> 418 | atomic_op(DbOrTx, Key, Param, bit_or). 419 | 420 | bit_xor(DbOrTx, Key, Param) -> 421 | atomic_op(DbOrTx, Key, Param, bit_xor). 422 | 423 | min(DbOrTx, Key, Param) -> 424 | atomic_op(DbOrTx, Key, Param, min). 425 | 426 | max(DbOrTx, Key, Param) -> 427 | atomic_op(DbOrTx, Key, Param, max). 428 | 429 | byte_min(DbOrTx, Key, Param) -> 430 | atomic_op(DbOrTx, Key, Param, byte_min). 431 | 432 | byte_max(DbOrTx, Key, Param) -> 433 | atomic_op(DbOrTx, Key, Param, byte_max). 434 | 435 | set_versionstamped_key(DbOrTx, Key, Param) -> 436 | atomic_op(DbOrTx, Key, Param, set_versionstamped_key). 437 | 438 | set_versionstamped_value(DbOrTx, Key, Param) -> 439 | atomic_op(DbOrTx, Key, Param, set_versionstamped_value). 440 | 441 | atomic_op(?IS_DB = Db, Key, Param, Op) -> 442 | transactional(Db, fun(Tx) -> 443 | atomic_op(Tx, Key, Param, Op) 444 | end); 445 | atomic_op(?IS_TX = Tx, Key, Param, Op) -> 446 | erlfdb_nif:transaction_atomic_op(Tx, Key, Param, Op); 447 | atomic_op(?IS_SS = SS, Key, Param, Op) -> 448 | atomic_op(?GET_TX(SS), Key, Param, Op). 449 | 450 | watch(?IS_DB = Db, Key) -> 451 | transactional(Db, fun(Tx) -> 452 | watch(Tx, Key) 453 | end); 454 | watch(?IS_TX = Tx, Key) -> 455 | erlfdb_nif:transaction_watch(Tx, Key); 456 | watch(?IS_SS = SS, Key) -> 457 | watch(?GET_TX(SS), Key). 458 | 459 | get_and_watch(?IS_DB = Db, Key) -> 460 | transactional(Db, fun(Tx) -> 461 | KeyFuture = get(Tx, Key), 462 | WatchFuture = watch(Tx, Key), 463 | {wait(KeyFuture), WatchFuture} 464 | end). 465 | 466 | set_and_watch(?IS_DB = Db, Key, Value) -> 467 | transactional(Db, fun(Tx) -> 468 | set(Tx, Key, Value), 469 | watch(Tx, Key) 470 | end). 471 | 472 | clear_and_watch(?IS_DB = Db, Key) -> 473 | transactional(Db, fun(Tx) -> 474 | clear(Tx, Key), 475 | watch(Tx, Key) 476 | end). 477 | 478 | add_read_conflict_key(TxObj, Key) -> 479 | add_read_conflict_range(TxObj, Key, <>). 480 | 481 | add_read_conflict_range(TxObj, StartKey, EndKey) -> 482 | add_conflict_range(TxObj, StartKey, EndKey, read). 483 | 484 | add_write_conflict_key(TxObj, Key) -> 485 | add_write_conflict_range(TxObj, Key, <>). 486 | 487 | add_write_conflict_range(TxObj, StartKey, EndKey) -> 488 | add_conflict_range(TxObj, StartKey, EndKey, write). 489 | 490 | add_conflict_range(?IS_TX = Tx, StartKey, EndKey, Type) -> 491 | erlfdb_nif:transaction_add_conflict_range(Tx, StartKey, EndKey, Type); 492 | add_conflict_range(?IS_SS = SS, StartKey, EndKey, Type) -> 493 | add_conflict_range(?GET_TX(SS), StartKey, EndKey, Type). 494 | 495 | set_read_version(?IS_TX = Tx, Version) -> 496 | erlfdb_nif:transaction_set_read_version(Tx, Version); 497 | set_read_version(?IS_SS = SS, Version) -> 498 | set_read_version(?GET_TX(SS), Version). 499 | 500 | get_read_version(?IS_TX = Tx) -> 501 | erlfdb_nif:transaction_get_read_version(Tx); 502 | get_read_version(?IS_SS = SS) -> 503 | get_read_version(?GET_TX(SS)). 504 | 505 | get_committed_version(?IS_TX = Tx) -> 506 | erlfdb_nif:transaction_get_committed_version(Tx); 507 | get_committed_version(?IS_SS = SS) -> 508 | get_committed_version(?GET_TX(SS)). 509 | 510 | get_versionstamp(?IS_TX = Tx) -> 511 | erlfdb_nif:transaction_get_versionstamp(Tx); 512 | get_versionstamp(?IS_SS = SS) -> 513 | get_versionstamp(?GET_TX(SS)). 514 | 515 | get_approximate_size(?IS_TX = Tx) -> 516 | erlfdb_nif:transaction_get_approximate_size(Tx); 517 | get_approximate_size(?IS_SS = SS) -> 518 | get_approximate_size(?GET_TX(SS)). 519 | 520 | get_next_tx_id(?IS_TX = Tx) -> 521 | erlfdb_nif:transaction_get_next_tx_id(Tx); 522 | get_next_tx_id(?IS_SS = SS) -> 523 | get_next_tx_id(?GET_TX(SS)). 524 | 525 | is_read_only(?IS_TX = Tx) -> 526 | erlfdb_nif:transaction_is_read_only(Tx); 527 | is_read_only(?IS_SS = SS) -> 528 | is_read_only(?GET_TX(SS)). 529 | 530 | has_watches(?IS_TX = Tx) -> 531 | erlfdb_nif:transaction_has_watches(Tx); 532 | has_watches(?IS_SS = SS) -> 533 | has_watches(?GET_TX(SS)). 534 | 535 | get_writes_allowed(?IS_TX = Tx) -> 536 | erlfdb_nif:transaction_get_writes_allowed(Tx); 537 | get_writes_allowed(?IS_SS = SS) -> 538 | get_writes_allowed(?GET_TX(SS)). 539 | 540 | get_addresses_for_key(?IS_DB = Db, Key) -> 541 | transactional(Db, fun(Tx) -> 542 | wait(get_addresses_for_key(Tx, Key)) 543 | end); 544 | get_addresses_for_key(?IS_TX = Tx, Key) -> 545 | erlfdb_nif:transaction_get_addresses_for_key(Tx, Key); 546 | get_addresses_for_key(?IS_SS = SS, Key) -> 547 | get_addresses_for_key(?GET_TX(SS), Key). 548 | 549 | get_estimated_range_size(?IS_TX = Tx, StartKey, EndKey) -> 550 | erlfdb_nif:transaction_get_estimated_range_size(Tx, StartKey, EndKey); 551 | get_estimated_range_size(?IS_SS = SS, StartKey, EndKey) -> 552 | erlfdb_nif:transaction_get_estimated_range_size(?GET_TX(SS), StartKey, EndKey). 553 | 554 | get_conflicting_keys(?IS_TX = Tx) -> 555 | StartKey = <<16#FF, 16#FF, "/transaction/conflicting_keys/">>, 556 | EndKey = <<16#FF, 16#FF, "/transaction/conflicting_keys/", 16#FF>>, 557 | get_range(Tx, StartKey, EndKey). 558 | 559 | on_error(?IS_TX = Tx, {erlfdb_error, ErrorCode}) -> 560 | on_error(Tx, ErrorCode); 561 | on_error(?IS_TX = Tx, ErrorCode) -> 562 | erlfdb_nif:transaction_on_error(Tx, ErrorCode); 563 | on_error(?IS_SS = SS, Error) -> 564 | on_error(?GET_TX(SS), Error). 565 | 566 | error_predicate(Predicate, {erlfdb_error, ErrorCode}) -> 567 | error_predicate(Predicate, ErrorCode); 568 | error_predicate(Predicate, ErrorCode) -> 569 | erlfdb_nif:error_predicate(Predicate, ErrorCode). 570 | 571 | get_last_error() -> 572 | erlang:get(?ERLFDB_ERROR). 573 | 574 | get_error_string(ErrorCode) when is_integer(ErrorCode) -> 575 | erlfdb_nif:get_error(ErrorCode). 576 | 577 | clear_erlfdb_error() -> 578 | put(?ERLFDB_ERROR, undefined). 579 | 580 | do_transaction(?IS_TX = Tx, UserFun) -> 581 | try 582 | Ret = UserFun(Tx), 583 | case is_read_only(Tx) andalso not has_watches(Tx) of 584 | true -> ok; 585 | false -> wait(commit(Tx), [{timeout, infinity}]) 586 | end, 587 | Ret 588 | catch 589 | error:{erlfdb_error, Code} -> 590 | put(?ERLFDB_ERROR, Code), 591 | wait(on_error(Tx, Code), [{timeout, infinity}]), 592 | do_transaction(Tx, UserFun) 593 | end. 594 | 595 | fold_range_int(?IS_TX = Tx, StartKey, EndKey, Fun, Acc, Options) -> 596 | St = options_to_fold_st(StartKey, EndKey, Options), 597 | fold_range_int(Tx, St, Fun, Acc). 598 | 599 | fold_range_int(Tx, #fold_st{} = St, Fun, Acc) -> 600 | RangeFuture = fold_range_future_int(Tx, St), 601 | fold_range_int(Tx, RangeFuture, Fun, Acc); 602 | fold_range_int(Tx, ?IS_FOLD_FUTURE = FI, Fun, Acc) -> 603 | {fold_info, St, Future} = FI, 604 | #fold_st{ 605 | start_key = StartKey, 606 | end_key = EndKey, 607 | limit = Limit, 608 | iteration = Iteration, 609 | reverse = Reverse 610 | } = St, 611 | 612 | {RawRows, Count, HasMore} = wait(Future), 613 | 614 | Count = length(RawRows), 615 | 616 | % If our limit is within the current set of 617 | % rows we need to truncate the list 618 | Rows = 619 | if 620 | Limit == 0 orelse Limit > Count -> RawRows; 621 | true -> lists:sublist(RawRows, Limit) 622 | end, 623 | 624 | % Invoke our callback to update the accumulator 625 | NewAcc = 626 | if 627 | Rows == [] -> Acc; 628 | true -> Fun(Rows, Acc) 629 | end, 630 | 631 | % Determine if we have more rows to iterate 632 | Recurse = (Rows /= []) and (Limit == 0 orelse Limit > Count) and HasMore, 633 | 634 | if 635 | not Recurse -> 636 | NewAcc; 637 | true -> 638 | LastKey = element(1, lists:last(Rows)), 639 | {NewStartKey, NewEndKey} = 640 | case Reverse /= 0 of 641 | true -> 642 | {StartKey, erlfdb_key:first_greater_or_equal(LastKey)}; 643 | false -> 644 | {erlfdb_key:first_greater_than(LastKey), EndKey} 645 | end, 646 | NewSt = St#fold_st{ 647 | start_key = NewStartKey, 648 | end_key = NewEndKey, 649 | limit = 650 | if 651 | Limit == 0 -> 0; 652 | true -> Limit - Count 653 | end, 654 | iteration = Iteration + 1 655 | }, 656 | fold_range_int(Tx, NewSt, Fun, NewAcc) 657 | end. 658 | 659 | fold_range_future_int(?IS_TX = Tx, #fold_st{} = St) -> 660 | #fold_st{ 661 | start_key = StartKey, 662 | end_key = EndKey, 663 | limit = Limit, 664 | target_bytes = TargetBytes, 665 | streaming_mode = StreamingMode, 666 | iteration = Iteration, 667 | snapshot = Snapshot, 668 | reverse = Reverse 669 | } = St, 670 | 671 | Future = erlfdb_nif:transaction_get_range( 672 | Tx, 673 | StartKey, 674 | EndKey, 675 | Limit, 676 | TargetBytes, 677 | StreamingMode, 678 | Iteration, 679 | Snapshot, 680 | Reverse 681 | ), 682 | 683 | {fold_info, St, Future}. 684 | 685 | options_to_fold_st(StartKey, EndKey, Options) -> 686 | Reverse = 687 | case erlfdb_util:get(Options, reverse, false) of 688 | true -> 1; 689 | false -> 0; 690 | I when is_integer(I) -> I 691 | end, 692 | #fold_st{ 693 | start_key = erlfdb_key:to_selector(StartKey), 694 | end_key = erlfdb_key:to_selector(EndKey), 695 | limit = erlfdb_util:get(Options, limit, 0), 696 | target_bytes = erlfdb_util:get(Options, target_bytes, 0), 697 | streaming_mode = erlfdb_util:get(Options, streaming_mode, want_all), 698 | iteration = erlfdb_util:get(Options, iteration, 1), 699 | snapshot = erlfdb_util:get(Options, snapshot, false), 700 | reverse = Reverse 701 | }. 702 | 703 | flush_future_message(?IS_FUTURE = Future) -> 704 | erlfdb_nif:future_silence(Future), 705 | {erlfdb_future, MsgRef, _Res} = Future, 706 | receive 707 | {MsgRef, ready} -> ok 708 | after 0 -> ok 709 | end. 710 | -------------------------------------------------------------------------------- /src/erlfdb_directory.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_directory). 14 | 15 | -export([ 16 | root/0, 17 | root/1, 18 | 19 | create_or_open/3, 20 | create_or_open/4, 21 | create/3, 22 | create/4, 23 | open/3, 24 | open/4, 25 | 26 | list/2, 27 | list/3, 28 | 29 | exists/2, 30 | exists/3, 31 | 32 | move/4, 33 | move_to/3, 34 | 35 | remove/2, 36 | remove/3, 37 | remove_if_exists/2, 38 | remove_if_exists/3, 39 | 40 | get_id/1, 41 | get_name/1, 42 | get_root/1, 43 | get_root_for_path/2, 44 | get_node_prefix/1, 45 | get_path/1, 46 | get_layer/1, 47 | get_subspace/1, 48 | 49 | subspace/2, 50 | key/1, 51 | pack/2, 52 | pack_vs/2, 53 | unpack/2, 54 | range/1, 55 | range/2, 56 | contains/2, 57 | 58 | debug_nodes/2 59 | ]). 60 | 61 | -include("erlfdb.hrl"). 62 | 63 | -define(LAYER_VERSION, {1, 0, 0}). 64 | -define(DEFAULT_NODE_PREFIX, <<16#FE>>). 65 | -define(SUBDIRS, 0). 66 | 67 | root() -> 68 | init_root([]). 69 | 70 | root(Options) -> 71 | init_root(Options). 72 | 73 | create_or_open(TxObj, Node, Path) -> 74 | create_or_open(TxObj, Node, Path, <<>>). 75 | 76 | create_or_open(TxObj, Node, PathIn, Layer) -> 77 | {Root, Path} = adj_path(Node, PathIn), 78 | if 79 | Path /= [] -> ok; 80 | true -> ?ERLFDB_ERROR({open_error, cannot_open_root}) 81 | end, 82 | case create_or_open_int(TxObj, Root, Path, Layer) of 83 | #{is_absolute_root := true} -> 84 | ?ERLFDB_ERROR({open_error, cannot_open_root}); 85 | Else -> 86 | Else 87 | end. 88 | 89 | create(TxObj, Node, Path) -> 90 | create(TxObj, Node, Path, []). 91 | 92 | create(TxObj, Node, PathIn, Options) -> 93 | {Root, Path} = adj_path(Node, PathIn), 94 | check_manual_node_name(Root, Options), 95 | erlfdb:transactional(TxObj, fun(Tx) -> 96 | Layer = erlfdb_util:get(Options, layer, <<>>), 97 | NodeName = erlfdb_util:get(Options, node_name, undefined), 98 | create_int(Tx, Root, Path, Layer, NodeName) 99 | end). 100 | 101 | open(TxObj, Node, Path) -> 102 | open(TxObj, Node, Path, []). 103 | 104 | open(TxObj, Node, PathIn, Options) -> 105 | {Root, Path} = adj_path(Node, PathIn), 106 | if 107 | Path /= [] -> ok; 108 | true -> ?ERLFDB_ERROR({open_error, cannot_open_root}) 109 | end, 110 | erlfdb:transactional(TxObj, fun(Tx) -> 111 | Layer = erlfdb_util:get(Options, layer, <<>>), 112 | open_int(Tx, Root, Path, Layer) 113 | end). 114 | 115 | list(TxObj, Node) -> 116 | list(TxObj, Node, {}). 117 | 118 | list(TxObj, Node, PathIn) -> 119 | {Root, Path} = adj_path(Node, PathIn), 120 | erlfdb:transactional(TxObj, fun(Tx) -> 121 | check_version(Tx, Root, read), 122 | case find(Tx, Root, Path) of 123 | not_found -> 124 | ?ERLFDB_ERROR({list_error, missing_path, Path}); 125 | ListNode -> 126 | Subdirs = ?ERLFDB_EXTEND(get_id(ListNode), ?SUBDIRS), 127 | SDLen = size(Subdirs), 128 | SDStart = <>, 129 | SDEnd = <>, 130 | SubDirKVs = erlfdb:wait(erlfdb:get_range(Tx, SDStart, SDEnd)), 131 | lists:map( 132 | fun({Key, NodeName}) -> 133 | {DName} = ?ERLFDB_EXTRACT(Subdirs, Key), 134 | ChildNode = init_node(Tx, ListNode, NodeName, DName), 135 | {DName, ChildNode} 136 | end, 137 | SubDirKVs 138 | ) 139 | end 140 | end). 141 | 142 | exists(TxObj, Node) -> 143 | exists(TxObj, Node, {}). 144 | 145 | exists(TxObj, Node, PathIn) -> 146 | %Root = get_root(Node), 147 | Root = get_root_for_path(Node, PathIn), 148 | {Root, Path} = adj_path(Root, Node, PathIn), 149 | erlfdb:transactional(TxObj, fun(Tx) -> 150 | check_version(Tx, Root, read), 151 | case find(Tx, Root, Path) of 152 | not_found -> 153 | false; 154 | _ChildNode -> 155 | true 156 | end 157 | end). 158 | 159 | move(TxObj, Node, OldPathIn, NewPathIn) -> 160 | {Root, OldPath} = adj_path(Node, OldPathIn), 161 | {Root, NewPath} = adj_path(Node, NewPathIn), 162 | erlfdb:transactional(TxObj, fun(Tx) -> 163 | check_version(Tx, Root, write), 164 | check_not_subpath(OldPath, NewPath), 165 | 166 | OldNode = find(Tx, Root, OldPath), 167 | NewNode = find(Tx, Root, NewPath), 168 | 169 | if 170 | OldNode /= not_found -> ok; 171 | true -> ?ERLFDB_ERROR({move_error, missing_source, OldPath}) 172 | end, 173 | 174 | if 175 | NewNode == not_found -> ok; 176 | true -> ?ERLFDB_ERROR({move_error, target_exists, NewPath}) 177 | end, 178 | 179 | {NewParentPath, [NewName]} = lists:split(length(NewPath) - 1, NewPath), 180 | case find(Tx, Root, NewParentPath) of 181 | not_found -> 182 | ?ERLFDB_ERROR({move_error, missing_parent_node, NewParentPath}); 183 | NewParentNode -> 184 | check_same_partition(OldNode, NewParentNode), 185 | ParentId = get_id(NewParentNode), 186 | NodeEntryId = ?ERLFDB_PACK(ParentId, {?SUBDIRS, NewName}), 187 | erlfdb:set(Tx, NodeEntryId, get_name(OldNode)), 188 | remove_from_parent(Tx, OldNode), 189 | OldNode#{path := get_path(Root) ++ NewPath} 190 | end 191 | end). 192 | 193 | move_to(_TxObj, #{is_absolute_root := true}, _NewPath) -> 194 | ?ERLFDB_ERROR({move_error, root_cannot_be_moved}); 195 | move_to(TxObj, Node, NewAbsPathIn) -> 196 | Root = get_root_for_path(Node, []), 197 | RootPath = get_path(Root), 198 | RootPathLen = length(RootPath), 199 | NewAbsPath = path_init(NewAbsPathIn), 200 | IsPrefix = lists:prefix(RootPath, NewAbsPath), 201 | if 202 | IsPrefix -> ok; 203 | true -> ?ERLFDB_ERROR({move_error, partition_mismatch, RootPath, NewAbsPath}) 204 | end, 205 | NodePath = get_path(Node), 206 | SrcPath = lists:nthtail(RootPathLen, NodePath), 207 | TgtPath = lists:nthtail(RootPathLen, NewAbsPath), 208 | move(TxObj, Root, SrcPath, TgtPath). 209 | 210 | remove(TxObj, Node) -> 211 | remove_int(TxObj, Node, {}, false). 212 | 213 | remove(TxObj, Node, Path) -> 214 | remove_int(TxObj, Node, Path, false). 215 | 216 | remove_if_exists(TxObj, Node) -> 217 | remove_int(TxObj, Node, {}, true). 218 | 219 | remove_if_exists(TxObj, Node, Path) -> 220 | remove_int(TxObj, Node, Path, true). 221 | 222 | get_id(Node) -> 223 | invoke(Node, get_id, []). 224 | 225 | get_name(Node) -> 226 | invoke(Node, get_name, []). 227 | 228 | get_root(Node) -> 229 | invoke(Node, get_root, []). 230 | 231 | get_root_for_path(Node, Path) -> 232 | invoke(Node, get_root_for_path, [Path]). 233 | 234 | get_partition(Node) -> 235 | invoke(Node, get_partition, []). 236 | 237 | get_node_prefix(Node) -> 238 | invoke(Node, get_node_prefix, []). 239 | 240 | get_path(Node) -> 241 | invoke(Node, get_path, []). 242 | 243 | get_layer(Node) -> 244 | invoke(Node, get_layer, []). 245 | 246 | get_subspace(Node) -> 247 | invoke(Node, get_subspace, []). 248 | 249 | subspace(Node, Tuple) -> 250 | erlfdb_subspace:create(get_subspace(Node), Tuple). 251 | 252 | key(Node) -> 253 | erlfdb_subspace:key(get_subspace(Node)). 254 | 255 | pack(Node, Tuple) -> 256 | erlfdb_subspace:pack(get_subspace(Node), Tuple). 257 | 258 | pack_vs(Node, Tuple) -> 259 | erlfdb_subspace:pack_vs(get_subspace(Node), Tuple). 260 | 261 | unpack(Node, Key) -> 262 | erlfdb_subspace:unpack(get_subspace(Node), Key). 263 | 264 | range(Node) -> 265 | range(Node, {}). 266 | 267 | range(Node, Tuple) -> 268 | erlfdb_subspace:range(get_subspace(Node), Tuple). 269 | 270 | contains(Node, Key) -> 271 | erlfdb_subspace:contains(get_subspace(Node), Key). 272 | 273 | debug_nodes(TxObj, _Node) -> 274 | erlfdb:fold_range( 275 | TxObj, 276 | <<16#02>>, 277 | <<16#FF>>, 278 | fun({K, V}, _Acc) -> 279 | io:format(standard_error, "~s => ~s~n", [ 280 | erlfdb_util:repr(K), 281 | erlfdb_util:repr(V) 282 | ]) 283 | end, 284 | nil 285 | ). 286 | 287 | invoke(not_found, _, _) -> 288 | erlang:error(broken); 289 | invoke(Node, FunName, Args) -> 290 | case Node of 291 | #{FunName := Fun} -> 292 | erlang:apply(Fun, [Node | Args]); 293 | #{} -> 294 | ?ERLFDB_ERROR({op_not_supported, FunName, Node}) 295 | end. 296 | 297 | init_root(Options) -> 298 | DefNodePref = ?DEFAULT_NODE_PREFIX, 299 | NodePrefix = erlfdb_util:get(Options, node_prefix, DefNodePref), 300 | RootNodeId = ?ERLFDB_EXTEND(NodePrefix, NodePrefix), 301 | ContentPrefix = erlfdb_util:get(Options, content_prefix, <<>>), 302 | AllowManual = erlfdb_util:get(Options, allow_manual_names, false), 303 | Allocator = erlfdb_hca:create(?ERLFDB_EXTEND(RootNodeId, <<"hca">>)), 304 | #{ 305 | id => ?ERLFDB_EXTEND(NodePrefix, NodePrefix), 306 | node_prefix => NodePrefix, 307 | content_prefix => ContentPrefix, 308 | allocator => Allocator, 309 | allow_manual_names => AllowManual, 310 | is_absolute_root => true, 311 | 312 | get_id => fun(Self) -> maps:get(id, Self) end, 313 | get_root => fun(Self) -> Self end, 314 | get_root_for_path => fun(Self, _Path) -> Self end, 315 | get_partition => fun(Self) -> Self end, 316 | get_node_prefix => fun(Self) -> maps:get(node_prefix, Self) end, 317 | get_path => fun(_Self) -> [] end, 318 | get_layer => fun(_Self) -> <<>> end, 319 | get_subspace => fun(_Self) -> 320 | ?ERLFDB_ERROR({subspace_error, subspace_unsupported_for_root}) 321 | end 322 | }. 323 | 324 | init_node(Tx, Node, NodeName, PathName) -> 325 | NodePrefix = get_node_prefix(Node), 326 | NodeLayerId = ?ERLFDB_PACK(NodePrefix, {NodeName, <<"layer">>}), 327 | Layer = 328 | case erlfdb:wait(erlfdb:get(Tx, NodeLayerId)) of 329 | not_found -> 330 | ?ERLFDB_ERROR({internal_error, missing_node_layer, NodeLayerId}); 331 | LName -> 332 | LName 333 | end, 334 | case Layer of 335 | <<"partition">> -> 336 | init_partition(Node, NodeName, PathName); 337 | _ -> 338 | init_directory(Node, NodeName, PathName, Layer) 339 | end. 340 | 341 | init_partition(ParentNode, NodeName, PathName) -> 342 | NodeNameLen = size(NodeName), 343 | NodePrefix = <>, 344 | RootNodeId = ?ERLFDB_EXTEND(NodePrefix, NodePrefix), 345 | Allocator = erlfdb_hca:create(?ERLFDB_EXTEND(RootNodeId, <<"hca">>)), 346 | #{ 347 | id => RootNodeId, 348 | name => NodeName, 349 | root => get_root(ParentNode), 350 | node_prefix => NodePrefix, 351 | content_prefix => NodeName, 352 | allocator => Allocator, 353 | allow_manual_names => false, 354 | path => path_append(get_path(ParentNode), PathName), 355 | is_partition => true, 356 | 357 | get_id => fun(Self) -> maps:get(id, Self) end, 358 | get_name => fun(Self) -> maps:get(name, Self) end, 359 | get_root => fun(Self) -> Self end, 360 | get_root_for_path => fun(Self, PathIn) -> 361 | case PathIn of 362 | {} -> maps:get(root, Self); 363 | [] -> maps:get(root, Self); 364 | _ -> Self 365 | end 366 | end, 367 | get_partition => fun(Self) -> maps:get(root, Self) end, 368 | get_node_prefix => fun(Self) -> maps:get(node_prefix, Self) end, 369 | get_path => fun(Self) -> maps:get(path, Self) end, 370 | get_layer => fun(_Self) -> <<"partition">> end, 371 | get_subspace => fun(_Self) -> 372 | ?ERLFDB_ERROR({subspace_error, subspace_unsupported_for_partition}) 373 | end 374 | }. 375 | 376 | init_directory(ParentNode, NodeName, PathName, Layer) -> 377 | NodePrefix = get_node_prefix(ParentNode), 378 | ParentPath = get_path(ParentNode), 379 | #{ 380 | id => ?ERLFDB_EXTEND(NodePrefix, NodeName), 381 | name => NodeName, 382 | root => get_root(ParentNode), 383 | path => path_append(ParentPath, PathName), 384 | layer => Layer, 385 | 386 | get_id => fun(Self) -> maps:get(id, Self) end, 387 | get_name => fun(Self) -> maps:get(name, Self) end, 388 | get_root => fun(Self) -> maps:get(root, Self) end, 389 | get_root_for_path => fun(Self, Path) -> 390 | NewPath = maps:get(path, Self) ++ path_init(Path), 391 | get_root_for_path(maps:get(root, Self), NewPath) 392 | end, 393 | get_partition => fun(Self) -> maps:get(root, Self) end, 394 | get_node_prefix => fun(Self) -> 395 | Root = maps:get(root, Self), 396 | get_node_prefix(Root) 397 | end, 398 | get_path => fun(Self) -> maps:get(path, Self) end, 399 | get_layer => fun(Self) -> maps:get(layer, Self) end, 400 | get_subspace => fun(Self) -> 401 | erlfdb_subspace:create({}, maps:get(name, Self)) 402 | end 403 | }. 404 | 405 | find(_Tx, Node, []) -> 406 | Node; 407 | find(Tx, Node, [PathName | RestPath]) -> 408 | NodeEntryId = ?ERLFDB_PACK(get_id(Node), {?SUBDIRS, PathName}), 409 | case erlfdb:wait(erlfdb:get(Tx, NodeEntryId)) of 410 | not_found -> 411 | not_found; 412 | ChildNodeName -> 413 | ChildNode = init_node(Tx, Node, ChildNodeName, PathName), 414 | find(Tx, ChildNode, RestPath) 415 | end. 416 | 417 | find_deepest(_Tx, Node, []) -> 418 | Node; 419 | find_deepest(Tx, Node, [PathName | RestPath]) -> 420 | NodeEntryId = ?ERLFDB_PACK(get_id(Node), {?SUBDIRS, PathName}), 421 | case erlfdb:wait(erlfdb:get(Tx, NodeEntryId)) of 422 | not_found -> 423 | Node; 424 | ChildNodeName -> 425 | ChildNode = init_node(Tx, Node, ChildNodeName, PathName), 426 | find_deepest(Tx, ChildNode, RestPath) 427 | end. 428 | 429 | create_or_open_int(_TxObj, Node, [], LayerIn) -> 430 | Layer = 431 | case LayerIn of 432 | <<>> -> <<>>; 433 | null -> <<>>; 434 | undefined -> <<>>; 435 | Else when is_binary(Else) -> Else 436 | end, 437 | NodeLayer = get_layer(Node), 438 | if 439 | Layer == <<>> orelse Layer == NodeLayer -> ok; 440 | true -> ?ERLFDB_ERROR({open_error, layer_mismatch, Layer, NodeLayer}) 441 | end, 442 | Node; 443 | create_or_open_int(TxObj, Node, PathIn, Layer) -> 444 | {Root, Path} = adj_path(Node, PathIn), 445 | erlfdb:transactional(TxObj, fun(Tx) -> 446 | {ParentPath, [PathName]} = lists:split(length(Path) - 1, Path), 447 | 448 | Parent = lists:foldl( 449 | fun(Name, CurrNode) -> 450 | try 451 | open_int(Tx, CurrNode, Name, <<>>) 452 | catch 453 | error:{?MODULE, {open_error, path_missing, _}} -> 454 | create_int(Tx, CurrNode, Name, <<>>, undefined) 455 | end 456 | end, 457 | Root, 458 | ParentPath 459 | ), 460 | 461 | try 462 | open_int(Tx, Parent, PathName, Layer) 463 | catch 464 | error:{?MODULE, {open_error, path_missing, _}} -> 465 | create_int(Tx, Parent, PathName, Layer, undefined) 466 | end 467 | end). 468 | 469 | create_int(Tx, Node, PathIn, Layer, NodeNameIn) -> 470 | Path = path_init(PathIn), 471 | try 472 | open_int(Tx, Node, Path, <<>>), 473 | ?ERLFDB_ERROR({create_error, path_exists, Path}) 474 | catch 475 | error:{?MODULE, {open_error, path_missing, _}} -> 476 | Deepest = find_deepest(Tx, Node, Path), 477 | NodeName = create_node_name(Tx, Deepest, NodeNameIn), 478 | {ParentPath, [PathName]} = lists:split(length(Path) - 1, Path), 479 | case create_or_open_int(Tx, Node, ParentPath, <<>>) of 480 | not_found -> 481 | ?ERLFDB_ERROR({create_error, missing_parent, ParentPath}); 482 | Parent -> 483 | check_version(Tx, Parent, write), 484 | create_node(Tx, Parent, PathName, NodeName, Layer), 485 | R = find(Tx, Parent, [PathName]), 486 | if 487 | R /= not_found -> R; 488 | true -> erlang:error(broken) 489 | end 490 | end 491 | end. 492 | 493 | create_node(Tx, Parent, PathName, NodeName, LayerIn) -> 494 | NodeEntryId = ?ERLFDB_PACK(get_id(Parent), {?SUBDIRS, PathName}), 495 | erlfdb:set(Tx, NodeEntryId, NodeName), 496 | 497 | NodePrefix = get_node_prefix(Parent), 498 | NodeLayerId = ?ERLFDB_PACK(NodePrefix, {NodeName, <<"layer">>}), 499 | Layer = 500 | if 501 | LayerIn == undefined -> <<>>; 502 | true -> LayerIn 503 | end, 504 | erlfdb:set(Tx, NodeLayerId, Layer). 505 | 506 | open_int(Tx, Node, PathIn, Layer) -> 507 | check_version(Tx, Node, read), 508 | Path = path_init(PathIn), 509 | case find(Tx, Node, Path) of 510 | not_found -> 511 | ?ERLFDB_ERROR({open_error, path_missing, Path}); 512 | #{is_absolute_root := true} -> 513 | ?ERLFDB_ERROR({open_error, cannot_open_root}); 514 | Opened -> 515 | NodeLayer = get_layer(Opened), 516 | if 517 | Layer == <<>> orelse Layer == NodeLayer -> ok; 518 | true -> ?ERLFDB_ERROR({open_error, layer_mismatch, Layer, NodeLayer}) 519 | end, 520 | Opened 521 | end. 522 | 523 | remove_int(TxObj, Node, PathIn, IgnoreMissing) -> 524 | Root = get_root_for_path(Node, PathIn), 525 | {Root, Path} = adj_path(Root, Node, PathIn), 526 | erlfdb:transactional(TxObj, fun(Tx) -> 527 | check_version(Tx, Root, write), 528 | case find(Tx, Root, Path) of 529 | not_found when IgnoreMissing -> 530 | ok; 531 | not_found -> 532 | ?ERLFDB_ERROR({remove_error, path_missing, Path}); 533 | #{is_absolute_root := true} -> 534 | ?ERLFDB_ERROR({remove_error, cannot_remove_root}); 535 | ToRem -> 536 | remove_recursive(Tx, ToRem), 537 | remove_from_parent(Tx, ToRem) 538 | end 539 | end). 540 | 541 | remove_recursive(Tx, Node) -> 542 | % Remove all subdirectories 543 | lists:foreach( 544 | fun({_DirName, ChildNode}) -> 545 | remove_recursive(Tx, ChildNode) 546 | end, 547 | list(Tx, Node) 548 | ), 549 | 550 | % Delete all content for the node. 551 | ContentSS = erlfdb_subspace:create({}, get_name(Node)), 552 | {ContentStart, ContentEnd} = erlfdb_subspace:range(ContentSS), 553 | erlfdb:clear_range(Tx, ContentStart, ContentEnd), 554 | 555 | % Delete this node from the tree hierarchy 556 | NodeSubspace = erlfdb_subspace:create({}, get_id(Node)), 557 | {NodeStart, NodeEnd} = erlfdb_subspace:range(NodeSubspace), 558 | erlfdb:clear_range(Tx, NodeStart, NodeEnd). 559 | 560 | remove_from_parent(Tx, Node) -> 561 | {Root, Path} = adj_path(get_root_for_path(Node, []), Node, []), 562 | {ParentPath, [PathName]} = lists:split(length(Path) - 1, Path), 563 | Parent = find(Tx, Root, ParentPath), 564 | 565 | NodeEntryId = ?ERLFDB_PACK(get_id(Parent), {?SUBDIRS, PathName}), 566 | erlfdb:clear(Tx, NodeEntryId). 567 | 568 | check_manual_node_name(Root, Options) -> 569 | AllowManual = maps:get(allow_manual_names, Root), 570 | IsManual = lists:keyfind(node_name, 1, Options) /= false, 571 | if 572 | not (IsManual and not AllowManual) -> ok; 573 | true -> ?ERLFDB_ERROR({create_error, manual_node_names_prohibited}) 574 | end. 575 | 576 | create_node_name(Tx, Parent, NameIn) -> 577 | #{ 578 | content_prefix := ContentPrefix, 579 | allow_manual_names := AllowManual, 580 | allocator := Allocator 581 | } = get_root(Parent), 582 | Name = 583 | case NameIn of 584 | null -> undefined; 585 | undefined -> undefined; 586 | _ when is_binary(NameIn) -> NameIn 587 | end, 588 | case Name of 589 | _ when Name == undefined -> 590 | BaseId = erlfdb_hca:allocate(Allocator, Tx), 591 | CPLen = size(ContentPrefix), 592 | NewName = <>, 593 | 594 | KeysExist = erlfdb:get_range_startswith(Tx, NewName, [{limit, 1}]), 595 | if 596 | KeysExist == [] -> 597 | ok; 598 | true -> 599 | ?ERLFDB_ERROR({ 600 | create_error, 601 | keys_exist_for_allocated_name, 602 | NewName 603 | }) 604 | end, 605 | 606 | IsFree = is_prefix_free(erlfdb:snapshot(Tx), Parent, NewName), 607 | if 608 | IsFree -> 609 | ok; 610 | true -> 611 | ?ERLFDB_ERROR({ 612 | create_error, 613 | manual_names_conflict_with_allocated_name, 614 | NewName 615 | }) 616 | end, 617 | 618 | NewName; 619 | _ when AllowManual andalso is_binary(Name) -> 620 | case is_prefix_free(Tx, Parent, NameIn) of 621 | true -> 622 | ok; 623 | false -> 624 | ?ERLFDB_ERROR({create_error, node_name_in_use, NameIn}) 625 | end, 626 | NameIn; 627 | _ -> 628 | ?ERLFDB_ERROR({create_error, manual_node_names_prohibited}) 629 | end. 630 | 631 | is_prefix_free(Tx, Parent, NodeName) -> 632 | % We have to make sure that NodeName does not interact with 633 | % anything that currently exists in the tree. This means that 634 | % it must not be a prefix of any existing node id and also 635 | % that no existing node id is a prefix of this NodeName. 636 | % 637 | % A motivating example for why is that deletion of nodes 638 | % in the tree would end up deleting unrelated portions 639 | % of the tree when node ids overlapped. There would also 640 | % be other badness if keys overlapped with the layer 641 | % or ?SUBDIRS spaces. 642 | 643 | try 644 | % An empty name would obviously be kind of bonkers. 645 | if 646 | NodeName /= <<>> -> ok; 647 | true -> throw(false) 648 | end, 649 | 650 | Root = get_root(Parent), 651 | RootId = get_id(Root), 652 | NodePrefix = get_node_prefix(Root), 653 | NPLen = size(NodePrefix), 654 | 655 | % First check that the special case of the root node 656 | case bin_startswith(NodeName, RootId) of 657 | true -> throw(false); 658 | false -> ok 659 | end, 660 | 661 | % Check if any node id is a prefix of NodeName 662 | Start1 = <>, 663 | End1 = ?ERLFDB_PACK(NodePrefix, {NodeName, null}), 664 | Opts1 = [{reverse, true}, {limit, 1}, {streaming_mode, exact}], 665 | Subspace = erlfdb_subspace:create({}, get_node_prefix(Parent)), 666 | erlfdb:fold_range( 667 | Tx, 668 | Start1, 669 | End1, 670 | fun({Key, _} = _E, _) -> 671 | KeyNodeId = element(1, erlfdb_subspace:unpack(Subspace, Key)), 672 | case bin_startswith(NodeName, KeyNodeId) of 673 | true -> throw(false); 674 | false -> ok 675 | end 676 | end, 677 | nil, 678 | Opts1 679 | ), 680 | 681 | % Check if NodeName is a prefix of any existing key 682 | Start2 = ?ERLFDB_EXTEND(NodePrefix, NodeName), 683 | End2 = ?ERLFDB_EXTEND(NodePrefix, erlfdb_key:strinc(NodeName)), 684 | Opts2 = [{limit, 1}, {streaming_mode, exact}], 685 | case erlfdb:wait(erlfdb:get_range(Tx, Start2, End2, Opts2)) of 686 | [_E | _] -> throw(false); 687 | [] -> ok 688 | end, 689 | 690 | true 691 | catch 692 | throw:false -> 693 | false 694 | end. 695 | 696 | bin_startswith(Subject, Prefix) -> 697 | PrefixLen = size(Prefix), 698 | case Subject of 699 | <> -> true; 700 | _ -> false 701 | end. 702 | 703 | check_version(Tx, Node, PermLevel) -> 704 | Root = get_root(Node), 705 | VsnKey = ?ERLFDB_EXTEND(get_id(Root), <<"version">>), 706 | {LV1, LV2, _LV3} = ?LAYER_VERSION, 707 | {Major, Minor, Patch} = 708 | case erlfdb:wait(erlfdb:get(Tx, VsnKey)) of 709 | not_found when PermLevel == write -> 710 | initialize_directory(Tx, VsnKey); 711 | not_found -> 712 | ?LAYER_VERSION; 713 | VsnBin -> 714 | << 715 | V1:32/little-unsigned, 716 | V2:32/little-unsigned, 717 | V3:32/little-unsigned 718 | >> = VsnBin, 719 | {V1, V2, V3} 720 | end, 721 | 722 | Path = get_path(Node), 723 | 724 | if 725 | Major =< LV1 -> ok; 726 | true -> ?ERLFDB_ERROR({version_error, unreadable, Path, {Major, Minor, Patch}}) 727 | end, 728 | 729 | if 730 | not (Minor > LV2 andalso PermLevel /= read) -> ok; 731 | true -> ?ERLFDB_ERROR({version_error, unwritable, Path, {Major, Minor, Patch}}) 732 | end. 733 | 734 | initialize_directory(Tx, VsnKey) -> 735 | {V1, V2, V3} = ?LAYER_VERSION, 736 | Packed = << 737 | V1:32/little-unsigned, 738 | V2:32/little-unsigned, 739 | V3:32/little-unsigned 740 | >>, 741 | erlfdb:set(Tx, VsnKey, Packed), 742 | ?LAYER_VERSION. 743 | 744 | check_same_partition(OldNode, NewParentNode) -> 745 | OldRoot = get_partition(OldNode), 746 | NewRoot = get_root(NewParentNode), 747 | if 748 | NewRoot == OldRoot -> ok; 749 | true -> ?ERLFDB_ERROR({move_error, partition_mismatch, OldRoot, NewRoot}) 750 | end. 751 | 752 | adj_path(Node, PathIn) -> 753 | adj_path(get_root(Node), Node, PathIn). 754 | 755 | adj_path(Root, Node, PathIn) -> 756 | RootPathLen = length(get_path(Root)), 757 | NodePath = get_path(Node), 758 | NodeRelPath = lists:nthtail(RootPathLen, NodePath), 759 | Path = NodeRelPath ++ path_init(PathIn), 760 | {Root, Path}. 761 | 762 | path_init(<<_/binary>> = Bin) -> 763 | check_utf8(0, Bin), 764 | [{utf8, Bin}]; 765 | path_init({utf8, <<_/binary>> = Bin} = Path) -> 766 | check_utf8(0, Bin), 767 | [Path]; 768 | path_init(Path) when is_list(Path) -> 769 | lists:flatmap( 770 | fun(Part) -> 771 | path_init(Part) 772 | end, 773 | Path 774 | ); 775 | path_init(Path) when is_tuple(Path) -> 776 | path_init(tuple_to_list(Path)); 777 | path_init(Else) -> 778 | ?ERLFDB_ERROR({path_error, invalid_path_component, Else}). 779 | 780 | check_utf8(Offset, Binary) -> 781 | case Binary of 782 | <<_:Offset/binary>> -> 783 | true; 784 | <<_:Offset/binary, _/utf8, Rest/binary>> -> 785 | % Recalculating offset as a subtraction here is 786 | % slightly odd but this is to avoid having to 787 | % re-encode the utf8 code point and adding the 788 | % size of that new binary. 789 | check_utf8(size(Binary) - size(Rest), Binary); 790 | <<_:Offset/binary, _/binary>> -> 791 | ?ERLFDB_ERROR({path_error, invalid_utf8, Binary}) 792 | end. 793 | 794 | path_append(Path, Part) -> 795 | Path ++ path_init(Part). 796 | 797 | check_not_subpath(OldPath, NewPath) -> 798 | case lists:prefix(OldPath, NewPath) of 799 | true -> 800 | ?ERLFDB_ERROR({ 801 | move_error, 802 | target_is_subdirectory, 803 | OldPath, 804 | NewPath 805 | }); 806 | false -> 807 | ok 808 | end. 809 | -------------------------------------------------------------------------------- /src/erlfdb_float.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_float). 14 | 15 | -export([ 16 | decode/1, 17 | encode/1 18 | ]). 19 | 20 | decode(<<_Sign:1, 0:8, _Fraction:23>> = F) -> 21 | {float, denormalized, F}; 22 | decode(<<_Sign:1, 255:8, 0:23>> = F) -> 23 | {float, infinite, F}; 24 | decode(<<_Sign:1, 255:8, _Fraction:23>> = F) -> 25 | {float, nan, F}; 26 | decode(<>) -> 27 | {float, F}; 28 | decode(<<_Sign:1, 0:11, _Fraction:52>> = D) -> 29 | {double, denormalized, D}; 30 | decode(<<_Sign:1, 2047:11, 0:52>> = D) -> 31 | {double, infinite, D}; 32 | decode(<<_Sign:1, 2047:11, _Fraction:52>> = D) -> 33 | {double, nan, D}; 34 | decode(<>) -> 35 | D. 36 | 37 | encode({float, F}) -> <>; 38 | encode({float, _Type, F}) -> F; 39 | encode(F) when is_float(F) -> <>; 40 | encode({double, _Type, D}) -> D. 41 | -------------------------------------------------------------------------------- /src/erlfdb_hca.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_hca). 14 | 15 | % This is based on the FoundationDB Python bindings as well 16 | % as this excellent blog post describing the logic: 17 | % https://ananthakumaran.in/2018/08/05/high-contention-allocator.html 18 | 19 | -export([ 20 | create/1, 21 | allocate/2 22 | ]). 23 | 24 | -include("erlfdb.hrl"). 25 | 26 | -record(erlfdb_hca, { 27 | counters, 28 | recent 29 | }). 30 | 31 | create(Prefix) -> 32 | #erlfdb_hca{ 33 | counters = ?ERLFDB_EXTEND(Prefix, 0), 34 | recent = ?ERLFDB_EXTEND(Prefix, 1) 35 | }. 36 | 37 | allocate(HCA, Db) -> 38 | try 39 | erlfdb:transactional(Db, fun(Tx) -> 40 | Start = current_start(HCA, Tx), 41 | CandidateRange = range(HCA, Tx, Start, false), 42 | search_candidate(HCA, Tx, CandidateRange) 43 | end) 44 | catch 45 | throw:hca_retry -> 46 | allocate(HCA, Db) 47 | end. 48 | 49 | current_start(HCA, Tx) -> 50 | #erlfdb_hca{ 51 | counters = Counters 52 | } = HCA, 53 | {CRangeStart, CRangeEnd} = counter_range(Counters), 54 | Options = [ 55 | {snapshot, true}, 56 | {reverse, true}, 57 | {streaming_mode, exact}, 58 | {limit, 1} 59 | ], 60 | case erlfdb:wait(erlfdb:get_range(Tx, CRangeStart, CRangeEnd, Options)) of 61 | [] -> 62 | 0; 63 | [{CounterKey, _}] -> 64 | {Start} = ?ERLFDB_EXTRACT(Counters, CounterKey), 65 | Start 66 | end. 67 | 68 | range(HCA, Tx, Start, WindowAdvanced) -> 69 | #erlfdb_hca{ 70 | counters = Counters 71 | } = HCA, 72 | 73 | if 74 | not WindowAdvanced -> ok; 75 | true -> clear_previous_window(HCA, Tx, Start) 76 | end, 77 | 78 | CounterKey = ?ERLFDB_EXTEND(Counters, Start), 79 | erlfdb:add(Tx, CounterKey, 1), 80 | 81 | Count = 82 | case erlfdb:wait(erlfdb:get_ss(Tx, CounterKey)) of 83 | <> -> C; 84 | not_found -> 0 85 | end, 86 | 87 | WindowSize = window_size(Start), 88 | case Count * 2 < WindowSize of 89 | true -> {Start, WindowSize}; 90 | false -> range(HCA, Tx, Start + WindowSize, true) 91 | end. 92 | 93 | search_candidate(HCA, Tx, {Start, WindowSize}) -> 94 | #erlfdb_hca{ 95 | counters = Counters, 96 | recent = Recent 97 | } = HCA, 98 | 99 | % -1 because random:uniform is 1 =< X $=< WindowSize 100 | Candidate = Start + rand:uniform(WindowSize) - 1, 101 | CandidateValueKey = ?ERLFDB_EXTEND(Recent, Candidate), 102 | 103 | Options = [ 104 | {snapshot, true}, 105 | {reverse, true}, 106 | {streaming_mode, exact}, 107 | {limit, 1} 108 | ], 109 | {CRangeStart, CRangeEnd} = counter_range(Counters), 110 | 111 | CFuture = erlfdb:get_range(Tx, CRangeStart, CRangeEnd, Options), 112 | CVFuture = erlfdb:get(Tx, CandidateValueKey), 113 | 114 | erlfdb:set_option(Tx, next_write_no_write_conflict_range), 115 | erlfdb:set(Tx, CandidateValueKey, <<>>), 116 | 117 | [LatestCounter, CandidateValue] = erlfdb:wait_for_all([CFuture, CVFuture]), 118 | 119 | % Check that we're still in the same counter window 120 | case LatestCounter of 121 | [{CounterKey, _}] -> 122 | {LStart} = ?ERLFDB_EXTRACT(Counters, CounterKey), 123 | if 124 | LStart == Start -> ok; 125 | true -> throw(hca_retry) 126 | end; 127 | _ -> 128 | ok 129 | end, 130 | 131 | case CandidateValue of 132 | not_found -> 133 | erlfdb:add_write_conflict_key(Tx, CandidateValueKey), 134 | erlfdb_tuple:pack({Candidate}); 135 | _ -> 136 | throw(hca_retry) 137 | end. 138 | 139 | clear_previous_window(HCA, Tx, Start) -> 140 | #erlfdb_hca{ 141 | counters = Counters, 142 | recent = Recent 143 | } = HCA, 144 | 145 | {CRangeStart, CRangeEnd} = ?ERLFDB_RANGE(Counters, Start), 146 | {RRangeStart, RRangeEnd} = ?ERLFDB_RANGE(Recent, Start), 147 | 148 | erlfdb:clear_range(Tx, CRangeStart, CRangeEnd), 149 | erlfdb:set_option(Tx, next_write_no_write_conflict_range), 150 | erlfdb:clear_range(Tx, RRangeStart, RRangeEnd). 151 | 152 | counter_range(Counters) -> 153 | S = erlfdb_subspace:create({}, Counters), 154 | erlfdb_subspace:range(S). 155 | 156 | window_size(Start) -> 157 | if 158 | Start < 255 -> 64; 159 | Start < 65535 -> 1024; 160 | true -> 8192 161 | end. 162 | -------------------------------------------------------------------------------- /src/erlfdb_key.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_key). 14 | 15 | -export([ 16 | to_selector/1, 17 | 18 | last_less_than/1, 19 | last_less_or_equal/1, 20 | first_greater_than/1, 21 | first_greater_or_equal/1, 22 | 23 | strinc/1 24 | ]). 25 | 26 | to_selector(<<_/binary>> = Key) -> 27 | {Key, gteq}; 28 | to_selector({<<_/binary>>, _} = Sel) -> 29 | Sel; 30 | to_selector({<<_/binary>>, _, _} = Sel) -> 31 | Sel; 32 | to_selector(Else) -> 33 | erlang:error({invalid_key_selector, Else}). 34 | 35 | last_less_than(Key) when is_binary(Key) -> 36 | {Key, lt}. 37 | 38 | last_less_or_equal(Key) when is_binary(Key) -> 39 | {Key, lteq}. 40 | 41 | first_greater_than(Key) when is_binary(Key) -> 42 | {Key, gt}. 43 | 44 | first_greater_or_equal(Key) when is_binary(Key) -> 45 | {Key, gteq}. 46 | 47 | strinc(Key) when is_binary(Key) -> 48 | Prefix = rstrip_ff(Key), 49 | PrefixLen = size(Prefix), 50 | Head = binary:part(Prefix, {0, PrefixLen - 1}), 51 | Tail = binary:at(Prefix, PrefixLen - 1), 52 | <>. 53 | 54 | rstrip_ff(<<>>) -> 55 | erlang:error("Key must contain at least one byte not equal to 0xFF"); 56 | rstrip_ff(Key) -> 57 | KeyLen = size(Key), 58 | case binary:at(Key, KeyLen - 1) of 59 | 16#FF -> rstrip_ff(binary:part(Key, {0, KeyLen - 1})); 60 | _ -> Key 61 | end. 62 | -------------------------------------------------------------------------------- /src/erlfdb_nif.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_nif). 14 | 15 | -compile(no_native). 16 | -on_load(init/0). 17 | 18 | -export([ 19 | ohai/0, 20 | 21 | get_max_api_version/0, 22 | 23 | future_cancel/1, 24 | future_silence/1, 25 | future_is_ready/1, 26 | future_get_error/1, 27 | future_get/1, 28 | 29 | create_database/1, 30 | database_set_option/2, 31 | database_set_option/3, 32 | database_create_transaction/1, 33 | 34 | transaction_set_option/2, 35 | transaction_set_option/3, 36 | transaction_set_read_version/2, 37 | transaction_get_read_version/1, 38 | transaction_get/3, 39 | transaction_get_estimated_range_size/3, 40 | transaction_get_key/3, 41 | transaction_get_addresses_for_key/2, 42 | transaction_get_range/9, 43 | transaction_set/3, 44 | transaction_clear/2, 45 | transaction_clear_range/3, 46 | transaction_atomic_op/4, 47 | transaction_commit/1, 48 | transaction_get_committed_version/1, 49 | transaction_get_versionstamp/1, 50 | transaction_watch/2, 51 | transaction_on_error/2, 52 | transaction_reset/1, 53 | transaction_cancel/1, 54 | transaction_add_conflict_range/4, 55 | transaction_get_next_tx_id/1, 56 | transaction_is_read_only/1, 57 | transaction_has_watches/1, 58 | transaction_get_writes_allowed/1, 59 | transaction_get_approximate_size/1, 60 | 61 | get_error/1, 62 | error_predicate/2 63 | ]). 64 | 65 | -define(DEFAULT_API_VERSION, 620). 66 | 67 | -type error() :: {erlfdb_error, Code :: integer()}. 68 | -type future() :: {erlfdb_future, reference(), reference()}. 69 | -type database() :: {erlfdb_database, reference()}. 70 | -type transaction() :: {erlfdb_transaction, reference()}. 71 | 72 | -type option_value() :: integer() | binary(). 73 | 74 | -type key_selector() :: 75 | {Key :: binary(), lt | lteq | gt | gteq} 76 | | {Key :: binary(), OrEqual :: boolean(), Offset :: integer()}. 77 | 78 | -type future_result() :: 79 | database() 80 | | integer() 81 | | binary() 82 | | {[{binary(), binary()}], integer(), boolean()} 83 | | not_found 84 | | {error, invalid_future_type}. 85 | 86 | -type network_option() :: 87 | local_address 88 | | cluster_file 89 | | trace_enable 90 | | trace_format 91 | | trace_roll_size 92 | | trace_max_logs_size 93 | | trace_log_group 94 | | knob 95 | | tls_plugin 96 | | tls_cert_bytes 97 | | tls_cert_path 98 | | tls_key_bytes 99 | | tls_key_path 100 | | tls_verify_peers 101 | | client_buggify_enable 102 | | client_buggify_disable 103 | | client_buggify_section_activated_probability 104 | | client_buggify_section_fired_probability 105 | | tls_ca_bytes 106 | | tls_ca_path 107 | | tls_password 108 | | disable_multi_version_client_api 109 | | callbacks_on_external_threads 110 | | external_client_library 111 | | external_client_directory 112 | | disable_local_client 113 | | disable_client_statistics_logging 114 | | enable_slow_task_profiling 115 | % API version 630+ 116 | | enable_run_loop_profiling. 117 | 118 | -type database_option() :: 119 | location_cache_size 120 | | max_watches 121 | | machine_id 122 | | datacenter_id. 123 | 124 | -type transaction_option() :: 125 | causal_write_risky 126 | | causal_read_risky 127 | | causal_read_disable 128 | | next_write_no_write_conflict_range 129 | | read_your_writes_disable 130 | | read_ahead_disable 131 | | durability_datacenter 132 | | durability_risky 133 | | durability_dev_null_is_web_scale 134 | | priority_system_immediate 135 | | priority_batch 136 | | initialize_new_database 137 | | access_system_keys 138 | | read_system_keys 139 | | debug_retry_logging 140 | | transaction_logging_enable 141 | | timeout 142 | | retry_limit 143 | | max_retry_delay 144 | | snapshot_ryw_enable 145 | | snapshot_ryw_disable 146 | | lock_aware 147 | | used_during_commit_protection_disable 148 | | read_lock_aware 149 | | size_limit 150 | | allow_writes 151 | | disallow_writes. 152 | 153 | -type streaming_mode() :: 154 | stream_want_all 155 | | stream_iterator 156 | | stream_exact 157 | | stream_small 158 | | stream_medium 159 | | stream_large 160 | | stream_serial. 161 | 162 | -type atomic_mode() :: 163 | add 164 | | bit_and 165 | | bit_or 166 | | bit_xor 167 | | append_if_fits 168 | | max 169 | | min 170 | | byte_min 171 | | byte_max 172 | | set_versionstamped_key 173 | | set_versionstamped_value. 174 | 175 | -type atomic_operand() :: integer() | binary(). 176 | 177 | -type conflict_type() :: read | write. 178 | 179 | -type error_predicate() :: 180 | retryable 181 | | maybe_committed 182 | | retryable_not_committed. 183 | 184 | ohai() -> 185 | foo. 186 | 187 | -spec get_max_api_version() -> {ok, integer()}. 188 | get_max_api_version() -> 189 | erlfdb_get_max_api_version(). 190 | 191 | -spec future_cancel(future()) -> ok. 192 | future_cancel({erlfdb_future, _Ref, Ft}) -> 193 | erlfdb_future_cancel(Ft). 194 | 195 | -spec future_silence(future()) -> ok. 196 | future_silence({erlfdb_future, _Ref, Ft}) -> 197 | erlfdb_future_silence(Ft). 198 | 199 | -spec future_is_ready(future()) -> boolean(). 200 | future_is_ready({erlfdb_future, _Ref, Ft}) -> 201 | erlfdb_future_is_ready(Ft). 202 | 203 | -spec future_get_error(future()) -> error(). 204 | future_get_error({erlfdb_future, _Ref, Ft}) -> 205 | erlfdb_future_get_error(Ft). 206 | 207 | -spec future_get(future()) -> future_result(). 208 | future_get({erlfdb_future, _Ref, Ft}) -> 209 | erlfdb_future_get(Ft). 210 | 211 | -spec create_database(ClusterFilePath :: binary()) -> database(). 212 | create_database(<<>>) -> 213 | create_database(<<0>>); 214 | create_database(ClusterFilePath) -> 215 | Size = size(ClusterFilePath) - 1, 216 | % Make sure we pass a NULL-terminated string 217 | % to FoundationDB 218 | NifPath = 219 | case ClusterFilePath of 220 | <<_:Size/binary, 0>> -> 221 | ClusterFilePath; 222 | _ -> 223 | <> 224 | end, 225 | erlfdb_create_database(NifPath). 226 | 227 | -spec database_set_option(database(), Option :: database_option()) -> ok. 228 | database_set_option(Database, Option) -> 229 | database_set_option(Database, Option, <<>>). 230 | 231 | -spec database_set_option( 232 | database(), 233 | Option :: database_option(), 234 | Value :: option_value() 235 | ) -> ok. 236 | database_set_option({erlfdb_database, Db}, Opt, Val) -> 237 | BinVal = option_val_to_binary(Val), 238 | erlfdb_database_set_option(Db, Opt, BinVal). 239 | 240 | -spec database_create_transaction(database()) -> transaction(). 241 | database_create_transaction({erlfdb_database, Db}) -> 242 | erlfdb_database_create_transaction(Db). 243 | 244 | -spec transaction_set_option(transaction(), Option :: transaction_option()) -> ok. 245 | transaction_set_option(Transaction, Option) -> 246 | transaction_set_option(Transaction, Option, <<>>). 247 | 248 | -spec transaction_set_option( 249 | transaction(), 250 | Option :: transaction_option(), 251 | Value :: option_value() 252 | ) -> ok. 253 | transaction_set_option({erlfdb_transaction, Tx}, Opt, Val) -> 254 | BinVal = option_val_to_binary(Val), 255 | erlfdb_transaction_set_option(Tx, Opt, BinVal). 256 | 257 | -spec transaction_set_read_version(transaction(), Version :: integer()) -> ok. 258 | transaction_set_read_version({erlfdb_transaction, Tx}, Version) -> 259 | erlfdb_transaction_set_read_version(Tx, Version). 260 | 261 | -spec transaction_get_read_version(transaction()) -> future(). 262 | transaction_get_read_version({erlfdb_transaction, Tx}) -> 263 | erlfdb_transaction_get_read_version(Tx). 264 | 265 | -spec transaction_get(transaction(), Key :: binary(), Snapshot :: boolean()) -> 266 | future(). 267 | transaction_get({erlfdb_transaction, Tx}, Key, Snapshot) -> 268 | erlfdb_transaction_get(Tx, Key, Snapshot). 269 | 270 | -spec transaction_get_estimated_range_size( 271 | transaction(), 272 | StartKey :: binary(), 273 | EndKey :: binary() 274 | ) -> future(). 275 | transaction_get_estimated_range_size({erlfdb_transaction, Tx}, StartKey, EndKey) -> 276 | erlfdb_transaction_get_estimated_range_size(Tx, StartKey, EndKey). 277 | 278 | -spec transaction_get_key( 279 | transaction(), 280 | KeySelector :: key_selector(), 281 | Snapshot :: boolean() 282 | ) -> future(). 283 | transaction_get_key({erlfdb_transaction, Tx}, KeySelector, Snapshot) -> 284 | erlfdb_transaction_get_key(Tx, KeySelector, Snapshot). 285 | 286 | -spec transaction_get_addresses_for_key(transaction(), Key :: binary()) -> 287 | future(). 288 | transaction_get_addresses_for_key({erlfdb_transaction, Tx}, Key) -> 289 | erlfdb_transaction_get_addresses_for_key(Tx, Key). 290 | 291 | -spec transaction_get_range( 292 | transaction(), 293 | StartKeySelector :: key_selector(), 294 | EndKeySelector :: key_selector(), 295 | Limit :: non_neg_integer(), 296 | TargetBytes :: non_neg_integer(), 297 | StreamingMode :: streaming_mode(), 298 | Iteration :: non_neg_integer(), 299 | Snapshot :: boolean(), 300 | Reverse :: integer() 301 | ) -> future(). 302 | transaction_get_range( 303 | {erlfdb_transaction, Tx}, 304 | StartKeySelector, 305 | EndKeySelector, 306 | Limit, 307 | TargetBytes, 308 | StreamingMode, 309 | Iteration, 310 | Snapshot, 311 | Reverse 312 | ) -> 313 | erlfdb_transaction_get_range( 314 | Tx, 315 | StartKeySelector, 316 | EndKeySelector, 317 | Limit, 318 | TargetBytes, 319 | StreamingMode, 320 | Iteration, 321 | Snapshot, 322 | Reverse 323 | ). 324 | 325 | -spec transaction_set(transaction(), Key :: binary(), Val :: binary()) -> ok. 326 | transaction_set({erlfdb_transaction, Tx}, Key, Val) -> 327 | erlfdb_transaction_set(Tx, Key, Val). 328 | 329 | -spec transaction_clear(transaction(), Key :: binary()) -> ok. 330 | transaction_clear({erlfdb_transaction, Tx}, Key) -> 331 | erlfdb_transaction_clear(Tx, Key). 332 | 333 | -spec transaction_clear_range( 334 | transaction(), 335 | StartKey :: binary(), 336 | EndKey :: binary() 337 | ) -> ok. 338 | transaction_clear_range({erlfdb_transaction, Tx}, StartKey, EndKey) -> 339 | erlfdb_transaction_clear_range(Tx, StartKey, EndKey). 340 | 341 | -spec transaction_atomic_op( 342 | transaction(), 343 | Key :: binary(), 344 | Operand :: atomic_operand(), 345 | Mode :: atomic_mode() 346 | ) -> ok. 347 | transaction_atomic_op({erlfdb_transaction, Tx}, Key, Operand, OpName) -> 348 | BinOperand = 349 | case Operand of 350 | Bin when is_binary(Bin) -> 351 | Bin; 352 | Int when is_integer(Int) -> 353 | <> 354 | end, 355 | erlfdb_transaction_atomic_op(Tx, Key, BinOperand, OpName). 356 | 357 | -spec transaction_commit(transaction()) -> future(). 358 | transaction_commit({erlfdb_transaction, Tx}) -> 359 | erlfdb_transaction_commit(Tx). 360 | 361 | -spec transaction_get_committed_version(transaction()) -> integer(). 362 | transaction_get_committed_version({erlfdb_transaction, Tx}) -> 363 | erlfdb_transaction_get_committed_version(Tx). 364 | 365 | -spec transaction_get_versionstamp(transaction()) -> future(). 366 | transaction_get_versionstamp({erlfdb_transaction, Tx}) -> 367 | erlfdb_transaction_get_versionstamp(Tx). 368 | 369 | -spec transaction_watch(transaction(), Key :: binary()) -> future(). 370 | transaction_watch({erlfdb_transaction, Tx}, Key) -> 371 | erlfdb_transaction_watch(Tx, Key). 372 | 373 | -spec transaction_on_error(transaction(), Error :: integer()) -> future(). 374 | transaction_on_error({erlfdb_transaction, Tx}, Error) -> 375 | erlfdb_transaction_on_error(Tx, Error). 376 | 377 | -spec transaction_reset(transaction()) -> ok. 378 | transaction_reset({erlfdb_transaction, Tx}) -> 379 | erlfdb_transaction_reset(Tx). 380 | 381 | -spec transaction_cancel(transaction()) -> ok. 382 | transaction_cancel({erlfdb_transaction, Tx}) -> 383 | erlfdb_transaction_cancel(Tx). 384 | 385 | -spec transaction_add_conflict_range( 386 | transaction(), 387 | StartKey :: binary(), 388 | EndKey :: binary(), 389 | ConflictType :: conflict_type() 390 | ) -> ok. 391 | transaction_add_conflict_range( 392 | {erlfdb_transaction, Tx}, 393 | StartKey, 394 | EndKey, 395 | ConflictType 396 | ) -> 397 | erlfdb_transaction_add_conflict_range(Tx, StartKey, EndKey, ConflictType). 398 | 399 | -spec transaction_get_approximate_size(transaction()) -> non_neg_integer(). 400 | transaction_get_approximate_size({erlfdb_transaction, Tx}) -> 401 | erlfdb_transaction_get_approximate_size(Tx). 402 | 403 | -spec transaction_get_next_tx_id(transaction()) -> non_neg_integer(). 404 | transaction_get_next_tx_id({erlfdb_transaction, Tx}) -> 405 | erlfdb_transaction_get_next_tx_id(Tx). 406 | 407 | -spec transaction_is_read_only(transaction()) -> true | false. 408 | transaction_is_read_only({erlfdb_transaction, Tx}) -> 409 | erlfdb_transaction_is_read_only(Tx). 410 | 411 | -spec transaction_has_watches(transaction()) -> true | false. 412 | transaction_has_watches({erlfdb_transaction, Tx}) -> 413 | erlfdb_transaction_has_watches(Tx). 414 | 415 | -spec transaction_get_writes_allowed(transaction()) -> true | false. 416 | transaction_get_writes_allowed({erlfdb_transaction, Tx}) -> 417 | erlfdb_transaction_get_writes_allowed(Tx). 418 | 419 | -spec get_error(integer()) -> binary(). 420 | get_error(Error) -> 421 | erlfdb_get_error(Error). 422 | 423 | -spec error_predicate(Predicate :: error_predicate(), Error :: integer()) -> 424 | boolean(). 425 | error_predicate(Predicate, Error) -> 426 | erlfdb_error_predicate(Predicate, Error). 427 | 428 | -spec option_val_to_binary(binary() | integer()) -> binary(). 429 | option_val_to_binary(Val) when is_binary(Val) -> 430 | Val; 431 | option_val_to_binary(Val) when is_integer(Val) -> 432 | <>. 433 | 434 | init() -> 435 | PrivDir = 436 | case code:priv_dir(?MODULE) of 437 | {error, _} -> 438 | EbinDir = filename:dirname(code:which(?MODULE)), 439 | AppPath = filename:dirname(EbinDir), 440 | filename:join(AppPath, "priv"); 441 | Path -> 442 | Path 443 | end, 444 | Status = erlang:load_nif(filename:join(PrivDir, "erlfdb_nif"), 0), 445 | if 446 | Status /= ok -> 447 | Status; 448 | true -> 449 | true = erlfdb_can_initialize(), 450 | 451 | Vsn = 452 | case application:get_env(erlfdb, api_version) of 453 | {ok, V} -> V; 454 | undefined -> ?DEFAULT_API_VERSION 455 | end, 456 | ok = select_api_version(Vsn), 457 | 458 | Opts = 459 | case application:get_env(erlfdb, network_options) of 460 | {ok, O} when is_list(O) -> O; 461 | undefined -> [] 462 | end, 463 | 464 | lists:foreach( 465 | fun(Option) -> 466 | case Option of 467 | Name when is_atom(Name) -> 468 | ok = network_set_option(Name, <<>>); 469 | {Name, Value} when is_atom(Name) -> 470 | ok = network_set_option(Name, Value) 471 | end 472 | end, 473 | Opts 474 | ), 475 | 476 | ok = erlfdb_setup_network() 477 | end. 478 | 479 | -define(NOT_LOADED, erlang:nif_error({erlfdb_nif_not_loaded, ?FILE, ?LINE})). 480 | 481 | -spec select_api_version(Version :: pos_integer()) -> ok. 482 | select_api_version(Version) when is_integer(Version), Version > 0 -> 483 | erlfdb_select_api_version(Version). 484 | 485 | -spec network_set_option(Option :: network_option(), Value :: option_value()) -> 486 | ok | error(). 487 | network_set_option(Name, Value) -> 488 | BinValue = 489 | case Value of 490 | B when is_binary(B) -> B; 491 | I when is_integer(I) -> <> 492 | end, 493 | erlfdb_network_set_option(Name, BinValue). 494 | 495 | % Sentinel Check 496 | erlfdb_can_initialize() -> ?NOT_LOADED. 497 | 498 | % Versioning 499 | erlfdb_get_max_api_version() -> ?NOT_LOADED. 500 | erlfdb_select_api_version(_Version) -> ?NOT_LOADED. 501 | 502 | % Networking Setup 503 | erlfdb_network_set_option(_NetworkOption, _Value) -> ?NOT_LOADED. 504 | erlfdb_setup_network() -> ?NOT_LOADED. 505 | 506 | % Futures 507 | erlfdb_future_cancel(_Future) -> ?NOT_LOADED. 508 | erlfdb_future_silence(_Future) -> ?NOT_LOADED. 509 | erlfdb_future_is_ready(_Future) -> ?NOT_LOADED. 510 | erlfdb_future_get_error(_Future) -> ?NOT_LOADED. 511 | erlfdb_future_get(_Future) -> ?NOT_LOADED. 512 | 513 | % Databases 514 | erlfdb_create_database(_ClusterFilePath) -> ?NOT_LOADED. 515 | erlfdb_database_set_option(_Database, _DatabaseOption, _Value) -> ?NOT_LOADED. 516 | erlfdb_database_create_transaction(_Database) -> ?NOT_LOADED. 517 | 518 | % Transactions 519 | erlfdb_transaction_set_option(_Transaction, _TransactionOption, _Value) -> ?NOT_LOADED. 520 | erlfdb_transaction_set_read_version(_Transaction, _Version) -> ?NOT_LOADED. 521 | erlfdb_transaction_get_read_version(_Transaction) -> ?NOT_LOADED. 522 | erlfdb_transaction_get(_Transaction, _Key, _Snapshot) -> ?NOT_LOADED. 523 | erlfdb_transaction_get_estimated_range_size(_Transaction, _SKey, _EKey) -> ?NOT_LOADED. 524 | erlfdb_transaction_get_key(_Transaction, _KeySelector, _Snapshot) -> ?NOT_LOADED. 525 | erlfdb_transaction_get_addresses_for_key(_Transaction, _Key) -> ?NOT_LOADED. 526 | erlfdb_transaction_get_range( 527 | _Transaction, 528 | _StartKeySelector, 529 | _EndKeySelector, 530 | _Limit, 531 | _TargetBytes, 532 | _StreamingMode, 533 | _Iteration, 534 | _Snapshot, 535 | _Reverse 536 | ) -> 537 | ?NOT_LOADED. 538 | erlfdb_transaction_set(_Transaction, _Key, _Value) -> ?NOT_LOADED. 539 | erlfdb_transaction_clear(_Transaction, _Key) -> ?NOT_LOADED. 540 | erlfdb_transaction_clear_range(_Transaction, _StartKey, _EndKey) -> ?NOT_LOADED. 541 | erlfdb_transaction_atomic_op(_Transaction, _Mutation, _Key, _Value) -> ?NOT_LOADED. 542 | erlfdb_transaction_commit(_Transaction) -> ?NOT_LOADED. 543 | erlfdb_transaction_get_committed_version(_Transaction) -> ?NOT_LOADED. 544 | erlfdb_transaction_get_versionstamp(_Transaction) -> ?NOT_LOADED. 545 | erlfdb_transaction_watch(_Transaction, _Key) -> ?NOT_LOADED. 546 | erlfdb_transaction_on_error(_Transaction, _Error) -> ?NOT_LOADED. 547 | erlfdb_transaction_reset(_Transaction) -> ?NOT_LOADED. 548 | erlfdb_transaction_cancel(_Transaction) -> ?NOT_LOADED. 549 | erlfdb_transaction_add_conflict_range(_Transaction, _StartKey, _EndKey, _Type) -> ?NOT_LOADED. 550 | erlfdb_transaction_get_next_tx_id(_Transaction) -> ?NOT_LOADED. 551 | erlfdb_transaction_is_read_only(_Transaction) -> ?NOT_LOADED. 552 | erlfdb_transaction_has_watches(_Transaction) -> ?NOT_LOADED. 553 | erlfdb_transaction_get_writes_allowed(_Transaction) -> ?NOT_LOADED. 554 | erlfdb_transaction_get_approximate_size(_Transaction) -> ?NOT_LOADED. 555 | 556 | % Misc 557 | erlfdb_get_error(_Error) -> ?NOT_LOADED. 558 | erlfdb_error_predicate(_Predicate, _Error) -> ?NOT_LOADED. 559 | -------------------------------------------------------------------------------- /src/erlfdb_subspace.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_subspace). 14 | 15 | -record(erlfdb_subspace, { 16 | prefix 17 | }). 18 | 19 | -export([ 20 | create/1, 21 | create/2, 22 | 23 | add/2, 24 | 25 | key/1, 26 | 27 | pack/1, 28 | pack/2, 29 | pack_vs/1, 30 | pack_vs/2, 31 | unpack/2, 32 | 33 | range/1, 34 | range/2, 35 | 36 | contains/2, 37 | 38 | subspace/2 39 | ]). 40 | 41 | -define(PREFIX(S), S#erlfdb_subspace.prefix). 42 | 43 | create(Tuple) -> 44 | create(Tuple, <<>>). 45 | 46 | create(#erlfdb_subspace{} = Subspace, Tuple) when is_tuple(Tuple) -> 47 | create(Tuple, ?PREFIX(Subspace)); 48 | create(Tuple, Prefix) when is_tuple(Tuple), is_binary(Prefix) -> 49 | #erlfdb_subspace{ 50 | prefix = erlfdb_tuple:pack(Tuple, Prefix) 51 | }. 52 | 53 | add(#erlfdb_subspace{} = Subspace, Item) -> 54 | create({Item}, ?PREFIX(Subspace)). 55 | 56 | key(#erlfdb_subspace{} = Subspace) -> 57 | Subspace#erlfdb_subspace.prefix. 58 | 59 | pack(Subspace) -> 60 | pack(Subspace, {}). 61 | 62 | pack(#erlfdb_subspace{} = Subspace, Tuple) when is_tuple(Tuple) -> 63 | erlfdb_tuple:pack(Tuple, ?PREFIX(Subspace)). 64 | 65 | pack_vs(Subspace) -> 66 | pack_vs(Subspace, {}). 67 | 68 | pack_vs(#erlfdb_subspace{} = Subspace, Tuple) when is_tuple(Tuple) -> 69 | erlfdb_tuple:pack_vs(Tuple, ?PREFIX(Subspace)). 70 | 71 | unpack(#erlfdb_subspace{} = Subspace, Key) -> 72 | case contains(Subspace, Key) of 73 | true -> 74 | Prefix = ?PREFIX(Subspace), 75 | SubKey = binary:part(Key, size(Prefix), size(Key) - size(Prefix)), 76 | erlfdb_tuple:unpack(SubKey); 77 | false -> 78 | erlang:error({key_not_in_subspace, Subspace, Key}) 79 | end. 80 | 81 | range(Subspace) -> 82 | range(Subspace, {}). 83 | 84 | range(#erlfdb_subspace{} = Subspace, Tuple) when is_tuple(Tuple) -> 85 | Prefix = ?PREFIX(Subspace), 86 | PrefixLen = size(Prefix), 87 | {Start, End} = erlfdb_tuple:range(Tuple), 88 | { 89 | <>, 90 | <> 91 | }. 92 | 93 | contains(#erlfdb_subspace{} = Subspace, Key) -> 94 | Prefix = ?PREFIX(Subspace), 95 | PrefLen = size(Prefix), 96 | case Key of 97 | <> -> 98 | true; 99 | _ -> 100 | false 101 | end. 102 | 103 | subspace(#erlfdb_subspace{} = Subspace, Tuple) -> 104 | create(Subspace, Tuple). 105 | -------------------------------------------------------------------------------- /src/erlfdb_tuple.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_tuple). 14 | 15 | -export([ 16 | pack/1, 17 | pack/2, 18 | pack_vs/1, 19 | pack_vs/2, 20 | unpack/1, 21 | unpack/2, 22 | range/1, 23 | range/2, 24 | compare/2 25 | ]). 26 | 27 | % Codes 16#03, 16#04, 16#23, and 16#24 are reserved 28 | % for historical reasons. 29 | 30 | % NULL is a single byte 31 | -define(NULL, 16#00). 32 | 33 | % Bytes/Strings - Variable length, ends at the 34 | % first \x00 that's not followed by \xFF 35 | -define(BYTES, 16#01). 36 | -define(STRING, 16#02). 37 | 38 | % Tuples all the way down 39 | -define(NESTED, 16#05). 40 | 41 | % Negative numbers: 9-255 byte numbers, then 8-1 42 | -define(NEG_INT_9P, 16#0B). 43 | -define(NEG_INT_8, 16#0C). 44 | -define(NEG_INT_7, 16#0D). 45 | -define(NEG_INT_6, 16#0E). 46 | -define(NEG_INT_5, 16#0F). 47 | -define(NEG_INT_4, 16#10). 48 | -define(NEG_INT_3, 16#11). 49 | -define(NEG_INT_2, 16#12). 50 | -define(NEG_INT_1, 16#13). 51 | 52 | % Zero is a single byte 53 | -define(ZERO, 16#14). 54 | 55 | % Positive numbers: 1-8 bytes, then all 9-255 byte numbers 56 | -define(POS_INT_1, 16#15). 57 | -define(POS_INT_2, 16#16). 58 | -define(POS_INT_3, 16#17). 59 | -define(POS_INT_4, 16#18). 60 | -define(POS_INT_5, 16#19). 61 | -define(POS_INT_6, 16#1A). 62 | -define(POS_INT_7, 16#1B). 63 | -define(POS_INT_8, 16#1C). 64 | -define(POS_INT_9P, 16#1D). 65 | 66 | % Floats and Doubles 67 | -define(FLOAT, 16#20). 68 | -define(DOUBLE, 16#21). 69 | 70 | % Booleans are a single byte each 71 | -define(FALSE, 16#26). 72 | -define(TRUE, 16#27). 73 | 74 | % UUIDs: single code byte followed by 16 bytes 75 | -define(UUID, 16#30). 76 | 77 | % 64 bit identifer 78 | -define(ID64, 16#31). 79 | 80 | % 80 bit VersionStamp: 8 byte integer, 2 byte batch 81 | -define(VS80, 16#32). 82 | 83 | % 96 bit VersionStamp: 8 byte integer, 2 byte 84 | % batch, 2 byte transaction order 85 | -define(VS96, 16#33). 86 | 87 | % Not an actual type code but reserved 88 | % for use when escaping \x00 bytes 89 | -define(ESCAPE, 16#FF). 90 | 91 | -define(UNSET_VERSIONSTAMP80, <<16#FFFFFFFFFFFFFFFFFFFF:80>>). 92 | -define(UNSET_VERSIONSTAMP96, <<16#FFFFFFFFFFFFFFFFFFFF:80, _:16>>). 93 | 94 | pack(Tuple) when is_tuple(Tuple) -> 95 | pack(Tuple, <<>>). 96 | 97 | pack(Tuple, Prefix) -> 98 | Elems = tuple_to_list(Tuple), 99 | Encoded = [encode(E, 0) || E <- Elems], 100 | iolist_to_binary([Prefix | Encoded]). 101 | 102 | pack_vs(Tuple) -> 103 | pack_vs(Tuple, <<>>). 104 | 105 | pack_vs(Tuple, Prefix) -> 106 | Elems = tuple_to_list(Tuple), 107 | Encoded = [encode(E, 0) || E <- Elems], 108 | case find_incomplete_versionstamp(Encoded) of 109 | {found, Pos} -> 110 | VsnPos = Pos + size(Prefix), 111 | Parts = [Prefix, Encoded, <>], 112 | iolist_to_binary(Parts); 113 | {not_found, _} -> 114 | E = {erlfdb_tuple_error, missing_incomplete_versionstamp}, 115 | erlang:error(E) 116 | end. 117 | 118 | unpack(Binary) -> 119 | unpack(Binary, <<>>). 120 | 121 | unpack(Binary, Prefix) -> 122 | PrefixLen = size(Prefix), 123 | case Binary of 124 | <> -> 125 | case decode(Rest, 0) of 126 | {Elems, <<>>} -> 127 | list_to_tuple(Elems); 128 | {_, Tail} -> 129 | erlang:error({invalid_trailing_data, Tail}) 130 | end; 131 | _ -> 132 | E = {erlfdb_tuple_error, invalid_unpack_prefix}, 133 | erlang:error(E) 134 | end. 135 | 136 | % Returns a {StartKey, EndKey} pair of binaries 137 | % that includes all possible sub-tuples 138 | range(Tuple) -> 139 | range(Tuple, <<>>). 140 | 141 | range(Tuple, Prefix) -> 142 | Base = pack(Tuple, Prefix), 143 | {<>, <>}. 144 | 145 | compare(A, B) when is_tuple(A), is_tuple(B) -> 146 | AElems = tuple_to_list(A), 147 | BElems = tuple_to_list(B), 148 | compare_impl(AElems, BElems). 149 | 150 | compare_impl([], []) -> 151 | 0; 152 | compare_impl([], [_ | _]) -> 153 | -1; 154 | compare_impl([_ | _], []) -> 155 | 1; 156 | compare_impl([A | RestA], [B | RestB]) -> 157 | case compare_elems(A, B) of 158 | -1 -> -1; 159 | 0 -> compare_impl(RestA, RestB); 160 | 1 -> 1 161 | end. 162 | 163 | %% erlfmt-ignore 164 | encode(null, 0) -> 165 | <>; 166 | 167 | encode(null, Depth) when Depth > 0 -> 168 | [<>]; 169 | 170 | encode(false, _) -> 171 | <>; 172 | 173 | encode(true, _) -> 174 | <>; 175 | 176 | encode({utf8, Bin}, _) when is_binary(Bin) -> 177 | [<>, enc_null_terminated(Bin)]; 178 | 179 | encode({float, F} = Float, _) when is_float(F) -> 180 | [<>, enc_float(Float)]; 181 | 182 | encode({float, _, _F} = Float, _) -> 183 | [<>, enc_float(Float)]; 184 | 185 | encode({double, _, _D} = Double, _) -> 186 | [<>, enc_float(Double)]; 187 | 188 | encode({uuid, Bin}, _) when is_binary(Bin), size(Bin) == 16 -> 189 | [<>, Bin]; 190 | 191 | encode({id64, Int}, _) -> 192 | [<>, <>]; 193 | 194 | encode({versionstamp, Id, Batch}, _) -> 195 | [<>, <>]; 196 | 197 | encode({versionstamp, Id, Batch, Tx}, _) -> 198 | [<>, <>]; 199 | 200 | encode(Bin, _Depth) when is_binary(Bin) -> 201 | [<>, enc_null_terminated(Bin)]; 202 | 203 | encode(Int, _Depth) when is_integer(Int), Int < 0 -> 204 | Bin1 = binary:encode_unsigned(-Int), 205 | % Take the one's complement so that 206 | % they sort properly 207 | Bin2 = << <<(B bxor 16#FF)>> || <> <= Bin1 >>, 208 | case size(Bin2) of 209 | 1 -> [<>, Bin2]; 210 | 2 -> [<>, Bin2]; 211 | 3 -> [<>, Bin2]; 212 | 4 -> [<>, Bin2]; 213 | 5 -> [<>, Bin2]; 214 | 6 -> [<>, Bin2]; 215 | 7 -> [<>, Bin2]; 216 | 8 -> [<>, Bin2]; 217 | N when N =< 255 -> [<>, Bin2] 218 | end; 219 | 220 | encode(0, _) -> 221 | [<>]; 222 | 223 | encode(Int, _Depth) when is_integer(Int), Int > 0 -> 224 | Bin = binary:encode_unsigned(Int), 225 | case size(Bin) of 226 | 1 -> [<>, Bin]; 227 | 2 -> [<>, Bin]; 228 | 3 -> [<>, Bin]; 229 | 4 -> [<>, Bin]; 230 | 5 -> [<>, Bin]; 231 | 6 -> [<>, Bin]; 232 | 7 -> [<>, Bin]; 233 | 8 -> [<>, Bin]; 234 | N when N =< 255 -> [<>, Bin] 235 | end; 236 | 237 | encode(Double, _) when is_float(Double) -> 238 | [<>, enc_float(Double)]; 239 | 240 | encode(Tuple, Depth) when is_tuple(Tuple) -> 241 | Elems = tuple_to_list(Tuple), 242 | Encoded = [encode(E, Depth + 1) || E <- Elems], 243 | [<>, Encoded, <>]; 244 | 245 | encode(BadTerm, _) -> 246 | erlang:error({invalid_tuple_term, BadTerm}). 247 | 248 | enc_null_terminated(Bin) -> 249 | enc_null_terminated(Bin, 0). 250 | 251 | enc_null_terminated(Bin, Offset) -> 252 | case Bin of 253 | <> -> 254 | [Head, <>]; 255 | <> -> 256 | [Head, <> | enc_null_terminated(Tail, 0)]; 257 | <<_Head:Offset/binary, _, _Tail/binary>> = Bin -> 258 | enc_null_terminated(Bin, Offset + 1) 259 | end. 260 | 261 | enc_float(Float) -> 262 | Bin = erlfdb_float:encode(Float), 263 | case Bin of 264 | <<0:1, B:7, Rest/binary>> -> 265 | <<1:1, B:7, Rest/binary>>; 266 | <<1:1, _:7, _/binary>> -> 267 | <<<<(B bxor 16#FF)>> || <> <= Bin>> 268 | end. 269 | 270 | %% erlfmt-ignore 271 | decode(<<>>, 0) -> 272 | {[], <<>>}; 273 | 274 | decode(<>, 0) -> 275 | {Values, Tail} = decode(Rest, 0), 276 | {[null | Values], Tail}; 277 | 278 | decode(<>, Depth) when Depth > 0 -> 279 | {Values, Tail} = decode(Rest, Depth), 280 | {[null | Values], Tail}; 281 | 282 | decode(<>, Depth) when Depth > 0 -> 283 | {[], Rest}; 284 | 285 | decode(<>, Depth) -> 286 | {Bin, NewRest} = dec_null_terminated(Rest), 287 | {Values, Tail} = decode(NewRest, Depth), 288 | {[Bin | Values], Tail}; 289 | 290 | decode(<>, Depth) -> 291 | {Bin, NewRest} = dec_null_terminated(Rest), 292 | {Values, Tail} = decode(NewRest, Depth), 293 | {[{utf8, Bin} | Values], Tail}; 294 | 295 | decode(<>, Depth) -> 296 | {NestedValues, Tail1} = decode(Rest, Depth + 1), 297 | {RestValues, Tail2} = decode(Tail1, Depth), 298 | NestedTuple = list_to_tuple(NestedValues), 299 | {[NestedTuple | RestValues], Tail2}; 300 | 301 | decode(<>, Depth) -> 302 | Size = InvertedSize bxor 16#FF, 303 | dec_neg_int(Rest, Size, Depth); 304 | 305 | decode(<>, Depth) -> dec_neg_int(Rest, 8, Depth); 306 | decode(<>, Depth) -> dec_neg_int(Rest, 7, Depth); 307 | decode(<>, Depth) -> dec_neg_int(Rest, 6, Depth); 308 | decode(<>, Depth) -> dec_neg_int(Rest, 5, Depth); 309 | decode(<>, Depth) -> dec_neg_int(Rest, 4, Depth); 310 | decode(<>, Depth) -> dec_neg_int(Rest, 3, Depth); 311 | decode(<>, Depth) -> dec_neg_int(Rest, 2, Depth); 312 | decode(<>, Depth) -> dec_neg_int(Rest, 1, Depth); 313 | 314 | decode(<>, Depth) -> 315 | {Values, Tail} = decode(Rest, Depth), 316 | {[0 | Values], Tail}; 317 | 318 | decode(<>, Depth) -> dec_pos_int(Rest, 1, Depth); 319 | decode(<>, Depth) -> dec_pos_int(Rest, 2, Depth); 320 | decode(<>, Depth) -> dec_pos_int(Rest, 3, Depth); 321 | decode(<>, Depth) -> dec_pos_int(Rest, 4, Depth); 322 | decode(<>, Depth) -> dec_pos_int(Rest, 5, Depth); 323 | decode(<>, Depth) -> dec_pos_int(Rest, 6, Depth); 324 | decode(<>, Depth) -> dec_pos_int(Rest, 7, Depth); 325 | decode(<>, Depth) -> dec_pos_int(Rest, 8, Depth); 326 | 327 | decode(<>, Depth) -> 328 | dec_pos_int(Rest, Size, Depth); 329 | 330 | decode(<>, Depth) -> 331 | {Values, Tail} = decode(Rest, Depth), 332 | {[dec_float(Raw) | Values], Tail}; 333 | 334 | decode(<>, Depth) -> 335 | {Values, Tail} = decode(Rest, Depth), 336 | {[dec_float(Raw) | Values], Tail}; 337 | 338 | decode(<>, Depth) -> 339 | {Values, Tail} = decode(Rest, Depth), 340 | {[false | Values], Tail}; 341 | 342 | decode(<>, Depth) -> 343 | {Values, Tail} = decode(Rest, Depth), 344 | {[true | Values], Tail}; 345 | 346 | decode(<>, Depth) -> 347 | {Values, Tail} = decode(Rest, Depth), 348 | {[{uuid, UUID} | Values], Tail}; 349 | 350 | decode(<>, Depth) -> 351 | {Values, Tail} = decode(Rest, Depth), 352 | {[{id64, Id} | Values], Tail}; 353 | 354 | decode(<>, Depth) -> 355 | {Values, Tail} = decode(Rest, Depth), 356 | {[{versionstamp, Id, Batch} | Values], Tail}; 357 | 358 | decode(<>, Depth) -> 359 | {Values, Tail} = decode(Rest, Depth), 360 | {[{versionstamp, Id, Batch, Tx} | Values], Tail}. 361 | 362 | dec_null_terminated(Bin) -> 363 | {Parts, Tail} = dec_null_terminated(Bin, 0), 364 | {iolist_to_binary(Parts), Tail}. 365 | 366 | dec_null_terminated(Bin, Offset) -> 367 | case Bin of 368 | <> -> 369 | {Parts, RestTail} = dec_null_terminated(Tail, 0), 370 | {[Head, <> | Parts], RestTail}; 371 | <> -> 372 | {[Head], Tail}; 373 | <<_:Offset/binary, _, _/binary>> -> 374 | dec_null_terminated(Bin, Offset + 1); 375 | <<_Head:Offset/binary>> -> 376 | erlang:error({invalid_null_termination, Bin}) 377 | end. 378 | 379 | dec_neg_int(Bin, Size, Depth) -> 380 | case Bin of 381 | <> -> 382 | Val = Raw - (1 bsl (Size * 8)) + 1, 383 | {Values, Tail} = decode(Rest, Depth), 384 | {[Val | Values], Tail}; 385 | _ -> 386 | erlang:error({invalid_negative_int, Size, Bin}) 387 | end. 388 | 389 | dec_pos_int(Bin, Size, Depth) -> 390 | case Bin of 391 | <> -> 392 | {Values, Tail} = decode(Rest, Depth), 393 | {[Val | Values], Tail}; 394 | _ -> 395 | erlang:error({invalid_positive_int, Size, Bin}) 396 | end. 397 | 398 | dec_float(<<0:1, _:7, _/binary>> = Bin) -> 399 | erlfdb_float:decode(<<<<(B bxor 16#FF)>> || <> <= Bin>>); 400 | dec_float(<>) -> 401 | erlfdb_float:decode(<<(Byte bxor 16#80):8/integer, Rest/binary>>). 402 | 403 | find_incomplete_versionstamp(Items) -> 404 | find_incomplete_versionstamp(Items, 0). 405 | 406 | find_incomplete_versionstamp([], Pos) -> 407 | {not_found, Pos}; 408 | find_incomplete_versionstamp([<>, ?UNSET_VERSIONSTAMP80 | Rest], Pos) -> 409 | case find_incomplete_versionstamp(Rest, Pos + 11) of 410 | {not_found, _} -> 411 | {found, Pos + 1}; 412 | {found, _} -> 413 | E = {erlfdb_tuple_error, multiple_incomplete_versionstamps}, 414 | erlang:error(E) 415 | end; 416 | find_incomplete_versionstamp([<>, ?UNSET_VERSIONSTAMP96 | Rest], Pos) -> 417 | case find_incomplete_versionstamp(Rest, Pos + 13) of 418 | {not_found, _} -> 419 | {found, Pos + 1}; 420 | {found, _} -> 421 | E = {erlfdb_tuple_error, multiple_incomplete_versionstamps}, 422 | erlang:error(E) 423 | end; 424 | find_incomplete_versionstamp([Item | Rest], Pos) when is_list(Item) -> 425 | case find_incomplete_versionstamp(Item, Pos) of 426 | {not_found, NewPos} -> 427 | find_incomplete_versionstamp(Rest, NewPos); 428 | {found, FoundPos} -> 429 | % The second versionstamp that is found will not 430 | % report a correct version. For now that's fine 431 | % as more than one version stamp is an error. 432 | case find_incomplete_versionstamp(Rest, Pos) of 433 | {not_found, _} -> 434 | {found, FoundPos}; 435 | {found, _} -> 436 | E = {erlfdb_tuple_error, multiple_incomplete_versionstamps}, 437 | erlang:error(E) 438 | end 439 | end; 440 | find_incomplete_versionstamp([Bin | Rest], Pos) when is_binary(Bin) -> 441 | find_incomplete_versionstamp(Rest, Pos + size(Bin)). 442 | 443 | compare_elems(A, B) -> 444 | CodeA = code_for(A), 445 | CodeB = code_for(B), 446 | 447 | case {code_for(A), code_for(B)} of 448 | {CodeA, CodeB} when CodeA < CodeB -> 449 | -1; 450 | {CodeA, CodeB} when CodeA > CodeB -> 451 | 1; 452 | {Code, Code} when Code == ?NULL -> 453 | 0; 454 | {Code, Code} when Code == ?FLOAT; Code == ?DOUBLE -> 455 | compare_floats(A, B); 456 | {Code, Code} when Code == ?NESTED -> 457 | compare(A, B); 458 | {Code, Code} when A < B -> 459 | -1; 460 | {Code, Code} when A > B -> 461 | 1; 462 | {Code, Code} when A == B -> 463 | 0 464 | end. 465 | 466 | compare_floats(F1, F2) -> 467 | B1 = pack({F1}), 468 | B2 = pack({F2}), 469 | if 470 | B1 < B2 -> -1; 471 | B1 > B2 -> 1; 472 | true -> 0 473 | end. 474 | 475 | code_for(null) -> ?NULL; 476 | code_for(<<_/binary>>) -> ?BYTES; 477 | code_for({utf8, <<_/binary>>}) -> ?STRING; 478 | code_for(I) when is_integer(I) -> ?ZERO; 479 | code_for({float, F}) when is_float(F) -> ?FLOAT; 480 | code_for({float, _, B}) when is_binary(B) -> ?FLOAT; 481 | code_for(F) when is_float(F) -> ?DOUBLE; 482 | code_for({double, _, B}) when is_binary(B) -> ?DOUBLE; 483 | code_for(false) -> ?FALSE; 484 | code_for(true) -> ?TRUE; 485 | code_for({uuid, <<_:16/binary>>}) -> ?UUID; 486 | code_for({id64, _Id}) -> ?ID64; 487 | code_for({versionstamp, _Id, _Batch}) -> ?VS80; 488 | code_for({versionstamp, _Id, _Batch, _Tx}) -> ?VS96; 489 | code_for(T) when is_tuple(T) -> ?NESTED; 490 | code_for(Bad) -> erlang:error({invalid_tuple_element, Bad}). 491 | 492 | -ifdef(TEST). 493 | 494 | -include_lib("eunit/include/eunit.hrl"). 495 | 496 | fdb_vectors_test() -> 497 | % These are all from the tuple layer spec: 498 | % https://github.com/apple/foundationdb/blob/master/design/tuple.md 499 | Cases = [ 500 | { 501 | {null}, 502 | <<0>> 503 | }, 504 | { 505 | {<<"foo", 16#00, "bar">>}, 506 | <<16#01, "foo", 16#00, 16#FF, "bar", 16#00>> 507 | }, 508 | { 509 | {{utf8, unicode:characters_to_binary(["F", 16#D4, "O", 16#00, "bar"])}}, 510 | <<16#02, "F", 16#C3, 16#94, "O", 16#00, 16#FF, "bar", 16#00>> 511 | }, 512 | { 513 | {{<<"foo", 16#00, "bar">>, null, {}}}, 514 | <<16#05, 16#01, "foo", 16#00, 16#FF, "bar", 16#00, 16#00, 16#FF, 16#05, 16#00, 16#00>> 515 | }, 516 | { 517 | {-5551212}, 518 | <<16#11, 16#AB, 16#4B, 16#93>> 519 | }, 520 | { 521 | {{float, -42.0}}, 522 | <<16#20, 16#3D, 16#D7, 16#FF, 16#FF>> 523 | } 524 | ], 525 | lists:foreach( 526 | fun({Test, Expect}) -> 527 | ?assertEqual(Expect, pack(Test)), 528 | ?assertEqual(Test, unpack(pack(Test))) 529 | end, 530 | Cases 531 | ). 532 | 533 | bindingstester_discoveries_test() -> 534 | Pairs = [ 535 | {<<33, 134, 55, 204, 184, 171, 21, 15, 128>>, {1.048903115625475e-278}} 536 | ], 537 | lists:foreach( 538 | fun({Packed, Unpacked}) -> 539 | ?assertEqual(Packed, pack(Unpacked)), 540 | ?assertEqual(Unpacked, unpack(Packed)) 541 | end, 542 | Pairs 543 | ), 544 | 545 | PackUnpackCases = [ 546 | {-87469399449579948399912777925908893746277401541675257432930} 547 | ], 548 | lists:foreach( 549 | fun(Test) -> 550 | ?assertEqual(Test, unpack(pack(Test))) 551 | end, 552 | PackUnpackCases 553 | ), 554 | 555 | UnpackPackCases = [ 556 | <<33, 134, 55, 204, 184, 171, 21, 15, 128>> 557 | ], 558 | lists:foreach( 559 | fun(Test) -> 560 | ?assertEqual(Test, pack(unpack(Test))) 561 | end, 562 | UnpackPackCases 563 | ). 564 | 565 | -endif. 566 | -------------------------------------------------------------------------------- /src/erlfdb_util.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_util). 14 | 15 | -export([ 16 | get_test_db/0, 17 | get_test_db/1, 18 | 19 | init_test_cluster/1, 20 | 21 | get/2, 22 | get/3, 23 | 24 | repr/1, 25 | 26 | debug_cluster/1, 27 | debug_cluster/3 28 | ]). 29 | 30 | get_test_db() -> 31 | get_test_db([]). 32 | 33 | get_test_db(Options) -> 34 | {ok, ClusterFile} = init_test_cluster(Options), 35 | Db = erlfdb:open(ClusterFile), 36 | case proplists:get_value(empty, Options) of 37 | true -> 38 | erlfdb:transactional(Db, fun(Tx) -> 39 | erlfdb:clear_range(Tx, <<>>, <<16#FE, 16#FF, 16#FF, 16#FF>>) 40 | end); 41 | _ -> 42 | ok 43 | end, 44 | Db. 45 | 46 | init_test_cluster(Options) -> 47 | % Hack to ensure erlfdb app environment is loaded during unit tests 48 | ok = application:ensure_started(erlfdb), 49 | case application:get_env(erlfdb, test_cluster_file) of 50 | {ok, system_default} -> 51 | {ok, <<>>}; 52 | {ok, ClusterFile} -> 53 | {ok, ClusterFile}; 54 | undefined -> 55 | init_test_cluster_int(Options) 56 | end. 57 | 58 | get(List, Key) -> 59 | get(List, Key, undefined). 60 | 61 | get(List, Key, Default) -> 62 | case lists:keyfind(Key, 1, List) of 63 | {Key, Value} -> Value; 64 | _ -> Default 65 | end. 66 | 67 | repr(Bin) when is_binary(Bin) -> 68 | [$'] ++ 69 | lists:map( 70 | fun(C) -> 71 | case C of 72 | 9 -> "\\t"; 73 | 10 -> "\\n"; 74 | 13 -> "\\r"; 75 | 39 -> "\\'"; 76 | 92 -> "\\\\"; 77 | _ when C >= 32, C =< 126 -> C; 78 | _ -> io_lib:format("\\x~2.16.0b", [C]) 79 | end 80 | end, 81 | binary_to_list(Bin) 82 | ) ++ [$']. 83 | 84 | debug_cluster(Tx) -> 85 | debug_cluster(Tx, <<>>, <<16#FE, 16#FF, 16#FF>>). 86 | 87 | debug_cluster(Tx, Start, End) -> 88 | lists:foreach( 89 | fun({Key, Val}) -> 90 | io:format(standard_error, "~s => ~s~n", [ 91 | string:pad(erlfdb_util:repr(Key), 60), 92 | repr(Val) 93 | ]) 94 | end, 95 | erlfdb:get_range(Tx, Start, End) 96 | ). 97 | 98 | init_test_cluster_int(Options) -> 99 | {ok, CWD} = file:get_cwd(), 100 | DefaultIpAddr = {127, 0, 0, 1}, 101 | DefaultPort = get_available_port(), 102 | DefaultDir = filename:join(CWD, ".erlfdb"), 103 | 104 | IpAddr = ?MODULE:get(Options, ip_addr, DefaultIpAddr), 105 | Port = ?MODULE:get(Options, port, DefaultPort), 106 | Dir = ?MODULE:get(Options, dir, DefaultDir), 107 | ClusterName = ?MODULE:get(Options, cluster_name, <<"erlfdbtest">>), 108 | ClusterId = ?MODULE:get(Options, cluster_id, <<"erlfdbtest">>), 109 | 110 | DefaultClusterFile = filename:join(Dir, <<"erlfdb.cluster">>), 111 | ClusterFile = ?MODULE:get(Options, cluster_file, DefaultClusterFile), 112 | 113 | write_cluster_file(ClusterFile, ClusterName, ClusterId, IpAddr, Port), 114 | 115 | FDBServerBin = find_fdbserver_bin(Options), 116 | 117 | {FDBPid, _} = spawn_monitor(fun() -> 118 | % Open the fdbserver port 119 | FDBPortName = {spawn_executable, FDBServerBin}, 120 | FDBPortArgs = [ 121 | <<"-p">>, 122 | ip_port_to_str(IpAddr, Port), 123 | <<"-C">>, 124 | ClusterFile, 125 | <<"-d">>, 126 | Dir, 127 | <<"-L">>, 128 | Dir 129 | ], 130 | FDBPortOpts = [{args, FDBPortArgs}], 131 | FDBServer = erlang:open_port(FDBPortName, FDBPortOpts), 132 | {os_pid, FDBPid} = erlang:port_info(FDBServer, os_pid), 133 | 134 | % Open the monitor pid 135 | MonitorPath = get_monitor_path(), 136 | ErlPid = os:getpid(), 137 | 138 | MonitorPortName = {spawn_executable, MonitorPath}, 139 | MonitorPortArgs = [{args, [ErlPid, integer_to_binary(FDBPid)]}], 140 | Monitor = erlang:open_port(MonitorPortName, MonitorPortArgs), 141 | 142 | init_fdb_db(ClusterFile, Options), 143 | 144 | receive 145 | {wait_for_init, ParentPid} -> 146 | ParentPid ! {initialized, self()} 147 | after 5000 -> 148 | true = erlang:port_close(FDBServer), 149 | true = erlang:port_close(Monitor), 150 | erlang:error(fdb_parent_died) 151 | end, 152 | 153 | port_loop(FDBServer, Monitor), 154 | 155 | true = erlang:port_close(FDBServer), 156 | true = erlang:port_close(Monitor) 157 | end), 158 | 159 | FDBPid ! {wait_for_init, self()}, 160 | receive 161 | {initialized, FDBPid} -> 162 | ok; 163 | Msg -> 164 | erlang:error({fdbserver_error, Msg}) 165 | end, 166 | 167 | ok = application:set_env(erlfdb, test_cluster_file, ClusterFile), 168 | ok = application:set_env(erlfdb, test_cluster_pid, FDBPid), 169 | {ok, ClusterFile}. 170 | 171 | get_available_port() -> 172 | {ok, Socket} = gen_tcp:listen(0, []), 173 | {ok, Port} = inet:port(Socket), 174 | ok = gen_tcp:close(Socket), 175 | Port. 176 | 177 | find_fdbserver_bin(Options) -> 178 | Locations = 179 | case ?MODULE:get(Options, fdbserver_bin) of 180 | undefined -> 181 | [ 182 | <<"/usr/sbin/fdbserver">>, 183 | <<"/usr/local/bin/fdbserver">>, 184 | <<"/usr/local/sbin/fdbserver">>, 185 | <<"/usr/local/libexec/fdbserver">> 186 | ]; 187 | Else -> 188 | [Else] 189 | end, 190 | case lists:filter(fun filelib:is_file/1, Locations) of 191 | [Path | _] -> Path; 192 | [] -> erlang:error(fdbserver_bin_not_found) 193 | end. 194 | 195 | write_cluster_file(FileName, ClusterName, ClusterId, IpAddr, Port) -> 196 | Args = [ClusterName, ClusterId, ip_port_to_str(IpAddr, Port)], 197 | Contents = io_lib:format("~s:~s@~s~n", Args), 198 | ok = filelib:ensure_dir(FileName), 199 | ok = file:write_file(FileName, iolist_to_binary(Contents)). 200 | 201 | get_monitor_path() -> 202 | PrivDir = 203 | case code:priv_dir(erlfdb) of 204 | {error, _} -> 205 | EbinDir = filename:dirname(code:which(?MODULE)), 206 | AppPath = filename:dirname(EbinDir), 207 | filename:join(AppPath, "priv"); 208 | Path -> 209 | Path 210 | end, 211 | filename:join(PrivDir, "monitor.py"). 212 | 213 | init_fdb_db(ClusterFile, Options) -> 214 | DefaultFDBCli = os:find_executable("fdbcli"), 215 | FDBCli = 216 | case ?MODULE:get(Options, fdbcli_bin, DefaultFDBCli) of 217 | false -> erlang:error(fdbcli_not_found); 218 | DefaultFDBCli -> "fdbcli"; 219 | FDBCli0 -> FDBCli0 220 | end, 221 | Fmt = "~s -C ~s --exec \"configure new single ssd\"", 222 | Cmd = lists:flatten(io_lib:format(Fmt, [FDBCli, ClusterFile])), 223 | case os:cmd(Cmd) of 224 | "Database created" ++ _ -> ok; 225 | "ERROR: Database already exists!" ++ _ -> ok; 226 | Msg -> erlang:error({fdb_init_error, Msg}) 227 | end. 228 | 229 | port_loop(FDBServer, Monitor) -> 230 | receive 231 | close -> 232 | ok; 233 | {FDBServer, {data, "FDBD joined cluster.\n"}} -> 234 | % Silence start message 235 | port_loop(FDBServer, Monitor); 236 | {Port, {data, Msg}} when Port == FDBServer orelse Port == Monitor -> 237 | io:format(standard_error, "~p", [Msg]), 238 | port_loop(FDBServer, Monitor); 239 | Error -> 240 | erlang:exit({fdb_cluster_error, Error}) 241 | end. 242 | 243 | ip_port_to_str({I1, I2, I3, I4}, Port) -> 244 | Fmt = "~b.~b.~b.~b:~b", 245 | iolist_to_binary(io_lib:format(Fmt, [I1, I2, I3, I4, Port])). 246 | -------------------------------------------------------------------------------- /test/erlfdb_01_basic_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_01_basic_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | load_test() -> 18 | erlfdb_nif:ohai(). 19 | 20 | get_error_string_test() -> 21 | ?assertEqual(<<"Success">>, erlfdb_nif:get_error(0)), 22 | ?assertEqual( 23 | <<"Transaction exceeds byte limit">>, 24 | erlfdb_nif:get_error(2101) 25 | ), 26 | ?assertEqual(<<"UNKNOWN_ERROR">>, erlfdb_nif:get_error(9999)). 27 | -------------------------------------------------------------------------------- /test/erlfdb_02_anon_fdbserver_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_02_anon_fdbserver_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | basic_init_test() -> 18 | {ok, ClusterFile} = erlfdb_util:init_test_cluster([]), 19 | ?assert(is_binary(ClusterFile)). 20 | 21 | basic_open_test() -> 22 | {ok, ClusterFile} = erlfdb_util:init_test_cluster([]), 23 | Db = erlfdb:open(ClusterFile), 24 | erlfdb:transactional(Db, fun(Tx) -> 25 | ?assert(true) 26 | end). 27 | 28 | get_db_test() -> 29 | Db = erlfdb_util:get_test_db(), 30 | erlfdb:transactional(Db, fun(Tx) -> 31 | ?assert(true) 32 | end). 33 | 34 | get_set_get_test() -> 35 | Db = erlfdb_util:get_test_db(), 36 | Key = gen_key(8), 37 | Val = crypto:strong_rand_bytes(8), 38 | erlfdb:transactional(Db, fun(Tx) -> 39 | ?assertEqual(not_found, erlfdb:wait(erlfdb:get(Tx, Key))) 40 | end), 41 | erlfdb:transactional(Db, fun(Tx) -> 42 | ?assertEqual(ok, erlfdb:set(Tx, Key, Val)) 43 | end), 44 | erlfdb:transactional(Db, fun(Tx) -> 45 | ?assertEqual(Val, erlfdb:wait(erlfdb:get(Tx, Key))) 46 | end). 47 | 48 | get_empty_test() -> 49 | Db1 = erlfdb_util:get_test_db(), 50 | Key = gen_key(8), 51 | Val = crypto:strong_rand_bytes(8), 52 | erlfdb:transactional(Db1, fun(Tx) -> 53 | ok = erlfdb:set(Tx, Key, Val) 54 | end), 55 | erlfdb:transactional(Db1, fun(Tx) -> 56 | ?assertEqual(Val, erlfdb:wait(erlfdb:get(Tx, Key))) 57 | end), 58 | 59 | % Check we can get an empty db 60 | Db2 = erlfdb_util:get_test_db([empty]), 61 | erlfdb:transactional(Db2, fun(Tx) -> 62 | ?assertEqual(not_found, erlfdb:wait(erlfdb:get(Tx, Key))) 63 | end), 64 | 65 | % And check state that the old db handle is 66 | % the same 67 | erlfdb:transactional(Db1, fun(Tx) -> 68 | ?assertEqual(not_found, erlfdb:wait(erlfdb:get(Tx, Key))) 69 | end). 70 | 71 | gen_key(Size) when is_integer(Size), Size > 1 -> 72 | RandBin = crypto:strong_rand_bytes(Size - 1), 73 | <<0, RandBin/binary>>. 74 | -------------------------------------------------------------------------------- /test/erlfdb_03_transaction_options_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_03_transaction_options_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | get_approximate_tx_size_test() -> 18 | Db1 = erlfdb_util:get_test_db(), 19 | erlfdb:transactional(Db1, fun(Tx) -> 20 | ok = erlfdb:set(Tx, gen(10), gen(5000)), 21 | TxSize1 = erlfdb:wait(erlfdb:get_approximate_size(Tx)), 22 | ?assert(TxSize1 > 5000 andalso TxSize1 < 6000), 23 | ok = erlfdb:set(Tx, gen(10), gen(5000)), 24 | TxSize2 = erlfdb:wait(erlfdb:get_approximate_size(Tx)), 25 | ?assert(TxSize2 > 10000) 26 | end). 27 | 28 | size_limit_test() -> 29 | Db1 = erlfdb_util:get_test_db(), 30 | ?assertError( 31 | {erlfdb_error, 2101}, 32 | erlfdb:transactional(Db1, fun(Tx) -> 33 | erlfdb:set_option(Tx, size_limit, 10000), 34 | erlfdb:set(Tx, gen(10), gen(11000)) 35 | end) 36 | ). 37 | 38 | writes_allowed_test() -> 39 | Db1 = erlfdb_util:get_test_db(), 40 | ?assertError( 41 | writes_not_allowed, 42 | erlfdb:transactional(Db1, fun(Tx) -> 43 | ?assert(erlfdb:get_writes_allowed(Tx)), 44 | 45 | erlfdb:set_option(Tx, disallow_writes), 46 | ?assert(not erlfdb:get_writes_allowed(Tx)), 47 | 48 | erlfdb:set_option(Tx, allow_writes), 49 | ?assert(erlfdb:get_writes_allowed(Tx)), 50 | 51 | erlfdb:set_option(Tx, disallow_writes), 52 | erlfdb:set(Tx, gen(10), gen(10)) 53 | end) 54 | ). 55 | 56 | once_writes_happend_cannot_disallow_them_test() -> 57 | Db1 = erlfdb_util:get_test_db(), 58 | ?assertError( 59 | badarg, 60 | erlfdb:transactional(Db1, fun(Tx) -> 61 | ok = erlfdb:set(Tx, gen(10), gen(10)), 62 | erlfdb:set_option(Tx, disallow_writes) 63 | end) 64 | ). 65 | 66 | has_watches_test() -> 67 | Db1 = erlfdb_util:get_test_db(), 68 | {Before, After, AfterReset} = (erlfdb:transactional(Db1, fun(Tx) -> 69 | Before = erlfdb:has_watches(Tx), 70 | erlfdb:watch(Tx, gen(10)), 71 | After = erlfdb:has_watches(Tx), 72 | erlfdb:reset(Tx), 73 | AfterReset = erlfdb:has_watches(Tx), 74 | {Before, After, AfterReset} 75 | end)), 76 | ?assert(not Before), 77 | ?assert(After), 78 | ?assert(not AfterReset). 79 | 80 | cannot_set_watches_if_writes_disallowed_test() -> 81 | Db1 = erlfdb_util:get_test_db(), 82 | ?assertError( 83 | writes_not_allowed, 84 | erlfdb:transactional(Db1, fun(Tx) -> 85 | erlfdb:set_option(Tx, disallow_writes), 86 | erlfdb:watch(Tx, gen(10)) 87 | end) 88 | ). 89 | 90 | size_limit_on_db_handle_test() -> 91 | Db1 = erlfdb_util:get_test_db(), 92 | erlfdb:set_option(Db1, size_limit, 10000), 93 | ?assertError( 94 | {erlfdb_error, 2101}, 95 | erlfdb:transactional(Db1, fun(Tx) -> 96 | erlfdb:set(Tx, gen(10), gen(11000)) 97 | end) 98 | ). 99 | 100 | gen(Size) when is_integer(Size), Size > 1 -> 101 | RandBin = crypto:strong_rand_bytes(Size - 1), 102 | <<0, RandBin/binary>>. 103 | -------------------------------------------------------------------------------- /test/erlfdb_04_snapshot_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_04_snapshot_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | snapshot_from_tx_test() -> 18 | Db = erlfdb_util:get_test_db(), 19 | Key = gen(10), 20 | Val = gen(10), 21 | erlfdb:set(Db, Key, Val), 22 | erlfdb:transactional(Db, fun(Tx) -> 23 | ?assertEqual(Val, erlfdb:wait(erlfdb:get_ss(Tx, Key))), 24 | Ss = erlfdb:snapshot(Tx), 25 | ?assertEqual(Val, erlfdb:wait(erlfdb:get(Ss, Key))) 26 | end). 27 | 28 | snapshot_from_a_snapshot_test() -> 29 | Db = erlfdb_util:get_test_db(), 30 | Key = gen(10), 31 | Val = gen(10), 32 | erlfdb:set(Db, Key, Val), 33 | erlfdb:transactional(Db, fun(Tx) -> 34 | Ss = erlfdb:snapshot(Tx), 35 | ?assertEqual(Val, erlfdb:wait(erlfdb:get_ss(Ss, Key))), 36 | Ss = erlfdb:snapshot(Ss) 37 | end). 38 | 39 | gen(Size) when is_integer(Size), Size > 1 -> 40 | RandBin = crypto:strong_rand_bytes(Size - 1), 41 | <<0, RandBin/binary>>. 42 | -------------------------------------------------------------------------------- /test/erlfdb_05_get_next_tx_id_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_05_get_next_tx_id_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | get_tx_id_test() -> 18 | Db = erlfdb_util:get_test_db(), 19 | erlfdb:transactional(Db, fun(Tx) -> 20 | lists:foreach( 21 | fun(I) -> 22 | ?assertEqual(I, erlfdb:get_next_tx_id(Tx)) 23 | end, 24 | lists:seq(0, 65535) 25 | ), 26 | ?assertError(badarg, erlfdb:get_next_tx_id(Tx)) 27 | end). 28 | -------------------------------------------------------------------------------- /test/erlfdb_06_get_addresses_test.erl: -------------------------------------------------------------------------------- 1 | % Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | % use this file except in compliance with the License. You may obtain a copy of 3 | % the License at 4 | % 5 | % http://www.apache.org/licenses/LICENSE-2.0 6 | % 7 | % Unless required by applicable law or agreed to in writing, software 8 | % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | % License for the specific language governing permissions and limitations under 11 | % the License. 12 | 13 | -module(erlfdb_06_get_addresses_test). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | get_addresses_for_key_test() -> 18 | Db = erlfdb_util:get_test_db(), 19 | % Does not matter whether foo exists in Db 20 | Result = erlfdb:get_addresses_for_key(Db, <<"foo">>), 21 | ?assertMatch([X | _] when is_binary(X), Result). 22 | -------------------------------------------------------------------------------- /test/tester.es: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | -mode(compile). 4 | 5 | 6 | -define(DIRECTORY_CREATE_OPS, [ 7 | <<"DIRECTORY_CREATE_SUBSPACE">>, 8 | <<"DIRECTORY_CREATE_LAYER">>, 9 | <<"DIRECTORY_CREATE_OR_OPEN">>, 10 | <<"DIRECTORY_CREATE">>, 11 | <<"DIRECTORY_OPEN">>, 12 | <<"DIRECTORY_MOVE">>, 13 | <<"DIRECTORY_MOVE_TO">>, 14 | <<"DIRECTORY_OPEN_SUBSPACE">> 15 | ]). 16 | 17 | 18 | -record(st, { 19 | db, 20 | tx_mgr, 21 | tx_name, 22 | instructions, 23 | op_tuple, 24 | stack, 25 | index, 26 | is_db, 27 | is_snapshot, 28 | last_version, 29 | pids, 30 | 31 | % Directory Layer tests 32 | is_directory_op, 33 | dir_list, 34 | dir_index, 35 | dir_error_index 36 | }). 37 | 38 | 39 | init_rand() -> 40 | case os:getenv("RANDOM_SEED") of 41 | false -> 42 | ok; 43 | Seed -> 44 | rand:seed(exsplus, {list_to_integer(Seed), 0, 0}) 45 | end. 46 | 47 | 48 | stack_create() -> 49 | Pid = spawn_link(fun() -> stack_loop([]) end), 50 | spawn(fun() -> 51 | erlang:monitor(process, Pid), 52 | receive 53 | {'DOWN', _, _, Pid, Reason} -> 54 | io:format(standard_error, "STACK DIED: ~p~n", [Reason]) 55 | end 56 | end), 57 | Pid. 58 | 59 | 60 | stack_loop(St) -> 61 | receive 62 | Msg -> 63 | NewSt = stack_handle(Msg, St), 64 | stack_loop(NewSt) 65 | end. 66 | 67 | 68 | stack_handle({From, log}, St) -> 69 | lists:foldl(fun(I, Pos) -> 70 | io:format("~p: ~p~n", [Pos, I]), 71 | Pos + 1 72 | end, 1, St), 73 | From ! {self(), ok}, 74 | St; 75 | 76 | stack_handle({From, clear}, _St) -> 77 | From ! {self(), ok}, 78 | []; 79 | 80 | stack_handle({From, length}, St) -> 81 | From ! {self(), length(St)}, 82 | St; 83 | 84 | stack_handle({From, get, Pos}, St) -> 85 | From ! {self(), lists:nth(Pos + 1, St)}, 86 | St; 87 | 88 | stack_handle({From, set, Pos, Value}, St) -> 89 | Head = lists:sublist(St, Pos), 90 | Tail = lists:nthtail(Pos + 1, St), 91 | NewSt = Head ++ [Value] ++ Tail, 92 | From ! {self(), ok}, 93 | NewSt; 94 | 95 | stack_handle({From, push, Value}, St) -> 96 | NewSt = [Value | St], 97 | From ! {self(), ok}, 98 | NewSt; 99 | 100 | stack_handle({From, pop, Count}, St) -> 101 | Items = lists:sublist(St, Count), 102 | NewSt = lists:nthtail(Count, St), 103 | From ! {self(), Items}, 104 | NewSt. 105 | 106 | 107 | %% stack_log(#st{stack = Pid}) -> 108 | %% stack_log(Pid); 109 | %% 110 | %% stack_log(Pid) -> 111 | %% Pid ! {self(), log}, 112 | %% receive {Pid, ok} -> ok end. 113 | 114 | 115 | stack_clear(#st{stack = Pid}) -> 116 | stack_clear(Pid); 117 | 118 | stack_clear(Pid) -> 119 | Pid ! {self(), clear}, 120 | receive {Pid, ok} -> ok end. 121 | 122 | 123 | stack_size(#st{stack = Pid}) -> 124 | stack_size(Pid); 125 | 126 | stack_size(Pid) -> 127 | Pid ! {self(), length}, 128 | receive {Pid, Length} -> Length end. 129 | 130 | 131 | stack_get(#st{stack = Pid}, Pos) -> 132 | stack_get(Pid, Pos); 133 | 134 | stack_get(Pid, Pos) -> 135 | Pid ! {self(), get, Pos}, 136 | receive {Pid, Value} -> Value end. 137 | 138 | 139 | stack_set(#st{stack = Pid}, Pos, Value) -> 140 | stack_set(Pid, Pos, Value); 141 | 142 | stack_set(Pid, Pos, Value) -> 143 | Pid ! {self(), set, Pos, Value}, 144 | receive {Pid, ok} -> ok end. 145 | 146 | 147 | stack_push(#st{stack = Pid, index = Idx}, Value) -> 148 | stack_push(Pid, {Idx, Value}); 149 | 150 | stack_push(_Pid, {_Idx, ok}) -> 151 | erlang:error(broken_here); 152 | stack_push(Pid, Value) -> 153 | Pid ! {self(), push, Value}, 154 | receive {Pid, ok} -> ok end. 155 | 156 | 157 | stack_pop_with_idx(#st{stack = Pid}, Count) -> 158 | stack_pop_with_idx(Pid, Count); 159 | 160 | stack_pop_with_idx(Pid, Count) -> 161 | Pid ! {self(), pop, Count}, 162 | Items = receive {Pid, Items0} -> Items0 end, 163 | lists:map(fun({Idx, Item}) -> 164 | case Item of 165 | {erlfdb_future, _, _} = Future -> 166 | try 167 | case erlfdb:wait(Future) of 168 | ok -> {Idx, <<"RESULT_NOT_PRESENT">>}; 169 | not_found -> {Idx, <<"RESULT_NOT_PRESENT">>}; 170 | Else -> {Idx, Else} 171 | end 172 | catch error:{erlfdb_error, Code} -> 173 | CodeBin = integer_to_binary(Code), 174 | ErrBin = erlfdb_tuple:pack({<<"ERROR">>, CodeBin}), 175 | {Idx, ErrBin} 176 | end; 177 | _ -> 178 | {Idx, Item} 179 | end 180 | end, Items). 181 | 182 | 183 | stack_pop(Obj, Count) -> 184 | Items = stack_pop_with_idx(Obj, Count), 185 | [Val || {_Idx, Val} <- Items]. 186 | 187 | 188 | stack_pop_with_idx(Obj) -> 189 | [Item] = stack_pop_with_idx(Obj, 1), 190 | Item. 191 | 192 | 193 | stack_pop(Obj) -> 194 | {_Idx, Val} = stack_pop_with_idx(Obj), 195 | Val. 196 | 197 | 198 | stack_push_range(St, {erlfdb_future, _, _} = Future) -> 199 | stack_push_range(St, erlfdb:wait(Future), <<>>); 200 | 201 | stack_push_range(St, Results) -> 202 | stack_push_range(St, Results, <<>>). 203 | 204 | 205 | stack_push_range(St, {erlfdb_future, _, _} = Future, PrefixFilter) -> 206 | stack_push_range(St, erlfdb:wait(Future), PrefixFilter); 207 | 208 | stack_push_range(#st{stack = Pid, index = Idx}, Results, PrefixFilter) -> 209 | PFLen = size(PrefixFilter), 210 | List = lists:foldr(fun 211 | ({<> = K, V}, Acc) when PF == PrefixFilter -> 212 | [K, V | Acc]; 213 | (_, Acc) -> 214 | Acc 215 | end, [], Results), 216 | Value = erlfdb_tuple:pack(list_to_tuple(List)), 217 | stack_push(Pid, {Idx, Value}). 218 | 219 | 220 | stack_pop_tuples(St) -> 221 | {Tuple} = stack_pop_tuples(St, 1), 222 | Tuple. 223 | 224 | 225 | stack_pop_tuples(St, Count) -> 226 | TupleList = lists:map(fun(_) -> 227 | TupleSize = stack_pop(St), 228 | TupleElems = stack_pop(St, TupleSize), 229 | list_to_tuple(TupleElems) 230 | end, lists:seq(1, Count)), 231 | list_to_tuple(TupleList). 232 | 233 | 234 | get_transaction(TxName) -> 235 | get({'$erlfdb_tx', TxName}). 236 | 237 | 238 | new_transaction(Db, TxName) -> 239 | Tx = erlfdb:create_transaction(Db), 240 | put({'$erlfdb_tx', TxName}, Tx). 241 | 242 | 243 | switch_transaction(Db, TxName) -> 244 | case get_transaction(TxName) of 245 | undefined -> 246 | new_transaction(Db, TxName); 247 | _ -> 248 | ok 249 | end. 250 | 251 | 252 | has_prefix(Subject, Prefix) -> 253 | PrefLen = size(Prefix), 254 | case Subject of 255 | <> -> true; 256 | _ -> false 257 | end. 258 | 259 | 260 | has_suffix(Subject, Suffix) -> 261 | SubjSize = size(Subject), 262 | SuffSize = size(Suffix), 263 | Offset = SubjSize - SuffSize, 264 | case Subject of 265 | <<_:Offset/binary, Suffix/binary>> -> true; 266 | _ -> false 267 | end. 268 | 269 | 270 | log_stack(_Db, _Prefix, []) -> 271 | ok; 272 | 273 | log_stack(Db, Prefix, Items) when length(Items) =< 100 -> 274 | erlfdb:transactional(Db, fun(Tx) -> 275 | lists:foreach(fun({StackPos, {Idx, Item}}) -> 276 | KeyTail = erlfdb_tuple:pack({StackPos, Idx}), 277 | Key = <>, 278 | RawVal = erlfdb_tuple:pack({Item}), 279 | Val = case size(RawVal) > 40000 of 280 | true -> binary:part(RawVal, {0, 40000}); 281 | false -> RawVal 282 | end, 283 | erlfdb:set(Tx, Key, Val) 284 | end, Items) 285 | end); 286 | 287 | log_stack(Db, Prefix, Items) -> 288 | {Head, Tail} = lists:split(100, Items), 289 | log_stack(Db, Prefix, Head), 290 | log_stack(Db, Prefix, Tail). 291 | 292 | 293 | wait_for_empty(Db, Prefix) -> 294 | erlfdb:transactional(Db, fun(Tx) -> 295 | Future = erlfdb:get_range_startswith(Tx, Prefix, [{limit, 1}]), 296 | case erlfdb:wait(Future) of 297 | [_|_] -> erlang:error({erlfdb_error, 1020}); 298 | [] -> ok 299 | end 300 | end). 301 | 302 | 303 | append_dir(St, Dir) -> 304 | case Dir of 305 | not_found -> erlang:error(broken); 306 | _ -> ok 307 | end, 308 | #st{ 309 | dir_list = DirList 310 | } = St, 311 | St#st{ 312 | dir_list = DirList ++ [Dir] 313 | }. 314 | 315 | 316 | init_run_loop(Db, Prefix) -> 317 | init_rand(), 318 | {StartKey, EndKey} = erlfdb_tuple:range({Prefix}), 319 | St = #st{ 320 | db = Db, 321 | tx_name = Prefix, 322 | instructions = erlfdb:get_range(Db, StartKey, EndKey), 323 | op_tuple = undefined, 324 | stack = stack_create(), 325 | index = 0, 326 | is_db = undefined, 327 | is_snapshot = undefined, 328 | last_version = 0, 329 | pids = [], 330 | 331 | dir_list = [erlfdb_directory:root()], 332 | dir_index = 0, 333 | dir_error_index = 0 334 | }, 335 | %% lists:foreach(fun({K, V}) -> 336 | %% io:format("'~s'~n'~s'~n", [erlfdb_util:repr(K), erlfdb_util:repr(V)]) 337 | %% end, St#st.instructions), 338 | run_loop(St). 339 | 340 | 341 | run_loop(#st{instructions = []} = St) -> 342 | lists:foreach(fun({Pid, Ref}) -> 343 | receive {'DOWN', Ref, _, Pid, _} -> ok end 344 | end, St#st.pids); 345 | 346 | run_loop(#st{} = St) -> 347 | #st{ 348 | db = Db, 349 | tx_name = TxName, 350 | instructions = Instructions, 351 | index = Index 352 | } = St, 353 | 354 | [{_InstrKey, InstrVal} | RestStuctions] = Instructions, 355 | 356 | OpTuple = erlfdb_tuple:unpack(InstrVal), 357 | {utf8, Op} = element(1, OpTuple), 358 | 359 | %% if Op == <<"PUSH">> orelse Op == <<"SWAP">> -> ok; true -> 360 | %% io:format("~b. Instruction is ~s~n", [Index, Op]) 361 | %% end, 362 | %% stack_log(St), 363 | 364 | IsDb = has_suffix(Op, <<"_DATABASE">>), 365 | IsSS = has_suffix(Op, <<"_SNAPSHOT">>), 366 | IsDir = has_prefix(Op, <<"DIRECTORY_">>), 367 | 368 | OpName = if not (IsDb or IsSS) -> Op; true -> 369 | binary:part(Op, {0, size(Op) - 9}) % strip off _DATABASE/_SNAPSHOT 370 | end, 371 | 372 | TxObj = case {IsDb, IsSS} of 373 | {true, false} -> 374 | Db; 375 | {false, false} -> 376 | get_transaction(TxName); 377 | {false, true} -> 378 | erlfdb:snapshot(get_transaction(TxName)) 379 | end, 380 | 381 | PreSt = St#st{ 382 | op_tuple = OpTuple, 383 | is_db = IsDb, 384 | is_snapshot = IsSS, 385 | is_directory_op = IsDir 386 | }, 387 | 388 | PostSt = try 389 | #st{} = execute(TxObj, PreSt, OpName) 390 | catch error:{erlfdb_error, Code} -> 391 | CodeBin = integer_to_binary(Code), 392 | ErrBin = erlfdb_tuple:pack({<<"ERROR">>, CodeBin}), 393 | stack_push(St#st.stack, {Index, ErrBin}), 394 | PreSt 395 | end, 396 | 397 | run_loop(PostSt#st{ 398 | instructions = RestStuctions, 399 | index = Index + 1, 400 | op_tuple = undefined, 401 | is_db = undefined, 402 | is_snapshot = undefined, 403 | is_directory_op = undefined 404 | }). 405 | 406 | 407 | execute(_TxObj, St, <<"PUSH">>) -> 408 | Value = element(2, St#st.op_tuple), 409 | stack_push(St, Value), 410 | St; 411 | 412 | execute(_TxObj, St, <<"DUP">>) -> 413 | Item = stack_pop_with_idx(St), 414 | stack_push(St#st.stack, Item), 415 | stack_push(St#st.stack, Item), 416 | St; 417 | 418 | execute(_TxObj, St, <<"EMPTY_STACK">>) -> 419 | stack_clear(St), 420 | St; 421 | 422 | execute(_TxObj, St, <<"SWAP">>) -> 423 | Idx = stack_pop(St), 424 | Item1 = stack_get(St, 0), 425 | Item2 = stack_get(St, Idx), 426 | stack_set(St, Idx, Item1), 427 | stack_set(St, 0, Item2), 428 | St; 429 | 430 | execute(_TxObj, St, <<"POP">>) -> 431 | stack_pop(St), 432 | St; 433 | 434 | execute(_TxObj, St, <<"SUB">>) -> 435 | [A, B] = stack_pop(St, 2), 436 | stack_push(St, A - B), 437 | St; 438 | 439 | execute(_TxObj, St, <<"CONCAT">>) -> 440 | [A, B] = stack_pop(St, 2), 441 | NewValue = case {A, B} of 442 | _ when is_integer(A), is_integer(B) -> 443 | A + B; 444 | _ when is_binary(A), is_binary(B) -> 445 | <> 446 | end, 447 | stack_push(St, NewValue), 448 | St; 449 | 450 | execute(_TxObj, St, <<"WAIT_FUTURE">>) -> 451 | Value = stack_pop_with_idx(St), 452 | stack_push(St#st.stack, Value), 453 | St; 454 | 455 | execute(_TxObj, St, <<"NEW_TRANSACTION">>) -> 456 | new_transaction(St#st.db, St#st.tx_name), 457 | St; 458 | 459 | execute(_TxObj, St, <<"USE_TRANSACTION">>) -> 460 | TxName = stack_pop(St), 461 | switch_transaction(St#st.db, TxName), 462 | St#st{ 463 | tx_name = TxName 464 | }; 465 | 466 | execute(TxObj, St, <<"ON_ERROR">>) -> 467 | Error = stack_pop(St), 468 | Future = erlfdb:on_error(TxObj, Error), 469 | stack_push(St, Future), 470 | St; 471 | 472 | execute(TxObj, St, <<"GET">>) -> 473 | Key = stack_pop(St), 474 | Value = case erlfdb:get(TxObj, Key) of 475 | {erlfdb_future, _, _} = Future -> 476 | erlfdb:wait(Future); 477 | Else -> 478 | Else 479 | end, 480 | case Value of 481 | not_found -> 482 | stack_push(St, <<"RESULT_NOT_PRESENT">>); 483 | _ -> 484 | stack_push(St, Value) 485 | end, 486 | St; 487 | 488 | execute(TxObj, St, <<"GET_ESTIMATED_RANGE_SIZE">>) -> 489 | [StartKey, EndKey] = stack_pop(St, 2), 490 | Value = case erlfdb:get_estimated_range_size(TxObj, StartKey, EndKey) of 491 | {erlfdb_future, _, _} = Future -> 492 | erlfdb:wait(Future); 493 | Else -> 494 | Else 495 | end, 496 | case Value of 497 | Size when is_integer(Size) -> 498 | stack_push(St, <<"GOT_ESTIMATED_RANGE_SIZE">>); 499 | BadResult -> 500 | stack_push(St, BadResult) 501 | end, 502 | St; 503 | 504 | execute(TxObj, St, <<"GET_KEY">>) -> 505 | [Key, OrEqual, Offset, Prefix] = stack_pop(St, 4), 506 | Selector = {Key, OrEqual, Offset}, 507 | Value = case erlfdb:get_key(TxObj, Selector) of 508 | {erlfdb_future, _, _} = Future -> 509 | erlfdb:wait(Future); 510 | Else -> 511 | Else 512 | end, 513 | PrefixSize = size(Prefix), 514 | case Value of 515 | <> -> 516 | stack_push(St, Value); 517 | _ when Value < Prefix -> 518 | stack_push(St, Prefix); 519 | _ -> 520 | stack_push(St, erlfdb_key:strinc(Prefix)) 521 | end, 522 | St; 523 | 524 | execute(TxObj, St, <<"GET_RANGE">>) -> 525 | [Start, End, Limit, Reverse, Mode] = stack_pop(St, 5), 526 | Options = [{limit, Limit}, {reverse, Reverse}, {streaming_mode, Mode}], 527 | Result = erlfdb:get_range(TxObj, Start, End, Options), 528 | stack_push_range(St, Result), 529 | St; 530 | 531 | execute(TxObj, St, <<"GET_RANGE_STARTS_WITH">>) -> 532 | [Prefix, Limit, Reverse, Mode] = stack_pop(St, 4), 533 | Options = [{limit, Limit}, {reverse, Reverse}, {streaming_mode, Mode}], 534 | Resp = erlfdb:get_range_startswith(TxObj, Prefix, Options), 535 | stack_push_range(St, Resp), 536 | St; 537 | 538 | execute(TxObj, St, <<"GET_RANGE_SELECTOR">>) -> 539 | [ 540 | StartKey, 541 | StartOrEqual, 542 | StartOffset, 543 | EndKey, 544 | EndOrEqual, 545 | EndOffset, 546 | Limit, 547 | Reverse, 548 | Mode, 549 | Prefix 550 | ] = stack_pop(St, 10), 551 | Start = {StartKey, StartOrEqual, StartOffset}, 552 | End = {EndKey, EndOrEqual, EndOffset}, 553 | Options = [{limit, Limit}, {reverse, Reverse}, {streaming_mode, Mode}], 554 | Resp = erlfdb:get_range(TxObj, Start, End, Options), 555 | stack_push_range(St, Resp, Prefix), 556 | St; 557 | 558 | execute(TxObj, St, <<"GET_READ_VERSION">>) -> 559 | LastVersion = erlfdb:wait(erlfdb:get_read_version(TxObj)), 560 | stack_push(St, <<"GOT_READ_VERSION">>), 561 | St#st{last_version = LastVersion}; 562 | 563 | execute(TxObj, St, <<"SET">>) -> 564 | [Key, Value] = stack_pop(St, 2), 565 | erlfdb:set(TxObj, Key, Value), 566 | if not St#st.is_db -> ok; true -> 567 | stack_push(St, <<"RESULT_NOT_PRESENT">>) 568 | end, 569 | St; 570 | 571 | execute(_TxObj, St, <<"LOG_STACK">>) -> 572 | Prefix = stack_pop(St), 573 | StackIdx = stack_size(St) - 1, 574 | AllItems = stack_pop_with_idx(St, StackIdx + 1), 575 | InitAcc = {StackIdx, []}, 576 | {_, RevItems} = lists:foldl(fun({Idx, Item}, {Pos, Acc}) -> 577 | {Pos - 1, [{Pos, {Idx, Item}} | Acc]} 578 | end, InitAcc, AllItems), 579 | log_stack(St#st.db, Prefix, RevItems), 580 | St; 581 | 582 | execute(TxObj, St, <<"ATOMIC_OP">>) -> 583 | [{utf8, OpTypeName}, Key, Val] = stack_pop(St, 3), 584 | OpType = list_to_atom(string:lowercase(binary_to_list(OpTypeName))), 585 | ok = erlfdb:atomic_op(TxObj, Key, Val, OpType), 586 | if not St#st.is_db -> ok; true -> 587 | stack_push(St, <<"RESULT_NOT_PRESENT">>) 588 | end, 589 | St; 590 | 591 | execute(TxObj, St, <<"SET_READ_VERSION">>) -> 592 | erlfdb:set_read_version(TxObj, St#st.last_version), 593 | St; 594 | 595 | execute(TxObj, St, <<"CLEAR">>) -> 596 | Key = stack_pop(St), 597 | erlfdb:clear(TxObj, Key), 598 | if not St#st.is_db -> ok; true -> 599 | stack_push(St, <<"RESULT_NOT_PRESENT">>) 600 | end, 601 | St; 602 | 603 | execute(TxObj, St, <<"CLEAR_RANGE">>) -> 604 | [Start, End] = stack_pop(St, 2), 605 | erlfdb:clear_range(TxObj, Start, End), 606 | if not St#st.is_db -> ok; true -> 607 | stack_push(St, <<"RESULT_NOT_PRESENT">>) 608 | end, 609 | St; 610 | 611 | execute(TxObj, St, <<"CLEAR_RANGE_STARTS_WITH">>) -> 612 | Prefix = stack_pop(St), 613 | erlfdb:clear_range_startswith(TxObj, Prefix), 614 | if not St#st.is_db -> ok; true -> 615 | stack_push(St, <<"RESULT_NOT_PRESENT">>) 616 | end, 617 | St; 618 | 619 | execute(TxObj, St, <<"READ_CONFLICT_RANGE">>) -> 620 | [Start, End] = stack_pop(St, 2), 621 | erlfdb:add_read_conflict_range(TxObj, Start, End), 622 | stack_push(St, <<"SET_CONFLICT_RANGE">>), 623 | St; 624 | 625 | execute(TxObj, St, <<"WRITE_CONFLICT_RANGE">>) -> 626 | [Start, End] = stack_pop(St, 2), 627 | erlfdb:add_write_conflict_range(TxObj, Start, End), 628 | stack_push(St, <<"SET_CONFLICT_RANGE">>), 629 | St; 630 | 631 | execute(TxObj, St, <<"READ_CONFLICT_KEY">>) -> 632 | Key = stack_pop(St), 633 | erlfdb:add_read_conflict_key(TxObj, Key), 634 | stack_push(St, <<"SET_CONFLICT_KEY">>), 635 | St; 636 | 637 | execute(TxObj, St, <<"WRITE_CONFLICT_KEY">>) -> 638 | Key = stack_pop(St), 639 | erlfdb:add_write_conflict_key(TxObj, Key), 640 | stack_push(St, <<"SET_CONFLICT_KEY">>), 641 | St; 642 | 643 | execute(TxObj, St, <<"DISABLE_WRITE_CONFLICT">>) -> 644 | erlfdb:set_option(TxObj, next_write_no_write_conflict_range), 645 | St; 646 | 647 | execute(TxObj, St, <<"COMMIT">>) -> 648 | Future = erlfdb:commit(TxObj), 649 | stack_push(St, Future), 650 | St; 651 | 652 | execute(TxObj, St, <<"RESET">>) -> 653 | erlfdb:reset(TxObj), 654 | St; 655 | 656 | execute(TxObj, St, <<"CANCEL">>) -> 657 | erlfdb:cancel(TxObj), 658 | St; 659 | 660 | execute(TxObj, St, <<"GET_COMMITTED_VERSION">>) -> 661 | Vsn = erlfdb:get_committed_version(TxObj), 662 | stack_push(St, <<"GOT_COMMITTED_VERSION">>), 663 | St#st{last_version = Vsn}; 664 | 665 | execute(TxObj, St, <<"GET_VERSIONSTAMP">>) -> 666 | VS = erlfdb:get_versionstamp(TxObj), 667 | stack_push(St, VS), 668 | St; 669 | 670 | execute(TxObj, St, <<"GET_APPROXIMATE_SIZE">>) -> 671 | erlfdb:wait(erlfdb:get_approximate_size(TxObj)), 672 | stack_push(St, <<"GOT_APPROXIMATE_SIZE">>), 673 | St; 674 | 675 | execute(_TxObj, St, <<"TUPLE_PACK">>) -> 676 | Count = stack_pop(St), 677 | Elems = stack_pop(St, Count), 678 | Val = erlfdb_tuple:pack(list_to_tuple(Elems)), 679 | stack_push(St, Val), 680 | St; 681 | 682 | execute(_TxObj, St, <<"TUPLE_PACK_WITH_VERSIONSTAMP">>) -> 683 | Prefix = stack_pop(St), 684 | Count = stack_pop(St), 685 | Items = stack_pop(St, Count), 686 | try 687 | Packed = erlfdb_tuple:pack_vs(list_to_tuple(Items), Prefix), 688 | stack_push(St, <<"OK">>), 689 | stack_push(St, Packed) 690 | catch 691 | error:{erlfdb_tuple_error, missing_incomplete_versionstamp} -> 692 | stack_push(St, <<"ERROR: NONE">>); 693 | error:{erlfdb_tuple_error, multiple_incomplete_versionstamps} -> 694 | stack_push(St, <<"ERROR: MULTIPLE">>) 695 | end, 696 | St; 697 | 698 | execute(_TxObj, St, <<"TUPLE_UNPACK">>) -> 699 | Bin = stack_pop(St), 700 | Tuple = erlfdb_tuple:unpack(Bin), 701 | lists:foreach(fun(Item) -> 702 | stack_push(St, erlfdb_tuple:pack({Item})) 703 | end, tuple_to_list(Tuple)), 704 | St; 705 | 706 | execute(_TxObj, St, <<"TUPLE_SORT">>) -> 707 | Count = stack_pop(St), 708 | Elems = stack_pop(St, Count), 709 | Items = [erlfdb_tuple:unpack(E) || E <- Elems], 710 | Sorted = lists:sort(fun(A, B) -> 711 | erlfdb_tuple:compare(A, B) =< 0 712 | end, Items), 713 | lists:foreach(fun(Item) -> 714 | stack_push(St, erlfdb_tuple:pack(Item)) 715 | end, Sorted), 716 | St; 717 | 718 | execute(_TxObj, St, <<"TUPLE_RANGE">>) -> 719 | Count = stack_pop(St), 720 | Items = stack_pop(St, Count), 721 | {Start, End} = erlfdb_tuple:range(list_to_tuple(Items)), 722 | stack_push(St, Start), 723 | stack_push(St, End), 724 | St; 725 | 726 | execute(_TxObj, St, <<"ENCODE_FLOAT">>) -> 727 | Val = erlfdb_float:decode(stack_pop(St)), 728 | stack_push(St, Val), 729 | St; 730 | 731 | execute(_TxObj, St, <<"ENCODE_DOUBLE">>) -> 732 | Val = erlfdb_float:decode(stack_pop(St)), 733 | stack_push(St, Val), 734 | St; 735 | 736 | execute(_TxObj, St, <<"DECODE_FLOAT">>) -> 737 | Val = erlfdb_float:encode(stack_pop(St)), 738 | stack_push(St, Val), 739 | St; 740 | 741 | execute(_TxObj, St, <<"DECODE_DOUBLE">>) -> 742 | Val = erlfdb_float:encode(stack_pop(St)), 743 | stack_push(St, Val), 744 | St; 745 | 746 | execute(_TxObj, St, <<"START_THREAD">>) -> 747 | #st{ 748 | db = Db, 749 | pids = Pids 750 | } = St, 751 | Prefix = stack_pop(St), 752 | Pid = spawn_monitor(fun() -> init_run_loop(Db, Prefix) end), 753 | St#st{pids = [Pid | Pids]}; 754 | 755 | execute(_TxObj, St, <<"WAIT_EMPTY">>) -> 756 | Prefix = stack_pop(St), 757 | wait_for_empty(St#st.db, Prefix), 758 | stack_push(St, <<"WAITED_FOR_EMPTY">>), 759 | St; 760 | 761 | execute(_TxObj, St, <<"UNIT_TESTS">>) -> 762 | % TODO 763 | St; 764 | 765 | execute(TxObj, #st{is_directory_op = true} = St, Op) -> 766 | #st{ 767 | dir_list = DirList, 768 | dir_index = DirIdx 769 | } = St, 770 | Dir = lists:nth(DirIdx + 1, DirList), 771 | try 772 | execute_dir(TxObj, St, Dir, Op) 773 | catch _T:_R -> 774 | NewSt = case lists:member(Op, ?DIRECTORY_CREATE_OPS) of 775 | true -> append_dir(St, null); 776 | false -> St 777 | end, 778 | stack_push(St, <<"DIRECTORY_ERROR">>), 779 | NewSt 780 | end; 781 | 782 | execute(_TxObj, _St, UnknownOp) -> 783 | erlang:error({unknown_op, UnknownOp}). 784 | 785 | 786 | execute_dir(_TxObj, St, _Dir, <<"DIRECTORY_CREATE_SUBSPACE">>) -> 787 | Path = stack_pop_tuples(St), 788 | RawPrefix = stack_pop(St), 789 | Subspace = erlfdb_subspace:create(Path, RawPrefix), 790 | append_dir(St, Subspace); 791 | 792 | execute_dir(_TxObj, St, _Dir, <<"DIRECTORY_CREATE_LAYER">>) -> 793 | #st{ 794 | dir_list = DirList 795 | } = St, 796 | [Index1, Index2, AllowManual] = stack_pop(St, 3), 797 | NodeSS = lists:nth(Index1 + 1, DirList), 798 | ContentSS = lists:nth(Index2 + 1, DirList), 799 | case (NodeSS == null orelse ContentSS == null) of 800 | true -> 801 | append_dir(St, null); 802 | false -> 803 | Opts = [ 804 | {node_prefix, erlfdb_subspace:key(NodeSS)}, 805 | {content_prefix, erlfdb_subspace:key(ContentSS)}, 806 | {allow_manual_names, AllowManual == 1} 807 | ], 808 | append_dir(St, erlfdb_directory:root(Opts)) 809 | end; 810 | 811 | execute_dir(_TxObj, St, _Dir, <<"DIRECTORY_CHANGE">>) -> 812 | #st{ 813 | dir_list = DirList, 814 | dir_error_index = ErrIdx 815 | } = St, 816 | DirIdx1 = stack_pop(St), 817 | DirIdx2 = case lists:nth(DirIdx1 + 1, DirList) of 818 | null -> ErrIdx; 819 | _ -> DirIdx1 820 | end, 821 | St#st{ 822 | dir_index = DirIdx2 823 | }; 824 | 825 | execute_dir(_TxObj, St, _Dir, <<"DIRECTORY_SET_ERROR_INDEX">>) -> 826 | St#st{ 827 | dir_error_index = stack_pop(St) 828 | }; 829 | 830 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_CREATE_OR_OPEN">>) -> 831 | Path = stack_pop_tuples(St), 832 | Layer = stack_pop(St), 833 | NewDir = erlfdb_directory:create_or_open(TxObj, Dir, Path, Layer), 834 | append_dir(St, NewDir); 835 | 836 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_CREATE">>) -> 837 | Path = stack_pop_tuples(St), 838 | [Layer, Prefix] = stack_pop(St, 2), 839 | Opts = [{layer, Layer}] ++ case Prefix of 840 | null -> []; 841 | _ -> [{node_name, Prefix}] 842 | end, 843 | NewDir = erlfdb_directory:create(TxObj, Dir, Path, Opts), 844 | append_dir(St, NewDir); 845 | 846 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_OPEN">>) -> 847 | Path = stack_pop_tuples(St), 848 | Layer = stack_pop(St), 849 | Opts = [{layer, Layer}], 850 | NewDir = erlfdb_directory:open(TxObj, Dir, Path, Opts), 851 | append_dir(St, NewDir); 852 | 853 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_MOVE">>) -> 854 | {OldPath, NewPath} = stack_pop_tuples(St, 2), 855 | NewDir = erlfdb_directory:move(TxObj, Dir, OldPath, NewPath), 856 | append_dir(St, NewDir); 857 | 858 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_MOVE_TO">>) -> 859 | NewAbsPath = stack_pop_tuples(St), 860 | NewDir = erlfdb_directory:move_to(TxObj, Dir, NewAbsPath), 861 | append_dir(St, NewDir); 862 | 863 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_REMOVE">>) -> 864 | Count = stack_pop(St), 865 | case Count == 0 of 866 | true -> 867 | erlfdb_directory:remove(TxObj, Dir); 868 | false -> 869 | Path = stack_pop_tuples(St), 870 | erlfdb_directory:remove(TxObj, Dir, Path) 871 | end, 872 | St; 873 | 874 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_REMOVE_IF_EXISTS">>) -> 875 | Count = stack_pop(St), 876 | case Count == 0 of 877 | true -> 878 | erlfdb_directory:remove_if_exists(TxObj, Dir); 879 | false -> 880 | Path = stack_pop_tuples(St), 881 | erlfdb_directory:remove_if_exists(TxObj, Dir, Path) 882 | end, 883 | St; 884 | 885 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_LIST">>) -> 886 | Count = stack_pop(St), 887 | Results = case Count == 0 of 888 | true -> 889 | erlfdb_directory:list(TxObj, Dir); 890 | false -> 891 | Path = stack_pop_tuples(St), 892 | erlfdb_directory:list(TxObj, Dir, Path) 893 | end, 894 | Names = lists:map(fun({N, _}) -> N end, Results), 895 | stack_push(St, erlfdb_tuple:pack(list_to_tuple(Names))), 896 | St; 897 | 898 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_EXISTS">>) -> 899 | Count = stack_pop(St), 900 | Result = case Count == 0 of 901 | true -> 902 | erlfdb_directory:exists(TxObj, Dir); 903 | false -> 904 | Path = stack_pop_tuples(St), 905 | erlfdb_directory:exists(TxObj, Dir, Path) 906 | end, 907 | case Result of 908 | true -> stack_push(St, 1); 909 | false -> stack_push(St, 0) 910 | end, 911 | St; 912 | 913 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_PACK_KEY">>) -> 914 | Tuple = stack_pop_tuples(St), 915 | Mod = get_dir_or_ss_mod(Dir), 916 | Result = Mod:pack(Dir, Tuple), 917 | stack_push(St, Result), 918 | St; 919 | 920 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_UNPACK_KEY">>) -> 921 | Key = stack_pop(St), 922 | Mod = get_dir_or_ss_mod(Dir), 923 | Tuple = Mod:unpack(Dir, Key), 924 | lists:foreach(fun(Elem) -> 925 | stack_push(St, Elem) 926 | end, tuple_to_list(Tuple)), 927 | St; 928 | 929 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_RANGE">>) -> 930 | Tuple = stack_pop_tuples(St), 931 | Mod = get_dir_or_ss_mod(Dir), 932 | {Start, End} = Mod:range(Dir, Tuple), 933 | stack_push(St, Start), 934 | stack_push(St, End), 935 | St; 936 | 937 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_CONTAINS">>) -> 938 | Key = stack_pop(St), 939 | Mod = get_dir_or_ss_mod(Dir), 940 | Result = Mod:contains(Dir, Key), 941 | case Result of 942 | true -> stack_push(St, 1); 943 | false -> stack_push(St, 0) 944 | end, 945 | St; 946 | 947 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_OPEN_SUBSPACE">>) -> 948 | Path = stack_pop_tuples(St), 949 | Mod = get_dir_or_ss_mod(Dir), 950 | Subspace = Mod:subspace(Dir, Path), 951 | append_dir(St, Subspace); 952 | 953 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_LOG_SUBSPACE">>) -> 954 | #st{ 955 | dir_index = DirIdx 956 | } = St, 957 | Prefix = stack_pop(St), 958 | LogKey = erlfdb_tuple:pack({DirIdx}, Prefix), 959 | Mod = get_dir_or_ss_mod(Dir), 960 | erlfdb:set(TxObj, LogKey, Mod:key(Dir)), 961 | St; 962 | 963 | execute_dir(TxObj, St, Dir, <<"DIRECTORY_LOG_DIRECTORY">>) -> 964 | #st{ 965 | dir_index = DirIdx 966 | } = St, 967 | Prefix = stack_pop(St), 968 | LogPrefix = erlfdb_tuple:pack({DirIdx}, Prefix), 969 | 970 | Exists = erlfdb_directory:exists(TxObj, Dir), 971 | Children = case Exists of 972 | true -> 973 | ListResult = erlfdb_directory:list(TxObj, Dir), 974 | Names = lists:map(fun({N, _}) -> N end, ListResult), 975 | list_to_tuple(Names); 976 | false -> 977 | {} 978 | end, 979 | 980 | PathKey = erlfdb_tuple:pack({{utf8, <<"path">>}}, LogPrefix), 981 | Path = erlfdb_tuple:pack(list_to_tuple(erlfdb_directory:get_path(Dir))), 982 | erlfdb:set(TxObj, PathKey, Path), 983 | 984 | LayerKey = erlfdb_tuple:pack({{utf8, <<"layer">>}}, LogPrefix), 985 | Layer = erlfdb_tuple:pack({erlfdb_directory:get_layer(Dir)}), 986 | erlfdb:set(TxObj, LayerKey, Layer), 987 | 988 | ExistsKey = erlfdb_tuple:pack({{utf8, <<"exists">>}}, LogPrefix), 989 | ExistsVal = erlfdb_tuple:pack({if Exists -> 1; true -> 0 end}), 990 | erlfdb:set(TxObj, ExistsKey, ExistsVal), 991 | 992 | ChildrenKey = erlfdb_tuple:pack({{utf8, <<"children">>}}, LogPrefix), 993 | ChildrenVal = erlfdb_tuple:pack(Children), 994 | erlfdb:set(TxObj, ChildrenKey, ChildrenVal), 995 | 996 | St; 997 | 998 | execute_dir(_TxObj, St, Dir, <<"DIRECTORY_STRIP_PREFIX">>) -> 999 | ToStrip = stack_pop(St), 1000 | Mod = get_dir_or_ss_mod(Dir), 1001 | DirKey = Mod:key(Dir), 1002 | DKLen = size(DirKey), 1003 | case ToStrip of 1004 | _ when not is_binary(ToStrip) -> 1005 | erlang:error({erlfdb_directory, prefix_not_a_binary}); 1006 | <> -> 1007 | stack_push(St, Rest); 1008 | _ -> 1009 | erlang:error({erlfdb_directory, {invalid_prefix_strip, ToStrip, DirKey}}) 1010 | end, 1011 | St; 1012 | 1013 | execute_dir(_TxObj, _St, _Dir, UnknownOp) -> 1014 | erlang:error({unknown_directory_op, UnknownOp}). 1015 | 1016 | 1017 | get_dir_or_ss_mod(Subspace) when element(1, Subspace) == erlfdb_subspace -> 1018 | erlfdb_subspace; 1019 | get_dir_or_ss_mod(#{}) -> 1020 | erlfdb_directory. 1021 | 1022 | 1023 | maybe_cover_compile() -> 1024 | case os:getenv("COVER_ENABLED") of 1025 | Cover when Cover == false; Cover == "" -> 1026 | ok; 1027 | _ -> 1028 | cover:compile_beam_directory(code:lib_dir(erlfdb, ebin)) 1029 | end. 1030 | 1031 | maybe_write_coverdata(Prefix, APIVsn) -> 1032 | case os:getenv("COVER_ENABLED") of 1033 | Cover when Cover == false; Cover == "" -> 1034 | ok; 1035 | _ -> 1036 | CoverDir = filename:join(code:lib_dir(erlfdb), "../../cover/"), 1037 | Filename = io_lib:format("bindingtest-~s-~s.coverdata", [Prefix, APIVsn]), 1038 | Path = filename:join(CoverDir, Filename), 1039 | filelib:ensure_dir(Path), 1040 | cover:export(Path) 1041 | end. 1042 | 1043 | 1044 | main([Prefix, APIVsn]) -> 1045 | main([Prefix, APIVsn, ""]); 1046 | 1047 | main([Prefix, APIVsn, ClusterFileStr]) -> 1048 | %% Prompt = io_lib:format("GDB Attach to: ~s~n", [os:getpid()]), 1049 | %% io:get_line(Prompt), 1050 | %% io:format("Running tests: ~s ~s ~s~n", [Prefix, APIVsn, ClusterFileStr]), 1051 | 1052 | maybe_cover_compile(), 1053 | application:set_env(erlfdb, api_version, list_to_integer(APIVsn)), 1054 | Db = erlfdb:open(iolist_to_binary(ClusterFileStr)), 1055 | init_run_loop(Db, iolist_to_binary(Prefix)), 1056 | maybe_write_coverdata(Prefix, APIVsn). 1057 | --------------------------------------------------------------------------------