├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE-APACHEv2 ├── LICENSE-MIT ├── README.md ├── config.nims ├── examples ├── .gitignore └── simple_example.nim ├── nim.cfg ├── rocksdb.nim ├── rocksdb.nimble ├── rocksdb ├── backup.nim ├── columnfamily.nim ├── columnfamily │ ├── cfdescriptor.nim │ ├── cfhandle.nim │ └── cfopts.nim ├── internal │ ├── cftable.nim │ └── utils.nim ├── lib │ ├── librocksdb.nim │ ├── rocksdb.h │ └── rocksdb_gen.nim ├── optimistictxdb.nim ├── options │ ├── backupopts.nim │ ├── cache.nim │ ├── dbopts.nim │ ├── readopts.nim │ ├── tableopts.nim │ └── writeopts.nim ├── rocksdb.nim ├── rocksiterator.nim ├── rocksresult.nim ├── snapshot.nim ├── sstfilewriter.nim ├── transactiondb.nim ├── transactions │ ├── otxopts.nim │ ├── transaction.nim │ ├── txdbopts.nim │ └── txopts.nim ├── writebatch.nim └── writebatchwi.nim ├── scripts ├── build_dlls_windows.bat ├── build_dlls_windows.sh ├── build_shared_deps_linux.sh ├── build_shared_deps_osx.sh ├── build_static_deps.sh ├── clean_build_artifacts.sh └── generate_wrapper.sh ├── tests ├── .gitignore ├── columnfamily │ ├── test_cfdescriptor.nim │ ├── test_cfhandle.nim │ └── test_cfopts.nim ├── internal │ └── test_cftable.nim ├── lib │ └── test_librocksdb.nim ├── options │ ├── test_backupopts.nim │ ├── test_dbopts.nim │ ├── test_readopts.nim │ ├── test_tableopts.nim │ └── test_writeopts.nim ├── test_all.nim ├── test_backup.nim ├── test_columnfamily.nim ├── test_helper.nim ├── test_optimistictxdb.nim ├── test_rocksdb.nim ├── test_rocksiterator.nim ├── test_sstfilewriter.nim ├── test_transactiondb.nim ├── test_writebatch.nim ├── test_writebatchwi.nim └── transactions │ ├── test_otxopts.nim │ ├── test_txdbopts.nim │ └── test_txopts.nim └── triplets ├── x64-linux-rocksdb.cmake ├── x64-osx-rocksdb.cmake └── x64-windows-rocksdb.cmake /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | nph: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Check `nph` formatting 15 | uses: arnetheduck/nph-action@v1 16 | with: 17 | version: 0.6.1 18 | build: 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | target: 23 | - os: linux 24 | cpu: amd64 25 | - os: macos 26 | cpu: amd64 27 | - os: windows 28 | cpu: amd64 29 | branch: [version-2-0, version-2-2, devel] 30 | include: 31 | - target: 32 | os: linux 33 | builder: ubuntu-latest 34 | shell: bash 35 | - target: 36 | os: macos 37 | builder: macos-13 38 | shell: bash 39 | - target: 40 | os: windows 41 | builder: windows-latest 42 | shell: msys2 {0} 43 | 44 | defaults: 45 | run: 46 | shell: ${{ matrix.shell }} 47 | 48 | name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})' 49 | runs-on: ${{ matrix.builder }} 50 | #continue-on-error: ${{ matrix.branch == 'version-1-6' || matrix.branch == 'devel' }} 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v4 54 | with: 55 | submodules: true 56 | 57 | - name: MSYS2 (Windows amd64) 58 | if: runner.os == 'Windows' && matrix.target.cpu == 'amd64' 59 | uses: msys2/setup-msys2@v2 60 | with: 61 | path-type: inherit 62 | install: >- 63 | base-devel 64 | git 65 | mingw-w64-x86_64-toolchain 66 | 67 | - name: Derive environment variables 68 | run: | 69 | if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then 70 | PLATFORM=x64 71 | else 72 | PLATFORM=x86 73 | fi 74 | echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV 75 | 76 | ncpu= 77 | MAKE_CMD="make" 78 | case '${{ runner.os }}' in 79 | 'Linux') 80 | ncpu=$(nproc) 81 | ;; 82 | 'macOS') 83 | ncpu=$(sysctl -n hw.ncpu) 84 | ;; 85 | 'Windows') 86 | ncpu=$NUMBER_OF_PROCESSORS 87 | MAKE_CMD="mingw32-make" 88 | ;; 89 | esac 90 | [[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1 91 | echo "ncpu=$ncpu" >> $GITHUB_ENV 92 | echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV 93 | 94 | - name: Build Nim and Nimble 95 | run: | 96 | curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh 97 | env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \ 98 | QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \ 99 | bash build_nim.sh nim csources dist/nimble NimBinaries 100 | echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH 101 | 102 | - name: Run tests 103 | run: | 104 | export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib" 105 | if [[ "${{ matrix.target.os }}" == "windows" ]]; then 106 | # https://github.com/status-im/nimbus-eth2/issues/3121 107 | export NIMFLAGS="-d:nimRawSetjmp" 108 | fi 109 | 110 | nim --version 111 | nimble --version 112 | nimble install -y 113 | nimble test 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | 3 | # Executables shall be put in an ignored build/ directory 4 | build/ 5 | 6 | nimble.paths 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/rocksdb"] 2 | path = vendor/rocksdb 3 | url = https://github.com/facebook/rocksdb 4 | ignore = dirty 5 | branch = master 6 | [submodule "vendor/vcpkg"] 7 | path = vendor/vcpkg 8 | url = https://github.com/microsoft/vcpkg 9 | ignore = dirty 10 | branch = master 11 | -------------------------------------------------------------------------------- /LICENSE-APACHEv2: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Status Research & Development GmbH 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Status Research & Development GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nim-RocksDb 2 | 3 | ![Github action](https://github.com/status-im/nim-rocksdb/workflows/CI/badge.svg) 4 | [![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 6 | ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) 7 | 8 | A Nim wrapper for [Facebook's RocksDB](https://github.com/facebook/rocksdb), a persistent key-value store for Flash and RAM Storage. 9 | 10 | ## Current status 11 | 12 | Nim-RocksDb provides a wrapper for the low-level functions in the librocksdb c 13 | library. 14 | 15 | ## Installation 16 | 17 | Nim-RocksDb requires Nim and the Nimble package manager. For Windows you will 18 | need Visual Studio 2015 Update 3 or greater with the English language pack. 19 | 20 | To get started run: 21 | ``` 22 | nimble install rocksdb 23 | ``` 24 | 25 | This will download and install the RocksDB libraries for your platform and copy 26 | them into the `build/` directory of the project. On Linux and MacOS only static 27 | linking to the RocksDb libraries is supported and on Windows only dynamic linking 28 | is supported. 29 | 30 | On Windows you may want to copy the dll into another location or set your PATH 31 | to include the `build/` directory so that your application can find the dll on 32 | startup. 33 | 34 | ### Compression libraries 35 | 36 | RocksDb supports using a number of compression libraries. This library builds 37 | and only supports the following compression libraries: 38 | - lz4 39 | - zstd 40 | 41 | On Linux and MacOS these libraries are staticly linked into the final binary 42 | along with the RocksDb static library. On Windows they are staticly linked into 43 | the RocksDb dll. 44 | 45 | 46 | ### Static linking 47 | 48 | On Linux and MacOS your Nim program will need to use the C++ linker profile 49 | because RocksDb is a C++ library. For example: 50 | 51 | ``` 52 | when defined(macosx): 53 | switch("clang.linkerexe", "clang++") 54 | when defined(linux): 55 | switch("gcc.linkerexe", "g++") 56 | ``` 57 | 58 | Note that static linking is currently not supported on windows. 59 | 60 | ## Usage 61 | 62 | See [simple_example](examples/simple_example.nim) 63 | 64 | ### Contribution 65 | 66 | Any contribution intentionally submitted for inclusion in the work by you shall 67 | be dual licensed as above, without any additional terms or conditions. 68 | 69 | ## Versioning 70 | 71 | The library generally follows the upstream RocksDb version number, adding one 72 | more number for tracking changes to the Nim wrapper itself. 73 | 74 | ## License 75 | 76 | ### Wrapper License 77 | 78 | This repository is licensed and distributed under either of 79 | 80 | * MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT 81 | 82 | or 83 | 84 | * Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) 85 | 86 | at your option. This file may not be copied, modified, or distributed except 87 | according to those terms. 88 | 89 | ### Dependency License 90 | 91 | RocksDB is developed and maintained by Facebook Database Engineering Team. 92 | It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com) 93 | and Jeff Dean (jeff@google.com) 94 | 95 | RocksDB is dual-licensed under both the [GPLv2](https://github.com/facebook/rocksdb/blob/master/COPYING) and Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0). You may select, at your option, one of the above-listed licenses. 96 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | # nim-rocksdb 2 | # Copyright (c) 2019-2025 Status Research & Development GmbH 3 | # Licensed under either of 4 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) 5 | # * MIT license ([LICENSE-MIT](LICENSE-MIT)) 6 | # at your option. 7 | # This file may not be copied, modified, or distributed except according to 8 | # those terms. 9 | 10 | # begin Nimble config (version 1) 11 | when fileExists("nimble.paths"): 12 | include "nimble.paths" 13 | # end Nimble config 14 | 15 | when not defined(rocksdb_dynamic_linking) and not defined(windows): 16 | # use the C++ linker profile because it's a C++ library 17 | when defined(macosx): 18 | switch("clang.linkerexe", "clang++") 19 | else: 20 | switch("gcc.linkerexe", "g++") 21 | 22 | --styleCheck: 23 | usages 24 | --styleCheck: 25 | error 26 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all executable files 2 | * 3 | !*.* 4 | !*/ 5 | *.exe 6 | 7 | -------------------------------------------------------------------------------- /examples/simple_example.nim: -------------------------------------------------------------------------------- 1 | import ../rocksdb/lib/librocksdb, cpuinfo 2 | 3 | const 4 | dbPath: cstring = "/tmp/rocksdb_simple_example" 5 | dbBackupPath: cstring = "/tmp/rocksdb_simple_example_backup" 6 | 7 | proc main() = 8 | var 9 | db: ptr rocksdb_t 10 | be: ptr rocksdb_backup_engine_t 11 | options = rocksdb_options_create() 12 | # Optimize RocksDB. This is the easiest way to 13 | # get RocksDB to perform well 14 | let cpus = countProcessors() 15 | rocksdb_options_increase_parallelism(options, cpus.int32) 16 | # This requires snappy - disabled because rocksdb is not always compiled with 17 | # snappy support (for example Fedora 28, certain Ubuntu versions) 18 | # rocksdb_options_optimize_level_style_compaction(options, 0); 19 | # create the DB if it's not already present 20 | rocksdb_options_set_create_if_missing(options, 1) 21 | 22 | var # open DB 23 | err: cstring # memory leak: example code does not free error string! 24 | db = rocksdb_open(options, dbPath, cast[cstringArray](err.addr)) 25 | doAssert err.isNil, $err 26 | 27 | # open Backup Engine that we will use for backing up our database 28 | be = rocksdb_backup_engine_open(options, dbBackupPath, cast[cstringArray](err.addr)) 29 | doAssert err.isNil, $err 30 | 31 | # Put key-value 32 | var writeOptions = rocksdb_writeoptions_create() 33 | let key = "key" 34 | let put_value = "value" 35 | rocksdb_put( 36 | db, 37 | writeOptions, 38 | key.cstring, 39 | key.len.csize_t, 40 | put_value.cstring, 41 | put_value.len.csize_t, 42 | cast[cstringArray](err.addr), 43 | ) 44 | doAssert err.isNil, $err 45 | 46 | # Get value 47 | var readOptions = rocksdb_readoptions_create() 48 | var len: csize_t 49 | let raw_value = rocksdb_get( 50 | db, 51 | readOptions, 52 | key.cstring, 53 | key.len.csize_t, 54 | addr len, 55 | cast[cstringArray](err.addr), 56 | ) # Important: rocksdb_get is not null-terminated 57 | doAssert err.isNil, $err 58 | 59 | # Copy it to a regular Nim string (copyMem workaround because raw value is NOT null-terminated) 60 | var get_value = newString(len.int) 61 | copyMem(addr get_value[0], unsafeAddr raw_value[0], len.int * sizeof(char)) 62 | 63 | doAssert get_value == put_value 64 | 65 | # create new backup in a directory specified by DBBackupPath 66 | rocksdb_backup_engine_create_new_backup(be, db, cast[cstringArray](err.addr)) 67 | doAssert err.isNil, $err 68 | 69 | rocksdb_close(db) 70 | 71 | # If something is wrong, you might want to restore data from last backup 72 | var restoreOptions = rocksdb_restore_options_create() 73 | rocksdb_backup_engine_restore_db_from_latest_backup( 74 | be, dbPath, dbPath, restoreOptions, cast[cstringArray](err.addr) 75 | ) 76 | doAssert err.isNil, $err 77 | rocksdb_restore_options_destroy(restore_options) 78 | 79 | db = rocksdb_open(options, dbPath, cast[cstringArray](err.addr)) 80 | doAssert err.isNil, $err 81 | 82 | # cleanup 83 | rocksdb_writeoptions_destroy(writeOptions) 84 | rocksdb_readoptions_destroy(readOptions) 85 | rocksdb_options_destroy(options) 86 | rocksdb_backup_engine_close(be) 87 | rocksdb_close(db) 88 | 89 | main() 90 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --threads:on 2 | --outdir:build 3 | --hints:off 4 | -------------------------------------------------------------------------------- /rocksdb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2018-2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | import 11 | ./rocksdb/[ 12 | backup, columnfamily, optimistictxdb, rocksdb, rocksiterator, sstfilewriter, 13 | transactiondb, writebatch, writebatchwi, 14 | ] 15 | 16 | export 17 | backup, columnfamily, optimistictxdb, rocksdb, rocksiterator, sstfilewriter, 18 | transactiondb, writebatch, writebatchwi 19 | -------------------------------------------------------------------------------- /rocksdb.nimble: -------------------------------------------------------------------------------- 1 | packageName = "rocksdb" 2 | version = "10.2.1.0" 3 | author = "Status Research & Development GmbH" 4 | description = 5 | "A wrapper for Facebook's RocksDB, an embeddable, persistent key-value store for fast storage" 6 | license = "Apache License 2.0 or GPLv2" 7 | skipDirs = @["examples", "tests"] 8 | mode = ScriptMode.Verbose 9 | installDirs = @["build"] 10 | 11 | ### Dependencies 12 | requires "nim >= 2.0", "results", "tempfile", "unittest2" 13 | 14 | template build() = 15 | when defined(windows): 16 | exec ".\\scripts\\build_dlls_windows.bat" 17 | else: 18 | exec "scripts/build_static_deps.sh" 19 | 20 | before install: 21 | build() 22 | 23 | task format, "Format nim code using nph": 24 | exec "nimble install nph" 25 | exec "nph ." 26 | 27 | task test, "Run tests": 28 | build() 29 | when defined(windows): 30 | exec "nim c -d:nimDebugDlOpen -r --threads:on tests/test_all.nim" 31 | else: 32 | exec "nim c -r --threads:on tests/test_all.nim" 33 | -------------------------------------------------------------------------------- /rocksdb/backup.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `BackupEngineRef` is used to create and manage backups against a RocksDB database. 11 | 12 | {.push raises: [].} 13 | 14 | import 15 | ./lib/librocksdb, ./internal/utils, ./options/backupopts, ./rocksdb, ./rocksresult 16 | 17 | export backupopts, rocksdb, rocksresult 18 | 19 | type 20 | BackupEnginePtr* = ptr rocksdb_backup_engine_t 21 | EnvPtr = ptr rocksdb_env_t 22 | 23 | BackupEngineRef* = ref object 24 | cPtr: BackupEnginePtr 25 | env: EnvPtr 26 | path: string 27 | backupOpts: BackupEngineOptionsRef 28 | 29 | proc openBackupEngine*( 30 | path: string, backupOpts = defaultBackupEngineOptions(path, autoClose = true) 31 | ): RocksDBResult[BackupEngineRef] = 32 | ## Create a new backup engine. The `path` parameter is the path of the backup 33 | ## directory. Note that the same directory should not be used for both backups 34 | ## and the database itself. 35 | ## 36 | ## If no `backupOpts` are provided, the default options will be used. These 37 | ## default backup options will be closed when the backup engine is closed. 38 | ## If `backupOpts` are provided, they will need to be closed manually. 39 | 40 | let env = rocksdb_create_default_env() 41 | var errors: cstring 42 | let backupEnginePtr = rocksdb_backup_engine_open_opts( 43 | backupOpts.cPtr, env, cast[cstringArray](errors.addr) 44 | ) 45 | bailOnErrorsWithCleanup(errors): 46 | autoCloseNonNil(backupOpts) 47 | rocksdb_env_destroy(env) 48 | 49 | let engine = 50 | BackupEngineRef(cPtr: backupEnginePtr, path: path, backupOpts: backupOpts) 51 | ok(engine) 52 | 53 | proc isClosed*(backupEngine: BackupEngineRef): bool {.inline.} = 54 | ## Returns `true` if the `BackupEngineRef` has been closed. 55 | backupEngine.cPtr.isNil() 56 | 57 | proc createNewBackup*( 58 | backupEngine: BackupEngineRef, db: RocksDbRef 59 | ): RocksDBResult[void] = 60 | ## Create a new backup of the database. 61 | doAssert not backupEngine.isClosed() 62 | doAssert not db.isClosed() 63 | 64 | var errors: cstring 65 | rocksdb_backup_engine_create_new_backup( 66 | backupEngine.cPtr, db.cPtr, cast[cstringArray](errors.addr) 67 | ) 68 | bailOnErrors(errors) 69 | 70 | ok() 71 | 72 | proc restoreDbFromLatestBackup*( 73 | backupEngine: BackupEngineRef, dbDir: string, walDir = dbDir, keepLogFiles = false 74 | ): RocksDBResult[void] = 75 | ## Restore the database from the latest backup. 76 | doAssert not backupEngine.isClosed() 77 | 78 | let restoreOptions = rocksdb_restore_options_create() 79 | rocksdb_restore_options_set_keep_log_files(restoreOptions, keepLogFiles.cint) 80 | 81 | var errors: cstring 82 | rocksdb_backup_engine_restore_db_from_latest_backup( 83 | backupEngine.cPtr, 84 | dbDir.cstring, 85 | walDir.cstring, 86 | restoreOptions, 87 | cast[cstringArray](errors.addr), 88 | ) 89 | bailOnErrors(errors) 90 | 91 | rocksdb_restore_options_destroy(restoreOptions) 92 | 93 | ok() 94 | 95 | proc close*(backupEngine: BackupEngineRef) = 96 | ## Close the `BackupEngineRef`. 97 | if not backupEngine.isClosed(): 98 | rocksdb_backup_engine_close(backupEngine.cPtr) 99 | backupEngine.cPtr = nil 100 | 101 | if not backupEngine.env.isNil(): 102 | rocksdb_env_destroy(backupEngine.env) 103 | backupEngine.env = nil 104 | 105 | autoCloseNonNil(backupEngine.backupOpts) 106 | -------------------------------------------------------------------------------- /rocksdb/columnfamily.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## `ColFamilyReadOnly` and `ColFamilyReadWrite` types both hold a reference to a 11 | ## `RocksDbReadOnlyRef` or `RocksDbReadWriteRef` respectively. They are convenience 12 | ## types which enable writing to a specific column family without having to specify the 13 | ## column family in each call. 14 | ## 15 | ## These column family types do not own the underlying `RocksDbRef` and therefore 16 | ## to close the database, simply call `columnFamily.db.close()` which will close 17 | ## the underlying `RocksDbRef`. Note that doing so will also impact any other column 18 | ## families that hold a reference to the same `RocksDbRef`. 19 | 20 | {.push raises: [].} 21 | 22 | import ./rocksdb 23 | import ./columnfamily/cfhandle, ./rocksdb 24 | 25 | export rocksdb 26 | 27 | type 28 | ColFamilyReadOnly* = object 29 | db: RocksDbReadOnlyRef 30 | name: string 31 | handle: ColFamilyHandleRef 32 | 33 | ColFamilyReadWrite* = object 34 | db: RocksDbReadWriteRef 35 | name: string 36 | handle: ColFamilyHandleRef 37 | 38 | proc getColFamily*( 39 | db: RocksDbReadOnlyRef, name: string 40 | ): RocksDBResult[ColFamilyReadOnly] = 41 | ## Creates a new `ColFamilyReadOnly` from the given `RocksDbReadOnlyRef` and 42 | ## column family name. 43 | doAssert not db.isClosed() 44 | 45 | ok(ColFamilyReadOnly(db: db, name: name, handle: ?db.getColFamilyHandle(name))) 46 | 47 | proc getColFamily*( 48 | db: RocksDbReadWriteRef, name: string 49 | ): RocksDBResult[ColFamilyReadWrite] = 50 | ## Create a new `ColFamilyReadWrite` from the given `RocksDbReadWriteRef` and 51 | ## column family name. 52 | doAssert not db.isClosed() 53 | 54 | ok(ColFamilyReadWrite(db: db, name: name, handle: ?db.getColFamilyHandle(name))) 55 | 56 | proc db*(cf: ColFamilyReadOnly | ColFamilyReadWrite): auto {.inline.} = 57 | ## Returns the underlying `RocksDbReadOnlyRef` or `RocksDbReadWriteRef`. 58 | cf.db 59 | 60 | proc name*(cf: ColFamilyReadOnly | ColFamilyReadWrite): string {.inline.} = 61 | ## Returns the name of the column family. 62 | cf.name 63 | 64 | proc handle*( 65 | cf: ColFamilyReadOnly | ColFamilyReadWrite 66 | ): ColFamilyHandleRef {.inline.} = 67 | ## Returns the name of the column family. 68 | cf.handle 69 | 70 | proc get*( 71 | cf: ColFamilyReadOnly | ColFamilyReadWrite, key: openArray[byte], onData: DataProc 72 | ): RocksDBResult[bool] {.inline.} = 73 | ## Gets the value of the given key from the column family using the `onData` 74 | ## callback. 75 | cf.db.get(key, onData, cf.handle) 76 | 77 | proc get*( 78 | cf: ColFamilyReadOnly | ColFamilyReadWrite, key: openArray[byte] 79 | ): RocksDBResult[seq[byte]] {.inline.} = 80 | ## Gets the value of the given key from the column family. 81 | cf.db.get(key, cf.handle) 82 | 83 | proc put*( 84 | cf: ColFamilyReadWrite, key, val: openArray[byte] 85 | ): RocksDBResult[void] {.inline.} = 86 | ## Puts a value for the given key into the column family. 87 | cf.db.put(key, val, cf.handle) 88 | 89 | proc keyExists*( 90 | cf: ColFamilyReadOnly | ColFamilyReadWrite, key: openArray[byte] 91 | ): RocksDBResult[bool] {.inline.} = 92 | ## Checks if the given key exists in the column family. 93 | cf.db.keyExists(key, cf.handle) 94 | 95 | proc delete*( 96 | cf: ColFamilyReadWrite, key: openArray[byte] 97 | ): RocksDBResult[void] {.inline.} = 98 | ## Deletes the given key from the column family. 99 | cf.db.delete(key, cf.handle) 100 | 101 | proc openIterator*( 102 | cf: ColFamilyReadOnly | ColFamilyReadWrite, 103 | readOpts = defaultReadOptions(autoClose = true), 104 | ): RocksDBResult[RocksIteratorRef] {.inline.} = 105 | ## Opens an `RocksIteratorRef` for the given column family. 106 | cf.db.openIterator(readOpts, cf.handle) 107 | 108 | proc openWriteBatch*(cf: ColFamilyReadWrite): WriteBatchRef {.inline.} = 109 | ## Opens a `WriteBatchRef` for the given column family. 110 | cf.db.openWriteBatch(cf.handle) 111 | 112 | proc openWriteBatchWithIndex*( 113 | cf: ColFamilyReadWrite, reservedBytes = 0, overwriteKey = false 114 | ): WriteBatchWIRef {.inline.} = 115 | ## Opens a `WriteBatchRef` for the given column family. 116 | cf.db.openWriteBatchWithIndex(reservedBytes, overwriteKey, cf.handle) 117 | 118 | proc write*( 119 | cf: ColFamilyReadWrite, updates: WriteBatchRef | WriteBatchWIRef 120 | ): RocksDBResult[void] {.inline.} = 121 | ## Writes the updates in the `WriteBatchRef` to the column family. 122 | cf.db.write(updates) 123 | -------------------------------------------------------------------------------- /rocksdb/columnfamily/cfdescriptor.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ./cfopts 13 | 14 | export cfopts 15 | 16 | const DEFAULT_COLUMN_FAMILY_NAME* = "default" 17 | 18 | type ColFamilyDescriptor* = object 19 | name: string 20 | options: ColFamilyOptionsRef 21 | 22 | proc initColFamilyDescriptor*( 23 | name: string, options: ColFamilyOptionsRef 24 | ): ColFamilyDescriptor = 25 | ColFamilyDescriptor(name: name, options: options) 26 | 27 | proc name*(descriptor: ColFamilyDescriptor): string {.inline.} = 28 | descriptor.name 29 | 30 | proc options*(descriptor: ColFamilyDescriptor): ColFamilyOptionsRef {.inline.} = 31 | descriptor.options 32 | 33 | proc autoClose*(descriptor: ColFamilyDescriptor): bool {.inline.} = 34 | descriptor.options.autoClose 35 | 36 | proc isDefault*(descriptor: ColFamilyDescriptor): bool {.inline.} = 37 | descriptor.name == DEFAULT_COLUMN_FAMILY_NAME 38 | 39 | proc defaultColFamilyDescriptor*(autoClose = false): ColFamilyDescriptor {.inline.} = 40 | initColFamilyDescriptor( 41 | DEFAULT_COLUMN_FAMILY_NAME, defaultColFamilyOptions(autoClose = autoClose) 42 | ) 43 | 44 | proc isClosed*(descriptor: ColFamilyDescriptor): bool {.inline.} = 45 | descriptor.options.isClosed() 46 | 47 | proc close*(descriptor: ColFamilyDescriptor) {.inline.} = 48 | descriptor.options.close() 49 | -------------------------------------------------------------------------------- /rocksdb/columnfamily/cfhandle.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | ColFamilyHandlePtr* = ptr rocksdb_column_family_handle_t 16 | 17 | ColFamilyHandleRef* = ref object 18 | cPtr: ColFamilyHandlePtr 19 | 20 | proc newColFamilyHandle*(cPtr: ColFamilyHandlePtr): ColFamilyHandleRef = 21 | ColFamilyHandleRef(cPtr: cPtr) 22 | 23 | proc isClosed*(handle: ColFamilyHandleRef): bool {.inline.} = 24 | handle.cPtr.isNil() 25 | 26 | proc cPtr*(handle: ColFamilyHandleRef): ColFamilyHandlePtr = 27 | doAssert not handle.isClosed() 28 | handle.cPtr 29 | 30 | proc close*(handle: ColFamilyHandleRef) = 31 | if not handle.isClosed(): 32 | rocksdb_column_family_handle_destroy(handle.cPtr) 33 | handle.cPtr = nil 34 | -------------------------------------------------------------------------------- /rocksdb/columnfamily/cfopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb, ../internal/utils, ../options/tableopts 13 | 14 | export tableopts 15 | 16 | type 17 | SlicetransformPtr* = ptr rocksdb_slicetransform_t 18 | 19 | SlicetransformRef* = ref object 20 | cPtr: SlicetransformPtr 21 | 22 | ColFamilyOptionsPtr* = ptr rocksdb_options_t 23 | 24 | ColFamilyOptionsRef* = ref object 25 | # In the C API, both family and database options are exposed using the same 26 | # type - CF options are a subset of rocksdb_options_t - when in doubt, check: 27 | # https://github.com/facebook/rocksdb/blob/b8c9a2576af6a1d0ffcfbb517dfcb7e7037bd460/include/rocksdb/options.h#L66 28 | cPtr: ColFamilyOptionsPtr 29 | tableOpts: TableOptionsRef 30 | autoClose*: bool # if true then close will be called when the database is closed 31 | 32 | Compression* {.pure.} = enum 33 | # Use a slightly clunky name here to avoid global symbol conflicts 34 | noCompression = rocksdb_no_compression 35 | snappyCompression = rocksdb_snappy_compression 36 | zlibCompression = rocksdb_zlib_compression 37 | bz2Compression = rocksdb_bz2_compression 38 | lz4Compression = rocksdb_lz4_compression 39 | lz4hcCompression = rocksdb_lz4hc_compression 40 | xpressCompression = rocksdb_xpress_compression 41 | zstdCompression = rocksdb_zstd_compression 42 | 43 | proc createFixedPrefix*(value: int): SlicetransformRef = 44 | SlicetransformRef(cPtr: rocksdb_slicetransform_create_fixed_prefix(value.csize_t)) 45 | 46 | proc isClosed*(s: SlicetransformRef): bool {.inline.} = 47 | s.cPtr.isNil() 48 | 49 | proc cPtr*(s: SlicetransformRef): SlicetransformPtr = 50 | doAssert not s.isClosed() 51 | s.cPtr 52 | 53 | proc close*(s: SlicetransformRef) = 54 | if not s.isClosed(): 55 | rocksdb_slicetransform_destroy(s.cPtr) 56 | s.cPtr = nil 57 | 58 | proc createColFamilyOptions*(autoClose = false): ColFamilyOptionsRef = 59 | ColFamilyOptionsRef(cPtr: rocksdb_options_create(), autoClose: autoClose) 60 | 61 | proc isClosed*(cfOpts: ColFamilyOptionsRef): bool {.inline.} = 62 | cfOpts.cPtr.isNil() 63 | 64 | proc cPtr*(cfOpts: ColFamilyOptionsRef): ColFamilyOptionsPtr = 65 | doAssert not cfOpts.isClosed() 66 | cfOpts.cPtr 67 | 68 | proc close*(cfOpts: ColFamilyOptionsRef) = 69 | if not cfOpts.isClosed(): 70 | rocksdb_options_destroy(cfOpts.cPtr) 71 | cfOpts.cPtr = nil 72 | 73 | autoCloseNonNil(cfOpts.tableOpts) 74 | 75 | template opt(nname, ntyp, ctyp: untyped) = 76 | proc `nname=`*(cfOpts: ColFamilyOptionsRef, value: ntyp) = 77 | doAssert not cfOpts.isClosed 78 | `rocksdb_options_set nname`(cfOpts.cPtr, value.ctyp) 79 | 80 | proc `nname`*(cfOpts: ColFamilyOptionsRef): ntyp = 81 | doAssert not cfOpts.isClosed 82 | ntyp `rocksdb_options_get nname`(cfOpts.cPtr) 83 | 84 | opt writeBufferSize, int, csize_t 85 | opt compression, Compression, cint 86 | opt bottommostCompression, Compression, cint 87 | opt level0FileNumCompactionTrigger, int, cint 88 | opt maxBytesForLevelBase, int, uint64 89 | opt disableAutoCompactions, bool, cint 90 | 91 | opt maxWriteBufferNumber, int, cint 92 | opt minWriteBufferNumberToMerge, int, cint 93 | opt maxWriteBufferSizeToMaintain, int, int64 94 | opt inplaceUpdateSupport, bool, uint8 95 | opt inplaceUpdateNumLocks, int, csize_t 96 | opt memtablePrefixBloomSizeRatio, float, cdouble 97 | opt memtableHugePageSize, int, csize_t 98 | opt bloomLocality, int, uint32 99 | opt arenaBlockSize, int, csize_t 100 | opt numLevels, int, cint 101 | opt level0SlowdownWritesTrigger, int, cint 102 | opt level0StopWritesTrigger, int, cint 103 | opt targetFileSizeBase, int, uint64 104 | opt targetFileSizeMultiplier, int, cint 105 | opt maxBytesForLevelMultiplier, float, cdouble 106 | opt maxCompactionBytes, int, uint64 107 | opt softPendingCompactionBytesLimit, int, csize_t 108 | opt hardPendingCompactionBytesLimit, int, csize_t 109 | opt maxSequentialSkipInIterations, int, uint64 110 | opt maxSuccessiveMerges, int, csize_t 111 | opt optimizeFiltersForHits, bool, cint 112 | opt paranoidChecks, bool, uint8 113 | opt reportBgIoStats, bool, cint 114 | opt ttl, int, uint64 115 | opt periodicCompactionSeconds, int, uint64 116 | opt enableBlobFiles, bool, uint8 117 | opt minBlobSize, int, uint64 118 | opt blobFileSize, int, uint64 119 | opt blobCompressionType, Compression, cint 120 | opt enableBlobGC, bool, uint8 121 | opt blobGCAgeCutoff, float, cdouble 122 | opt blobGCForceThreshold, float, cdouble 123 | opt blobCompactionReadaheadSize, int, uint64 124 | opt blobFileStartingLevel, int, cint 125 | 126 | opt compressionOptionsZstdMaxTrainBytes, int, cint 127 | opt compressionOptionsUseZstdDictTrainer, bool, uint8 128 | opt compressionOptionsParallelThreads, int, cint 129 | opt compressionOptionsMaxDictBufferBytes, int, uint64 130 | 131 | proc defaultColFamilyOptions*(autoClose = false): ColFamilyOptionsRef = 132 | createColFamilyOptions(autoClose) 133 | 134 | # proc setFixedPrefixExtractor*(dbOpts: ColFamilyOptionsRef, length: int) = 135 | # doAssert not dbOpts.isClosed() 136 | # rocksdb_options_set_prefix_extractor( 137 | # dbOpts.cPtr, rocksdb_slicetransform_create_fixed_prefix(length.csize_t)) 138 | 139 | proc `setPrefixExtractor`*(cfOpts: ColFamilyOptionsRef, value: SlicetransformRef) = 140 | doAssert not cfOpts.isClosed() 141 | 142 | # Destroys the existing slice transform if there is one attached to the column 143 | # family options and takes ownership of the passed slice transform. After this call, 144 | # the ColFamilyOptionsRef is responsible for cleaning up the policy when it is no 145 | # longer needed so we set the filter policy to nil so that isClosed() will return true 146 | # and prevent the filter policy from being double freed which was causing a seg fault. 147 | rocksdb_options_set_prefix_extractor(cfOpts.cPtr, value.cPtr) 148 | value.cPtr = nil 149 | 150 | proc `blockBasedTableFactory=`*( 151 | cfOpts: ColFamilyOptionsRef, tableOpts: TableOptionsRef 152 | ) = 153 | doAssert not cfOpts.isClosed() 154 | doAssert cfOpts.tableOpts.isNil() 155 | # don't allow overwriting an existing tableOpts which could leak memory 156 | 157 | rocksdb_options_set_block_based_table_factory(cfOpts.cPtr, tableOpts.cPtr) 158 | cfOpts.tableOpts = tableOpts 159 | 160 | # https://github.com/facebook/rocksdb/wiki/MemTable 161 | proc setHashSkipListRep*( 162 | cfOpts: ColFamilyOptionsRef, 163 | bucketCount, skipListHeight, skipListBranchingFactor: int, 164 | ) = 165 | doAssert not cfOpts.isClosed() 166 | rocksdb_options_set_hash_skip_list_rep( 167 | cfOpts.cPtr, bucketCount.csize_t, skipListHeight.cint, skipListBranchingFactor.cint 168 | ) 169 | 170 | proc setHashLinkListRep*(cfOpts: ColFamilyOptionsRef, bucketCount: int) = 171 | doAssert not cfOpts.isClosed() 172 | rocksdb_options_set_hash_link_list_rep(cfOpts.cPtr, bucketCount.csize_t) 173 | 174 | proc setMemtableVectorRep*(cfOpts: ColFamilyOptionsRef) = 175 | doAssert not cfOpts.isClosed() 176 | rocksdb_options_set_memtable_vector_rep(cfOpts.cPtr) 177 | 178 | proc `memtableWholeKeyFiltering=`*(dbOpts: ColFamilyOptionsRef, value: bool) = 179 | doAssert not dbOpts.isClosed() 180 | rocksdb_options_set_memtable_whole_key_filtering(dbOpts.cPtr, value.uint8) 181 | 182 | proc setCompressionOptions*( 183 | cfOpts: ColFamilyOptionsRef, 184 | windowBits = -15, 185 | level = 32767, 186 | strategy: int = 0, 187 | maxDictBytes: int = 0, 188 | ) = 189 | doAssert not cfOpts.isClosed() 190 | rocksdb_options_set_compression_options( 191 | cfOpts.cPtr, windowBits.cint, level.cint, strategy.cint, maxDictBytes.cint 192 | ) 193 | 194 | proc setBottommostCompressionOptions*( 195 | cfOpts: ColFamilyOptionsRef, 196 | windowBits = -15, 197 | level = 32767, 198 | strategy: int = 0, 199 | maxDictBytes: int = 0, 200 | enabled: bool = true, 201 | ) = 202 | doAssert not cfOpts.isClosed() 203 | rocksdb_options_set_bottommost_compression_options( 204 | cfOpts.cPtr, windowBits.cint, level.cint, strategy.cint, maxDictBytes.cint, 205 | enabled.uint8, 206 | ) 207 | 208 | proc `bottommostCompressionOptionsZstdMaxTrainBytes=`*( 209 | dbOpts: ColFamilyOptionsRef, value: int 210 | ) = 211 | doAssert not dbOpts.isClosed() 212 | rocksdb_options_set_bottommost_compression_options_zstd_max_train_bytes( 213 | dbOpts.cPtr, value.cint, 1 214 | ) 215 | 216 | proc `bottommostCompressionOptionsUseZstdDictTrainer=`*( 217 | dbOpts: ColFamilyOptionsRef, value: bool 218 | ) = 219 | doAssert not dbOpts.isClosed() 220 | rocksdb_options_set_bottommost_compression_options_use_zstd_dict_trainer( 221 | dbOpts.cPtr, value.uint8, 1 222 | ) 223 | 224 | proc `bottommostCompressionOptionsMaxDictBufferBytes=`*( 225 | dbOpts: ColFamilyOptionsRef, value: int 226 | ) = 227 | doAssert not dbOpts.isClosed() 228 | rocksdb_options_set_bottommost_compression_options_max_dict_buffer_bytes( 229 | dbOpts.cPtr, value.uint64, 1 230 | ) 231 | -------------------------------------------------------------------------------- /rocksdb/internal/cftable.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import std/tables, ../columnfamily/cfhandle 13 | 14 | export cfhandle 15 | 16 | type ColFamilyTableRef* = ref object 17 | columnFamilies: TableRef[string, ColFamilyHandleRef] 18 | 19 | proc newColFamilyTable*( 20 | names: openArray[string], handles: openArray[ColFamilyHandlePtr] 21 | ): ColFamilyTableRef = 22 | doAssert names.len() == handles.len() 23 | 24 | let cfTable = newTable[string, ColFamilyHandleRef]() 25 | for i, name in names: 26 | cfTable[name] = newColFamilyHandle(handles[i]) 27 | 28 | ColFamilyTableRef(columnFamilies: cfTable) 29 | 30 | proc isClosed*(table: ColFamilyTableRef): bool {.inline.} = 31 | table.columnFamilies.isNil() 32 | 33 | proc get*(table: ColFamilyTableRef, name: string): ColFamilyHandleRef {.inline.} = 34 | table.columnFamilies.getOrDefault(name) 35 | 36 | proc close*(table: ColFamilyTableRef) = 37 | if not table.isClosed(): 38 | for _, v in table.columnFamilies.mpairs(): 39 | v.close() 40 | table.columnFamilies = nil 41 | -------------------------------------------------------------------------------- /rocksdb/internal/utils.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import std/locks, ../lib/librocksdb 13 | 14 | proc createLock*(): Lock = 15 | var lock = Lock() 16 | initLock(lock) 17 | lock 18 | 19 | template autoCloseNonNil*(closable: typed) = 20 | if not closable.isNil and closable.autoClose: 21 | closable.close() 22 | 23 | template autoCloseAll*(closables: openArray[typed]) = 24 | for c in closables: 25 | if c.autoClose: 26 | c.close() 27 | 28 | template bailOnErrorsWithCleanup*(errors: cstring, cleanup: untyped): auto = 29 | if not errors.isNil: 30 | cleanup 31 | 32 | let res = err($(errors)) 33 | rocksdb_free(errors) 34 | return res 35 | 36 | template bailOnErrors*(errors: cstring): auto = 37 | if not errors.isNil: 38 | let res = err($(errors)) 39 | rocksdb_free(errors) 40 | return res 41 | 42 | template unsafeAddrOrNil*(s: openArray[byte]): auto = 43 | if s.len > 0: 44 | unsafeAddr s[0] 45 | else: 46 | nil 47 | -------------------------------------------------------------------------------- /rocksdb/lib/librocksdb.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2025 Status Research & Development GmbH 2 | # Licensed under either of 3 | # 4 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 5 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 6 | # 7 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 8 | 9 | # Nim-RocksDB is a wrapper for Facebook's RocksDB 10 | # RocksDB License 11 | # Copyright (c) 2011-present, Facebook, Inc. All rights reserved. 12 | # Source code can be found at https://github.com/facebook/rocksdb 13 | # under both the GPLv2 (found in the COPYING file in the RocksDB root directory) and Apache 2.0 License 14 | # (found in the LICENSE.Apache file in the RocksDB root directory). 15 | 16 | # RocksDB is derived work of LevelDB 17 | # LevelDB License 18 | # Copyright (c) 2011 The LevelDB Authors. All rights reserved. 19 | # Source code can be found at https://github.com/google/leveldb 20 | # Use of this source code is governed by a BSD-style license that can be 21 | # found in the LevelDB LICENSE file. See the AUTHORS file for names of contributors. 22 | 23 | ## This file exposes the low-level C API of RocksDB 24 | 25 | import std/[os, strutils] 26 | 27 | {.push raises: [].} 28 | 29 | type 30 | rocksdb_t* = object 31 | rocksdb_backup_engine_t* = object 32 | rocksdb_backup_engine_info_t* = object 33 | rocksdb_backup_engine_options_t* = object 34 | rocksdb_restore_options_t* = object 35 | rocksdb_cache_t* = object 36 | rocksdb_compactionfilter_t* = object 37 | rocksdb_compactionfiltercontext_t* = object 38 | rocksdb_compactionfilterfactory_t* = object 39 | rocksdb_comparator_t* = object 40 | rocksdb_dbpath_t* = object 41 | rocksdb_env_t* = object 42 | rocksdb_fifo_compaction_options_t* = object 43 | rocksdb_filelock_t* = object 44 | rocksdb_filterpolicy_t* = object 45 | rocksdb_flushoptions_t* = object 46 | rocksdb_iterator_t* = object 47 | rocksdb_logger_t* = object 48 | rocksdb_mergeoperator_t* = object 49 | rocksdb_options_t* = object 50 | rocksdb_compactoptions_t* = object 51 | rocksdb_block_based_table_options_t* = object 52 | rocksdb_cuckoo_table_options_t* = object 53 | rocksdb_randomfile_t* = object 54 | rocksdb_readoptions_t* = object 55 | rocksdb_seqfile_t* = object 56 | rocksdb_slicetransform_t* = object 57 | rocksdb_snapshot_t* = object 58 | rocksdb_writablefile_t* = object 59 | rocksdb_writebatch_t* = object 60 | rocksdb_writebatch_wi_t* = object 61 | rocksdb_writeoptions_t* = object 62 | rocksdb_universal_compaction_options_t* = object 63 | rocksdb_livefiles_t* = object 64 | rocksdb_column_family_handle_t* = object 65 | rocksdb_envoptions_t* = object 66 | rocksdb_ingestexternalfileoptions_t* = object 67 | rocksdb_sstfilewriter_t* = object 68 | rocksdb_ratelimiter_t* = object 69 | rocksdb_pinnableslice_t* = object 70 | rocksdb_transactiondb_options_t* = object 71 | rocksdb_transactiondb_t* = object 72 | rocksdb_transaction_options_t* = object 73 | rocksdb_optimistictransactiondb_t* = object 74 | rocksdb_optimistictransaction_options_t* = object 75 | rocksdb_transaction_t* = object 76 | rocksdb_checkpoint_t* = object 77 | rocksdb_wal_readoptions_t* = object 78 | rocksdb_wal_iterator_t* = object 79 | rocksdb_write_buffer_manager_t* = object 80 | rocksdb_statistics_histogram_data_t* = object 81 | rocksdb_perfcontext_t* = object 82 | rocksdb_memory_allocator_t* = object 83 | rocksdb_lru_cache_options_t* = object 84 | rocksdb_hyper_clock_cache_options_t* = object 85 | rocksdb_level_metadata_t* = object 86 | rocksdb_sst_file_metadata_t* = object 87 | rocksdb_column_family_metadata_t* = object 88 | rocksdb_memory_usage_t* = object 89 | rocksdb_memory_consumers_t* = object 90 | rocksdb_wait_for_compact_options_t* = object 91 | 92 | when defined(windows): 93 | const librocksdb = "librocksdb.dll" 94 | elif defined(macosx): 95 | const librocksdb = "librocksdb.dylib" 96 | else: 97 | const librocksdb = "librocksdb.so" 98 | 99 | when defined(rocksdb_dynamic_linking) or defined(windows): 100 | {.push importc, cdecl, dynlib: librocksdb.} 101 | else: 102 | const 103 | topLevelPath = currentSourcePath.parentDir().parentDir().parentDir() 104 | libsDir = topLevelPath.replace('\\', '/') & "/build/" 105 | 106 | {.passl: libsDir & "/librocksdb.a".} 107 | {.passl: libsDir & "/liblz4.a".} 108 | {.passl: libsDir & "/libzstd.a".} 109 | 110 | when defined(windows): 111 | {.passl: "-lshlwapi -lrpcrt4".} 112 | 113 | {.push importc, cdecl.} 114 | 115 | include ./rocksdb_gen.nim 116 | -------------------------------------------------------------------------------- /rocksdb/optimistictxdb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `OptimisticTxDbRef` can be used to open a connection to the RocksDB database 11 | ## with support for transactional operations against multiple column families. 12 | ## To create a new transaction call `beginTransaction` which will return a 13 | ## `TransactionRef`. To commit or rollback the transaction call `commit` or 14 | ## `rollback` on the `TransactionRef` type after applying changes to the transaction. 15 | 16 | {.push raises: [].} 17 | 18 | import 19 | std/[sequtils, locks], 20 | ./lib/librocksdb, 21 | ./options/[dbopts, readopts, writeopts], 22 | ./transactions/[transaction, otxopts], 23 | ./columnfamily/[cfopts, cfdescriptor, cfhandle], 24 | ./internal/[cftable, utils], 25 | ./rocksresult 26 | 27 | export dbopts, cfdescriptor, readopts, writeopts, otxopts, transaction, rocksresult 28 | 29 | type 30 | OptimisticTxDbPtr* = ptr rocksdb_optimistictransactiondb_t 31 | 32 | OptimisticTxDbRef* = ref object 33 | lock: Lock 34 | cPtr: OptimisticTxDbPtr 35 | path: string 36 | dbOpts: DbOptionsRef 37 | cfDescriptors: seq[ColFamilyDescriptor] 38 | defaultCfHandle: ColFamilyHandleRef 39 | cfTable: ColFamilyTableRef 40 | 41 | proc openOptimisticTxDb*( 42 | path: string, 43 | dbOpts = defaultDbOptions(autoClose = true), 44 | columnFamilies: openArray[ColFamilyDescriptor] = [], 45 | ): RocksDBResult[OptimisticTxDbRef] = 46 | ## Open a `OptimisticTxDbRef` with the given options and column families. 47 | ## If no column families are provided the default column family will be used. 48 | ## If no options are provided the default options will be used. 49 | ## These default options will be closed when the database is closed. 50 | ## If any options are provided, they will need to be closed manually. 51 | 52 | var cfs = columnFamilies.toSeq() 53 | if DEFAULT_COLUMN_FAMILY_NAME notin columnFamilies.mapIt(it.name()): 54 | cfs.add(defaultColFamilyDescriptor(autoClose = true)) 55 | 56 | var 57 | cfNames = cfs.mapIt(it.name().cstring) 58 | cfOpts = cfs.mapIt(it.options.cPtr) 59 | cfHandles = newSeq[ColFamilyHandlePtr](cfs.len) 60 | errors: cstring 61 | 62 | let txDbPtr = rocksdb_optimistictransactiondb_open_column_families( 63 | dbOpts.cPtr, 64 | path.cstring, 65 | cfNames.len().cint, 66 | cast[cstringArray](cfNames[0].addr), 67 | cfOpts[0].addr, 68 | cfHandles[0].addr, 69 | cast[cstringArray](errors.addr), 70 | ) 71 | bailOnErrorsWithCleanup(errors): 72 | autoCloseNonNil(dbOpts) 73 | autoCloseAll(cfs) 74 | 75 | let 76 | cfTable = newColFamilyTable(cfNames.mapIt($it), cfHandles) 77 | db = OptimisticTxDbRef( 78 | lock: createLock(), 79 | cPtr: txDbPtr, 80 | path: path, 81 | dbOpts: dbOpts, 82 | cfDescriptors: cfs, 83 | defaultCfHandle: cfTable.get(DEFAULT_COLUMN_FAMILY_NAME), 84 | cfTable: cfTable, 85 | ) 86 | ok(db) 87 | 88 | proc getColFamilyHandle*( 89 | db: OptimisticTxDbRef, name: string 90 | ): RocksDBResult[ColFamilyHandleRef] = 91 | let cfHandle = db.cfTable.get(name) 92 | if cfHandle.isNil(): 93 | err("rocksdb: unknown column family") 94 | else: 95 | ok(cfHandle) 96 | 97 | proc isClosed*(db: OptimisticTxDbRef): bool {.inline.} = 98 | ## Returns `true` if the `OptimisticTxDbRef` has been closed. 99 | db.cPtr.isNil() 100 | 101 | proc beginTransaction*( 102 | db: OptimisticTxDbRef, 103 | readOpts = defaultReadOptions(autoClose = true), 104 | writeOpts = defaultWriteOptions(autoClose = true), 105 | otxOpts = defaultOptimisticTxOptions(autoClose = true), 106 | cfHandle = db.defaultCfHandle, 107 | ): TransactionRef = 108 | ## Begin a new transaction against the database. The transaction will default 109 | ## to using the specified column family. If no column family is specified 110 | ## then the default column family will be used. 111 | doAssert not db.isClosed() 112 | 113 | let txPtr = 114 | rocksdb_optimistictransaction_begin(db.cPtr, writeOpts.cPtr, otxOpts.cPtr, nil) 115 | 116 | newTransaction(txPtr, readOpts, writeOpts, nil, otxOpts, cfHandle) 117 | 118 | proc close*(db: OptimisticTxDbRef) = 119 | ## Close the `OptimisticTxDbRef`. 120 | 121 | withLock(db.lock): 122 | if not db.isClosed(): 123 | # the column families should be closed before the database 124 | db.cfTable.close() 125 | 126 | rocksdb_optimistictransactiondb_close(db.cPtr) 127 | db.cPtr = nil 128 | 129 | # opts should be closed after the database is closed 130 | autoCloseNonNil(db.dbOpts) 131 | autoCloseAll(db.cfDescriptors) 132 | -------------------------------------------------------------------------------- /rocksdb/options/backupopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | BackupEngineOptionsPtr* = ptr rocksdb_backup_engine_options_t 16 | 17 | BackupEngineOptionsRef* = ref object 18 | cPtr: BackupEngineOptionsPtr 19 | autoClose*: bool # if true then close will be called when the backup engine is closed 20 | 21 | proc createBackupEngineOptions*( 22 | backupDir: string, autoClose = false 23 | ): BackupEngineOptionsRef = 24 | BackupEngineOptionsRef( 25 | cPtr: rocksdb_backup_engine_options_create(backupDir.cstring), autoClose: autoClose 26 | ) 27 | 28 | proc isClosed*(backupOpts: BackupEngineOptionsRef): bool {.inline.} = 29 | backupOpts.cPtr.isNil() 30 | 31 | proc cPtr*(backupOpts: BackupEngineOptionsRef): BackupEngineOptionsPtr = 32 | doAssert not backupOpts.isClosed() 33 | backupOpts.cPtr 34 | 35 | template opt(nname, ntyp, ctyp: untyped) = 36 | proc `nname=`*(backupOpts: BackupEngineOptionsRef, value: ntyp) = 37 | doAssert not backupOpts.isClosed() 38 | `rocksdb_backup_engine_options_set nname`(backupOpts.cPtr, value.ctyp) 39 | 40 | proc `nname`*(backupOpts: BackupEngineOptionsRef): ntyp = 41 | doAssert not backupOpts.isClosed() 42 | ntyp `rocksdb_backup_engine_options_get nname`(backupOpts.cPtr) 43 | 44 | opt shareTableFiles, bool, uint8 45 | opt sync, bool, uint8 46 | opt destroyOldData, bool, uint8 47 | opt backupLogFiles, bool, uint8 48 | opt backupRateLimit, int, uint64 49 | opt restoreRateLimit, int, uint64 50 | opt shareFilesWithChecksumNaming, bool, cint 51 | opt maxBackgroundOperations, int, cint 52 | opt callbackTriggerIntervalSize, int, uint64 53 | 54 | proc defaultBackupEngineOptions*( 55 | backupDir: string, autoClose = false 56 | ): BackupEngineOptionsRef {.inline.} = 57 | let backupOpts = createBackupEngineOptions(backupDir, autoClose) 58 | 59 | # TODO: set defaults here 60 | backupOpts 61 | 62 | proc close*(backupOpts: BackupEngineOptionsRef) = 63 | if not backupOpts.isClosed(): 64 | rocksdb_backup_engine_options_destroy(backupOpts.cPtr) 65 | backupOpts.cPtr = nil 66 | -------------------------------------------------------------------------------- /rocksdb/options/cache.nim: -------------------------------------------------------------------------------- 1 | import ../lib/librocksdb 2 | 3 | type 4 | CachePtr* = ptr rocksdb_cache_t 5 | 6 | CacheRef* = ref object 7 | cPtr: CachePtr 8 | autoClose*: bool # if true then close will be called when it's parent is closed 9 | 10 | proc cacheCreateLRU*(size: int, autoClose = false): CacheRef = 11 | CacheRef(cPtr: rocksdb_cache_create_lru(size.csize_t), autoClose: autoClose) 12 | 13 | proc isClosed*(cache: CacheRef): bool = 14 | isNil(cache.cPtr) 15 | 16 | proc cPtr*(cache: CacheRef): CachePtr = 17 | doAssert not cache.isClosed() 18 | cache.cPtr 19 | 20 | proc close*(cache: CacheRef) = 21 | if cache.cPtr != nil: 22 | rocksdb_cache_destroy(cache.cPtr) 23 | cache.cPtr = nil 24 | -------------------------------------------------------------------------------- /rocksdb/options/dbopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import std/cpuinfo, ../lib/librocksdb, ../internal/utils, ./[cache, tableopts] 13 | 14 | export cache, tableopts 15 | 16 | type 17 | DbOptionsPtr* = ptr rocksdb_options_t 18 | 19 | DbOptionsRef* = ref object 20 | cPtr: DbOptionsPtr 21 | cache: CacheRef 22 | autoClose*: bool # if true then close will be called when the database is closed 23 | 24 | proc createDbOptions*(autoClose = false): DbOptionsRef = 25 | DbOptionsRef(cPtr: rocksdb_options_create(), autoClose: autoClose) 26 | 27 | proc isClosed*(dbOpts: DbOptionsRef): bool {.inline.} = 28 | dbOpts.cPtr.isNil() 29 | 30 | proc cPtr*(dbOpts: DbOptionsRef): DbOptionsPtr = 31 | doAssert not dbOpts.isClosed() 32 | dbOpts.cPtr 33 | 34 | proc increaseParallelism*(dbOpts: DbOptionsRef, totalThreads: int) = 35 | doAssert totalThreads > 0 36 | doAssert not dbOpts.isClosed() 37 | rocksdb_options_increase_parallelism(dbOpts.cPtr, totalThreads.cint) 38 | 39 | # Options roughly in the order found in `options.h` 40 | 41 | template opt(nname, ntyp, ctyp: untyped) = 42 | proc `nname=`*(dbOpts: DbOptionsRef, value: ntyp) = 43 | doAssert not dbOpts.isClosed() 44 | `rocksdb_options_set nname`(dbOpts.cPtr, value.ctyp) 45 | 46 | proc `nname`*(dbOpts: DbOptionsRef): ntyp = 47 | doAssert not dbOpts.isClosed() 48 | ntyp `rocksdb_options_get nname`(dbOpts.cPtr) 49 | 50 | opt createIfMissing, bool, uint8 51 | opt createMissingColumnFamilies, bool, uint8 52 | opt errorIfExists, bool, uint8 53 | opt paranoidChecks, bool, uint8 54 | 55 | opt maxOpenFiles, int, cint 56 | opt maxFileOpeningThreads, int, cint 57 | opt maxTotalWalSize, int, uint64 58 | opt useFsync, bool, cint 59 | opt deleteObsoleteFilesPeriodMicros, int, uint64 60 | opt maxBackgroundJobs, int, cint 61 | opt maxBackgroundCompactions, int, cint 62 | opt maxSubcompactions, int, uint32 63 | opt maxLogFileSize, int, csize_t 64 | opt logFiletimeToRoll, int, csize_t 65 | opt keepLogFileNum, int, csize_t 66 | opt recycleLogFileNum, int, csize_t 67 | opt maxManifestFileSize, int, csize_t 68 | opt tableCacheNumshardbits, int, cint 69 | opt walTtlSeconds, int, uint64 70 | opt walSizeLimitMB, int, uint64 71 | opt manifestPreallocationSize, int, csize_t 72 | opt allowMmapReads, bool, uint8 73 | opt allowMmapWrites, bool, uint8 74 | opt useDirectReads, bool, uint8 75 | opt useDirectIoForFlushAndCompaction, bool, uint8 76 | opt isFdCloseOnExec, bool, uint8 77 | opt statsDumpPeriodSec, int, cuint 78 | opt statsPersistPeriodSec, int, cuint 79 | opt adviseRandomOnOpen, bool, uint8 80 | opt dbWriteBufferSize, int, csize_t 81 | opt writableFileMaxBufferSize, int, csize_t 82 | opt useAdaptiveMutex, bool, uint8 83 | opt bytesPerSync, int, uint64 84 | opt walBytesPerSync, int, uint64 85 | opt enablePipelinedWrite, bool, uint8 86 | opt unorderedWrite, bool, uint8 87 | opt allowConcurrentMemtableWrite, bool, uint8 88 | opt enableWriteThreadAdaptiveYield, bool, uint8 89 | opt skipStatsUpdateOnDbOpen, bool, uint8 90 | opt skipCheckingSstFileSizesOnDbOpen, bool, uint8 91 | opt allowIngestBehind, bool, uint8 92 | opt manualWalFlush, bool, uint8 93 | opt atomicFlush, bool, uint8 94 | opt avoidUnnecessaryBlockingIo, bool, uint8 95 | 96 | proc `rowCache=`*(dbOpts: DbOptionsRef, cache: CacheRef) = 97 | doAssert not dbOpts.isClosed() 98 | doAssert dbOpts.cache.isNil() 99 | # don't allow overwriting an existing cache which could leak memory 100 | 101 | rocksdb_options_set_row_cache(dbOpts.cPtr, cache.cPtr) 102 | dbOpts.cache = cache 103 | 104 | proc defaultDbOptions*(autoClose = false): DbOptionsRef = 105 | let dbOpts: DbOptionsRef = createDbOptions(autoClose) 106 | 107 | # Optimize RocksDB. This is the easiest way to get RocksDB to perform well: 108 | dbOpts.increaseParallelism(countProcessors()) 109 | dbOpts.createIfMissing = true 110 | 111 | # Enable creating column families if they do not exist 112 | dbOpts.createMissingColumnFamilies = true 113 | 114 | # Options recommended by rocksdb devs themselves, for new databases 115 | # https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options 116 | 117 | dbOpts.maxBackgroundJobs = 6 118 | dbOpts.bytesPerSync = 1048576 119 | 120 | dbOpts 121 | 122 | proc close*(dbOpts: DbOptionsRef) = 123 | if not dbOpts.isClosed(): 124 | rocksdb_options_destroy(dbOpts.cPtr) 125 | dbOpts.cPtr = nil 126 | 127 | autoCloseNonNil(dbOpts.cache) 128 | -------------------------------------------------------------------------------- /rocksdb/options/readopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb, ../snapshot 13 | 14 | export snapshot.SnapshotRef, snapshot.isClosed, snapshot.getSequenceNumber 15 | 16 | type 17 | ReadOptionsPtr* = ptr rocksdb_readoptions_t 18 | 19 | ReadOptionsRef* = ref object 20 | cPtr: ReadOptionsPtr 21 | autoClose*: bool # if true then close will be called when the database is closed 22 | 23 | proc createReadOptions*(autoClose = false): ReadOptionsRef = 24 | ReadOptionsRef(cPtr: rocksdb_readoptions_create(), autoClose: autoClose) 25 | 26 | proc isClosed*(readOpts: ReadOptionsRef): bool {.inline.} = 27 | readOpts.cPtr.isNil() 28 | 29 | proc cPtr*(readOpts: ReadOptionsRef): ReadOptionsPtr = 30 | doAssert not readOpts.isClosed() 31 | readOpts.cPtr 32 | 33 | template opt(nname, ntyp, ctyp: untyped) = 34 | proc `nname=`*(readOpts: ReadOptionsRef, value: ntyp) = 35 | doAssert not readOpts.isClosed() 36 | `rocksdb_readoptions_set nname`(readOpts.cPtr, value.ctyp) 37 | 38 | proc `nname`*(readOpts: ReadOptionsRef): ntyp = 39 | doAssert not readOpts.isClosed() 40 | ntyp `rocksdb_readoptions_get nname`(readOpts.cPtr) 41 | 42 | opt verifyChecksums, bool, uint8 43 | opt fillCache, bool, uint8 44 | opt readTier, int, cint 45 | opt tailing, bool, uint8 46 | opt totalOrderSeek, bool, uint8 47 | opt prefixSameAsStart, bool, uint8 48 | opt pinData, bool, uint8 49 | opt backgroundPurgeOnIteratorCleanup, bool, uint8 50 | opt readaheadSize, int, csize_t 51 | opt maxSkippableInternalKeys, int, csize_t 52 | opt ignoreRangeDeletions, bool, uint8 53 | opt deadline, int, uint64 54 | opt ioTimeout, int, uint64 55 | 56 | proc setSnapshot*(readOpts: ReadOptionsRef, snapshot: SnapshotRef) = 57 | doAssert not readOpts.isClosed() 58 | rocksdb_readoptions_set_snapshot(readOpts.cPtr, snapshot.cPtr) 59 | 60 | proc defaultReadOptions*(autoClose = false): ReadOptionsRef {.inline.} = 61 | let readOpts = createReadOptions(autoClose) 62 | 63 | # TODO: set prefered defaults 64 | readOpts 65 | 66 | proc close*(readOpts: ReadOptionsRef) = 67 | if not readOpts.isClosed(): 68 | rocksdb_readoptions_destroy(readOpts.cPtr) 69 | readOpts.cPtr = nil 70 | -------------------------------------------------------------------------------- /rocksdb/options/tableopts.nim: -------------------------------------------------------------------------------- 1 | import ../lib/librocksdb, ../internal/utils, ./cache 2 | 3 | export cache 4 | 5 | type 6 | # TODO might eventually wrap this 7 | TableOptionsPtr* = ptr rocksdb_block_based_table_options_t 8 | 9 | TableOptionsRef* = ref object 10 | cPtr: TableOptionsPtr 11 | cache: CacheRef 12 | autoClose*: bool # if true then close will be called when it's parent is closed 13 | 14 | FilterPolicyPtr* = ptr rocksdb_filterpolicy_t 15 | 16 | FilterPolicyRef* = ref object 17 | cPtr: FilterPolicyPtr 18 | 19 | IndexType* {.pure.} = enum 20 | binarySearch = rocksdb_block_based_table_index_type_binary_search 21 | hashSearch = rocksdb_block_based_table_index_type_hash_search 22 | twoLevelIndexSearch = rocksdb_block_based_table_index_type_two_level_index_search 23 | 24 | DataBlockIndexType* {.pure.} = enum 25 | binarySearch = rocksdb_block_based_table_data_block_index_type_binary_search 26 | binarySearchAndHash = 27 | rocksdb_block_based_table_data_block_index_type_binary_search_and_hash 28 | 29 | proc createRibbon*(bitsPerKey: float): FilterPolicyRef = 30 | FilterPolicyRef(cPtr: rocksdb_filterpolicy_create_ribbon(bitsPerKey)) 31 | 32 | proc createRibbonHybrid*( 33 | bitsPerKey: float, bloomBeforeLevel: int = 0 34 | ): FilterPolicyRef = 35 | FilterPolicyRef( 36 | cPtr: rocksdb_filterpolicy_create_ribbon_hybrid(bitsPerKey, bloomBeforeLevel.cint) 37 | ) 38 | 39 | proc isClosed*(policy: FilterPolicyRef): bool = 40 | isNil(policy.cPtr) 41 | 42 | proc cPtr*(policy: FilterPolicyRef): FilterPolicyPtr = 43 | doAssert not policy.isClosed() 44 | policy.cPtr 45 | 46 | proc close*(policy: FilterPolicyRef) = 47 | if not isClosed(policy): 48 | rocksdb_filterpolicy_destroy(policy.cPtr) 49 | policy.cPtr = nil 50 | 51 | proc createTableOptions*(autoClose = false): TableOptionsRef = 52 | TableOptionsRef(cPtr: rocksdb_block_based_options_create(), autoClose: autoClose) 53 | 54 | proc isClosed*(opts: TableOptionsRef): bool = 55 | isNil(opts.cPtr) 56 | 57 | proc cPtr*(opts: TableOptionsRef): TableOptionsPtr = 58 | doAssert not opts.isClosed() 59 | opts.cPtr 60 | 61 | template opt(nname, ntyp, ctyp: untyped) = 62 | proc `nname=`*(opts: TableOptionsRef, value: ntyp) = 63 | doAssert not opts.isClosed 64 | `rocksdb_block_based_options_set nname`(opts.cPtr, value.ctyp) 65 | 66 | opt cacheIndexAndFilterBlocks, bool, uint8 67 | opt cacheIndexAndFilterBlocksWithHighPriority, bool, uint8 68 | opt pinL0FilterAndIndexBlocksInCache, bool, uint8 69 | opt pinTopLevelIndexAndFilter, bool, uint8 70 | opt indexType, IndexType, cint 71 | opt dataBlockIndexType, DataBlockIndexType, cint 72 | opt dataBlockHashRatio, float, cdouble 73 | opt noBlockCache, bool, uint8 74 | opt blockSize, int, csize_t 75 | opt blockSizeDeviation, int, cint 76 | opt blockRestartInterval, int, cint 77 | opt indexBlockRestartInterval, int, cint 78 | opt metadataBlockSize, int, csize_t 79 | opt partitionFilters, bool, uint8 80 | opt optimizeFiltersForMemory, bool, uint8 81 | opt useDeltaEncoding, bool, uint8 82 | opt wholeKeyFiltering, bool, uint8 83 | opt formatVersion, int, cint 84 | 85 | proc `blockCache=`*(opts: TableOptionsRef, cache: CacheRef) = 86 | doAssert not opts.isClosed() 87 | doAssert opts.cache.isNil() 88 | # don't allow overwriting an existing cache which could leak memory 89 | 90 | rocksdb_block_based_options_set_block_cache(opts.cPtr, cache.cPtr) 91 | opts.cache = cache 92 | 93 | proc `filterPolicy=`*(opts: TableOptionsRef, policy: FilterPolicyRef) = 94 | doAssert not opts.isClosed() 95 | 96 | # Destroys the existing policy if there is one attached to the table options 97 | # and takes ownership of the passed in policy. After this call, the TableOptionsRef 98 | # is responsible for cleaning up the policy when it is no longer needed 99 | # so we set the filter policy to nil so that isClosed() will return true 100 | # and prevent the filter policy from being double freed which was causing a seg fault. 101 | # See here: https://github.com/facebook/rocksdb/blob/22fe23edc89e9842ed72b613de172cd80d3b00da/include/rocksdb/filter_policy.h#L152 102 | rocksdb_block_based_options_set_filter_policy(opts.cPtr, policy.cPtr) 103 | policy.cPtr = nil 104 | 105 | proc defaultTableOptions*(autoClose = false): TableOptionsRef = 106 | # https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options 107 | let opts = createTableOptions(autoClose) 108 | opts.blockSize = 16 * 1024 109 | opts.cacheIndexAndFilterBlocks = true 110 | opts.pinL0FilterAndIndexBlocksInCache = true 111 | 112 | opts 113 | 114 | proc close*(opts: TableOptionsRef) = 115 | if not isClosed(opts): 116 | rocksdb_block_based_options_destroy(opts.cPtr) 117 | opts.cPtr = nil 118 | 119 | autoCloseNonNil(opts.cache) 120 | -------------------------------------------------------------------------------- /rocksdb/options/writeopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | WriteOptionsPtr* = ptr rocksdb_writeoptions_t 16 | 17 | WriteOptionsRef* = ref object 18 | cPtr: WriteOptionsPtr 19 | autoClose*: bool # if true then close will be called when the database is closed 20 | 21 | proc createWriteOptions*(autoClose = false): WriteOptionsRef = 22 | WriteOptionsRef(cPtr: rocksdb_writeoptions_create(), autoClose: autoClose) 23 | 24 | proc isClosed*(writeOpts: WriteOptionsRef): bool {.inline.} = 25 | writeOpts.cPtr.isNil() 26 | 27 | proc cPtr*(writeOpts: WriteOptionsRef): WriteOptionsPtr = 28 | doAssert not writeOpts.isClosed() 29 | writeOpts.cPtr 30 | 31 | template opt(nname, ntyp, ctyp: untyped) = 32 | proc `nname=`*(writeOpts: WriteOptionsRef, value: ntyp) = 33 | doAssert not writeOpts.isClosed() 34 | `rocksdb_writeoptions_set nname`(writeOpts.cPtr, value.ctyp) 35 | 36 | proc `nname`*(writeOpts: WriteOptionsRef): ntyp = 37 | doAssert not writeOpts.isClosed() 38 | ntyp `rocksdb_writeoptions_get nname`(writeOpts.cPtr) 39 | 40 | opt sync, bool, uint8 41 | opt ignoreMissingColumnFamilies, bool, uint8 42 | opt noSlowdown, bool, uint8 43 | opt lowPri, bool, uint8 44 | opt memtableInsertHintPerBatch, bool, uint8 45 | 46 | proc `disableWAL=`*(writeOpts: WriteOptionsRef, value: bool) = 47 | doAssert not writeOpts.isClosed() 48 | rocksdb_writeoptions_disable_WAL(writeOpts.cPtr, value.cint) 49 | 50 | proc disableWAL*(writeOpts: WriteOptionsRef): bool = 51 | doAssert not writeOpts.isClosed() 52 | rocksdb_writeoptions_get_disable_WAL(writeOpts.cPtr).bool 53 | 54 | proc defaultWriteOptions*(autoClose = false): WriteOptionsRef {.inline.} = 55 | let writeOpts = createWriteOptions(autoClose) 56 | 57 | # TODO: set prefered defaults 58 | writeOpts 59 | 60 | proc close*(writeOpts: WriteOptionsRef) = 61 | if not writeOpts.isClosed(): 62 | rocksdb_writeoptions_destroy(writeOpts.cPtr) 63 | writeOpts.cPtr = nil 64 | -------------------------------------------------------------------------------- /rocksdb/rocksiterator.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `RocksIteratorRef` is a reference to a RocksDB iterator which supports 11 | ## iterating over the key value pairs in a column family. 12 | 13 | {.push raises: [].} 14 | 15 | import ./lib/librocksdb, ./internal/utils, ./options/readopts, ./rocksresult 16 | 17 | export rocksresult 18 | 19 | type 20 | RocksIteratorPtr* = ptr rocksdb_iterator_t 21 | 22 | RocksIteratorRef* = ref object 23 | cPtr: RocksIteratorPtr 24 | readOpts: ReadOptionsRef 25 | 26 | proc newRocksIterator*( 27 | cPtr: RocksIteratorPtr, readOpts: ReadOptionsRef 28 | ): RocksIteratorRef = 29 | doAssert not cPtr.isNil() 30 | RocksIteratorRef(cPtr: cPtr, readOpts: readOpts) 31 | 32 | proc isClosed*(iter: RocksIteratorRef): bool {.inline.} = 33 | ## Returns `true` if the iterator is closed and `false` otherwise. 34 | iter.cPtr.isNil() 35 | 36 | proc seekToKey*(iter: RocksIteratorRef, key: openArray[byte]) = 37 | ## Seeks to the `key` argument in the column family. If the return code is 38 | ## `false`, the iterator has become invalid and should be closed. 39 | ## 40 | ## It is not clear what happens when the `key` does not exist in the column 41 | ## family. The guess is that the interation will proceed at the next key 42 | ## position. This is suggested by a comment from the GO port at 43 | ## 44 | ## //github.com/DanielMorsing/rocksdb/blob/master/iterator.go: 45 | ## 46 | ## Seek moves the iterator the position of the key given or, if the key 47 | ## doesn't exist, the next key that does exist in the database. If the 48 | ## key doesn't exist, and there is no next key, the Iterator becomes 49 | ## invalid. 50 | ## 51 | doAssert not iter.isClosed() 52 | rocksdb_iter_seek(iter.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len)) 53 | 54 | proc seekToFirst*(iter: RocksIteratorRef) = 55 | ## Seeks to the first entry in the column family. 56 | doAssert not iter.isClosed() 57 | rocksdb_iter_seek_to_first(iter.cPtr) 58 | 59 | proc seekToLast*(iter: RocksIteratorRef) = 60 | ## Seeks to the last entry in the column family. 61 | doAssert not iter.isClosed() 62 | rocksdb_iter_seek_to_last(iter.cPtr) 63 | 64 | proc isValid*(iter: RocksIteratorRef): bool = 65 | ## Returns `true` if the iterator is valid and `false` otherwise. 66 | rocksdb_iter_valid(iter.cPtr).bool 67 | 68 | proc next*(iter: RocksIteratorRef) = 69 | ## Seeks to the next entry in the column family. 70 | rocksdb_iter_next(iter.cPtr) 71 | 72 | proc prev*(iter: RocksIteratorRef) = 73 | ## Seeks to the previous entry in the column family. 74 | rocksdb_iter_prev(iter.cPtr) 75 | 76 | proc key*(iter: RocksIteratorRef, onData: DataProc) = 77 | ## Returns the current key using the provided `onData` callback. 78 | 79 | var kLen: csize_t 80 | let kData = rocksdb_iter_key(iter.cPtr, kLen.addr) 81 | 82 | if kData.isNil or kLen == 0: 83 | onData([]) 84 | else: 85 | onData(kData.toOpenArrayByte(0, kLen.int - 1)) 86 | 87 | proc key*(iter: RocksIteratorRef): seq[byte] = 88 | ## Returns the current key. 89 | 90 | var res: seq[byte] 91 | proc onData(data: openArray[byte]) = 92 | res = @data 93 | 94 | iter.key(onData) 95 | res 96 | 97 | proc value*(iter: RocksIteratorRef, onData: DataProc) = 98 | ## Returns the current value using the provided `onData` callback. 99 | 100 | var vLen: csize_t 101 | let vData = rocksdb_iter_value(iter.cPtr, vLen.addr) 102 | 103 | if vData.isNil or vLen == 0: 104 | onData([]) 105 | else: 106 | onData(vData.toOpenArrayByte(0, vLen.int - 1)) 107 | 108 | proc value*(iter: RocksIteratorRef): seq[byte] = 109 | ## Returns the current value. 110 | 111 | var res: seq[byte] 112 | proc onData(data: openArray[byte]) = 113 | res = @data 114 | 115 | iter.value(onData) 116 | res 117 | 118 | proc status*(iter: RocksIteratorRef): RocksDBResult[void] = 119 | ## Returns the status of the iterator. 120 | doAssert not iter.isClosed() 121 | 122 | var errors: cstring 123 | rocksdb_iter_get_error(iter.cPtr, cast[cstringArray](errors.addr)) 124 | bailOnErrors(errors) 125 | 126 | ok() 127 | 128 | proc close*(iter: RocksIteratorRef) = 129 | ## Closes the `RocksIteratorRef`. 130 | if not iter.isClosed(): 131 | rocksdb_iter_destroy(iter.cPtr) 132 | iter.cPtr = nil 133 | 134 | autoCloseNonNil(iter.readOpts) 135 | 136 | iterator pairs*(iter: RocksIteratorRef): tuple[key: seq[byte], value: seq[byte]] = 137 | ## Iterates over the key value pairs in the column family yielding them in 138 | ## the form of a tuple. The iterator is automatically closed after the 139 | ## iteration. 140 | doAssert not iter.isClosed() 141 | defer: 142 | iter.close() 143 | 144 | iter.seekToFirst() 145 | while iter.isValid(): 146 | var 147 | key: seq[byte] 148 | value: seq[byte] 149 | iter.key( 150 | proc(data: openArray[byte]) = 151 | key = @data 152 | ) 153 | iter.value( 154 | proc(data: openArray[byte]) = 155 | value = @data 156 | ) 157 | 158 | iter.next() 159 | yield (key, value) 160 | -------------------------------------------------------------------------------- /rocksdb/rocksresult.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import results 13 | 14 | export results 15 | 16 | type 17 | RocksDBResult*[T] = Result[T, string] 18 | 19 | DataProc* = proc(val: openArray[byte]) {.gcsafe, raises: [].} 20 | -------------------------------------------------------------------------------- /rocksdb/snapshot.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `SnapshotRef` represents a view of the state of the database at some point in time. 11 | 12 | {.push raises: [].} 13 | 14 | import ./lib/librocksdb 15 | 16 | type 17 | SnapshotPtr* = ptr rocksdb_snapshot_t 18 | 19 | SnapshotType* = enum 20 | rocksdb 21 | transactiondb 22 | 23 | SnapshotRef* = ref object 24 | cPtr: SnapshotPtr 25 | kind: SnapshotType 26 | 27 | proc newSnapshot*(cPtr: SnapshotPtr, kind: SnapshotType): SnapshotRef = 28 | doAssert not cPtr.isNil() 29 | SnapshotRef(cPtr: cPtr, kind: kind) 30 | 31 | proc isClosed*(snapshot: SnapshotRef): bool {.inline.} = 32 | ## Returns `true` if the `SnapshotRef` has been closed and `false` otherwise. 33 | snapshot.cPtr.isNil() 34 | 35 | proc cPtr*(snapshot: SnapshotRef): SnapshotPtr = 36 | ## Get the underlying database pointer. 37 | doAssert not snapshot.isClosed() 38 | snapshot.cPtr 39 | 40 | proc kind*(snapshot: SnapshotRef): SnapshotType = 41 | ## Get the kind of the `SnapshotRef`. 42 | snapshot.kind 43 | 44 | proc getSequenceNumber*(snapshot: SnapshotRef): uint64 = 45 | ## Return the associated sequence number. 46 | doAssert not snapshot.isClosed() 47 | rocksdb_snapshot_get_sequence_number(snapshot.cPtr).uint64 48 | 49 | proc setClosed*(snapshot: SnapshotRef) = 50 | # The snapshot should be released from `RocksDbRef` or `TransactionDbRef` 51 | snapshot.cPtr = nil 52 | -------------------------------------------------------------------------------- /rocksdb/sstfilewriter.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `SstFileWriterRef` is used to create sst files that can be added to the database later. 11 | 12 | {.push raises: [].} 13 | 14 | import ./lib/librocksdb, ./internal/utils, ./options/dbopts, ./rocksresult 15 | 16 | export rocksresult 17 | 18 | type 19 | SstFileWriterPtr* = ptr rocksdb_sstfilewriter_t 20 | EnvOptionsPtr = ptr rocksdb_envoptions_t 21 | 22 | SstFileWriterRef* = ref object 23 | cPtr: SstFileWriterPtr 24 | envOptsPtr: EnvOptionsPtr 25 | dbOpts: DbOptionsRef 26 | 27 | proc openSstFileWriter*( 28 | filePath: string, dbOpts = defaultDbOptions(autoClose = true) 29 | ): RocksDBResult[SstFileWriterRef] = 30 | ## Creates a new `SstFileWriterRef` and opens the file at the given `filePath`. 31 | ## If `dbOpts` is not supplied then the default options will be used. 32 | ## These default options will be closed when the file writer is closed. 33 | ## If any options are provided, they will need to be closed manually. 34 | doAssert not dbOpts.isClosed() 35 | 36 | let envOptsPtr = rocksdb_envoptions_create() 37 | let writer = SstFileWriterRef( 38 | cPtr: rocksdb_sstfilewriter_create(envOptsPtr, dbOpts.cPtr), 39 | envOptsPtr: envOptsPtr, 40 | dbOpts: dbOpts, 41 | ) 42 | 43 | var errors: cstring 44 | rocksdb_sstfilewriter_open( 45 | writer.cPtr, filePath.cstring, cast[cstringArray](errors.addr) 46 | ) 47 | bailOnErrorsWithCleanup(errors): 48 | autoCloseNonNil(dbOpts) 49 | 50 | ok(writer) 51 | 52 | proc isClosed*(writer: SstFileWriterRef): bool {.inline.} = 53 | ## Returns `true` if the `SstFileWriterRef` is closed and `false` otherwise. 54 | writer.cPtr.isNil() 55 | 56 | proc put*( 57 | writer: SstFileWriterRef, key: openArray[byte], val: openArray[byte] 58 | ): RocksDBResult[void] = 59 | ## Add a key-value pair to the sst file. 60 | 61 | var errors: cstring 62 | rocksdb_sstfilewriter_put( 63 | writer.cPtr, 64 | cast[cstring](key.unsafeAddrOrNil()), 65 | csize_t(key.len), 66 | cast[cstring](val.unsafeAddrOrNil()), 67 | csize_t(val.len), 68 | cast[cstringArray](errors.addr), 69 | ) 70 | bailOnErrors(errors) 71 | 72 | ok() 73 | 74 | proc delete*(writer: SstFileWriterRef, key: openArray[byte]): RocksDBResult[void] = 75 | ## Delete a key-value pair from the sst file. 76 | 77 | var errors: cstring 78 | rocksdb_sstfilewriter_delete( 79 | writer.cPtr, 80 | cast[cstring](key.unsafeAddrOrNil()), 81 | csize_t(key.len), 82 | cast[cstringArray](errors.addr), 83 | ) 84 | bailOnErrors(errors) 85 | 86 | ok() 87 | 88 | proc finish*(writer: SstFileWriterRef): RocksDBResult[void] = 89 | ## Finish the process and close the sst file. 90 | doAssert not writer.isClosed() 91 | 92 | var errors: cstring 93 | rocksdb_sstfilewriter_finish(writer.cPtr, cast[cstringArray](errors.addr)) 94 | bailOnErrors(errors) 95 | 96 | ok() 97 | 98 | proc close*(writer: SstFileWriterRef) = 99 | ## Closes the `SstFileWriterRef`. 100 | if not writer.isClosed(): 101 | rocksdb_envoptions_destroy(writer.envOptsPtr) 102 | writer.envOptsPtr = nil 103 | rocksdb_sstfilewriter_destroy(writer.cPtr) 104 | writer.cPtr = nil 105 | 106 | autoCloseNonNil(writer.dbOpts) 107 | -------------------------------------------------------------------------------- /rocksdb/transactiondb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `TransactionDbRef` can be used to open a connection to the RocksDB database 11 | ## with support for transactional operations against multiple column families. 12 | ## To create a new transaction call `beginTransaction` which will return a 13 | ## `TransactionRef`. To commit or rollback the transaction call `commit` or 14 | ## `rollback` on the `TransactionRef` type after applying changes to the transaction. 15 | 16 | {.push raises: [].} 17 | 18 | import 19 | std/[sequtils, locks], 20 | ./lib/librocksdb, 21 | ./options/[dbopts, readopts, writeopts], 22 | ./transactions/[transaction, txdbopts, txopts], 23 | ./columnfamily/[cfopts, cfdescriptor, cfhandle], 24 | ./internal/[cftable, utils], 25 | ./[rocksiterator, rocksresult, snapshot] 26 | 27 | export 28 | dbopts, txdbopts, cfdescriptor, readopts, writeopts, txopts, transaction, 29 | rocksiterator, rocksresult, snapshot.SnapshotRef, snapshot.isClosed, 30 | snapshot.getSequenceNumber 31 | 32 | type 33 | TransactionDbPtr* = ptr rocksdb_transactiondb_t 34 | 35 | TransactionDbRef* = ref object 36 | lock: Lock 37 | cPtr: TransactionDbPtr 38 | path: string 39 | dbOpts: DbOptionsRef 40 | txDbOpts: TransactionDbOptionsRef 41 | cfDescriptors: seq[ColFamilyDescriptor] 42 | defaultCfHandle: ColFamilyHandleRef 43 | cfTable: ColFamilyTableRef 44 | 45 | proc openTransactionDb*( 46 | path: string, 47 | dbOpts = defaultDbOptions(autoClose = true), 48 | txDbOpts = defaultTransactionDbOptions(autoClose = true), 49 | columnFamilies: openArray[ColFamilyDescriptor] = [], 50 | ): RocksDBResult[TransactionDbRef] = 51 | ## Open a `TransactionDbRef` with the given options and column families. 52 | ## If no column families are provided the default column family will be used. 53 | ## If no options are provided the default options will be used. 54 | ## These default options will be closed when the database is closed. 55 | ## If any options are provided, they will need to be closed manually. 56 | 57 | var cfs = columnFamilies.toSeq() 58 | if DEFAULT_COLUMN_FAMILY_NAME notin columnFamilies.mapIt(it.name()): 59 | cfs.add(defaultColFamilyDescriptor(autoClose = true)) 60 | 61 | var 62 | cfNames = cfs.mapIt(it.name().cstring) 63 | cfOpts = cfs.mapIt(it.options.cPtr) 64 | cfHandles = newSeq[ColFamilyHandlePtr](cfs.len) 65 | errors: cstring 66 | 67 | let txDbPtr = rocksdb_transactiondb_open_column_families( 68 | dbOpts.cPtr, 69 | txDbOpts.cPtr, 70 | path.cstring, 71 | cfNames.len().cint, 72 | cast[cstringArray](cfNames[0].addr), 73 | cfOpts[0].addr, 74 | cfHandles[0].addr, 75 | cast[cstringArray](errors.addr), 76 | ) 77 | bailOnErrorsWithCleanup(errors): 78 | autoCloseNonNil(dbOpts) 79 | autoCloseNonNil(txDbOpts) 80 | autoCloseAll(cfs) 81 | 82 | let 83 | cfTable = newColFamilyTable(cfNames.mapIt($it), cfHandles) 84 | db = TransactionDbRef( 85 | lock: createLock(), 86 | cPtr: txDbPtr, 87 | path: path, 88 | dbOpts: dbOpts, 89 | txDbOpts: txDbOpts, 90 | cfDescriptors: cfs, 91 | defaultCfHandle: cfTable.get(DEFAULT_COLUMN_FAMILY_NAME), 92 | cfTable: cfTable, 93 | ) 94 | ok(db) 95 | 96 | proc getColFamilyHandle*( 97 | db: TransactionDbRef, name: string 98 | ): RocksDBResult[ColFamilyHandleRef] = 99 | let cfHandle = db.cfTable.get(name) 100 | if cfHandle.isNil(): 101 | err("rocksdb: unknown column family") 102 | else: 103 | ok(cfHandle) 104 | 105 | proc isClosed*(db: TransactionDbRef): bool {.inline.} = 106 | ## Returns `true` if the `TransactionDbRef` has been closed. 107 | db.cPtr.isNil() 108 | 109 | proc beginTransaction*( 110 | db: TransactionDbRef, 111 | readOpts = defaultReadOptions(autoClose = true), 112 | writeOpts = defaultWriteOptions(autoClose = true), 113 | txOpts = defaultTransactionOptions(autoClose = true), 114 | cfHandle = db.defaultCfHandle, 115 | ): TransactionRef = 116 | ## Begin a new transaction against the database. The transaction will default 117 | ## to using the specified column family. If no column family is specified 118 | ## then the default column family will be used. 119 | doAssert not db.isClosed() 120 | 121 | let txPtr = rocksdb_transaction_begin(db.cPtr, writeOpts.cPtr, txOpts.cPtr, nil) 122 | 123 | newTransaction(txPtr, readOpts, writeOpts, txOpts, nil, cfHandle) 124 | 125 | proc openIterator*( 126 | db: TransactionDbRef, 127 | readOpts = defaultReadOptions(autoClose = true), 128 | cfHandle = db.defaultCfHandle, 129 | ): RocksDBResult[RocksIteratorRef] = 130 | ## Opens an `RocksIteratorRef` for the specified column family. 131 | ## The iterator should be closed using the `close` method after usage. 132 | doAssert not db.isClosed() 133 | 134 | let rocksIterPtr = 135 | rocksdb_transactiondb_create_iterator_cf(db.cPtr, readOpts.cPtr, cfHandle.cPtr) 136 | 137 | ok(newRocksIterator(rocksIterPtr, readOpts)) 138 | 139 | proc getSnapshot*(db: TransactionDbRef): RocksDBResult[SnapshotRef] = 140 | ## Return a handle to the current DB state. Iterators created with this handle 141 | ## will all observe a stable snapshot of the current DB state. The caller must 142 | ## call ReleaseSnapshot(result) when the snapshot is no longer needed. 143 | doAssert not db.isClosed() 144 | 145 | let sHandle = rocksdb_transactiondb_create_snapshot(db.cPtr) 146 | if sHandle.isNil(): 147 | err("rocksdb: failed to create snapshot") 148 | else: 149 | ok(newSnapshot(sHandle, SnapshotType.transactiondb)) 150 | 151 | proc releaseSnapshot*(db: TransactionDbRef, snapshot: SnapshotRef) = 152 | ## Release a previously acquired snapshot. The caller must not use "snapshot" 153 | ## after this call. 154 | doAssert not db.isClosed() 155 | doAssert snapshot.kind == SnapshotType.transactiondb 156 | 157 | if not snapshot.isClosed(): 158 | rocksdb_transactiondb_release_snapshot(db.cPtr, snapshot.cPtr) 159 | snapshot.setClosed() 160 | 161 | proc close*(db: TransactionDbRef) = 162 | ## Close the `TransactionDbRef`. 163 | 164 | withLock(db.lock): 165 | if not db.isClosed(): 166 | # the column families should be closed before the database 167 | db.cfTable.close() 168 | 169 | rocksdb_transactiondb_close(db.cPtr) 170 | db.cPtr = nil 171 | 172 | # opts should be closed after the database is closed 173 | autoCloseNonNil(db.dbOpts) 174 | autoCloseNonNil(db.txDbOpts) 175 | autoCloseAll(db.cfDescriptors) 176 | -------------------------------------------------------------------------------- /rocksdb/transactions/otxopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | OptimisticTxOptionsPtr* = ptr rocksdb_optimistictransaction_options_t 16 | 17 | OptimisticTxOptionsRef* = ref object 18 | cPtr: OptimisticTxOptionsPtr 19 | autoClose*: bool # if true then close will be called when the transaction is closed 20 | 21 | proc createOptimisticTxOptions*(autoClose = false): OptimisticTxOptionsRef = 22 | OptimisticTxOptionsRef( 23 | cPtr: rocksdb_optimistictransaction_options_create(), autoClose: autoClose 24 | ) 25 | 26 | proc isClosed*(txOpts: OptimisticTxOptionsRef): bool {.inline.} = 27 | txOpts.cPtr.isNil() 28 | 29 | proc cPtr*(txOpts: OptimisticTxOptionsRef): OptimisticTxOptionsPtr = 30 | doAssert not txOpts.isClosed() 31 | txOpts.cPtr 32 | 33 | template setOpt(nname, ntyp, ctyp: untyped) = 34 | proc `nname=`*(txOpts: OptimisticTxOptionsRef, value: ntyp) = 35 | doAssert not txOpts.isClosed() 36 | `rocksdb_optimistictransaction_options_set nname`(txOpts.cPtr, value.ctyp) 37 | 38 | setOpt setSnapshot, bool, uint8 39 | 40 | proc defaultOptimisticTxOptions*(autoClose = false): OptimisticTxOptionsRef {.inline.} = 41 | let txOpts = createOptimisticTxOptions(autoClose) 42 | 43 | # TODO: set prefered defaults 44 | txOpts 45 | 46 | proc close*(txOpts: OptimisticTxOptionsRef) = 47 | if not txOpts.isClosed(): 48 | rocksdb_optimistictransaction_options_destroy(txOpts.cPtr) 49 | txOpts.cPtr = nil 50 | -------------------------------------------------------------------------------- /rocksdb/transactions/transaction.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## To use transactions, you must first create a `TransactionDbRef`. Then to 11 | ## create a transaction call `beginTransaction` on the `TransactionDbRef`. 12 | ## `commit` and `rollback` are used to commit or rollback a transaction. 13 | ## The `TransactionDbRef` currently supports `put`, `delete` and `get` operations. 14 | ## Keys that have been writen to a transaction but are not yet committed can be 15 | ## read from the transaction using `get`. Uncommitted updates will not be visible 16 | ## to other transactions until they are committed to the database. 17 | ## Multiple column families can be written to and read from in a single transaction 18 | ## but a default column family will be used if none is specified in each call. 19 | 20 | {.push raises: [].} 21 | 22 | import 23 | ../lib/librocksdb, 24 | ../options/[readopts, writeopts], 25 | ../internal/[cftable, utils], 26 | ../[rocksiterator, rocksresult], 27 | ./[txopts, otxopts] 28 | 29 | export rocksiterator, rocksresult 30 | 31 | type 32 | TransactionPtr* = ptr rocksdb_transaction_t 33 | 34 | TransactionRef* = ref object 35 | cPtr: TransactionPtr 36 | readOpts: ReadOptionsRef 37 | writeOpts: WriteOptionsRef 38 | txOpts: TransactionOptionsRef 39 | otxOpts: OptimisticTxOptionsRef 40 | defaultCfHandle: ColFamilyHandleRef 41 | 42 | proc newTransaction*( 43 | cPtr: TransactionPtr, 44 | readOpts: ReadOptionsRef, 45 | writeOpts: WriteOptionsRef, 46 | txOpts: TransactionOptionsRef, 47 | otxOpts: OptimisticTxOptionsRef, 48 | defaultCfHandle: ColFamilyHandleRef, 49 | ): TransactionRef = 50 | TransactionRef( 51 | cPtr: cPtr, 52 | readOpts: readOpts, 53 | writeOpts: writeOpts, 54 | txOpts: txOpts, 55 | otxOpts: otxOpts, 56 | defaultCfHandle: defaultCfHandle, 57 | ) 58 | 59 | proc isClosed*(tx: TransactionRef): bool {.inline.} = 60 | ## Returns `true` if the `TransactionRef` has been closed. 61 | tx.cPtr.isNil() 62 | 63 | proc get*( 64 | tx: TransactionRef, 65 | key: openArray[byte], 66 | onData: DataProc, 67 | cfHandle = tx.defaultCfHandle, 68 | ): RocksDBResult[bool] = 69 | ## Get the value for a given key from the transaction using the provided 70 | ## `onData` callback. 71 | 72 | var 73 | len: csize_t 74 | errors: cstring 75 | let data = rocksdb_transaction_get_cf( 76 | tx.cPtr, 77 | tx.readOpts.cPtr, 78 | cfHandle.cPtr, 79 | cast[cstring](key.unsafeAddrOrNil()), 80 | csize_t(key.len), 81 | len.addr, 82 | cast[cstringArray](errors.addr), 83 | ) 84 | bailOnErrors(errors) 85 | 86 | if data.isNil(): 87 | doAssert len == 0 88 | ok(false) 89 | else: 90 | onData(toOpenArrayByte(data, 0, len.int - 1)) 91 | rocksdb_free(data) 92 | ok(true) 93 | 94 | proc get*( 95 | tx: TransactionRef, key: openArray[byte], cfHandle = tx.defaultCfHandle 96 | ): RocksDBResult[seq[byte]] = 97 | ## Get the value for a given key from the transaction. 98 | 99 | var dataRes: RocksDBResult[seq[byte]] 100 | proc onData(data: openArray[byte]) = 101 | dataRes.ok(@data) 102 | 103 | let res = tx.get(key, onData, cfHandle) 104 | if res.isOk(): 105 | return dataRes 106 | 107 | dataRes.err(res.error()) 108 | 109 | proc put*( 110 | tx: TransactionRef, key, val: openArray[byte], cfHandle = tx.defaultCfHandle 111 | ): RocksDBResult[void] = 112 | ## Put the value for the given key into the transaction. 113 | 114 | var errors: cstring 115 | rocksdb_transaction_put_cf( 116 | tx.cPtr, 117 | cfHandle.cPtr, 118 | cast[cstring](key.unsafeAddrOrNil()), 119 | csize_t(key.len), 120 | cast[cstring](val.unsafeAddrOrNil()), 121 | csize_t(val.len), 122 | cast[cstringArray](errors.addr), 123 | ) 124 | bailOnErrors(errors) 125 | 126 | ok() 127 | 128 | proc delete*( 129 | tx: TransactionRef, key: openArray[byte], cfHandle = tx.defaultCfHandle 130 | ): RocksDBResult[void] = 131 | ## Delete the value for the given key from the transaction. 132 | 133 | var errors: cstring 134 | rocksdb_transaction_delete_cf( 135 | tx.cPtr, 136 | cfHandle.cPtr, 137 | cast[cstring](key.unsafeAddrOrNil()), 138 | csize_t(key.len), 139 | cast[cstringArray](errors.addr), 140 | ) 141 | bailOnErrors(errors) 142 | 143 | ok() 144 | 145 | proc commit*(tx: TransactionRef): RocksDBResult[void] = 146 | ## Commit the transaction. 147 | doAssert not tx.isClosed() 148 | 149 | var errors: cstring 150 | rocksdb_transaction_commit(tx.cPtr, cast[cstringArray](errors.addr)) 151 | bailOnErrors(errors) 152 | 153 | ok() 154 | 155 | proc rollback*(tx: TransactionRef): RocksDBResult[void] = 156 | ## Rollback the transaction. 157 | doAssert not tx.isClosed() 158 | 159 | var errors: cstring 160 | rocksdb_transaction_rollback(tx.cPtr, cast[cstringArray](errors.addr)) 161 | bailOnErrors(errors) 162 | 163 | ok() 164 | 165 | proc openIterator*( 166 | db: TransactionRef, 167 | readOpts = defaultReadOptions(autoClose = true), 168 | cfHandle = db.defaultCfHandle, 169 | ): RocksDBResult[RocksIteratorRef] = 170 | ## Opens an `RocksIteratorRef` for the specified column family. 171 | ## The iterator should be closed using the `close` method after usage. 172 | doAssert not db.isClosed() 173 | 174 | let rocksIterPtr = 175 | rocksdb_transaction_create_iterator_cf(db.cPtr, readOpts.cPtr, cfHandle.cPtr) 176 | 177 | ok(newRocksIterator(rocksIterPtr, readOpts)) 178 | 179 | proc close*(tx: TransactionRef) = 180 | ## Close the `TransactionRef`. 181 | if not tx.isClosed(): 182 | rocksdb_transaction_destroy(tx.cPtr) 183 | tx.cPtr = nil 184 | 185 | # opts should be closed after the transaction is closed 186 | autoCloseNonNil(tx.readOpts) 187 | autoCloseNonNil(tx.writeOpts) 188 | autoCloseNonNil(tx.txOpts) 189 | autoCloseNonNil(tx.otxOpts) 190 | -------------------------------------------------------------------------------- /rocksdb/transactions/txdbopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | TransactionDbOptionsPtr* = ptr rocksdb_transactiondb_options_t 16 | 17 | TransactionDbOptionsRef* = ref object 18 | cPtr: TransactionDbOptionsPtr 19 | autoClose*: bool # if true then close will be called when the database is closed 20 | 21 | proc createTransactionDbOptions*(autoClose = false): TransactionDbOptionsRef = 22 | TransactionDbOptionsRef( 23 | cPtr: rocksdb_transactiondb_options_create(), autoClose: autoClose 24 | ) 25 | 26 | proc isClosed*(txDbOpts: TransactionDbOptionsRef): bool {.inline.} = 27 | txDbOpts.cPtr.isNil() 28 | 29 | proc cPtr*(txDbOpts: TransactionDbOptionsRef): TransactionDbOptionsPtr = 30 | doAssert not txDbOpts.isClosed() 31 | txDbOpts.cPtr 32 | 33 | template setOpt(nname, ntyp, ctyp: untyped) = 34 | proc `nname=`*(txDbOpts: TransactionDbOptionsRef, value: ntyp) = 35 | doAssert not txDbOpts.isClosed() 36 | `rocksdb_transactiondb_options_set nname`(txDbOpts.cPtr, value.ctyp) 37 | 38 | setOpt maxNumLocks, int, int64 39 | setOpt numStripes, int, csize_t 40 | setOpt transactionLockTimeout, int, int64 41 | setOpt defaultLockTimeout, int, int64 42 | 43 | proc defaultTransactionDbOptions*( 44 | autoClose = false 45 | ): TransactionDbOptionsRef {.inline.} = 46 | let txDbOpts = createTransactionDbOptions(autoClose) 47 | 48 | # TODO: set prefered defaults 49 | txDbOpts 50 | 51 | proc close*(txDbOpts: TransactionDbOptionsRef) = 52 | if not txDbOpts.isClosed(): 53 | rocksdb_transactiondb_options_destroy(txDbOpts.cPtr) 54 | txDbOpts.cPtr = nil 55 | -------------------------------------------------------------------------------- /rocksdb/transactions/txopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.push raises: [].} 11 | 12 | import ../lib/librocksdb 13 | 14 | type 15 | TransactionOptionsPtr* = ptr rocksdb_transaction_options_t 16 | 17 | TransactionOptionsRef* = ref object 18 | cPtr: TransactionOptionsPtr 19 | autoClose*: bool # if true then close will be called when the transaction is closed 20 | 21 | proc createTransactionOptions*(autoClose = false): TransactionOptionsRef = 22 | TransactionOptionsRef( 23 | cPtr: rocksdb_transaction_options_create(), autoClose: autoClose 24 | ) 25 | 26 | proc isClosed*(txOpts: TransactionOptionsRef): bool {.inline.} = 27 | txOpts.cPtr.isNil() 28 | 29 | proc cPtr*(txOpts: TransactionOptionsRef): TransactionOptionsPtr = 30 | doAssert not txOpts.isClosed() 31 | txOpts.cPtr 32 | 33 | template setOpt(nname, ntyp, ctyp: untyped) = 34 | proc `nname=`*(txOpts: TransactionOptionsRef, value: ntyp) = 35 | doAssert not txOpts.isClosed() 36 | `rocksdb_transaction_options_set nname`(txOpts.cPtr, value.ctyp) 37 | 38 | setOpt setSnapshot, bool, uint8 39 | setOpt deadlockDetect, bool, uint8 40 | setOpt lockTimeout, int, int64 41 | setOpt deadlockDetectDepth, int, int64 42 | setOpt maxWriteBatchSize, int, csize_t 43 | setOpt skipPrepare, bool, uint8 44 | 45 | proc defaultTransactionOptions*(autoClose = false): TransactionOptionsRef {.inline.} = 46 | let txOpts = createTransactionOptions(autoClose) 47 | 48 | # TODO: set prefered defaults 49 | txOpts 50 | 51 | proc close*(txOpts: TransactionOptionsRef) = 52 | if not txOpts.isClosed(): 53 | rocksdb_transaction_options_destroy(txOpts.cPtr) 54 | txOpts.cPtr = nil 55 | -------------------------------------------------------------------------------- /rocksdb/writebatch.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `WriteBatchRef` holds a collection of updates to apply atomically to the database. 11 | ## It depends on resources from an instance of `RocksDbRef' and therefore should be used 12 | ## and closed before the `RocksDbRef` is closed. 13 | 14 | {.push raises: [].} 15 | 16 | import ./lib/librocksdb, ./internal/[cftable, utils], ./rocksresult 17 | 18 | export rocksresult 19 | 20 | type 21 | WriteBatchPtr* = ptr rocksdb_writebatch_t 22 | 23 | WriteBatchRef* = ref object 24 | cPtr: WriteBatchPtr 25 | defaultCfHandle: ColFamilyHandleRef 26 | 27 | proc createWriteBatch*(defaultCfHandle: ColFamilyHandleRef): WriteBatchRef = 28 | WriteBatchRef(cPtr: rocksdb_writebatch_create(), defaultCfHandle: defaultCfHandle) 29 | 30 | proc isClosed*(batch: WriteBatchRef): bool {.inline.} = 31 | ## Returns `true` if the `WriteBatchRef` has been closed and `false` otherwise. 32 | batch.cPtr.isNil() 33 | 34 | proc cPtr*(batch: WriteBatchRef): WriteBatchPtr = 35 | ## Get the underlying database pointer. 36 | doAssert not batch.isClosed() 37 | batch.cPtr 38 | 39 | proc clear*(batch: WriteBatchRef) = 40 | ## Clears the write batch. 41 | doAssert not batch.isClosed() 42 | rocksdb_writebatch_clear(batch.cPtr) 43 | 44 | proc count*(batch: WriteBatchRef): int = 45 | ## Get the number of updates in the write batch. 46 | doAssert not batch.isClosed() 47 | rocksdb_writebatch_count(batch.cPtr).int 48 | 49 | proc put*( 50 | batch: WriteBatchRef, key, val: openArray[byte], cfHandle = batch.defaultCfHandle 51 | ): RocksDBResult[void] = 52 | ## Add a put operation to the write batch. 53 | 54 | rocksdb_writebatch_put_cf( 55 | batch.cPtr, 56 | cfHandle.cPtr, 57 | cast[cstring](key.unsafeAddrOrNil()), 58 | csize_t(key.len), 59 | cast[cstring](val.unsafeAddrOrNil()), 60 | csize_t(val.len), 61 | ) 62 | 63 | ok() 64 | 65 | proc delete*( 66 | batch: WriteBatchRef, key: openArray[byte], cfHandle = batch.defaultCfHandle 67 | ): RocksDBResult[void] = 68 | ## Add a delete operation to the write batch. 69 | 70 | rocksdb_writebatch_delete_cf( 71 | batch.cPtr, cfHandle.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len) 72 | ) 73 | 74 | ok() 75 | 76 | proc close*(batch: WriteBatchRef) = 77 | ## Close the `WriteBatchRef`. 78 | if not batch.isClosed(): 79 | rocksdb_writebatch_destroy(batch.cPtr) 80 | batch.cPtr = nil 81 | -------------------------------------------------------------------------------- /rocksdb/writebatchwi.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | ## A `WriteBatchWIRef` holds a collection of updates to apply atomically to the database. 11 | ## It depends on resources from an instance of `RocksDbRef' and therefore should be used 12 | ## and closed before the `RocksDbRef` is closed. 13 | ## 14 | ## `WriteBatchWIRef` is similar to `WriteBatchRef` but with a binary searchable index 15 | ## built for all the keys inserted which allows reading the data which has been writen 16 | ## to the batch. 17 | 18 | {.push raises: [].} 19 | 20 | import ./lib/librocksdb, ./internal/[cftable, utils], ./options/dbopts, ./rocksresult 21 | 22 | export rocksresult 23 | 24 | type 25 | WriteBatchWIPtr* = ptr rocksdb_writebatch_wi_t 26 | 27 | WriteBatchWIRef* = ref object 28 | cPtr: WriteBatchWIPtr 29 | dbOpts: DbOptionsRef 30 | defaultCfHandle: ColFamilyHandleRef 31 | 32 | proc createWriteBatch*( 33 | reservedBytes: int, 34 | overwriteKey: bool, 35 | dbOpts: DbOptionsRef, 36 | defaultCfHandle: ColFamilyHandleRef, 37 | ): WriteBatchWIRef = 38 | WriteBatchWIRef( 39 | cPtr: rocksdb_writebatch_wi_create(reservedBytes.csize_t, overwriteKey.uint8), 40 | dbOpts: dbOpts, 41 | defaultCfHandle: defaultCfHandle, 42 | ) 43 | 44 | proc isClosed*(batch: WriteBatchWIRef): bool {.inline.} = 45 | ## Returns `true` if the `WriteBatchWIRef` has been closed and `false` otherwise. 46 | batch.cPtr.isNil() 47 | 48 | proc cPtr*(batch: WriteBatchWIRef): WriteBatchWIPtr = 49 | ## Get the underlying write batch pointer. 50 | doAssert not batch.isClosed() 51 | batch.cPtr 52 | 53 | proc clear*(batch: WriteBatchWIRef) = 54 | ## Clears the write batch. 55 | doAssert not batch.isClosed() 56 | rocksdb_writebatch_wi_clear(batch.cPtr) 57 | 58 | proc count*(batch: WriteBatchWIRef): int = 59 | ## Get the number of updates in the write batch. 60 | doAssert not batch.isClosed() 61 | rocksdb_writebatch_wi_count(batch.cPtr).int 62 | 63 | proc put*( 64 | batch: WriteBatchWIRef, key, val: openArray[byte], cfHandle = batch.defaultCfHandle 65 | ): RocksDBResult[void] = 66 | ## Add a put operation to the write batch. 67 | 68 | rocksdb_writebatch_wi_put_cf( 69 | batch.cPtr, 70 | cfHandle.cPtr, 71 | cast[cstring](key.unsafeAddrOrNil()), 72 | csize_t(key.len), 73 | cast[cstring](val.unsafeAddrOrNil()), 74 | csize_t(val.len), 75 | ) 76 | 77 | ok() 78 | 79 | proc delete*( 80 | batch: WriteBatchWIRef, key: openArray[byte], cfHandle = batch.defaultCfHandle 81 | ): RocksDBResult[void] = 82 | ## Add a delete operation to the write batch. 83 | 84 | rocksdb_writebatch_wi_delete_cf( 85 | batch.cPtr, cfHandle.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len) 86 | ) 87 | 88 | ok() 89 | 90 | proc getFromBatch*( 91 | batch: WriteBatchWIRef, 92 | key: openArray[byte], 93 | onData: DataProc, 94 | cfHandle = batch.defaultCfHandle, 95 | ): RocksDBResult[bool] = 96 | ## Get the value for a given key from the batch using the provided 97 | ## `onData` callback. 98 | 99 | var 100 | len: csize_t 101 | errors: cstring 102 | let data = rocksdb_writebatch_wi_get_from_batch_cf( 103 | batch.cPtr, 104 | batch.dbOpts.cPtr, 105 | cfHandle.cPtr, 106 | cast[cstring](key.unsafeAddrOrNil()), 107 | csize_t(key.len), 108 | len.addr, 109 | cast[cstringArray](errors.addr), 110 | ) 111 | bailOnErrors(errors) 112 | 113 | if data.isNil(): 114 | doAssert len == 0 115 | ok(false) 116 | else: 117 | onData(toOpenArrayByte(data, 0, len.int - 1)) 118 | rocksdb_free(data) 119 | ok(true) 120 | 121 | proc getFromBatch*( 122 | batch: WriteBatchWIRef, key: openArray[byte], cfHandle = batch.defaultCfHandle 123 | ): RocksDBResult[seq[byte]] = 124 | ## Get the value for a given key from the batch. 125 | 126 | var dataRes: RocksDBResult[seq[byte]] 127 | proc onData(data: openArray[byte]) = 128 | dataRes.ok(@data) 129 | 130 | let res = batch.getFromBatch(key, onData, cfHandle) 131 | if res.isOk(): 132 | return dataRes 133 | 134 | dataRes.err(res.error()) 135 | 136 | proc close*(batch: WriteBatchWIRef) = 137 | ## Close the `WriteBatchWIRef`. 138 | if not batch.isClosed(): 139 | rocksdb_writebatch_wi_destroy(batch.cPtr) 140 | batch.cPtr = nil 141 | -------------------------------------------------------------------------------- /scripts/build_dlls_windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | SET SCRIPT_DIR=%~dp0 4 | 5 | cd %SCRIPT_DIR%\.. 6 | 7 | git submodule update --init 8 | 9 | CALL .\vendor\vcpkg\bootstrap-vcpkg.bat -disableMetrics 10 | 11 | .\vendor\vcpkg\vcpkg install rocksdb[lz4,zstd]:x64-windows-rocksdb --recurse --overlay-triplets=.\triplets 12 | 13 | mkdir .\build 14 | copy .\vendor\vcpkg\installed\x64-windows-rocksdb\bin\rocksdb-shared.dll .\build\librocksdb.dll 15 | -------------------------------------------------------------------------------- /scripts/build_dlls_windows.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 15 | 16 | REPO_DIR="${PWD}" 17 | 18 | git submodule update --init 19 | 20 | ${REPO_DIR}/vendor/vcpkg/bootstrap-vcpkg.sh -disableMetrics 21 | 22 | ${REPO_DIR}/vendor/vcpkg/vcpkg install rocksdb[lz4,zstd]:x64-windows-rocksdb --recurse --overlay-triplets=${REPO_DIR}/triplets 23 | 24 | mkdir -p ${REPO_DIR}/build 25 | cp ${REPO_DIR}/vendor/vcpkg/installed/x64-windows-rocksdb/bin/rocksdb-shared.dll ./build/librocksdb.dll 26 | -------------------------------------------------------------------------------- /scripts/build_shared_deps_linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024-2025 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 15 | 16 | REPO_DIR="${PWD}" 17 | BUILD_DEST="${REPO_DIR}/build/" 18 | 19 | git submodule update --init 20 | 21 | ${REPO_DIR}/vendor/vcpkg/bootstrap-vcpkg.sh -disableMetrics 22 | 23 | ${REPO_DIR}/vendor/vcpkg/vcpkg install rocksdb[lz4,zstd]:x64-linux-rocksdb --recurse --overlay-triplets=${REPO_DIR}/triplets 24 | 25 | mkdir -p "${BUILD_DEST}" 26 | 27 | cp "${REPO_DIR}/vendor/vcpkg/installed/x64-linux-rocksdb/lib/librocksdb.so" "${BUILD_DEST}/" 28 | -------------------------------------------------------------------------------- /scripts/build_shared_deps_osx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024-2025 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 15 | 16 | REPO_DIR="${PWD}" 17 | BUILD_DEST="${REPO_DIR}/build/" 18 | 19 | git submodule update --init 20 | 21 | ${REPO_DIR}/vendor/vcpkg/bootstrap-vcpkg.sh -disableMetrics 22 | 23 | ${REPO_DIR}/vendor/vcpkg/vcpkg install rocksdb[lz4,zstd]:x64-osx-rocksdb --recurse --overlay-triplets=${REPO_DIR}/triplets 24 | 25 | mkdir -p "${BUILD_DEST}" 26 | 27 | cp "${REPO_DIR}/vendor/vcpkg/installed/x64-osx-rocksdb/lib/librocksdb.dylib" "${BUILD_DEST}/" 28 | -------------------------------------------------------------------------------- /scripts/build_static_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024-2025 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 15 | 16 | REPO_DIR="${PWD}" 17 | ROCKSDB_LIB_DIR="${REPO_DIR}/vendor/rocksdb" 18 | BUILD_DEST="${REPO_DIR}/build" 19 | 20 | : "${MAKE:=make}" 21 | 22 | [[ -z "$NPROC" ]] && NPROC=2 # number of CPU cores available 23 | 24 | git submodule update --init 25 | 26 | export DISABLE_WARNING_AS_ERROR=1 27 | export ROCKSDB_DISABLE_SNAPPY=1 28 | export ROCKSDB_DISABLE_ZLIB=1 29 | export ROCKSDB_DISABLE_BZIP=1 30 | export PORTABLE=1 31 | export DEBUG_LEVEL=0 32 | #export USE_LTO=1 33 | 34 | if [ -f "${BUILD_DEST}/librocksdb.a" ] && \ 35 | [ -f "${BUILD_DEST}/liblz4.a" ] && \ 36 | [ -f "${BUILD_DEST}/libzstd.a" ] && \ 37 | [ -f "${BUILD_DEST}/version.txt" ]; then 38 | 39 | ROCKSDB_VERSION_BEFORE=$(cat "${BUILD_DEST}/version.txt") 40 | 41 | cd ${REPO_DIR}/vendor/rocksdb 42 | ROCKSDB_VERSION_AFTER=$(${REPO_DIR}/vendor/rocksdb/build_tools/version.sh full) 43 | cd ${REPO_DIR} 44 | 45 | if [[ ${ROCKSDB_VERSION_BEFORE} == ${ROCKSDB_VERSION_AFTER} ]]; then 46 | echo "RocksDb static libraries already built. Skipping build." 47 | exit 0 48 | fi 49 | fi 50 | 51 | if ${MAKE} -C "${ROCKSDB_LIB_DIR}" -q unity.a; then 52 | echo "RocksDb static libraries already built. Skipping build." 53 | 54 | # Copy the built libraries in case the build directory has been removed 55 | mkdir -p "${BUILD_DEST}" 56 | cp "${ROCKSDB_LIB_DIR}/liblz4.a" "${BUILD_DEST}/" 57 | cp "${ROCKSDB_LIB_DIR}/libzstd.a" "${BUILD_DEST}/" 58 | cp "${ROCKSDB_LIB_DIR}/unity.a" "${BUILD_DEST}/librocksdb.a" 59 | 60 | cd ${REPO_DIR}/vendor/rocksdb 61 | ${REPO_DIR}/vendor/rocksdb/build_tools/version.sh full > "${BUILD_DEST}/version.txt" 2>&1 62 | cd ${REPO_DIR} 63 | 64 | exit 0 65 | else 66 | ${REPO_DIR}/scripts/clean_build_artifacts.sh 67 | echo "Building RocksDb static libraries." 68 | fi 69 | 70 | ${MAKE} -j${NPROC} -C "${ROCKSDB_LIB_DIR}" liblz4.a libzstd.a --no-print-directory > /dev/null 2>&1 71 | 72 | export EXTRA_CFLAGS="-fpermissive -Wno-error -w -I${ROCKSDB_LIB_DIR}/lz4-1.9.4/lib -I${ROCKSDB_LIB_DIR}/zstd-1.5.5/lib -DLZ4 -DZSTD" 73 | export EXTRA_CXXFLAGS="-fpermissive -Wno-error -w -I${ROCKSDB_LIB_DIR}/lz4-1.9.4/lib -I${ROCKSDB_LIB_DIR}/zstd-1.5.5/lib -DLZ4 -DZSTD" 74 | 75 | ${MAKE} -j${NPROC} -C "${ROCKSDB_LIB_DIR}" unity.a --no-print-directory > /dev/null 2>&1 76 | 77 | #cat "${REPO_DIR}/vendor/rocksdb/make_config.mk" 78 | 79 | mkdir -p "${BUILD_DEST}" 80 | 81 | cp "${ROCKSDB_LIB_DIR}/liblz4.a" "${BUILD_DEST}/" 82 | cp "${ROCKSDB_LIB_DIR}/libzstd.a" "${BUILD_DEST}/" 83 | cp "${ROCKSDB_LIB_DIR}/unity.a" "${BUILD_DEST}/librocksdb.a" 84 | 85 | cd ${REPO_DIR}/vendor/rocksdb 86 | ${REPO_DIR}/vendor/rocksdb/build_tools/version.sh full > "${BUILD_DEST}/version.txt" 2>&1 87 | cd ${REPO_DIR} 88 | -------------------------------------------------------------------------------- /scripts/clean_build_artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 15 | 16 | echo "Cleaning up RocksDb build artifacts." 17 | 18 | rm -rf build 19 | make -C vendor/rocksdb clean --no-print-directory > /dev/null || true 20 | 21 | git submodule foreach --recursive git clean -fdx > /dev/null 22 | -------------------------------------------------------------------------------- /scripts/generate_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Nim-RocksDB 4 | # Copyright 2024 Status Research & Development GmbH 5 | # Licensed under either of 6 | # 7 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 9 | # 10 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 11 | 12 | set -e 13 | 14 | #nimble install c2nim 15 | 16 | cd "$(dirname "${BASH_SOURCE[0]}")"/.. 17 | 18 | VENDOR_HEADER_FILE="vendor/rocksdb/include/rocksdb/c.h" 19 | OUTPUT_HEADER_FILE="rocksdb/lib/rocksdb.h" 20 | C2NIM_GENERATED_WRAPPER="rocksdb/lib/rocksdb_gen.nim" 21 | 22 | # copy and rename vendor c.h to rocksdb.h 23 | cp ${VENDOR_HEADER_FILE} ${OUTPUT_HEADER_FILE} 24 | 25 | # update rocksdb.h file with c2nim settings required to generate nim wrapper 26 | sed -i ':a;N;$!ba;s/#ifdef _WIN32\ 27 | #ifdef ROCKSDB_DLL\ 28 | #ifdef ROCKSDB_LIBRARY_EXPORTS\ 29 | #define ROCKSDB_LIBRARY_API __declspec(dllexport)\ 30 | #else\ 31 | #define ROCKSDB_LIBRARY_API __declspec(dllimport)\ 32 | #endif\ 33 | #else\ 34 | #define ROCKSDB_LIBRARY_API\ 35 | #endif\ 36 | #else\ 37 | #define ROCKSDB_LIBRARY_API\ 38 | #endif/#ifdef C2NIM\ 39 | # def ROCKSDB_LIBRARY_API\ 40 | # cdecl\ 41 | # mangle uint32_t uint32\ 42 | # mangle uint16_t uint16\ 43 | # mangle uint8_t uint8\ 44 | # mangle uint64_t uint64\ 45 | # mangle int32_t int32\ 46 | # mangle int16_t int16\ 47 | # mangle int8_t int8\ 48 | # mangle int64_t int64\ 49 | # mangle cuchar uint8\ 50 | #else\ 51 | # ifdef _WIN32\ 52 | # ifdef ROCKSDB_DLL\ 53 | # ifdef ROCKSDB_LIBRARY_EXPORTS\ 54 | # define ROCKSDB_LIBRARY_API __declspec(dllexport)\ 55 | # else\ 56 | # define ROCKSDB_LIBRARY_API __declspec(dllimport)\ 57 | # endif\ 58 | # else\ 59 | # define ROCKSDB_LIBRARY_API\ 60 | # endif\ 61 | # else\ 62 | # define ROCKSDB_LIBRARY_API\ 63 | # endif\ 64 | #endif/g' ${OUTPUT_HEADER_FILE} 65 | 66 | # generate nim wrapper 67 | c2nim ${OUTPUT_HEADER_FILE} --out:"${C2NIM_GENERATED_WRAPPER}" 68 | 69 | #nimble format 70 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all executable files 2 | * 3 | !*.* 4 | !*/ 5 | *.exe 6 | 7 | -------------------------------------------------------------------------------- /tests/columnfamily/test_cfdescriptor.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/columnfamily/cfdescriptor 13 | 14 | suite "ColFamilyDescriptor Tests": 15 | const TEST_CF_NAME = "test" 16 | 17 | test "Test initColFamilyDescriptor with options": 18 | var descriptor = initColFamilyDescriptor(TEST_CF_NAME, defaultColFamilyOptions()) 19 | 20 | check: 21 | descriptor.name() == TEST_CF_NAME 22 | not descriptor.options().isNil() 23 | not descriptor.isDefault() 24 | 25 | descriptor.close() 26 | 27 | test "Test defaultColFamilyDescriptor": 28 | var descriptor = defaultColFamilyDescriptor() 29 | 30 | check: 31 | descriptor.name() == DEFAULT_COLUMN_FAMILY_NAME 32 | not descriptor.options().isNil() 33 | descriptor.isDefault() 34 | 35 | descriptor.close() 36 | 37 | test "Test close": 38 | var descriptor = defaultColFamilyDescriptor() 39 | 40 | check not descriptor.isClosed() 41 | descriptor.close() 42 | check descriptor.isClosed() 43 | descriptor.close() 44 | check descriptor.isClosed() 45 | -------------------------------------------------------------------------------- /tests/columnfamily/test_cfhandle.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import 13 | std/os, 14 | tempfile, 15 | unittest2, 16 | ../../rocksdb/lib/librocksdb, 17 | ../../rocksdb/columnfamily/cfhandle 18 | 19 | suite "ColFamilyHandleRef Tests": 20 | const TEST_CF_NAME = "test" 21 | 22 | setup: 23 | let 24 | dbPath = mkdtemp() / "data" 25 | dbOpts = rocksdb_options_create() 26 | cfOpts = rocksdb_options_create() 27 | 28 | var errors: cstring 29 | 30 | rocksdb_options_set_create_if_missing(dbOpts, 1) 31 | 32 | let db = rocksdb_open(dbOpts, dbPath.cstring, cast[cstringArray](errors.addr)) 33 | doAssert errors.isNil() 34 | doAssert not db.isNil() 35 | 36 | let cfHandlePtr = rocksdb_create_column_family( 37 | db, cfOpts, TEST_CF_NAME.cstring, cast[cstringArray](errors.addr) 38 | ) 39 | doAssert errors.isNil() 40 | doAssert not cfHandlePtr.isNil() 41 | 42 | teardown: 43 | rocksdb_close(db) 44 | removeDir($dbPath) 45 | 46 | test "Test newColFamilyHandle": 47 | let cfHandle = newColFamilyHandle(cfHandlePtr) 48 | 49 | check: 50 | not cfHandle.cPtr.isNil() 51 | cfHandle.cPtr == cfHandlePtr 52 | 53 | cfHandle.close() 54 | 55 | test "Test close": 56 | let cfHandle = newColFamilyHandle(cfHandlePtr) 57 | 58 | check not cfHandle.isClosed() 59 | cfHandle.close() 60 | check cfHandle.isClosed() 61 | cfHandle.close() 62 | check cfHandle.isClosed() 63 | -------------------------------------------------------------------------------- /tests/columnfamily/test_cfopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/columnfamily/cfopts 13 | 14 | suite "ColFamilyOptionsRef Tests": 15 | test "Test close": 16 | var cfOpts = defaultColFamilyOptions() 17 | 18 | check not cfOpts.isClosed() 19 | cfOpts.close() 20 | check cfOpts.isClosed() 21 | cfOpts.close() 22 | check cfOpts.isClosed() 23 | 24 | test "Test auto close enabled": 25 | let 26 | cfOpts = defaultColFamilyOptions() 27 | tableOpts = defaultTableOptions(autoClose = true) 28 | sliceTransform = createFixedPrefix(1000) 29 | 30 | cfOpts.blockBasedTableFactory = tableOpts 31 | cfOpts.setPrefixExtractor(sliceTransform) 32 | 33 | check: 34 | cfOpts.isClosed() == false 35 | tableOpts.isClosed() == false 36 | sliceTransform.isClosed() == true # closed because tableopts takes ownership 37 | 38 | cfOpts.close() 39 | 40 | check: 41 | cfOpts.isClosed() == true 42 | tableOpts.isClosed() == true 43 | sliceTransform.isClosed() == true 44 | 45 | test "Test auto close disabled": 46 | let 47 | cfOpts = defaultColFamilyOptions() 48 | tableOpts = defaultTableOptions(autoClose = false) 49 | sliceTransform = createFixedPrefix(1000) 50 | 51 | cfOpts.blockBasedTableFactory = tableOpts 52 | cfOpts.setPrefixExtractor(sliceTransform) 53 | 54 | check: 55 | cfOpts.isClosed() == false 56 | tableOpts.isClosed() == false 57 | sliceTransform.isClosed() == true # closed because tableopts takes ownership 58 | 59 | cfOpts.close() 60 | 61 | check: 62 | cfOpts.isClosed() == true 63 | tableOpts.isClosed() == false 64 | sliceTransform.isClosed() == true 65 | -------------------------------------------------------------------------------- /tests/internal/test_cftable.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import 13 | std/os, 14 | tempfile, 15 | unittest2, 16 | ../../rocksdb/lib/librocksdb, 17 | ../../rocksdb/columnfamily/cfhandle, 18 | ../../rocksdb/internal/cftable 19 | 20 | suite "ColFamilyTableRef Tests": 21 | const TEST_CF_NAME = "test" 22 | 23 | setup: 24 | let 25 | dbPath = mkdtemp() / "data" 26 | dbOpts = rocksdb_options_create() 27 | cfOpts = rocksdb_options_create() 28 | 29 | var errors: cstring 30 | 31 | rocksdb_options_set_create_if_missing(dbOpts, 1) 32 | 33 | let db = rocksdb_open(dbOpts, dbPath.cstring, cast[cstringArray](errors.addr)) 34 | doAssert errors.isNil() 35 | doAssert not db.isNil() 36 | 37 | let cfHandlePtr = rocksdb_create_column_family( 38 | db, cfOpts, TEST_CF_NAME.cstring, cast[cstringArray](errors.addr) 39 | ) 40 | doAssert errors.isNil() 41 | doAssert not cfHandlePtr.isNil() 42 | 43 | teardown: 44 | rocksdb_close(db) 45 | removeDir($dbPath) 46 | 47 | test "Test newColFamilyTable": 48 | var cfTable = 49 | newColFamilyTable(@[TEST_CF_NAME, TEST_CF_NAME], @[cfHandlePtr, cfHandlePtr]) 50 | 51 | check cfTable.get(TEST_CF_NAME).cPtr() == cfHandlePtr 52 | check not cfTable.isClosed() 53 | 54 | # doesn't exist 55 | check cfTable.get("other").isNil() 56 | check not cfTable.isClosed() 57 | 58 | cfTable.close() 59 | 60 | test "Test close": 61 | var cfTable = newColFamilyTable(@[TEST_CF_NAME], @[cfHandlePtr]) 62 | 63 | let cfHandle = cfTable.get(TEST_CF_NAME) 64 | 65 | check not cfHandle.isClosed() 66 | check not cfTable.isClosed() 67 | cfTable.close() 68 | check cfHandle.isClosed() 69 | check cfTable.isClosed() 70 | cfTable.close() 71 | check cfTable.isClosed() 72 | -------------------------------------------------------------------------------- /tests/lib/test_librocksdb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2018-2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/[cpuinfo, os], tempfile, unittest2, ../../rocksdb/lib/librocksdb 13 | 14 | suite "librocksdb C wrapper Tests": 15 | setup: 16 | let 17 | dbPath = mkdtemp().cstring 18 | dbBackupPath = mkdtemp().cstring 19 | 20 | teardown: 21 | removeDir($dbPath) 22 | removeDir($dbBackupPath) 23 | 24 | test "Simple create-update-close example": 25 | var 26 | db: ptr rocksdb_t 27 | be: ptr rocksdb_backup_engine_t 28 | options = rocksdb_options_create() 29 | 30 | let cpus = countProcessors() 31 | rocksdb_options_increase_parallelism(options, cpus.int32) 32 | # This requires snappy - disabled because rocksdb is not always compiled with 33 | # snappy support (for example Fedora 28, certain Ubuntu versions) 34 | # rocksdb_options_optimize_level_style_compaction(options, 0); 35 | # create the DB if it's not already present 36 | rocksdb_options_set_create_if_missing(options, 1) 37 | 38 | var # open DB 39 | err: cstringArray # memory leak: example code does not free error string! 40 | db = rocksdb_open(options, dbPath, err) 41 | check: 42 | err.isNil 43 | 44 | # open Backup Engine that we will use for backing up our database 45 | be = rocksdb_backup_engine_open(options, dbBackupPath, err) 46 | check: 47 | err.isNil 48 | 49 | # Put key-value 50 | var writeOptions = rocksdb_writeoptions_create() 51 | let key = "key" 52 | let put_value = "value" 53 | rocksdb_put( 54 | db, 55 | writeOptions, 56 | key.cstring, 57 | csize_t(key.len), 58 | put_value.cstring, 59 | csize_t(put_value.len), 60 | err, 61 | ) 62 | check: 63 | err.isNil 64 | 65 | # Get value 66 | var readOptions = rocksdb_readoptions_create() 67 | var len: csize_t 68 | let raw_value = 69 | rocksdb_get(db, readOptions, key.cstring, csize_t(key.len), addr len, err) 70 | # Important: rocksdb_get is not null-terminated 71 | check: 72 | err.isNil 73 | 74 | # Copy it to a regular Nim string (copyMem workaround because non-null terminated) 75 | var get_value = newString(int(len)) 76 | copyMem(addr get_value[0], unsafeAddr raw_value[0], int(len) * sizeof(char)) 77 | 78 | check: 79 | $get_value == $put_value 80 | 81 | # create new backup in a directory specified by DBBackupPath 82 | rocksdb_backup_engine_create_new_backup(be, db, err) 83 | check: 84 | err.isNil 85 | 86 | rocksdb_close(db) 87 | 88 | # If something is wrong, you might want to restore data from last backup 89 | var restoreOptions = rocksdb_restore_options_create() 90 | rocksdb_backup_engine_restore_db_from_latest_backup( 91 | be, dbPath, dbPath, restoreOptions, err 92 | ) 93 | check: 94 | err.isNil 95 | rocksdb_restore_options_destroy(restoreOptions) 96 | 97 | db = rocksdb_open(options, dbPath, err) 98 | check: 99 | err.isNil 100 | 101 | # cleanup 102 | rocksdb_writeoptions_destroy(writeOptions) 103 | rocksdb_readoptions_destroy(readOptions) 104 | rocksdb_options_destroy(options) 105 | rocksdb_backup_engine_close(be) 106 | rocksdb_close(db) 107 | -------------------------------------------------------------------------------- /tests/options/test_backupopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/options/backupopts 13 | 14 | suite "BackupEngineOptionsRef Tests": 15 | test "Test newBackupEngineOptions": 16 | let backupOpts = createBackupEngineOptions(".") 17 | 18 | check not backupOpts.cPtr.isNil() 19 | 20 | backupOpts.close() 21 | 22 | test "Test defaultBackupEngineOptions": 23 | let backupOpts = defaultBackupEngineOptions(".") 24 | 25 | check not backupOpts.cPtr.isNil() 26 | 27 | backupOpts.close() 28 | 29 | test "Test close": 30 | let backupOpts = defaultBackupEngineOptions(".") 31 | 32 | check not backupOpts.isClosed() 33 | backupOpts.close() 34 | check backupOpts.isClosed() 35 | backupOpts.close() 36 | check backupOpts.isClosed() 37 | -------------------------------------------------------------------------------- /tests/options/test_dbopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/options/dbopts 13 | 14 | suite "DbOptionsRef Tests": 15 | test "Test newDbOptions": 16 | let dbOpts = createDbOptions() 17 | 18 | check not dbOpts.cPtr.isNil() 19 | 20 | dbOpts.maxOpenFiles = 10 21 | dbOpts.createMissingColumnFamilies = false 22 | 23 | check: 24 | dbOpts.maxOpenFiles == 10 25 | not dbOpts.createMissingColumnFamilies 26 | 27 | dbOpts.close() 28 | 29 | test "Test close": 30 | let dbOpts = defaultDbOptions() 31 | 32 | check not dbOpts.isClosed() 33 | dbOpts.close() 34 | check dbOpts.isClosed() 35 | dbOpts.close() 36 | check dbOpts.isClosed() 37 | 38 | test "Test auto close enabled": 39 | let 40 | dbOpts = defaultDbOptions() 41 | cache = cacheCreateLRU(1000, autoClose = true) 42 | 43 | dbOpts.rowCache = cache 44 | 45 | check: 46 | dbOpts.isClosed() == false 47 | cache.isClosed() == false 48 | 49 | dbOpts.close() 50 | 51 | check: 52 | dbOpts.isClosed() == true 53 | cache.isClosed() == true 54 | -------------------------------------------------------------------------------- /tests/options/test_readopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/options/readopts 13 | 14 | suite "ReadOptionsRef Tests": 15 | test "Test newReadOptions": 16 | var readOpts = createReadOptions() 17 | 18 | check not readOpts.cPtr.isNil() 19 | 20 | readOpts.close() 21 | 22 | test "Test defaultReadOptions": 23 | var readOpts = defaultReadOptions() 24 | 25 | check not readOpts.cPtr.isNil() 26 | 27 | readOpts.close() 28 | 29 | test "Test close": 30 | var readOpts = defaultReadOptions() 31 | 32 | check not readOpts.isClosed() 33 | readOpts.close() 34 | check readOpts.isClosed() 35 | readOpts.close() 36 | check readOpts.isClosed() 37 | -------------------------------------------------------------------------------- /tests/options/test_tableopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/options/tableopts 13 | 14 | suite "TableOptionsRef Tests": 15 | test "Test createTableOptions": 16 | let tableOpts = createTableOptions() 17 | 18 | check not tableOpts.cPtr.isNil() 19 | 20 | tableOpts.close() 21 | 22 | test "Test defaultTableOptions": 23 | let tableOpts = defaultTableOptions() 24 | 25 | check not tableOpts.cPtr.isNil() 26 | 27 | tableOpts.close() 28 | 29 | test "Test close": 30 | let tableOpts = defaultTableOptions() 31 | 32 | check not tableOpts.isClosed() 33 | tableOpts.close() 34 | check tableOpts.isClosed() 35 | tableOpts.close() 36 | check tableOpts.isClosed() 37 | 38 | test "Test auto close enabled": 39 | let 40 | tableOpts = defaultTableOptions() 41 | cache = cacheCreateLRU(1000, autoClose = true) 42 | filter = createRibbon(9.9) 43 | 44 | tableOpts.blockCache = cache 45 | tableOpts.filterPolicy = filter 46 | 47 | check: 48 | tableOpts.isClosed() == false 49 | cache.isClosed() == false 50 | filter.isClosed() == true # closed because tableopts takes ownership 51 | 52 | tableOpts.close() 53 | 54 | check: 55 | tableOpts.isClosed() == true 56 | cache.isClosed() == true 57 | filter.isClosed() == true 58 | 59 | test "Test auto close disabled": 60 | let 61 | tableOpts = defaultTableOptions() 62 | cache = cacheCreateLRU(1000, autoClose = false) 63 | filter = createRibbon(9.9) 64 | 65 | tableOpts.blockCache = cache 66 | tableOpts.filterPolicy = filter 67 | 68 | check: 69 | tableOpts.isClosed() == false 70 | cache.isClosed() == false 71 | filter.isClosed() == true # closed because tableopts takes ownership 72 | 73 | tableOpts.close() 74 | 75 | check: 76 | tableOpts.isClosed() == true 77 | cache.isClosed() == false 78 | filter.isClosed() == true 79 | -------------------------------------------------------------------------------- /tests/options/test_writeopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/options/writeopts 13 | 14 | suite "WriteOptionsRef Tests": 15 | test "Test newWriteOptions": 16 | var writeOpts = createWriteOptions() 17 | 18 | check not writeOpts.cPtr.isNil() 19 | 20 | writeOpts.close() 21 | 22 | test "Test defaultWriteOptions": 23 | var writeOpts = defaultWriteOptions() 24 | 25 | check not writeOpts.cPtr.isNil() 26 | 27 | writeOpts.close() 28 | 29 | test "Test close": 30 | var writeOpts = defaultWriteOptions() 31 | 32 | check not writeOpts.isClosed() 33 | writeOpts.close() 34 | check writeOpts.isClosed() 35 | writeOpts.close() 36 | check writeOpts.isClosed() 37 | -------------------------------------------------------------------------------- /tests/test_all.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2018-2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | import 11 | ./columnfamily/test_cfdescriptor, 12 | ./columnfamily/test_cfhandle, 13 | ./columnfamily/test_cfopts, 14 | ./internal/test_cftable, 15 | ./lib/test_librocksdb, 16 | ./options/test_backupopts, 17 | ./options/test_dbopts, 18 | ./options/test_readopts, 19 | ./options/test_tableopts, 20 | ./options/test_writeopts, 21 | ./transactions/test_otxopts, 22 | ./transactions/test_txdbopts, 23 | ./transactions/test_txopts, 24 | ./test_backup, 25 | ./test_columnfamily, 26 | ./test_optimistictxdb, 27 | ./test_rocksdb, 28 | ./test_rocksiterator, 29 | ./test_sstfilewriter, 30 | ./test_transactiondb, 31 | ./test_writebatch, 32 | ./test_writebatchwi 33 | -------------------------------------------------------------------------------- /tests/test_backup.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/backup, ./test_helper 13 | 14 | suite "BackupEngineRef Tests": 15 | let 16 | key = @[byte(1), 2, 3, 4, 5] 17 | val = @[byte(1), 2, 3, 4, 5] 18 | 19 | setup: 20 | let 21 | dbPath = mkdtemp() / "data" 22 | dbBackupPath = mkdtemp() / "backup" 23 | dbRestorePath = mkdtemp() / "restore" 24 | db = initReadWriteDb(dbPath) 25 | 26 | teardown: 27 | db.close() 28 | removeDir($dbPath) 29 | removeDir($dbBackupPath) 30 | removeDir($dbRestorePath) 31 | 32 | test "Test backup": 33 | let engine = initBackupEngine(dbBackupPath) 34 | 35 | check: 36 | db.put(key, val).isOk() 37 | db.keyExists(key).value() 38 | 39 | check engine.createNewBackup(db).isOk() 40 | 41 | check: 42 | db.delete(key).isOk() 43 | not db.keyExists(key).value() 44 | 45 | check engine.restoreDbFromLatestBackup(dbRestorePath).isOk() 46 | 47 | let db2 = initReadWriteDb(dbRestorePath) 48 | check db2.keyExists(key).value() 49 | db2.close() 50 | 51 | engine.close() 52 | 53 | test "Test close": 54 | let engine = openBackupEngine(dbPath).get() 55 | 56 | check not engine.isClosed() 57 | engine.close() 58 | check engine.isClosed() 59 | engine.close() 60 | check engine.isClosed() 61 | 62 | test "Test auto close enabled": 63 | let 64 | backupOpts = defaultBackupEngineOptions(dbPath, autoClose = true) 65 | backupEngine = openBackupEngine(dbPath, backupOpts).get() 66 | 67 | check: 68 | backupOpts.isClosed() == false 69 | backupEngine.isClosed() == false 70 | 71 | backupEngine.close() 72 | 73 | check: 74 | backupOpts.isClosed() == true 75 | backupEngine.isClosed() == true 76 | 77 | test "Test auto close disabled": 78 | let 79 | backupOpts = defaultBackupEngineOptions(dbPath, autoClose = false) 80 | backupEngine = openBackupEngine(dbPath, backupOpts).get() 81 | 82 | check: 83 | backupOpts.isClosed() == false 84 | backupEngine.isClosed() == false 85 | 86 | backupEngine.close() 87 | 88 | check: 89 | backupOpts.isClosed() == false 90 | backupEngine.isClosed() == true 91 | -------------------------------------------------------------------------------- /tests/test_columnfamily.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2018-2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/columnfamily, ./test_helper 13 | 14 | suite "ColFamily Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key = @[byte(1), 2, 3, 4, 5] 21 | otherKey = @[byte(1), 2, 3, 4, 5, 6] 22 | val = @[byte(1), 2, 3, 4, 5] 23 | 24 | setup: 25 | let 26 | dbPath = mkdtemp() / "data" 27 | db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 28 | 29 | teardown: 30 | db.close() 31 | removeDir($dbPath) 32 | 33 | test "Basic operations": 34 | let r0 = db.getColFamily(CF_OTHER) 35 | check r0.isOk() 36 | let cf = r0.value() 37 | 38 | check cf.put(key, val).isOk() 39 | 40 | var bytes: seq[byte] 41 | check cf.get( 42 | key, 43 | proc(data: openArray[byte]) = 44 | bytes = @data, 45 | )[] 46 | check not cf.get( 47 | otherKey, 48 | proc(data: openArray[byte]) = 49 | bytes = @data, 50 | )[] 51 | 52 | var r1 = cf.get(key) 53 | check r1.isOk() and r1.value == val 54 | 55 | var r2 = cf.get(otherKey) 56 | # there's no error string for missing keys 57 | check r2.isOk() == false and r2.error.len == 0 58 | 59 | var e1 = cf.keyExists(key) 60 | check e1.isOk() and e1.value == true 61 | 62 | var e2 = cf.keyExists(otherKey) 63 | check e2.isOk() and e2.value == false 64 | 65 | var d = cf.delete(key) 66 | check d.isOk() 67 | 68 | e1 = cf.keyExists(key) 69 | check e1.isOk() and e1.value == false 70 | 71 | d = cf.delete(otherKey) 72 | check d.isOk() 73 | 74 | cf.db.close() 75 | check db.isClosed() 76 | 77 | # Open database in read only mode 78 | block: 79 | var res = initReadOnlyDb(dbPath).getColFamily(CF_DEFAULT) 80 | check res.isOk() 81 | 82 | let readOnlyCf = res.value() 83 | let r = readOnlyCf.keyExists(key) 84 | check r.isOk() and r.value == false 85 | 86 | readOnlyCf.db.close() 87 | check readOnlyCf.db.isClosed() 88 | 89 | test "Test iterator": 90 | let cf = db.getColFamily(CF_OTHER).get() 91 | check cf.put(key, val).isOk() 92 | 93 | let iter = cf.openIterator().get() 94 | defer: 95 | iter.close() 96 | 97 | iter.seekToKey(key) 98 | check: 99 | iter.isValid() == true 100 | iter.key() == key 101 | iter.value() == val 102 | iter.seekToKey(otherKey) 103 | check iter.isValid() == false 104 | -------------------------------------------------------------------------------- /tests/test_helper.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/sequtils, ../rocksdb/[backup, rocksdb, transactiondb, optimistictxdb] 13 | 14 | proc initReadWriteDb*( 15 | path: string, columnFamilyNames: openArray[string] = @[] 16 | ): RocksDbReadWriteRef = 17 | let res = openRocksDb( 18 | path, 19 | columnFamilies = columnFamilyNames.mapIt( 20 | initColFamilyDescriptor(it, defaultColFamilyOptions(autoClose = true)) 21 | ), 22 | ) 23 | if res.isErr(): 24 | echo res.error() 25 | doAssert res.isOk() 26 | res.value() 27 | 28 | proc initReadOnlyDb*( 29 | path: string, columnFamilyNames: openArray[string] = @[] 30 | ): RocksDbReadOnlyRef = 31 | let res = openRocksDbReadOnly( 32 | path, 33 | columnFamilies = columnFamilyNames.mapIt( 34 | initColFamilyDescriptor(it, defaultColFamilyOptions(autoClose = true)) 35 | ), 36 | ) 37 | if res.isErr(): 38 | echo res.error() 39 | doAssert res.isOk() 40 | res.value() 41 | 42 | proc initBackupEngine*(path: string): BackupEngineRef = 43 | let res = openBackupEngine(path) 44 | doAssert res.isOk() 45 | res.value() 46 | 47 | proc initTransactionDb*( 48 | path: string, columnFamilyNames: openArray[string] = @[] 49 | ): TransactionDbRef = 50 | let res = openTransactionDb( 51 | path, 52 | columnFamilies = columnFamilyNames.mapIt( 53 | initColFamilyDescriptor(it, defaultColFamilyOptions(autoClose = true)) 54 | ), 55 | ) 56 | if res.isErr(): 57 | echo res.error() 58 | doAssert res.isOk() 59 | res.value() 60 | 61 | proc initOptimisticTxDb*( 62 | path: string, columnFamilyNames: openArray[string] = @[] 63 | ): OptimisticTxDbRef = 64 | let res = openOptimisticTxDb( 65 | path, 66 | columnFamilies = columnFamilyNames.mapIt( 67 | initColFamilyDescriptor(it, defaultColFamilyOptions(autoClose = true)) 68 | ), 69 | ) 70 | if res.isErr(): 71 | echo res.error() 72 | doAssert res.isOk() 73 | res.value() 74 | -------------------------------------------------------------------------------- /tests/test_optimistictxdb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/optimistictxdb, ./test_helper 13 | 14 | suite "OptimisticTxDbRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key1 = @[byte(1)] 21 | val1 = @[byte(1)] 22 | key2 = @[byte(2)] 23 | val2 = @[byte(2)] 24 | key3 = @[byte(3)] 25 | val3 = @[byte(3)] 26 | 27 | setup: 28 | let 29 | dbPath = mkdtemp() / "data" 30 | db = initOptimisticTxDb(dbPath, columnFamilyNames = @[CF_OTHER]) 31 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 32 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 33 | 34 | teardown: 35 | db.close() 36 | removeDir($dbPath) 37 | 38 | # test multiple transactions 39 | test "Test rollback using default column family": 40 | var tx = db.beginTransaction() 41 | defer: 42 | tx.close() 43 | check not tx.isClosed() 44 | 45 | check: 46 | tx.put(key1, val1).isOk() 47 | tx.put(key2, val2).isOk() 48 | tx.put(key3, val3).isOk() 49 | 50 | tx.delete(key2).isOk() 51 | not tx.isClosed() 52 | 53 | check: 54 | tx.get(key1).get() == val1 55 | tx.get(key2).error() == "" 56 | tx.get(key3).get() == val3 57 | 58 | let res = tx.rollback() 59 | check: 60 | res.isOk() 61 | tx.get(key1).error() == "" 62 | tx.get(key2).error() == "" 63 | tx.get(key3).error() == "" 64 | 65 | test "Test commit using default column family": 66 | var tx = db.beginTransaction() 67 | defer: 68 | tx.close() 69 | check not tx.isClosed() 70 | 71 | check: 72 | tx.put(key1, val1).isOk() 73 | tx.put(key2, val2).isOk() 74 | tx.put(key3, val3).isOk() 75 | 76 | tx.delete(key2).isOk() 77 | not tx.isClosed() 78 | 79 | check: 80 | tx.get(key1).get() == val1 81 | tx.get(key2).error() == "" 82 | tx.get(key3).get() == val3 83 | 84 | let res = tx.commit() 85 | check: 86 | res.isOk() 87 | tx.get(key1).get() == val1 88 | tx.get(key2).error() == "" 89 | tx.get(key3).get() == val3 90 | 91 | test "Test setting column family in beginTransaction": 92 | var tx = db.beginTransaction(cfHandle = otherCfHandle) 93 | defer: 94 | tx.close() 95 | check not tx.isClosed() 96 | 97 | check: 98 | tx.put(key1, val1).isOk() 99 | tx.put(key2, val2).isOk() 100 | tx.put(key3, val3).isOk() 101 | 102 | tx.delete(key2).isOk() 103 | not tx.isClosed() 104 | 105 | check: 106 | tx.get(key1, defaultCfHandle).error() == "" 107 | tx.get(key2, defaultCfHandle).error() == "" 108 | tx.get(key3, defaultCfHandle).error() == "" 109 | tx.get(key1, otherCfHandle).get() == val1 110 | tx.get(key2, otherCfHandle).error() == "" 111 | tx.get(key3, otherCfHandle).get() == val3 112 | 113 | test "Test rollback and commit with multiple transactions": 114 | var tx1 = db.beginTransaction(cfHandle = defaultCfHandle) 115 | defer: 116 | tx1.close() 117 | check not tx1.isClosed() 118 | var tx2 = db.beginTransaction(cfHandle = otherCfHandle) 119 | defer: 120 | tx2.close() 121 | check not tx2.isClosed() 122 | 123 | check: 124 | tx1.put(key1, val1).isOk() 125 | tx1.put(key2, val2).isOk() 126 | tx1.put(key3, val3).isOk() 127 | tx1.delete(key2).isOk() 128 | not tx1.isClosed() 129 | tx2.put(key1, val1).isOk() 130 | tx2.put(key2, val2).isOk() 131 | tx2.put(key3, val3).isOk() 132 | tx2.delete(key2).isOk() 133 | not tx2.isClosed() 134 | 135 | check: 136 | tx1.get(key1, defaultCfHandle).get() == val1 137 | tx1.get(key2, defaultCfHandle).error() == "" 138 | tx1.get(key3, defaultCfHandle).get() == val3 139 | tx1.get(key1, otherCfHandle).error() == "" 140 | tx1.get(key2, otherCfHandle).error() == "" 141 | tx1.get(key3, otherCfHandle).error() == "" 142 | 143 | tx2.get(key1, defaultCfHandle).error() == "" 144 | tx2.get(key2, defaultCfHandle).error() == "" 145 | tx2.get(key3, defaultCfHandle).error() == "" 146 | tx2.get(key1, otherCfHandle).get() == val1 147 | tx2.get(key2, otherCfHandle).error() == "" 148 | tx2.get(key3, otherCfHandle).get() == val3 149 | 150 | block: 151 | let res = tx1.rollback() 152 | check: 153 | res.isOk() 154 | tx1.get(key1, defaultCfHandle).error() == "" 155 | tx1.get(key2, defaultCfHandle).error() == "" 156 | tx1.get(key3, defaultCfHandle).error() == "" 157 | tx1.get(key1, otherCfHandle).error() == "" 158 | tx1.get(key2, otherCfHandle).error() == "" 159 | tx1.get(key3, otherCfHandle).error() == "" 160 | 161 | block: 162 | let res = tx2.commit() 163 | check: 164 | res.isOk() 165 | tx2.get(key1, defaultCfHandle).error() == "" 166 | tx2.get(key2, defaultCfHandle).error() == "" 167 | tx2.get(key3, defaultCfHandle).error() == "" 168 | tx2.get(key1, otherCfHandle).get() == val1 169 | tx2.get(key2, otherCfHandle).error() == "" 170 | tx2.get(key3, otherCfHandle).get() == val3 171 | 172 | test "Test close": 173 | var tx = db.beginTransaction() 174 | 175 | check not tx.isClosed() 176 | tx.close() 177 | check tx.isClosed() 178 | tx.close() 179 | check tx.isClosed() 180 | 181 | check not db.isClosed() 182 | db.close() 183 | check db.isClosed() 184 | db.close() 185 | check db.isClosed() 186 | 187 | test "Test close multiple tx": 188 | var tx1 = db.beginTransaction() 189 | var tx2 = db.beginTransaction() 190 | 191 | check not db.isClosed() 192 | check not tx1.isClosed() 193 | tx1.close() 194 | check tx1.isClosed() 195 | tx1.close() 196 | check tx1.isClosed() 197 | 198 | check not db.isClosed() 199 | check not tx2.isClosed() 200 | tx2.close() 201 | check tx2.isClosed() 202 | tx2.close() 203 | check tx2.isClosed() 204 | 205 | test "Test auto close enabled": 206 | let 207 | dbPath = mkdtemp() / "autoclose-enabled" 208 | dbOpts = defaultDbOptions(autoClose = true) 209 | columnFamilies = 210 | @[ 211 | initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = true)) 212 | ] 213 | db = openOptimisticTxDb(dbPath, dbOpts, columnFamilies).get() 214 | 215 | check: 216 | dbOpts.isClosed() == false 217 | columnFamilies[0].isClosed() == false 218 | db.isClosed() == false 219 | 220 | db.close() 221 | 222 | check: 223 | dbOpts.isClosed() == true 224 | columnFamilies[0].isClosed() == true 225 | db.isClosed() == true 226 | 227 | test "Test auto close enabled": 228 | let 229 | dbPath = mkdtemp() / "autoclose-disabled" 230 | dbOpts = defaultDbOptions(autoClose = false) 231 | columnFamilies = 232 | @[ 233 | initColFamilyDescriptor( 234 | CF_DEFAULT, defaultColFamilyOptions(autoClose = false) 235 | ) 236 | ] 237 | db = openOptimisticTxDb(dbPath, dbOpts, columnFamilies).get() 238 | 239 | check: 240 | dbOpts.isClosed() == false 241 | columnFamilies[0].isClosed() == false 242 | db.isClosed() == false 243 | 244 | db.close() 245 | 246 | check: 247 | dbOpts.isClosed() == false 248 | columnFamilies[0].isClosed() == false 249 | db.isClosed() == true 250 | 251 | test "Test auto close tx enabled": 252 | let 253 | readOpts = defaultReadOptions(autoClose = true) 254 | writeOpts = defaultWriteOptions(autoClose = true) 255 | otxOpts = defaultOptimisticTxOptions(autoClose = true) 256 | tx = db.beginTransaction(readOpts, writeOpts, otxOpts) 257 | 258 | check: 259 | readOpts.isClosed() == false 260 | writeOpts.isClosed() == false 261 | otxOpts.isClosed() == false 262 | tx.isClosed() == false 263 | 264 | tx.close() 265 | 266 | check: 267 | readOpts.isClosed() == true 268 | writeOpts.isClosed() == true 269 | otxOpts.isClosed() == true 270 | tx.isClosed() == true 271 | 272 | test "Test auto close tx disabled": 273 | let 274 | readOpts = defaultReadOptions(autoClose = false) 275 | writeOpts = defaultWriteOptions(autoClose = false) 276 | otxOpts = defaultOptimisticTxOptions(autoClose = false) 277 | tx = db.beginTransaction(readOpts, writeOpts, otxOpts) 278 | 279 | check: 280 | readOpts.isClosed() == false 281 | writeOpts.isClosed() == false 282 | otxOpts.isClosed() == false 283 | tx.isClosed() == false 284 | 285 | tx.close() 286 | 287 | check: 288 | readOpts.isClosed() == false 289 | writeOpts.isClosed() == false 290 | otxOpts.isClosed() == false 291 | tx.isClosed() == true 292 | -------------------------------------------------------------------------------- /tests/test_rocksdb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2018-2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/rocksdb, ./test_helper 13 | 14 | suite "RocksDbRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key = @[byte(1), 2, 3, 4, 5] 21 | otherKey = @[byte(1), 2, 3, 4, 5, 6] 22 | val = @[byte(1), 2, 3, 4, 5] 23 | 24 | setup: 25 | let 26 | dbPath = mkdtemp() / "data" 27 | db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 28 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 29 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 30 | 31 | teardown: 32 | db.close() 33 | removeDir($dbPath) 34 | 35 | test "Basic operations": 36 | var s = db.put(key, val) 37 | check s.isOk() 38 | 39 | var bytes: seq[byte] 40 | check db.get( 41 | key, 42 | proc(data: openArray[byte]) = 43 | bytes = @data, 44 | )[] 45 | check not db.get( 46 | otherKey, 47 | proc(data: openArray[byte]) = 48 | bytes = @data, 49 | )[] 50 | 51 | var r1 = db.get(key) 52 | check r1.isOk() and r1.value == val 53 | 54 | var r2 = db.get(otherKey) 55 | # there's no error string for missing keys 56 | check r2.isOk() == false and r2.error.len == 0 57 | 58 | var e1 = db.keyExists(key) 59 | check e1.isOk() and e1.value == true 60 | 61 | var e2 = db.keyExists(otherKey) 62 | check e2.isOk() and e2.value == false 63 | 64 | var d = db.delete(key) 65 | check d.isOk() 66 | 67 | e1 = db.keyExists(key) 68 | check e1.isOk() and e1.value == false 69 | 70 | d = db.delete(otherKey) 71 | check d.isOk() 72 | 73 | close(db) 74 | check db.isClosed() 75 | 76 | # Open database in read only mode 77 | block: 78 | var 79 | readOnlyDb = initReadOnlyDb(dbPath) 80 | r = readOnlyDb.keyExists(key) 81 | check r.isOk() and r.value == false 82 | 83 | # This won't compile as designed: 84 | # var r2 = readOnlyDb.put(key, @[123.byte]) 85 | # check r2.isErr() 86 | 87 | readOnlyDb.close() 88 | check readOnlyDb.isClosed() 89 | 90 | test "Basic operations - default column family": 91 | var s = db.put(key, val, defaultCfHandle) 92 | check s.isOk() 93 | 94 | var bytes: seq[byte] 95 | check db.get( 96 | key, 97 | proc(data: openArray[byte]) = 98 | bytes = @data, 99 | defaultCfHandle, 100 | )[] 101 | check not db.get( 102 | otherKey, 103 | proc(data: openArray[byte]) = 104 | bytes = @data, 105 | defaultCfHandle, 106 | )[] 107 | 108 | var r1 = db.get(key) 109 | check r1.isOk() and r1.value == val 110 | 111 | var r2 = db.get(otherKey) 112 | # there's no error string for missing keys 113 | check r2.isOk() == false and r2.error.len == 0 114 | 115 | var e1 = db.keyExists(key, defaultCfHandle) 116 | check e1.isOk() and e1.value == true 117 | 118 | var e2 = db.keyExists(otherKey, defaultCfHandle) 119 | check e2.isOk() and e2.value == false 120 | 121 | var d = db.delete(key, defaultCfHandle) 122 | check d.isOk() 123 | 124 | e1 = db.keyExists(key, defaultCfHandle) 125 | check e1.isOk() and e1.value == false 126 | 127 | d = db.delete(otherKey, defaultCfHandle) 128 | check d.isOk() 129 | 130 | db.close() 131 | check db.isClosed() 132 | 133 | # Open database in read only mode 134 | block: 135 | var 136 | readOnlyDb = initReadOnlyDb(dbPath, columnFamilyNames = @[CF_DEFAULT]) 137 | r = readOnlyDb.keyExists(key) 138 | check r.isOk() and r.value == false 139 | 140 | # Won't compile as designed: 141 | # var r2 = readOnlyDb.put(key, @[123.byte], defaultCfHandle) 142 | # check r2.isErr() 143 | 144 | readOnlyDb.close() 145 | check readOnlyDb.isClosed() 146 | 147 | test "Basic operations - multiple column families": 148 | var s = db.put(key, val, defaultCfHandle) 149 | check s.isOk() 150 | 151 | var s2 = db.put(otherKey, val, otherCfHandle) 152 | check s2.isOk() 153 | 154 | var bytes: seq[byte] 155 | check db.get( 156 | key, 157 | proc(data: openArray[byte]) = 158 | bytes = @data, 159 | defaultCfHandle, 160 | )[] 161 | check not db.get( 162 | otherKey, 163 | proc(data: openArray[byte]) = 164 | bytes = @data, 165 | defaultCfHandle, 166 | )[] 167 | 168 | var bytes2: seq[byte] 169 | check db.get( 170 | otherKey, 171 | proc(data: openArray[byte]) = 172 | bytes2 = @data, 173 | otherCfHandle, 174 | )[] 175 | check not db.get( 176 | key, 177 | proc(data: openArray[byte]) = 178 | bytes2 = @data, 179 | otherCfHandle, 180 | )[] 181 | 182 | var e1 = db.keyExists(key, defaultCfHandle) 183 | check e1.isOk() and e1.value == true 184 | var e2 = db.keyExists(otherKey, defaultCfHandle) 185 | check e2.isOk() and e2.value == false 186 | 187 | var e3 = db.keyExists(key, otherCfHandle) 188 | check e3.isOk() and e3.value == false 189 | var e4 = db.keyExists(otherKey, otherCfHandle) 190 | check e4.isOk() and e4.value == true 191 | 192 | var d = db.delete(key, defaultCfHandle) 193 | check d.isOk() 194 | e1 = db.keyExists(key, defaultCfHandle) 195 | check e1.isOk() and e1.value == false 196 | d = db.delete(otherKey, defaultCfHandle) 197 | check d.isOk() 198 | 199 | var d2 = db.delete(key, otherCfHandle) 200 | check d2.isOk() 201 | e3 = db.keyExists(key, otherCfHandle) 202 | check e3.isOk() and e3.value == false 203 | d2 = db.delete(otherKey, otherCfHandle) 204 | check d2.isOk() 205 | d2 = db.delete(otherKey, otherCfHandle) 206 | check d2.isOk() 207 | 208 | db.close() 209 | check db.isClosed() 210 | 211 | # Open database in read only mode 212 | block: 213 | var readOnlyDb = 214 | initReadOnlyDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 215 | 216 | var r = readOnlyDb.keyExists(key, readOnlyDb.getColFamilyHandle(CF_OTHER).get()) 217 | check r.isOk() and r.value == false 218 | 219 | # Does not compile as designed: 220 | # var r2 = readOnlyDb.put(key, @[123.byte], otherCfHandle) 221 | # check r2.isErr() 222 | 223 | readOnlyDb.close() 224 | check readOnlyDb.isClosed() 225 | 226 | test "Test missing key and values": 227 | let 228 | key1 = @[byte(1)] # exists with non empty value 229 | val1 = @[byte(1)] 230 | key2 = @[byte(2)] # exists with empty seq value 231 | val2: seq[byte] = @[] 232 | key3 = @[byte(3)] # exists with empty array value 233 | val3: array[0, byte] = [] 234 | key4 = @[byte(4)] # deleted key 235 | key5 = @[byte(5)] # key not created 236 | 237 | check: 238 | db.put(key1, val1).isOk() 239 | db.put(key2, val2).isOk() 240 | db.put(key3, val3).isOk() 241 | db.delete(key4).isOk() 242 | 243 | db.keyExists(key1).get() == true 244 | db.keyExists(key2).get() == true 245 | db.keyExists(key3).get() == true 246 | db.keyExists(key4).get() == false 247 | db.keyExists(key5).get() == false 248 | 249 | block: 250 | var v: seq[byte] 251 | let r = db.get( 252 | key1, 253 | proc(data: openArray[byte]) = 254 | v = @data, 255 | ) 256 | check: 257 | r.isOk() 258 | r.value() == true 259 | v == val1 260 | db.get(key1).isOk() 261 | 262 | block: 263 | var v: seq[byte] 264 | let r = db.get( 265 | key2, 266 | proc(data: openArray[byte]) = 267 | v = @data, 268 | ) 269 | check: 270 | r.isOk() 271 | r.value() == true 272 | v.len() == 0 273 | db.get(key2).isOk() 274 | 275 | block: 276 | var v: seq[byte] 277 | let r = db.get( 278 | key3, 279 | proc(data: openArray[byte]) = 280 | v = @data, 281 | ) 282 | check: 283 | r.isOk() 284 | r.value() == true 285 | v.len() == 0 286 | db.get(key3).isOk() 287 | 288 | block: 289 | var v: seq[byte] 290 | let r = db.get( 291 | key4, 292 | proc(data: openArray[byte]) = 293 | v = @data, 294 | ) 295 | check: 296 | r.isOk() 297 | r.value() == false 298 | v.len() == 0 299 | db.get(key4).isErr() 300 | 301 | block: 302 | var v: seq[byte] 303 | let r = db.get( 304 | key5, 305 | proc(data: openArray[byte]) = 306 | v = @data, 307 | ) 308 | check: 309 | r.isOk() 310 | r.value() == false 311 | v.len() == 0 312 | db.get(key5).isErr() 313 | 314 | test "Test keyMayExist": 315 | let 316 | key1 = @[byte(1)] # exists with non empty value 317 | val1 = @[byte(1)] 318 | key2 = @[byte(2)] # exists with empty seq value 319 | val2: seq[byte] = @[] 320 | key3 = @[byte(3)] # exists with empty array value 321 | val3: array[0, byte] = [] 322 | key4 = @[byte(4)] # deleted key 323 | key5 = @[byte(5)] # key not created 324 | 325 | check: 326 | db.put(key1, val1).isOk() 327 | db.put(key2, val2).isOk() 328 | db.put(key3, val3).isOk() 329 | db.delete(key4).isOk() 330 | 331 | db.keyMayExist(key1).isOk() 332 | db.keyMayExist(key2).isOk() 333 | db.keyMayExist(key3).isOk() 334 | db.keyMayExist(key4).get() == false 335 | db.keyMayExist(key5).get() == false 336 | 337 | test "Put, get and delete empty key": 338 | let empty: seq[byte] = @[] 339 | 340 | check: 341 | db.put(empty, val).isOk() 342 | db.get(empty).get() == val 343 | db.delete(empty).isOk() 344 | db.get(empty).isErr() 345 | 346 | test "List column familes": 347 | let cfRes1 = listColumnFamilies(dbPath) 348 | check: 349 | cfRes1.isOk() 350 | cfRes1.value() == @[CF_DEFAULT, CF_OTHER] 351 | 352 | let 353 | dbPath2 = dbPath & "2" 354 | db2 = initReadWriteDb(dbPath2, columnFamilyNames = @[CF_DEFAULT]) 355 | cfRes2 = listColumnFamilies(dbPath2) 356 | check: 357 | cfRes2.isOk() 358 | cfRes2.value() == @[CF_DEFAULT] 359 | 360 | test "Unknown column family": 361 | const CF_UNKNOWN = "unknown" 362 | let cfHandleRes = db.getColFamilyHandle(CF_UNKNOWN) 363 | check cfHandleRes.isErr() and cfHandleRes.error() == "rocksdb: unknown column family" 364 | 365 | test "Close multiple times": 366 | check not db.isClosed() 367 | db.close() 368 | check db.isClosed() 369 | db.close() 370 | check db.isClosed() 371 | 372 | test "Test auto close enabled": 373 | let 374 | dbPath = mkdtemp() / "autoclose-enabled" 375 | dbOpts = defaultDbOptions(autoClose = true) 376 | readOpts = defaultReadOptions(autoClose = true) 377 | writeOpts = defaultWriteOptions(autoClose = true) 378 | columnFamilies = 379 | @[ 380 | initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = true)) 381 | ] 382 | db = openRocksDb(dbPath, dbOpts, readOpts, writeOpts, columnFamilies).get() 383 | 384 | check: 385 | dbOpts.isClosed() == false 386 | readOpts.isClosed() == false 387 | writeOpts.isClosed() == false 388 | columnFamilies[0].isClosed() == false 389 | db.isClosed() == false 390 | 391 | db.close() 392 | 393 | check: 394 | dbOpts.isClosed() == true 395 | readOpts.isClosed() == true 396 | writeOpts.isClosed() == true 397 | columnFamilies[0].isClosed() == true 398 | db.isClosed() == true 399 | 400 | test "Test auto close disabled": 401 | let 402 | dbPath = mkdtemp() / "autoclose-disabled" 403 | dbOpts = defaultDbOptions(autoClose = false) 404 | readOpts = defaultReadOptions(autoClose = false) 405 | writeOpts = defaultWriteOptions(autoClose = false) 406 | columnFamilies = 407 | @[ 408 | initColFamilyDescriptor( 409 | CF_DEFAULT, defaultColFamilyOptions(autoClose = false) 410 | ) 411 | ] 412 | db = openRocksDb(dbPath, dbOpts, readOpts, writeOpts, columnFamilies).get() 413 | 414 | check: 415 | dbOpts.isClosed() == false 416 | readOpts.isClosed() == false 417 | writeOpts.isClosed() == false 418 | columnFamilies[0].isClosed() == false 419 | db.isClosed() == false 420 | 421 | db.close() 422 | 423 | check: 424 | dbOpts.isClosed() == false 425 | readOpts.isClosed() == false 426 | writeOpts.isClosed() == false 427 | columnFamilies[0].isClosed() == false 428 | db.isClosed() == true 429 | 430 | test "Test compression libraries linked": 431 | let 432 | dbPath = mkdtemp() / "compression" 433 | cfOpts = defaultColFamilyOptions(autoClose = false) 434 | cfDescriptor = initColFamilyDescriptor(CF_DEFAULT, cfOpts) 435 | 436 | cfOpts.compression = lz4Compression 437 | check cfOpts.compression == lz4Compression 438 | let db1 = openRocksDb(dbPath, columnFamilies = @[cfDescriptor]).get() 439 | check db1.put(key, val).isOk() 440 | db1.close() 441 | 442 | cfOpts.compression = zstdCompression 443 | check cfOpts.compression == zstdCompression 444 | let db2 = openRocksDb(dbPath, columnFamilies = @[cfDescriptor]).get() 445 | check db2.put(key, val).isOk() 446 | db2.close() 447 | 448 | cfOpts.close() 449 | removeDir($dbPath) 450 | 451 | test "Test iterator": 452 | check db.put(key, val).isOk() 453 | 454 | let iter = db.openIterator().get() 455 | defer: 456 | iter.close() 457 | 458 | iter.seekToKey(key) 459 | check: 460 | iter.isValid() == true 461 | iter.key() == key 462 | iter.value() == val 463 | iter.seekToKey(otherKey) 464 | check iter.isValid() == false 465 | 466 | test "Create and restore snapshot": 467 | check: 468 | db.put(key, val).isOk() 469 | db.keyExists(key).get() == true 470 | db.keyMayExist(otherKey).get() == false 471 | 472 | let snapshot = db.getSnapshot().get() 473 | check: 474 | snapshot.getSequenceNumber() > 0 475 | not snapshot.isClosed() 476 | 477 | # after taking snapshot, update the db 478 | check: 479 | db.delete(key).isOk() 480 | db.put(otherKey, val).isOk() 481 | db.keyMayExist(key).get() == false 482 | db.keyExists(otherKey).get() == true 483 | 484 | let readOpts = defaultReadOptions(autoClose = true) 485 | readOpts.setSnapshot(snapshot) 486 | 487 | # read from the snapshot using an iterator 488 | let iter = db.openIterator(readOpts = readOpts).get() 489 | defer: 490 | iter.close() 491 | iter.seekToKey(key) 492 | check: 493 | iter.isValid() == true 494 | iter.key() == key 495 | iter.value() == val 496 | iter.seekToKey(otherKey) 497 | check: 498 | iter.isValid() == false 499 | 500 | db.releaseSnapshot(snapshot) 501 | check snapshot.isClosed() 502 | 503 | test "Test flush": 504 | check: 505 | db.put(key, val).isOk() 506 | db.flush().isOk() 507 | 508 | check: 509 | db.put(otherKey, val, otherCfHandle).isOk() 510 | db.flush(otherCfHandle).isOk() 511 | 512 | let cfHandles = [defaultCfHandle, otherCfHandle] 513 | check: 514 | db.put(otherKey, val, defaultCfHandle).isOk() 515 | db.put(key, val, otherCfHandle).isOk() 516 | db.flush(cfHandles).isOk() 517 | -------------------------------------------------------------------------------- /tests/test_rocksiterator.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/[rocksdb, rocksiterator], ./test_helper 13 | 14 | suite "RocksIteratorRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | CF_EMPTY = "empty" 19 | 20 | let 21 | key1 = @[byte(1)] 22 | val1 = @[byte(1)] 23 | key2 = @[byte(2)] 24 | val2 = @[byte(2)] 25 | key3 = @[byte(3)] 26 | val3 = @[byte(3)] 27 | 28 | setup: 29 | let 30 | dbPath = mkdtemp() / "data" 31 | db = 32 | initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER, CF_EMPTY]) 33 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 34 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 35 | emptyCfHandle = db.getColFamilyHandle(CF_EMPTY).get() 36 | 37 | doAssert db.put(key1, val1).isOk() 38 | doAssert db.put(key2, val2).isOk() 39 | doAssert db.put(key3, val3).isOk() 40 | doAssert db.put(key1, val1, otherCfHandle).isOk() 41 | doAssert db.put(key2, val2, otherCfHandle).isOk() 42 | doAssert db.put(key3, val3, otherCfHandle).isOk() 43 | 44 | teardown: 45 | db.close() 46 | removeDir($dbPath) 47 | 48 | test "Iterate forwards using default column family": 49 | let res = db.openIterator(cfHandle = defaultCfHandle) 50 | check res.isOk() 51 | 52 | var iter = res.get() 53 | defer: 54 | iter.close() 55 | 56 | iter.seekToFirst() 57 | check iter.isValid() 58 | 59 | var expected = byte(1) 60 | while iter.isValid(): 61 | let 62 | key = iter.key() 63 | val = iter.value() 64 | 65 | check: 66 | key == @[expected] 67 | val == @[expected] 68 | 69 | inc expected 70 | iter.next() 71 | 72 | check expected == byte(4) 73 | 74 | test "Iterate backwards using other column family": 75 | let res = db.openIterator(cfHandle = otherCfHandle) 76 | check res.isOk() 77 | 78 | var iter = res.get() 79 | defer: 80 | iter.close() 81 | 82 | iter.seekToLast() 83 | check iter.isValid() 84 | 85 | var expected = byte(3) 86 | while iter.isValid(): 87 | var key: seq[byte] 88 | iter.key( 89 | proc(data: openArray[byte]) = 90 | key = @data 91 | ) 92 | var val: seq[byte] 93 | iter.value( 94 | proc(data: openArray[byte]) = 95 | val = @data 96 | ) 97 | 98 | check: 99 | key == @[expected] 100 | val == @[expected] 101 | 102 | dec expected 103 | iter.prev() 104 | 105 | check expected == byte(0) 106 | iter.close() 107 | 108 | test "Open two iterators on the same column family": 109 | let res1 = db.openIterator(cfHandle = defaultCfHandle) 110 | check res1.isOk() 111 | var iter1 = res1.get() 112 | defer: 113 | iter1.close() 114 | let res2 = db.openIterator(cfHandle = defaultCfHandle) 115 | check res2.isOk() 116 | var iter2 = res2.get() 117 | defer: 118 | iter2.close() 119 | 120 | iter1.seekToFirst() 121 | check iter1.isValid() 122 | iter2.seekToLast() 123 | check iter2.isValid() 124 | 125 | check: 126 | iter1.key() == @[byte(1)] 127 | iter1.value() == @[byte(1)] 128 | iter2.key() == @[byte(3)] 129 | iter2.value() == @[byte(3)] 130 | 131 | test "Open two iterators on different column families": 132 | let res1 = db.openIterator(cfHandle = defaultCfHandle) 133 | check res1.isOk() 134 | var iter1 = res1.get() 135 | defer: 136 | iter1.close() 137 | let res2 = db.openIterator(cfHandle = otherCfHandle) 138 | check res2.isOk() 139 | var iter2 = res2.get() 140 | defer: 141 | iter2.close() 142 | 143 | iter1.seekToFirst() 144 | check iter1.isValid() 145 | iter2.seekToLast() 146 | check iter2.isValid() 147 | 148 | check: 149 | iter1.key() == @[byte(1)] 150 | iter1.value() == @[byte(1)] 151 | iter2.key() == @[byte(3)] 152 | iter2.value() == @[byte(3)] 153 | 154 | test "Iterate forwards using seek to key": 155 | let res = db.openIterator(cfHandle = defaultCfHandle) 156 | check res.isOk() 157 | 158 | var iter = res.get() 159 | defer: 160 | iter.close() 161 | 162 | iter.seekToKey(key2) 163 | check: 164 | iter.isValid() 165 | iter.key() == key2 166 | iter.value() == val2 167 | 168 | test "Seek to empty key": 169 | let empty: seq[byte] = @[] 170 | check db.put(empty, val1).isOk() 171 | 172 | let iter = db.openIterator().get() 173 | defer: 174 | iter.close() 175 | 176 | iter.seekToKey(empty) 177 | check: 178 | iter.isValid() 179 | iter.key() == empty 180 | iter.value() == val1 181 | 182 | test "Empty column family": 183 | let res = db.openIterator(cfHandle = emptyCfHandle) 184 | check res.isOk() 185 | var iter = res.get() 186 | defer: 187 | iter.close() 188 | 189 | iter.seekToFirst() 190 | check not iter.isValid() 191 | 192 | iter.seekToLast() 193 | check not iter.isValid() 194 | 195 | test "Test status": 196 | let res = db.openIterator(cfHandle = emptyCfHandle) 197 | check res.isOk() 198 | var iter = res.get() 199 | defer: 200 | iter.close() 201 | 202 | check iter.status().isOk() 203 | iter.seekToLast() 204 | check iter.status().isOk() 205 | 206 | test "Test pairs iterator": 207 | let res = db.openIterator(cfHandle = defaultCfHandle) 208 | check res.isOk() 209 | var iter = res.get() 210 | 211 | var expected = byte(1) 212 | for k, v in iter: 213 | check: 214 | k == @[expected] 215 | v == @[expected] 216 | inc expected 217 | check iter.isClosed() 218 | 219 | test "Test close": 220 | let res = db.openIterator() 221 | check res.isOk() 222 | var iter = res.get() 223 | 224 | check not iter.isClosed() 225 | iter.close() 226 | check iter.isClosed() 227 | iter.close() 228 | check iter.isClosed() 229 | -------------------------------------------------------------------------------- /tests/test_sstfilewriter.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/[rocksdb, sstfilewriter], ./test_helper 13 | 14 | suite "SstFileWriterRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key1 = @[byte(1)] 21 | val1 = @[byte(1)] 22 | key2 = @[byte(2)] 23 | val2 = @[byte(2)] 24 | key3 = @[byte(3)] 25 | val3 = @[byte(3)] 26 | 27 | setup: 28 | let 29 | dbPath = mkdtemp() / "data" 30 | sstFilePath = mkdtemp() / "sst" 31 | db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 32 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 33 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 34 | 35 | teardown: 36 | db.close() 37 | removeDir($dbPath) 38 | 39 | test "Write to sst file then load into db using default column family": 40 | let res = openSstFileWriter(sstFilePath) 41 | check res.isOk() 42 | let writer = res.get() 43 | defer: 44 | writer.close() 45 | 46 | check: 47 | writer.put(key1, val1).isOk() 48 | writer.put(key2, val2).isOk() 49 | writer.put(key3, val3).isOk() 50 | writer.delete(@[byte(4)]).isOk() 51 | writer.finish().isOk() 52 | 53 | db.ingestExternalFile(sstFilePath).isOk() 54 | db.get(key1).get() == val1 55 | db.get(key2).get() == val2 56 | db.get(key3).get() == val3 57 | 58 | test "Write to sst file then load into db using specific column family": 59 | let res = openSstFileWriter(sstFilePath) 60 | check res.isOk() 61 | let writer = res.get() 62 | defer: 63 | writer.close() 64 | 65 | check: 66 | writer.put(key1, val1).isOk() 67 | writer.put(key2, val2).isOk() 68 | writer.put(key3, val3).isOk() 69 | writer.finish().isOk() 70 | 71 | db.ingestExternalFile(sstFilePath, otherCfHandle).isOk() 72 | db.keyExists(key1, defaultCfHandle).get() == false 73 | db.keyExists(key2, defaultCfHandle).get() == false 74 | db.keyExists(key3, defaultCfHandle).get() == false 75 | db.get(key1, otherCfHandle).get() == val1 76 | db.get(key2, otherCfHandle).get() == val2 77 | db.get(key3, otherCfHandle).get() == val3 78 | 79 | test "Put, get and delete empty key": 80 | let writer = openSstFileWriter(sstFilePath).get() 81 | defer: 82 | writer.close() 83 | 84 | let empty: seq[byte] = @[] 85 | check: 86 | writer.put(empty, val1).isOk() 87 | writer.finish().isOk() 88 | db.ingestExternalFile(sstFilePath).isOk() 89 | db.keyExists(empty).get() == true 90 | db.get(empty).get() == val1 91 | 92 | test "Test close": 93 | let res = openSstFileWriter(sstFilePath) 94 | check res.isOk() 95 | let writer = res.get() 96 | 97 | check not writer.isClosed() 98 | writer.close() 99 | check writer.isClosed() 100 | writer.close() 101 | check writer.isClosed() 102 | 103 | test "Test auto close enabled": 104 | let 105 | dbOpts = defaultDbOptions(autoClose = true) 106 | writer = openSstFileWriter(sstFilePath, dbOpts).get() 107 | 108 | check: 109 | dbOpts.isClosed() == false 110 | writer.isClosed() == false 111 | 112 | writer.close() 113 | 114 | check: 115 | dbOpts.isClosed() == true 116 | writer.isClosed() == true 117 | 118 | test "Test auto close disabled": 119 | let 120 | dbOpts = defaultDbOptions(autoClose = false) 121 | writer = openSstFileWriter(sstFilePath, dbOpts).get() 122 | 123 | check: 124 | dbOpts.isClosed() == false 125 | writer.isClosed() == false 126 | 127 | writer.close() 128 | 129 | check: 130 | dbOpts.isClosed() == false 131 | writer.isClosed() == true 132 | -------------------------------------------------------------------------------- /tests/test_transactiondb.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/[transactiondb], ./test_helper 13 | 14 | suite "TransactionDbRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key1 = @[byte(1)] 21 | val1 = @[byte(1)] 22 | key2 = @[byte(2)] 23 | val2 = @[byte(2)] 24 | key3 = @[byte(3)] 25 | val3 = @[byte(3)] 26 | 27 | setup: 28 | let 29 | dbPath = mkdtemp() / "data" 30 | db = initTransactionDb(dbPath, columnFamilyNames = @[CF_OTHER]) 31 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 32 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 33 | 34 | teardown: 35 | db.close() 36 | removeDir($dbPath) 37 | 38 | # test multiple transactions 39 | test "Test rollback using default column family": 40 | var tx = db.beginTransaction() 41 | defer: 42 | tx.close() 43 | check not tx.isClosed() 44 | 45 | check: 46 | tx.put(key1, val1).isOk() 47 | tx.put(key2, val2).isOk() 48 | tx.put(key3, val3).isOk() 49 | 50 | tx.delete(key2).isOk() 51 | not tx.isClosed() 52 | 53 | check: 54 | tx.get(key1).get() == val1 55 | tx.get(key2).error() == "" 56 | tx.get(key3).get() == val3 57 | 58 | let res = tx.rollback() 59 | check: 60 | res.isOk() 61 | tx.get(key1).error() == "" 62 | tx.get(key2).error() == "" 63 | tx.get(key3).error() == "" 64 | 65 | test "Test commit using default column family": 66 | var tx = db.beginTransaction() 67 | defer: 68 | tx.close() 69 | check not tx.isClosed() 70 | 71 | check: 72 | tx.put(key1, val1).isOk() 73 | tx.put(key2, val2).isOk() 74 | tx.put(key3, val3).isOk() 75 | 76 | tx.delete(key2).isOk() 77 | not tx.isClosed() 78 | 79 | check: 80 | tx.get(key1).get() == val1 81 | tx.get(key2).error() == "" 82 | tx.get(key3).get() == val3 83 | 84 | let res = tx.commit() 85 | check: 86 | res.isOk() 87 | tx.get(key1).get() == val1 88 | tx.get(key2).error() == "" 89 | tx.get(key3).get() == val3 90 | 91 | test "Test setting column family in beginTransaction": 92 | var tx = db.beginTransaction(cfHandle = otherCfHandle) 93 | defer: 94 | tx.close() 95 | check not tx.isClosed() 96 | 97 | check: 98 | tx.put(key1, val1).isOk() 99 | tx.put(key2, val2).isOk() 100 | tx.put(key3, val3).isOk() 101 | 102 | tx.delete(key2).isOk() 103 | not tx.isClosed() 104 | 105 | check: 106 | tx.get(key1, defaultCfHandle).error() == "" 107 | tx.get(key2, defaultCfHandle).error() == "" 108 | tx.get(key3, defaultCfHandle).error() == "" 109 | tx.get(key1, otherCfHandle).get() == val1 110 | tx.get(key2, otherCfHandle).error() == "" 111 | tx.get(key3, otherCfHandle).get() == val3 112 | 113 | test "Test rollback and commit with multiple transactions": 114 | var tx1 = db.beginTransaction(cfHandle = defaultCfHandle) 115 | defer: 116 | tx1.close() 117 | check not tx1.isClosed() 118 | var tx2 = db.beginTransaction(cfHandle = otherCfHandle) 119 | defer: 120 | tx2.close() 121 | check not tx2.isClosed() 122 | 123 | check: 124 | tx1.put(key1, val1).isOk() 125 | tx1.put(key2, val2).isOk() 126 | tx1.put(key3, val3).isOk() 127 | tx1.delete(key2).isOk() 128 | not tx1.isClosed() 129 | tx2.put(key1, val1).isOk() 130 | tx2.put(key2, val2).isOk() 131 | tx2.put(key3, val3).isOk() 132 | tx2.delete(key2).isOk() 133 | not tx2.isClosed() 134 | 135 | check: 136 | tx1.get(key1, defaultCfHandle).get() == val1 137 | tx1.get(key2, defaultCfHandle).error() == "" 138 | tx1.get(key3, defaultCfHandle).get() == val3 139 | tx1.get(key1, otherCfHandle).error() == "" 140 | tx1.get(key2, otherCfHandle).error() == "" 141 | tx1.get(key3, otherCfHandle).error() == "" 142 | 143 | tx2.get(key1, defaultCfHandle).error() == "" 144 | tx2.get(key2, defaultCfHandle).error() == "" 145 | tx2.get(key3, defaultCfHandle).error() == "" 146 | tx2.get(key1, otherCfHandle).get() == val1 147 | tx2.get(key2, otherCfHandle).error() == "" 148 | tx2.get(key3, otherCfHandle).get() == val3 149 | 150 | block: 151 | let res = tx1.rollback() 152 | check: 153 | res.isOk() 154 | tx1.get(key1, defaultCfHandle).error() == "" 155 | tx1.get(key2, defaultCfHandle).error() == "" 156 | tx1.get(key3, defaultCfHandle).error() == "" 157 | tx1.get(key1, otherCfHandle).error() == "" 158 | tx1.get(key2, otherCfHandle).error() == "" 159 | tx1.get(key3, otherCfHandle).error() == "" 160 | 161 | block: 162 | let res = tx2.commit() 163 | check: 164 | res.isOk() 165 | tx2.get(key1, defaultCfHandle).error() == "" 166 | tx2.get(key2, defaultCfHandle).error() == "" 167 | tx2.get(key3, defaultCfHandle).error() == "" 168 | tx2.get(key1, otherCfHandle).get() == val1 169 | tx2.get(key2, otherCfHandle).error() == "" 170 | tx2.get(key3, otherCfHandle).get() == val3 171 | 172 | test "Put, get and delete empty key": 173 | let tx = db.beginTransaction() 174 | defer: 175 | tx.close() 176 | 177 | let empty: seq[byte] = @[] 178 | check: 179 | tx.put(empty, val1).isOk() 180 | tx.get(empty).get() == val1 181 | tx.delete(empty).isOk() 182 | tx.get(empty).isErr() 183 | 184 | test "Test close": 185 | var tx = db.beginTransaction() 186 | 187 | check not tx.isClosed() 188 | tx.close() 189 | check tx.isClosed() 190 | tx.close() 191 | check tx.isClosed() 192 | 193 | check not db.isClosed() 194 | db.close() 195 | check db.isClosed() 196 | db.close() 197 | check db.isClosed() 198 | 199 | test "Test close multiple tx": 200 | var tx1 = db.beginTransaction() 201 | var tx2 = db.beginTransaction() 202 | 203 | check not db.isClosed() 204 | check not tx1.isClosed() 205 | tx1.close() 206 | check tx1.isClosed() 207 | tx1.close() 208 | check tx1.isClosed() 209 | 210 | check not db.isClosed() 211 | check not tx2.isClosed() 212 | tx2.close() 213 | check tx2.isClosed() 214 | tx2.close() 215 | check tx2.isClosed() 216 | 217 | test "Test auto close enabled": 218 | let 219 | dbPath = mkdtemp() / "autoclose-enabled" 220 | dbOpts = defaultDbOptions(autoClose = true) 221 | txDbOpts = defaultTransactionDbOptions(autoClose = true) 222 | columnFamilies = 223 | @[ 224 | initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = true)) 225 | ] 226 | db = openTransactionDb(dbPath, dbOpts, txDbOpts, columnFamilies).get() 227 | 228 | check: 229 | dbOpts.isClosed() == false 230 | txDbOpts.isClosed() == false 231 | columnFamilies[0].isClosed() == false 232 | db.isClosed() == false 233 | 234 | db.close() 235 | 236 | check: 237 | dbOpts.isClosed() == true 238 | txDbOpts.isClosed() == true 239 | columnFamilies[0].isClosed() == true 240 | db.isClosed() == true 241 | 242 | test "Test auto close disabled": 243 | let 244 | dbPath = mkdtemp() / "autoclose-disabled" 245 | dbOpts = defaultDbOptions(autoClose = false) 246 | txDbOpts = defaultTransactionDbOptions(autoClose = false) 247 | columnFamilies = 248 | @[ 249 | initColFamilyDescriptor( 250 | CF_DEFAULT, defaultColFamilyOptions(autoClose = false) 251 | ) 252 | ] 253 | db = openTransactionDb(dbPath, dbOpts, txDbOpts, columnFamilies).get() 254 | 255 | check: 256 | dbOpts.isClosed() == false 257 | txDbOpts.isClosed() == false 258 | columnFamilies[0].isClosed() == false 259 | db.isClosed() == false 260 | 261 | db.close() 262 | 263 | check: 264 | dbOpts.isClosed() == false 265 | txDbOpts.isClosed() == false 266 | columnFamilies[0].isClosed() == false 267 | db.isClosed() == true 268 | 269 | test "Test auto close tx enabled": 270 | let 271 | readOpts = defaultReadOptions(autoClose = true) 272 | writeOpts = defaultWriteOptions(autoClose = true) 273 | txOpts = defaultTransactionOptions(autoClose = true) 274 | tx = db.beginTransaction(readOpts, writeOpts, txOpts) 275 | 276 | check: 277 | readOpts.isClosed() == false 278 | writeOpts.isClosed() == false 279 | txOpts.isClosed() == false 280 | tx.isClosed() == false 281 | 282 | tx.close() 283 | 284 | check: 285 | readOpts.isClosed() == true 286 | writeOpts.isClosed() == true 287 | txOpts.isClosed() == true 288 | tx.isClosed() == true 289 | 290 | test "Test auto close tx disabled": 291 | let 292 | readOpts = defaultReadOptions(autoClose = false) 293 | writeOpts = defaultWriteOptions(autoClose = false) 294 | txOpts = defaultTransactionOptions(autoClose = false) 295 | tx = db.beginTransaction(readOpts, writeOpts, txOpts) 296 | 297 | check: 298 | readOpts.isClosed() == false 299 | writeOpts.isClosed() == false 300 | txOpts.isClosed() == false 301 | tx.isClosed() == false 302 | 303 | tx.close() 304 | 305 | check: 306 | readOpts.isClosed() == false 307 | writeOpts.isClosed() == false 308 | txOpts.isClosed() == false 309 | tx.isClosed() == true 310 | 311 | test "Test iterator": 312 | let tx1 = db.beginTransaction() 313 | defer: 314 | tx1.close() 315 | check: 316 | tx1.put(key1, val1).isOk() 317 | tx1.commit().isOk() 318 | 319 | block: 320 | # test the db iterator 321 | let iter = db.openIterator().get() 322 | defer: 323 | iter.close() 324 | 325 | iter.seekToKey(key1) 326 | check: 327 | iter.isValid() == true 328 | iter.key() == key1 329 | iter.value() == val1 330 | iter.seekToKey(key2) 331 | check iter.isValid() == false 332 | 333 | block: 334 | # test the tx iterator 335 | let iter = tx1.openIterator().get() 336 | defer: 337 | iter.close() 338 | 339 | iter.seekToKey(key1) 340 | check: 341 | iter.isValid() == true 342 | iter.key() == key1 343 | iter.value() == val1 344 | iter.seekToKey(key2) 345 | check iter.isValid() == false 346 | 347 | test "Create and restore snapshot": 348 | let tx1 = db.beginTransaction() 349 | defer: 350 | tx1.close() 351 | check: 352 | tx1.put(key1, val1).isOk() 353 | tx1.commit().isOk() 354 | 355 | let snapshot = db.getSnapshot().get() 356 | check: 357 | snapshot.getSequenceNumber() > 0 358 | not snapshot.isClosed() 359 | 360 | # after taking snapshot, update the db 361 | let tx2 = db.beginTransaction() 362 | defer: 363 | tx2.close() 364 | check: 365 | tx2.delete(key1).isOk() 366 | tx2.put(key2, val2).isOk() 367 | tx2.commit().isOk() 368 | 369 | let readOpts = defaultReadOptions(autoClose = true) 370 | readOpts.setSnapshot(snapshot) 371 | 372 | # read from the snapshot using an iterator 373 | let iter = db.openIterator(readOpts = readOpts).get() 374 | defer: 375 | iter.close() 376 | iter.seekToKey(key1) 377 | check: 378 | iter.isValid() == true 379 | iter.key() == key1 380 | iter.value() == val1 381 | iter.seekToKey(key2) 382 | check iter.isValid() == false 383 | 384 | # read from the snapshot using a transaction 385 | let tx3 = db.beginTransaction(readOpts = readOpts) 386 | defer: 387 | tx3.close() 388 | check: 389 | tx3.get(key1).get() == val1 390 | tx3.get(key2).isErr() 391 | 392 | db.releaseSnapshot(snapshot) 393 | check snapshot.isClosed() 394 | -------------------------------------------------------------------------------- /tests/test_writebatch.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/[rocksdb, writebatch], ./test_helper 13 | 14 | suite "WriteBatchRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key1 = @[byte(1)] 21 | val1 = @[byte(1)] 22 | key2 = @[byte(2)] 23 | val2 = @[byte(2)] 24 | key3 = @[byte(3)] 25 | val3 = @[byte(3)] 26 | 27 | setup: 28 | let 29 | dbPath = mkdtemp() / "data" 30 | db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 31 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 32 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 33 | 34 | teardown: 35 | db.close() 36 | removeDir($dbPath) 37 | 38 | test "Test writing batch to the default column family": 39 | let batch = db.openWriteBatch() 40 | defer: 41 | batch.close() 42 | check not batch.isClosed() 43 | 44 | check: 45 | batch.put(key1, val1).isOk() 46 | batch.put(key2, val2).isOk() 47 | batch.put(key3, val3).isOk() 48 | batch.count() == 3 49 | 50 | batch.delete(key2).isOk() 51 | batch.count() == 4 52 | not batch.isClosed() 53 | 54 | let res = db.write(batch) 55 | check: 56 | res.isOk() 57 | db.write(batch).isOk() # test that it's idempotent 58 | db.get(key1).get() == val1 59 | db.keyExists(key2).get() == false 60 | db.get(key3).get() == val3 61 | 62 | batch.clear() 63 | check: 64 | batch.count() == 0 65 | not batch.isClosed() 66 | 67 | test "Test writing batch to column family": 68 | let batch = db.openWriteBatch() 69 | defer: 70 | batch.close() 71 | check not batch.isClosed() 72 | 73 | check: 74 | batch.put(key1, val1, otherCfHandle).isOk() 75 | batch.put(key2, val2, otherCfHandle).isOk() 76 | batch.put(key3, val3, otherCfHandle).isOk() 77 | batch.count() == 3 78 | 79 | batch.delete(key2, otherCfHandle).isOk() 80 | batch.count() == 4 81 | not batch.isClosed() 82 | 83 | let res = db.write(batch) 84 | check: 85 | res.isOk() 86 | db.get(key1, otherCfHandle).get() == val1 87 | db.keyExists(key2, otherCfHandle).get() == false 88 | db.get(key3, otherCfHandle).get() == val3 89 | 90 | batch.clear() 91 | check: 92 | batch.count() == 0 93 | not batch.isClosed() 94 | 95 | test "Test writing to multiple column families in single batch": 96 | let batch = db.openWriteBatch() 97 | defer: 98 | batch.close() 99 | check not batch.isClosed() 100 | 101 | check: 102 | batch.put(key1, val1, defaultCfHandle).isOk() 103 | batch.put(key1, val1, otherCfHandle).isOk() 104 | batch.put(key2, val2, otherCfHandle).isOk() 105 | batch.put(key3, val3, otherCfHandle).isOk() 106 | batch.count() == 4 107 | 108 | batch.delete(key2, otherCfHandle).isOk() 109 | batch.count() == 5 110 | not batch.isClosed() 111 | 112 | let res = db.write(batch) 113 | check: 114 | res.isOk() 115 | db.get(key1, defaultCfHandle).get() == val1 116 | db.get(key1, otherCfHandle).get() == val1 117 | db.keyExists(key2, otherCfHandle).get() == false 118 | db.get(key3, otherCfHandle).get() == val3 119 | 120 | batch.clear() 121 | check: 122 | batch.count() == 0 123 | not batch.isClosed() 124 | 125 | test "Test writing to multiple column families in multiple batches": 126 | let 127 | batch1 = db.openWriteBatch() 128 | batch2 = db.openWriteBatch() 129 | defer: 130 | batch1.close() 131 | batch2.close() 132 | 133 | check: 134 | not batch1.isClosed() 135 | not batch2.isClosed() 136 | batch1.put(key1, val1).isOk() 137 | batch1.delete(key2, otherCfHandle).isOk() 138 | batch1.put(key3, val3, otherCfHandle).isOk() 139 | batch2.put(key1, val1, otherCfHandle).isOk() 140 | batch2.delete(key1, otherCfHandle).isOk() 141 | batch2.put(key3, val3).isOk() 142 | batch1.count() == 3 143 | batch2.count() == 3 144 | 145 | let res1 = db.write(batch1) 146 | let res2 = db.write(batch2) 147 | check: 148 | res1.isOk() 149 | res2.isOk() 150 | db.get(key1).get() == val1 151 | db.keyExists(key2).get() == false 152 | db.get(key3).get() == val3 153 | db.keyExists(key1, otherCfHandle).get() == false 154 | db.keyExists(key2, otherCfHandle).get() == false 155 | db.get(key3, otherCfHandle).get() == val3 156 | 157 | # Write batch is unchanged after write 158 | batch1.count() == 3 159 | batch2.count() == 3 160 | not batch1.isClosed() 161 | not batch2.isClosed() 162 | 163 | test "Put, get and delete empty key": 164 | let batch = db.openWriteBatch() 165 | defer: 166 | batch.close() 167 | 168 | let empty: seq[byte] = @[] 169 | check: 170 | batch.put(empty, val1).isOk() 171 | db.write(batch).isOk() 172 | db.get(empty).get() == val1 173 | batch.delete(empty).isOk() 174 | db.write(batch).isOk() 175 | db.get(empty).isErr() 176 | 177 | test "Test write empty batch": 178 | let batch = db.openWriteBatch() 179 | defer: 180 | batch.close() 181 | check not batch.isClosed() 182 | 183 | check batch.count() == 0 184 | let res1 = db.write(batch) 185 | check: 186 | res1.isOk() 187 | batch.count() == 0 188 | not batch.isClosed() 189 | 190 | test "Test close": 191 | let batch = db.openWriteBatch() 192 | 193 | check not batch.isClosed() 194 | batch.close() 195 | check batch.isClosed() 196 | batch.close() 197 | check batch.isClosed() 198 | -------------------------------------------------------------------------------- /tests/test_writebatchwi.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import std/os, tempfile, unittest2, ../rocksdb/[rocksdb, writebatchwi], ./test_helper 13 | 14 | suite "WriteBatchWIRef Tests": 15 | const 16 | CF_DEFAULT = "default" 17 | CF_OTHER = "other" 18 | 19 | let 20 | key1 = @[byte(1)] 21 | val1 = @[byte(1)] 22 | key2 = @[byte(2)] 23 | val2 = @[byte(2)] 24 | key3 = @[byte(3)] 25 | val3 = @[byte(3)] 26 | 27 | setup: 28 | let 29 | dbPath = mkdtemp() / "data" 30 | db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER]) 31 | defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 32 | otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 33 | 34 | teardown: 35 | db.close() 36 | removeDir($dbPath) 37 | 38 | test "Test writing batch to the default column family": 39 | let batch = db.openWriteBatchWithIndex() 40 | defer: 41 | batch.close() 42 | check not batch.isClosed() 43 | 44 | check: 45 | batch.put(key1, val1).isOk() 46 | batch.put(key2, val2).isOk() 47 | batch.put(key3, val3).isOk() 48 | batch.count() == 3 49 | 50 | batch.delete(key2).isOk() 51 | batch.count() == 4 52 | not batch.isClosed() 53 | 54 | batch.getFromBatch(key1).get() == val1 55 | batch.getFromBatch(key2).isErr() 56 | batch.getFromBatch(key3).get() == val3 57 | 58 | let res = db.write(batch) 59 | check: 60 | res.isOk() 61 | db.write(batch).isOk() # test that it's idempotent 62 | db.get(key1).get() == val1 63 | db.keyExists(key2).get() == false 64 | db.get(key3).get() == val3 65 | 66 | batch.getFromBatch(key1).get() == val1 67 | batch.getFromBatch(key2).isErr() 68 | batch.getFromBatch(key3).get() == val3 69 | 70 | batch.clear() 71 | check: 72 | batch.count() == 0 73 | not batch.isClosed() 74 | 75 | test "Test writing batch to column family": 76 | let batch = db.openWriteBatchWithIndex() 77 | defer: 78 | batch.close() 79 | check not batch.isClosed() 80 | 81 | check: 82 | batch.put(key1, val1, otherCfHandle).isOk() 83 | batch.put(key2, val2, otherCfHandle).isOk() 84 | batch.put(key3, val3, otherCfHandle).isOk() 85 | batch.count() == 3 86 | 87 | batch.delete(key2, otherCfHandle).isOk() 88 | batch.count() == 4 89 | not batch.isClosed() 90 | 91 | batch.getFromBatch(key1, otherCfHandle).get() == val1 92 | batch.getFromBatch(key2, otherCfHandle).isErr() 93 | batch.getFromBatch(key3, otherCfHandle).get() == val3 94 | 95 | let res = db.write(batch) 96 | check: 97 | res.isOk() 98 | db.get(key1, otherCfHandle).get() == val1 99 | db.keyExists(key2, otherCfHandle).get() == false 100 | db.get(key3, otherCfHandle).get() == val3 101 | 102 | batch.getFromBatch(key1, otherCfHandle).get() == val1 103 | batch.getFromBatch(key2, otherCfHandle).isErr() 104 | batch.getFromBatch(key3, otherCfHandle).get() == val3 105 | 106 | batch.clear() 107 | check: 108 | batch.count() == 0 109 | not batch.isClosed() 110 | 111 | test "Test writing to multiple column families in single batch": 112 | let batch = db.openWriteBatchWithIndex() 113 | defer: 114 | batch.close() 115 | check not batch.isClosed() 116 | 117 | check: 118 | batch.put(key1, val1, defaultCfHandle).isOk() 119 | batch.put(key1, val1, otherCfHandle).isOk() 120 | batch.put(key2, val2, otherCfHandle).isOk() 121 | batch.put(key3, val3, otherCfHandle).isOk() 122 | batch.count() == 4 123 | 124 | batch.delete(key2, otherCfHandle).isOk() 125 | batch.count() == 5 126 | not batch.isClosed() 127 | 128 | let res = db.write(batch) 129 | check: 130 | res.isOk() 131 | db.get(key1, defaultCfHandle).get() == val1 132 | db.get(key1, otherCfHandle).get() == val1 133 | db.keyExists(key2, otherCfHandle).get() == false 134 | db.get(key3, otherCfHandle).get() == val3 135 | 136 | batch.clear() 137 | check: 138 | batch.count() == 0 139 | not batch.isClosed() 140 | 141 | test "Test writing to multiple column families in multiple batches": 142 | let 143 | batch1 = db.openWriteBatchWithIndex() 144 | batch2 = db.openWriteBatchWithIndex() 145 | defer: 146 | batch1.close() 147 | batch2.close() 148 | 149 | check: 150 | not batch1.isClosed() 151 | not batch2.isClosed() 152 | batch1.put(key1, val1).isOk() 153 | batch1.delete(key2, otherCfHandle).isOk() 154 | batch1.put(key3, val3, otherCfHandle).isOk() 155 | batch2.put(key1, val1, otherCfHandle).isOk() 156 | batch2.delete(key1, otherCfHandle).isOk() 157 | batch2.put(key3, val3).isOk() 158 | batch1.count() == 3 159 | batch2.count() == 3 160 | 161 | let res1 = db.write(batch1) 162 | let res2 = db.write(batch2) 163 | check: 164 | res1.isOk() 165 | res2.isOk() 166 | db.get(key1).get() == val1 167 | db.keyExists(key2).get() == false 168 | db.get(key3).get() == val3 169 | db.keyExists(key1, otherCfHandle).get() == false 170 | db.keyExists(key2, otherCfHandle).get() == false 171 | db.get(key3, otherCfHandle).get() == val3 172 | 173 | # Write batch is unchanged after write 174 | batch1.count() == 3 175 | batch2.count() == 3 176 | not batch1.isClosed() 177 | not batch2.isClosed() 178 | 179 | test "Test write empty batch": 180 | let batch = db.openWriteBatchWithIndex() 181 | defer: 182 | batch.close() 183 | check not batch.isClosed() 184 | 185 | check batch.count() == 0 186 | let res1 = db.write(batch) 187 | check: 188 | res1.isOk() 189 | batch.count() == 0 190 | not batch.isClosed() 191 | 192 | test "Test multiple writes to same key": 193 | let 194 | batch1 = db.openWriteBatchWithIndex(overwriteKey = false) 195 | batch2 = db.openWriteBatchWithIndex(overwriteKey = true) 196 | defer: 197 | batch1.close() 198 | batch2.close() 199 | check: 200 | not batch1.isClosed() 201 | not batch2.isClosed() 202 | 203 | check: 204 | batch1.put(key1, val1).isOk() 205 | batch1.delete(key1).isOk() 206 | batch1.put(key1, val3).isOk() 207 | batch1.count() == 3 208 | batch1.getFromBatch(key1).get() == val3 209 | 210 | batch2.put(key1, val3).isOk() 211 | batch2.put(key1, val2).isOk() 212 | batch2.put(key1, val1).isOk() 213 | batch2.count() == 3 214 | batch2.getFromBatch(key1).get() == val1 215 | 216 | test "Put, get and delete empty key": 217 | let batch = db.openWriteBatchWithIndex() 218 | defer: 219 | batch.close() 220 | 221 | let empty: seq[byte] = @[] 222 | check: 223 | batch.put(empty, val1).isOk() 224 | batch.getFromBatch(empty).get() == val1 225 | batch.delete(empty).isOk() 226 | batch.getFromBatch(empty).isErr() 227 | 228 | test "Test close": 229 | let batch = db.openWriteBatchWithIndex() 230 | 231 | check not batch.isClosed() 232 | batch.close() 233 | check batch.isClosed() 234 | batch.close() 235 | check batch.isClosed() 236 | -------------------------------------------------------------------------------- /tests/transactions/test_otxopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/transactions/otxopts 13 | 14 | suite "OptimisticTxOptionsRef Tests": 15 | test "Test createOptimisticTxOptions": 16 | let txOpts = createOptimisticTxOptions() 17 | 18 | check not txOpts.cPtr.isNil() 19 | 20 | txOpts.close() 21 | 22 | test "Test defaultTransactionOptions": 23 | let txOpts = defaultOptimisticTxOptions() 24 | 25 | check not txOpts.cPtr.isNil() 26 | 27 | txOpts.close() 28 | 29 | test "Test close": 30 | let txOpts = defaultOptimisticTxOptions() 31 | 32 | check not txOpts.isClosed() 33 | txOpts.close() 34 | check txOpts.isClosed() 35 | txOpts.close() 36 | check txOpts.isClosed() 37 | -------------------------------------------------------------------------------- /tests/transactions/test_txdbopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/transactions/txdbopts 13 | 14 | suite "TransactionDbOptionsRef Tests": 15 | test "Test newTransactionDbOptions": 16 | let txDbOpts = createTransactionDbOptions() 17 | 18 | check not txDbOpts.cPtr.isNil() 19 | 20 | txDbOpts.close() 21 | 22 | test "Test defaultTransactionDbOptions": 23 | let txDbOpts = defaultTransactionDbOptions() 24 | 25 | check not txDbOpts.cPtr.isNil() 26 | 27 | txDbOpts.close() 28 | 29 | test "Test close": 30 | let txDbOpts = defaultTransactionDbOptions() 31 | 32 | check not txDbOpts.isClosed() 33 | txDbOpts.close() 34 | check txDbOpts.isClosed() 35 | txDbOpts.close() 36 | check txDbOpts.isClosed() 37 | -------------------------------------------------------------------------------- /tests/transactions/test_txopts.nim: -------------------------------------------------------------------------------- 1 | # Nim-RocksDB 2 | # Copyright 2024 Status Research & Development GmbH 3 | # Licensed under either of 4 | # 5 | # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 | # 8 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 | 10 | {.used.} 11 | 12 | import unittest2, ../../rocksdb/transactions/txopts 13 | 14 | suite "TransactionOptionsRef Tests": 15 | test "Test newTransactionOptions": 16 | let txOpts = createTransactionOptions() 17 | 18 | check not txOpts.cPtr.isNil() 19 | 20 | txOpts.close() 21 | 22 | test "Test defaultTransactionOptions": 23 | let txOpts = defaultTransactionOptions() 24 | 25 | check not txOpts.cPtr.isNil() 26 | 27 | txOpts.close() 28 | 29 | test "Test close": 30 | let txOpts = defaultTransactionOptions() 31 | 32 | check not txOpts.isClosed() 33 | txOpts.close() 34 | check txOpts.isClosed() 35 | txOpts.close() 36 | check txOpts.isClosed() 37 | -------------------------------------------------------------------------------- /triplets/x64-linux-rocksdb.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_BUILD_TYPE release) 3 | set(VCPKG_CMAKE_SYSTEM_NAME Linux) 4 | set(VCPKG_FIXUP_ELF_RPATH ON) 5 | 6 | if(${PORT} MATCHES "snappy|zlib|lz4|zstd") 7 | set(VCPKG_CRT_LINKAGE dynamic) 8 | set(VCPKG_LIBRARY_LINKAGE static) 9 | else() 10 | set(VCPKG_CRT_LINKAGE dynamic) 11 | set(VCPKG_LIBRARY_LINKAGE dynamic) 12 | endif() 13 | -------------------------------------------------------------------------------- /triplets/x64-osx-rocksdb.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_BUILD_TYPE release) 3 | set(VCPKG_CMAKE_SYSTEM_NAME Darwin) 4 | set(VCPKG_OSX_ARCHITECTURES x86_64) 5 | 6 | if(${PORT} MATCHES "snappy|zlib|lz4|zstd") 7 | set(VCPKG_CRT_LINKAGE dynamic) 8 | set(VCPKG_LIBRARY_LINKAGE static) 9 | else() 10 | set(VCPKG_CRT_LINKAGE dynamic) 11 | set(VCPKG_LIBRARY_LINKAGE dynamic) 12 | endif() 13 | -------------------------------------------------------------------------------- /triplets/x64-windows-rocksdb.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_BUILD_TYPE release) 3 | 4 | if(${PORT} MATCHES "snappy|zlib|lz4|zstd") 5 | set(VCPKG_CRT_LINKAGE static) 6 | set(VCPKG_LIBRARY_LINKAGE static) 7 | else() 8 | set(VCPKG_CRT_LINKAGE static) 9 | set(VCPKG_LIBRARY_LINKAGE dynamic) 10 | endif() 11 | --------------------------------------------------------------------------------