├── .appveyor.yml ├── .gitignore ├── .gitmodules ├── .rustfmt.toml ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── azure-pipelines-template.yml ├── azure-pipelines.yml ├── benches ├── cursor.rs ├── transaction.rs └── utils.rs ├── lmdb-sys ├── .rustfmt.toml ├── Cargo.toml ├── bindgen.rs ├── build.rs ├── src │ ├── bindings.rs │ └── lib.rs └── tests │ ├── fixtures │ ├── testdb-32 │ │ ├── data.mdb │ │ └── lock.mdb │ └── testdb │ │ ├── data.mdb │ │ └── lock.mdb │ ├── lmdb.rs │ └── simple.rs └── src ├── cursor.rs ├── database.rs ├── environment.rs ├── error.rs ├── flags.rs ├── lib.rs └── transaction.rs /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - TARGET: x86_64-pc-windows-msvc 4 | TOOLCHAIN: stable 5 | - TARGET: i686-pc-windows-msvc 6 | TOOLCHAIN: stable 7 | - TARGET: x86_64-pc-windows-msvc 8 | TOOLCHAIN: beta 9 | - TARGET: i686-pc-windows-msvc 10 | TOOLCHAIN: beta 11 | - TARGET: x86_64-pc-windows-msvc 12 | TOOLCHAIN: nightly 13 | - TARGET: i686-pc-windows-msvc 14 | TOOLCHAIN: nightly 15 | 16 | install: 17 | - curl -sSf -o rustup-init.exe https://win.rustup.rs/ 18 | - rustup-init.exe -y --default-host %TARGET% --default-toolchain %TOOLCHAIN% 19 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 20 | - choco install make -y 21 | - choco install mingw -y 22 | - refreshenv 23 | - rustc -Vv 24 | - cargo -Vv 25 | - make -v 26 | - gcc -v 27 | 28 | build_script: 29 | - git submodule -q update --init 30 | 31 | test_script: 32 | - SET RUST_BACKTRACE=1 33 | - cargo test --target %TARGET% --all --verbose 34 | - cargo test --release --target %TARGET% --all --verbose 35 | - if [%TOOLCHAIN%]==[nightly] ( 36 | cargo bench --target %TARGET% --all --verbose 37 | ) 38 | cache: 39 | - C:\Users\appveyor\.cargo\registry 40 | - target 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lmdb-sys/lmdb"] 2 | path = lmdb-sys/lmdb 3 | url = https://github.com/mozilla/lmdb 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_layout = "Vertical" 2 | max_width = 120 3 | match_block_trailing_comma = true 4 | use_small_heuristics = "Off" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | os: 7 | - linux 8 | - osx 9 | 10 | rust: 11 | - 1.37.0 12 | - stable 13 | - beta 14 | - nightly 15 | 16 | matrix: 17 | allow_failures: 18 | - rust: nightly 19 | fast_finish: true 20 | 21 | before_script: 22 | # We install a known-to-have-rustfmt version of the nightly toolchain 23 | # in order to run the nightly version of rustfmt, which supports rules 24 | # that we depend upon. When updating, pick a suitable nightly version 25 | # from https://rust-lang.github.io/rustup-components-history/ 26 | - rustup toolchain install nightly-2019-09-11 27 | - rustup component add rustfmt --toolchain nightly-2019-09-11 28 | - rustup component add clippy --toolchain nightly-2019-09-11 29 | # Use official clang in order to test out libfuzzer on osx. 30 | - if [[ "$TRAVIS_OS_NAME" = "osx" ]]; then 31 | brew update; 32 | brew install llvm; 33 | export PATH="/usr/local/opt/llvm/bin:$PATH"; 34 | export LDFLAGS="-L/usr/local/opt/llvm/lib"; 35 | export CPPFLAGS="-I/usr/local/opt/llvm/include"; 36 | fi 37 | 38 | script: 39 | - cargo +nightly-2019-09-11 fmt --all -- --check 40 | - CC="clang" cargo +nightly-2019-09-11 clippy --all-features -- -D warnings -A clippy::match-ref-pats -A clippy::needless-lifetimes 41 | - CC="clang" cargo build --features with-asan --verbose 42 | - CC="clang" cargo build --features with-fuzzer --verbose 43 | - CC="clang" cargo build --features with-fuzzer-no-link --verbose 44 | - CC="clang" cargo build --features with-asan,with-fuzzer --verbose 45 | - CC="clang" cargo build --features with-asan,with-fuzzer-no-link --verbose 46 | - cargo build --verbose 47 | - export RUST_BACKTRACE=1 48 | - cargo test --all --verbose 49 | - cargo test --release --all --verbose 50 | - if [[ $TRAVIS_RUST_VERSION = nightly* ]]; then 51 | cargo bench --all --verbose; 52 | fi 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lmdb-rkv" 3 | # NB: When modifying, also modify html_root_url in lib.rs 4 | version = "0.14.0" 5 | authors = ["Dan Burkert ", "Victor Porof "] 6 | license = "Apache-2.0" 7 | description = "Idiomatic and safe LMDB wrapper." 8 | documentation = "https://docs.rs/lmdb-rkv" 9 | homepage = "https://github.com/mozilla/lmdb-rs" 10 | repository = "https://github.com/mozilla/lmdb-rs.git" 11 | readme = "README.md" 12 | keywords = ["LMDB", "database", "storage-engine", "bindings", "library"] 13 | categories = ["database"] 14 | 15 | exclude = [ 16 | # Exclude CI config files from package. 17 | "/.appveyor.yml", 18 | "/.travis.yml", 19 | "/azure-pipelines-template.yml", 20 | "/azure-pipelines.yml", 21 | ] 22 | 23 | [lib] 24 | name = "lmdb" 25 | 26 | [badges] 27 | travis-ci = { repository = "mozilla/lmdb-rs" } 28 | appveyor = { repository = "mozilla/lmdb-rs" } 29 | 30 | [workspace] 31 | members = [ 32 | "lmdb-sys", 33 | ] 34 | 35 | [dependencies] 36 | bitflags = "1" 37 | byteorder = "1" 38 | libc = "0.2" 39 | 40 | # In order to ensure that we test lmdb-rkv in CI against the in-tree version 41 | # of lmdb-rkv-sys, we specify the dependency as a path here. 42 | # 43 | # But we can't publish the lmdb-rkv crate to crates.io with a path dependency, 44 | # so we have to temporarily change this to point to the current version 45 | # of lmdb-rkv-sys on crates.io when publishing lmdb-rkv to that crate registry. 46 | # 47 | # (See "Publishing to crates.io" in README.md for more information.) 48 | lmdb-rkv-sys = { path = "./lmdb-sys" } 49 | 50 | [dev-dependencies] 51 | rand = "0.4" 52 | tempdir = "0.3" 53 | 54 | [features] 55 | default = [] 56 | with-asan = ["lmdb-rkv-sys/with-asan"] 57 | with-fuzzer = ["lmdb-rkv-sys/with-fuzzer"] 58 | with-fuzzer-no-link = ["lmdb-rkv-sys/with-fuzzer-no-link"] 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Dan Burkert 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mozilla/lmdb-rs.svg?branch=master)](https://travis-ci.org/mozilla/lmdb-rs) 2 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/id69kkymorycld55/branch/master?svg=true)](https://ci.appveyor.com/project/mykmelez/lmdb-rs-rrsb3/branch/master) 3 | 4 | # lmdb-rs 5 | 6 | Idiomatic and safe APIs for interacting with the 7 | [Symas Lightning Memory-Mapped Database (LMDB)](http://symas.com/mdb/). 8 | 9 | This repo is a fork of [danburkert/lmdb-rs](https://github.com/danburkert/lmdb-rs) 10 | with fixes for issues encountered by [mozilla/rkv](https://github.com/mozilla/rkv). 11 | 12 | ## Building from Source 13 | 14 | ```bash 15 | git clone --recursive git@github.com:mozilla/lmdb-rs.git 16 | cd lmdb-rs 17 | cargo build 18 | ``` 19 | 20 | ## Publishing to crates.io 21 | 22 | To publish the lmdb-rkv-sys crate to crates.io: 23 | 24 | ```bash 25 | git clone --recursive git@github.com:mozilla/lmdb-rs.git 26 | cd lmdb-rs/lmdb-sys 27 | # Update the version string in lmdb-sys/Cargo.toml and lmdb-sys/src/lib.rs. 28 | cargo publish 29 | git tag lmdb-rkv-sys-$VERSION # where $VERSION is the updated version string 30 | git push git@github.com:mozilla/lmdb-rs.git --tags 31 | ``` 32 | 33 | To publish the lmdb-rkv crate to crates.io: 34 | 35 | ```bash 36 | git clone --recursive git@github.com:mozilla/lmdb-rs.git 37 | cd lmdb-rs 38 | # Update the version string in Cargo.toml and src/lib.rs and temporarily change 39 | # the lmdb-rkv-sys dependency in Cargo.toml to the latest version on crates.io. 40 | cargo publish 41 | git tag $VERSION # where $VERSION is the updated version string 42 | git push git@github.com:mozilla/lmdb-rs.git --tags 43 | # Change the lmdb-rkv-sys dependency in Cargo.toml back to a path dependency 44 | # on the ./lmdb-sys directory. 45 | ``` 46 | 47 | ## Features 48 | 49 | * [x] lmdb-sys. 50 | * [x] Cursors. 51 | * [x] Zero-copy put API. 52 | * [x] Nested transactions. 53 | * [x] Database statistics. 54 | -------------------------------------------------------------------------------- /azure-pipelines-template.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ${{ parameters.name }} 3 | pool: 4 | vmImage: ${{ parameters.vmImage }} 5 | strategy: 6 | matrix: 7 | stable: 8 | rustup_toolchain: stable 9 | beta: 10 | rustup_toolchain: beta 11 | nightly: 12 | rustup_toolchain: nightly 13 | steps: 14 | # Linux and macOS. 15 | - ${{ if ne(parameters.name, 'Windows') }}: 16 | - script: | 17 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN 18 | echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" 19 | displayName: Install rust 20 | # Windows. 21 | - ${{ if eq(parameters.name, 'Windows') }}: 22 | - script: | 23 | curl -sSf -o rustup-init.exe https://win.rustup.rs 24 | rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% 25 | set PATH=%PATH%;%USERPROFILE%\.cargo\bin 26 | echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" 27 | displayName: Install rust (windows) 28 | # All platforms. 29 | - script: | 30 | rustc -Vv 31 | cargo -Vv 32 | displayName: Query rust and cargo versions 33 | - script: cargo build 34 | displayName: Build 35 | - script: | 36 | cargo test --all --verbose 37 | cargo test --release --all --verbose 38 | displayName: Test 39 | # Linux and macOS w/nightly toolchain. 40 | # Ideally we'd only run the script for the nightly toolchain, but I can't 41 | # figure out how to determine that within the Azure Pipelines conditional. 42 | - ${{ if ne(parameters.name, 'Windows') }}: 43 | - script: | 44 | export RUST_BACKTRACE=1 45 | if [ "$RUSTUP_TOOLCHAIN" = "nightly" ] 46 | then cargo bench --all --verbose; 47 | fi 48 | displayName: Bench 49 | # Windows w/nightly toolchain. 50 | # Ideally we'd only run the script for the nightly toolchain, but I can't 51 | # figure out how to determine that within the Azure Pipelines conditional. 52 | - ${{ if eq(parameters.name, 'Windows') }}: 53 | - script: | 54 | SET RUST_BACKTRACE=1 55 | if "%RUSTUP_TOOLCHAIN%" == "nightly" ( 56 | cargo bench --all --verbose 57 | ) 58 | displayName: Bench 59 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - template: azure-pipelines-template.yml 3 | parameters: 4 | name: macOS 5 | vmImage: macOS-10.13 6 | 7 | - template: azure-pipelines-template.yml 8 | parameters: 9 | name: Linux 10 | vmImage: ubuntu-16.04 11 | 12 | - template: azure-pipelines-template.yml 13 | parameters: 14 | name: Windows 15 | vmImage: vs2017-win2016 16 | -------------------------------------------------------------------------------- /benches/cursor.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate lmdb; 4 | extern crate lmdb_sys as ffi; 5 | extern crate test; 6 | 7 | mod utils; 8 | 9 | use ffi::*; 10 | use lmdb::{ 11 | Cursor, 12 | Result, 13 | RoCursor, 14 | Transaction, 15 | }; 16 | use std::ptr; 17 | use test::{ 18 | black_box, 19 | Bencher, 20 | }; 21 | use utils::*; 22 | 23 | /// Benchmark of iterator sequential read performance. 24 | #[bench] 25 | fn bench_get_seq_iter(b: &mut Bencher) { 26 | let n = 100; 27 | let (_dir, env) = setup_bench_db(n); 28 | let db = env.open_db(None).unwrap(); 29 | let txn = env.begin_ro_txn().unwrap(); 30 | 31 | b.iter(|| { 32 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 33 | let mut i = 0; 34 | let mut count = 0u32; 35 | 36 | for (key, data) in cursor.iter().map(Result::unwrap) { 37 | i = i + key.len() + data.len(); 38 | count += 1; 39 | } 40 | for (key, data) in cursor.iter().filter_map(Result::ok) { 41 | i = i + key.len() + data.len(); 42 | count += 1; 43 | } 44 | 45 | fn iterate(cursor: &mut RoCursor) -> Result<()> { 46 | let mut i = 0; 47 | for result in cursor.iter() { 48 | let (key, data) = result?; 49 | i = i + key.len() + data.len(); 50 | } 51 | Ok(()) 52 | } 53 | iterate(&mut cursor).unwrap(); 54 | 55 | black_box(i); 56 | assert_eq!(count, n); 57 | }); 58 | } 59 | 60 | /// Benchmark of cursor sequential read performance. 61 | #[bench] 62 | fn bench_get_seq_cursor(b: &mut Bencher) { 63 | let n = 100; 64 | let (_dir, env) = setup_bench_db(n); 65 | let db = env.open_db(None).unwrap(); 66 | let txn = env.begin_ro_txn().unwrap(); 67 | 68 | b.iter(|| { 69 | let cursor = txn.open_ro_cursor(db).unwrap(); 70 | let mut i = 0; 71 | let mut count = 0u32; 72 | 73 | while let Ok((key_opt, val)) = cursor.get(None, None, MDB_NEXT) { 74 | i += key_opt.map(|key| key.len()).unwrap_or(0) + val.len(); 75 | count += 1; 76 | } 77 | 78 | black_box(i); 79 | assert_eq!(count, n); 80 | }); 81 | } 82 | 83 | /// Benchmark of raw LMDB sequential read performance (control). 84 | #[bench] 85 | fn bench_get_seq_raw(b: &mut Bencher) { 86 | let n = 100; 87 | let (_dir, env) = setup_bench_db(n); 88 | let db = env.open_db(None).unwrap(); 89 | 90 | let dbi: MDB_dbi = db.dbi(); 91 | let _txn = env.begin_ro_txn().unwrap(); 92 | let txn = _txn.txn(); 93 | 94 | let mut key = MDB_val { 95 | mv_size: 0, 96 | mv_data: ptr::null_mut(), 97 | }; 98 | let mut data = MDB_val { 99 | mv_size: 0, 100 | mv_data: ptr::null_mut(), 101 | }; 102 | let mut cursor: *mut MDB_cursor = ptr::null_mut(); 103 | 104 | b.iter(|| unsafe { 105 | mdb_cursor_open(txn, dbi, &mut cursor); 106 | let mut i = 0; 107 | let mut count = 0u32; 108 | 109 | while mdb_cursor_get(cursor, &mut key, &mut data, MDB_NEXT) == 0 { 110 | i += key.mv_size + data.mv_size; 111 | count += 1; 112 | } 113 | 114 | black_box(i); 115 | assert_eq!(count, n); 116 | mdb_cursor_close(cursor); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /benches/transaction.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate libc; 4 | extern crate lmdb; 5 | extern crate lmdb_sys as ffi; 6 | extern crate rand; 7 | extern crate test; 8 | 9 | mod utils; 10 | 11 | use ffi::*; 12 | use libc::size_t; 13 | use lmdb::{ 14 | Transaction, 15 | WriteFlags, 16 | }; 17 | use rand::{ 18 | Rng, 19 | XorShiftRng, 20 | }; 21 | use std::ptr; 22 | use test::{ 23 | black_box, 24 | Bencher, 25 | }; 26 | use utils::*; 27 | 28 | #[bench] 29 | fn bench_get_rand(b: &mut Bencher) { 30 | let n = 100u32; 31 | let (_dir, env) = setup_bench_db(n); 32 | let db = env.open_db(None).unwrap(); 33 | let txn = env.begin_ro_txn().unwrap(); 34 | 35 | let mut keys: Vec = (0..n).map(get_key).collect(); 36 | XorShiftRng::new_unseeded().shuffle(&mut keys[..]); 37 | 38 | b.iter(|| { 39 | let mut i = 0usize; 40 | for key in &keys { 41 | i += txn.get(db, key).unwrap().len(); 42 | } 43 | black_box(i); 44 | }); 45 | } 46 | 47 | #[bench] 48 | fn bench_get_rand_raw(b: &mut Bencher) { 49 | let n = 100u32; 50 | let (_dir, env) = setup_bench_db(n); 51 | let db = env.open_db(None).unwrap(); 52 | let _txn = env.begin_ro_txn().unwrap(); 53 | 54 | let mut keys: Vec = (0..n).map(get_key).collect(); 55 | XorShiftRng::new_unseeded().shuffle(&mut keys[..]); 56 | 57 | let dbi = db.dbi(); 58 | let txn = _txn.txn(); 59 | 60 | let mut key_val: MDB_val = MDB_val { 61 | mv_size: 0, 62 | mv_data: ptr::null_mut(), 63 | }; 64 | let mut data_val: MDB_val = MDB_val { 65 | mv_size: 0, 66 | mv_data: ptr::null_mut(), 67 | }; 68 | 69 | b.iter(|| unsafe { 70 | let mut i: size_t = 0; 71 | for key in &keys { 72 | key_val.mv_size = key.len() as size_t; 73 | key_val.mv_data = key.as_bytes().as_ptr() as *mut _; 74 | 75 | mdb_get(txn, dbi, &mut key_val, &mut data_val); 76 | 77 | i += key_val.mv_size; 78 | } 79 | black_box(i); 80 | }); 81 | } 82 | 83 | #[bench] 84 | fn bench_put_rand(b: &mut Bencher) { 85 | let n = 100u32; 86 | let (_dir, env) = setup_bench_db(0); 87 | let db = env.open_db(None).unwrap(); 88 | 89 | let mut items: Vec<(String, String)> = (0..n).map(|n| (get_key(n), get_data(n))).collect(); 90 | XorShiftRng::new_unseeded().shuffle(&mut items[..]); 91 | 92 | b.iter(|| { 93 | let mut txn = env.begin_rw_txn().unwrap(); 94 | for &(ref key, ref data) in items.iter() { 95 | txn.put(db, key, data, WriteFlags::empty()).unwrap(); 96 | } 97 | txn.abort(); 98 | }); 99 | } 100 | 101 | #[bench] 102 | fn bench_put_rand_raw(b: &mut Bencher) { 103 | let n = 100u32; 104 | let (_dir, _env) = setup_bench_db(0); 105 | let db = _env.open_db(None).unwrap(); 106 | 107 | let mut items: Vec<(String, String)> = (0..n).map(|n| (get_key(n), get_data(n))).collect(); 108 | XorShiftRng::new_unseeded().shuffle(&mut items[..]); 109 | 110 | let dbi = db.dbi(); 111 | let env = _env.env(); 112 | 113 | let mut key_val: MDB_val = MDB_val { 114 | mv_size: 0, 115 | mv_data: ptr::null_mut(), 116 | }; 117 | let mut data_val: MDB_val = MDB_val { 118 | mv_size: 0, 119 | mv_data: ptr::null_mut(), 120 | }; 121 | 122 | b.iter(|| unsafe { 123 | let mut txn: *mut MDB_txn = ptr::null_mut(); 124 | mdb_txn_begin(env, ptr::null_mut(), 0, &mut txn); 125 | 126 | let mut i: ::libc::c_int = 0; 127 | for &(ref key, ref data) in items.iter() { 128 | key_val.mv_size = key.len() as size_t; 129 | key_val.mv_data = key.as_bytes().as_ptr() as *mut _; 130 | data_val.mv_size = data.len() as size_t; 131 | data_val.mv_data = data.as_bytes().as_ptr() as *mut _; 132 | 133 | i += mdb_put(txn, dbi, &mut key_val, &mut data_val, 0); 134 | } 135 | assert_eq!(0, i); 136 | mdb_txn_abort(txn); 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /benches/utils.rs: -------------------------------------------------------------------------------- 1 | extern crate lmdb; 2 | extern crate tempdir; 3 | 4 | use self::tempdir::TempDir; 5 | use lmdb::{ 6 | Environment, 7 | Transaction, 8 | WriteFlags, 9 | }; 10 | 11 | pub fn get_key(n: u32) -> String { 12 | format!("key{}", n) 13 | } 14 | 15 | pub fn get_data(n: u32) -> String { 16 | format!("data{}", n) 17 | } 18 | 19 | pub fn setup_bench_db(num_rows: u32) -> (TempDir, Environment) { 20 | let dir = TempDir::new("test").unwrap(); 21 | let env = Environment::new().open(dir.path()).unwrap(); 22 | 23 | { 24 | let db = env.open_db(None).unwrap(); 25 | let mut txn = env.begin_rw_txn().unwrap(); 26 | for i in 0..num_rows { 27 | txn.put(db, &get_key(i), &get_data(i), WriteFlags::empty()).unwrap(); 28 | } 29 | txn.commit().unwrap(); 30 | } 31 | (dir, env) 32 | } 33 | -------------------------------------------------------------------------------- /lmdb-sys/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | ignore = [ 2 | "src/bindings.rs" 3 | ] -------------------------------------------------------------------------------- /lmdb-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lmdb-rkv-sys" 3 | # NB: When modifying, also modify html_root_url in lib.rs 4 | version = "0.11.2" 5 | authors = ["Dan Burkert ", "Victor Porof "] 6 | license = "Apache-2.0" 7 | description = "Rust bindings for liblmdb." 8 | documentation = "https://docs.rs/lmdb-rkv-sys" 9 | homepage = "https://github.com/mozilla/lmdb-rs" 10 | repository = "https://github.com/mozilla/lmdb-rs.git" 11 | readme = "../README.md" 12 | keywords = ["LMDB", "database", "storage-engine", "bindings", "library"] 13 | categories = ["database", "external-ffi-bindings"] 14 | 15 | # NB: Use "--features bindgen" to generate bindings. 16 | build = "build.rs" 17 | 18 | [lib] 19 | name = "lmdb_sys" 20 | 21 | [badges] 22 | travis-ci = { repository = "mozilla/lmdb-rs" } 23 | appveyor = { repository = "mozilla/lmdb-rs" } 24 | 25 | [dependencies] 26 | libc = "0.2" 27 | 28 | [build-dependencies] 29 | pkg-config = "0.3" 30 | cc = "1.0" 31 | bindgen = { version = "0.53.2", default-features = false, optional = true, features = ["runtime"] } 32 | 33 | [features] 34 | default = [] 35 | with-asan = [] 36 | with-fuzzer = [] 37 | with-fuzzer-no-link = [] 38 | 39 | # These features configure the MDB_IDL_LOGN macro, which determines 40 | # the size of the free and dirty page lists (and thus the amount of memory 41 | # allocated when opening an LMDB environment in read-write mode). 42 | # 43 | # Each feature defines MDB_IDL_LOGN as the value in the name of the feature. 44 | # That means these features are mutually exclusive, and you must not specify 45 | # more than one at the same time (or the crate will fail to compile). 46 | # 47 | # For more information on the motivation for these features (and their effect), 48 | # see https://github.com/mozilla/lmdb/pull/2. 49 | mdb_idl_logn_8 = [] 50 | mdb_idl_logn_9 = [] 51 | mdb_idl_logn_10 = [] 52 | mdb_idl_logn_11 = [] 53 | mdb_idl_logn_12 = [] 54 | mdb_idl_logn_13 = [] 55 | mdb_idl_logn_14 = [] 56 | mdb_idl_logn_15 = [] 57 | -------------------------------------------------------------------------------- /lmdb-sys/bindgen.rs: -------------------------------------------------------------------------------- 1 | use bindgen::callbacks::IntKind; 2 | use bindgen::callbacks::ParseCallbacks; 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | #[derive(Debug)] 7 | struct Callbacks; 8 | 9 | impl ParseCallbacks for Callbacks { 10 | fn int_macro(&self, name: &str, _value: i64) -> Option { 11 | match name { 12 | "MDB_SUCCESS" 13 | | "MDB_KEYEXIST" 14 | | "MDB_NOTFOUND" 15 | | "MDB_PAGE_NOTFOUND" 16 | | "MDB_CORRUPTED" 17 | | "MDB_PANIC" 18 | | "MDB_VERSION_MISMATCH" 19 | | "MDB_INVALID" 20 | | "MDB_MAP_FULL" 21 | | "MDB_DBS_FULL" 22 | | "MDB_READERS_FULL" 23 | | "MDB_TLS_FULL" 24 | | "MDB_TXN_FULL" 25 | | "MDB_CURSOR_FULL" 26 | | "MDB_PAGE_FULL" 27 | | "MDB_MAP_RESIZED" 28 | | "MDB_INCOMPATIBLE" 29 | | "MDB_BAD_RSLOT" 30 | | "MDB_BAD_TXN" 31 | | "MDB_BAD_VALSIZE" 32 | | "MDB_BAD_DBI" 33 | | "MDB_LAST_ERRCODE" => Some(IntKind::Int), 34 | _ => Some(IntKind::UInt), 35 | } 36 | } 37 | } 38 | 39 | pub fn generate() { 40 | let mut lmdb = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); 41 | lmdb.push("lmdb"); 42 | lmdb.push("libraries"); 43 | lmdb.push("liblmdb"); 44 | 45 | let mut out_path = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); 46 | out_path.push("src"); 47 | 48 | let bindings = bindgen::Builder::default() 49 | .header(lmdb.join("lmdb.h").to_string_lossy()) 50 | .whitelist_var("^(MDB|mdb)_.*") 51 | .whitelist_type("^(MDB|mdb)_.*") 52 | .whitelist_function("^(MDB|mdb)_.*") 53 | .size_t_is_usize(true) 54 | .ctypes_prefix("::libc") 55 | .blacklist_item("mode_t") 56 | .blacklist_item("mdb_mode_t") 57 | .blacklist_item("mdb_filehandle_t") 58 | .blacklist_item("^__.*") 59 | .parse_callbacks(Box::new(Callbacks {})) 60 | .layout_tests(false) 61 | .prepend_enum_name(false) 62 | .rustfmt_bindings(true) 63 | .generate() 64 | .expect("Unable to generate bindings"); 65 | 66 | bindings 67 | .write_to_file(out_path.join("bindings.rs")) 68 | .expect("Couldn't write bindings!"); 69 | } 70 | -------------------------------------------------------------------------------- /lmdb-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | extern crate pkg_config; 3 | 4 | #[cfg(feature = "bindgen")] 5 | extern crate bindgen; 6 | 7 | #[cfg(feature = "bindgen")] 8 | #[path = "bindgen.rs"] 9 | mod generate; 10 | 11 | use std::env; 12 | use std::path::PathBuf; 13 | 14 | #[cfg(feature = "mdb_idl_logn_8")] 15 | const MDB_IDL_LOGN: u8 = 8; 16 | #[cfg(feature = "mdb_idl_logn_9")] 17 | const MDB_IDL_LOGN: u8 = 9; 18 | #[cfg(feature = "mdb_idl_logn_10")] 19 | const MDB_IDL_LOGN: u8 = 10; 20 | #[cfg(feature = "mdb_idl_logn_11")] 21 | const MDB_IDL_LOGN: u8 = 11; 22 | #[cfg(feature = "mdb_idl_logn_12")] 23 | const MDB_IDL_LOGN: u8 = 12; 24 | #[cfg(feature = "mdb_idl_logn_13")] 25 | const MDB_IDL_LOGN: u8 = 13; 26 | #[cfg(feature = "mdb_idl_logn_14")] 27 | const MDB_IDL_LOGN: u8 = 14; 28 | #[cfg(feature = "mdb_idl_logn_15")] 29 | const MDB_IDL_LOGN: u8 = 15; 30 | #[cfg(not(any( 31 | feature = "mdb_idl_logn_8", 32 | feature = "mdb_idl_logn_9", 33 | feature = "mdb_idl_logn_10", 34 | feature = "mdb_idl_logn_11", 35 | feature = "mdb_idl_logn_12", 36 | feature = "mdb_idl_logn_13", 37 | feature = "mdb_idl_logn_14", 38 | feature = "mdb_idl_logn_15", 39 | )))] 40 | const MDB_IDL_LOGN: u8 = 16; 41 | 42 | macro_rules! warn { 43 | ($message:expr) => { 44 | println!("cargo:warning={}", $message); 45 | }; 46 | } 47 | 48 | fn main() { 49 | #[cfg(feature = "bindgen")] 50 | generate::generate(); 51 | 52 | let mut lmdb = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); 53 | lmdb.push("lmdb"); 54 | lmdb.push("libraries"); 55 | lmdb.push("liblmdb"); 56 | 57 | if cfg!(feature = "with-fuzzer") && cfg!(feature = "with-fuzzer-no-link") { 58 | warn!("Features `with-fuzzer` and `with-fuzzer-no-link` are mutually exclusive."); 59 | warn!("Building with `-fsanitize=fuzzer`."); 60 | } 61 | 62 | if !pkg_config::find_library("liblmdb").is_ok() { 63 | let mut builder = cc::Build::new(); 64 | 65 | builder 66 | .define("MDB_IDL_LOGN", Some(MDB_IDL_LOGN.to_string().as_str())) 67 | .file(lmdb.join("mdb.c")) 68 | .file(lmdb.join("midl.c")) 69 | // https://github.com/mozilla/lmdb/blob/b7df2cac50fb41e8bd16aab4cc5fd167be9e032a/libraries/liblmdb/Makefile#L23 70 | .flag_if_supported("-Wno-unused-parameter") 71 | .flag_if_supported("-Wbad-function-cast") 72 | .flag_if_supported("-Wuninitialized"); 73 | 74 | if env::var("CARGO_FEATURE_WITH_ASAN").is_ok() { 75 | builder.flag("-fsanitize=address"); 76 | } 77 | 78 | if env::var("CARGO_FEATURE_WITH_FUZZER").is_ok() { 79 | builder.flag("-fsanitize=fuzzer"); 80 | } else if env::var("CARGO_FEATURE_WITH_FUZZER_NO_LINK").is_ok() { 81 | builder.flag("-fsanitize=fuzzer-no-link"); 82 | } 83 | 84 | builder.compile("liblmdb.a") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lmdb-sys/src/bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen */ 2 | 3 | pub const MDB_VERSION_MAJOR: ::libc::c_uint = 0; 4 | pub const MDB_VERSION_MINOR: ::libc::c_uint = 9; 5 | pub const MDB_VERSION_PATCH: ::libc::c_uint = 24; 6 | pub const MDB_VERSION_DATE: &'static [u8; 14usize] = b"July 24, 2019\0"; 7 | pub const MDB_FIXEDMAP: ::libc::c_uint = 1; 8 | pub const MDB_NOSUBDIR: ::libc::c_uint = 16384; 9 | pub const MDB_NOSYNC: ::libc::c_uint = 65536; 10 | pub const MDB_RDONLY: ::libc::c_uint = 131072; 11 | pub const MDB_NOMETASYNC: ::libc::c_uint = 262144; 12 | pub const MDB_WRITEMAP: ::libc::c_uint = 524288; 13 | pub const MDB_MAPASYNC: ::libc::c_uint = 1048576; 14 | pub const MDB_NOTLS: ::libc::c_uint = 2097152; 15 | pub const MDB_NOLOCK: ::libc::c_uint = 4194304; 16 | pub const MDB_NORDAHEAD: ::libc::c_uint = 8388608; 17 | pub const MDB_NOMEMINIT: ::libc::c_uint = 16777216; 18 | pub const MDB_REVERSEKEY: ::libc::c_uint = 2; 19 | pub const MDB_DUPSORT: ::libc::c_uint = 4; 20 | pub const MDB_INTEGERKEY: ::libc::c_uint = 8; 21 | pub const MDB_DUPFIXED: ::libc::c_uint = 16; 22 | pub const MDB_INTEGERDUP: ::libc::c_uint = 32; 23 | pub const MDB_REVERSEDUP: ::libc::c_uint = 64; 24 | pub const MDB_CREATE: ::libc::c_uint = 262144; 25 | pub const MDB_NOOVERWRITE: ::libc::c_uint = 16; 26 | pub const MDB_NODUPDATA: ::libc::c_uint = 32; 27 | pub const MDB_CURRENT: ::libc::c_uint = 64; 28 | pub const MDB_RESERVE: ::libc::c_uint = 65536; 29 | pub const MDB_APPEND: ::libc::c_uint = 131072; 30 | pub const MDB_APPENDDUP: ::libc::c_uint = 262144; 31 | pub const MDB_MULTIPLE: ::libc::c_uint = 524288; 32 | pub const MDB_CP_COMPACT: ::libc::c_uint = 1; 33 | pub const MDB_SUCCESS: ::libc::c_int = 0; 34 | pub const MDB_KEYEXIST: ::libc::c_int = -30799; 35 | pub const MDB_NOTFOUND: ::libc::c_int = -30798; 36 | pub const MDB_PAGE_NOTFOUND: ::libc::c_int = -30797; 37 | pub const MDB_CORRUPTED: ::libc::c_int = -30796; 38 | pub const MDB_PANIC: ::libc::c_int = -30795; 39 | pub const MDB_VERSION_MISMATCH: ::libc::c_int = -30794; 40 | pub const MDB_INVALID: ::libc::c_int = -30793; 41 | pub const MDB_MAP_FULL: ::libc::c_int = -30792; 42 | pub const MDB_DBS_FULL: ::libc::c_int = -30791; 43 | pub const MDB_READERS_FULL: ::libc::c_int = -30790; 44 | pub const MDB_TLS_FULL: ::libc::c_int = -30789; 45 | pub const MDB_TXN_FULL: ::libc::c_int = -30788; 46 | pub const MDB_CURSOR_FULL: ::libc::c_int = -30787; 47 | pub const MDB_PAGE_FULL: ::libc::c_int = -30786; 48 | pub const MDB_MAP_RESIZED: ::libc::c_int = -30785; 49 | pub const MDB_INCOMPATIBLE: ::libc::c_int = -30784; 50 | pub const MDB_BAD_RSLOT: ::libc::c_int = -30783; 51 | pub const MDB_BAD_TXN: ::libc::c_int = -30782; 52 | pub const MDB_BAD_VALSIZE: ::libc::c_int = -30781; 53 | pub const MDB_BAD_DBI: ::libc::c_int = -30780; 54 | pub const MDB_LAST_ERRCODE: ::libc::c_int = -30780; 55 | #[repr(C)] 56 | #[derive(Debug, Copy, Clone)] 57 | pub struct MDB_env { 58 | _unused: [u8; 0], 59 | } 60 | #[repr(C)] 61 | #[derive(Debug, Copy, Clone)] 62 | pub struct MDB_txn { 63 | _unused: [u8; 0], 64 | } 65 | #[doc = " @brief A handle for an individual database in the DB environment."] 66 | pub type MDB_dbi = ::libc::c_uint; 67 | #[repr(C)] 68 | #[derive(Debug, Copy, Clone)] 69 | pub struct MDB_cursor { 70 | _unused: [u8; 0], 71 | } 72 | #[doc = " @brief Generic structure used for passing keys and data in and out"] 73 | #[doc = " of the database."] 74 | #[doc = ""] 75 | #[doc = " Values returned from the database are valid only until a subsequent"] 76 | #[doc = " update operation, or the end of the transaction. Do not modify or"] 77 | #[doc = " free them, they commonly point into the database itself."] 78 | #[doc = ""] 79 | #[doc = " Key sizes must be between 1 and #mdb_env_get_maxkeysize() inclusive."] 80 | #[doc = " The same applies to data sizes in databases with the #MDB_DUPSORT flag."] 81 | #[doc = " Other data items can in theory be from 0 to 0xffffffff bytes long."] 82 | #[repr(C)] 83 | #[derive(Debug, Copy, Clone)] 84 | pub struct MDB_val { 85 | #[doc = "< size of the data item"] 86 | pub mv_size: usize, 87 | #[doc = "< address of the data item"] 88 | pub mv_data: *mut ::libc::c_void, 89 | } 90 | #[doc = " @brief A callback function used to compare two keys in a database"] 91 | pub type MDB_cmp_func = ::std::option::Option< 92 | unsafe extern "C" fn(a: *const MDB_val, b: *const MDB_val) -> ::libc::c_int, 93 | >; 94 | #[doc = " @brief A callback function used to relocate a position-dependent data item"] 95 | #[doc = " in a fixed-address database."] 96 | #[doc = ""] 97 | #[doc = " The \\b newptr gives the item's desired address in"] 98 | #[doc = " the memory map, and \\b oldptr gives its previous address. The item's actual"] 99 | #[doc = " data resides at the address in \\b item. This callback is expected to walk"] 100 | #[doc = " through the fields of the record in \\b item and modify any"] 101 | #[doc = " values based at the \\b oldptr address to be relative to the \\b newptr address."] 102 | #[doc = " @param[in,out] item The item that is to be relocated."] 103 | #[doc = " @param[in] oldptr The previous address."] 104 | #[doc = " @param[in] newptr The new address to relocate to."] 105 | #[doc = " @param[in] relctx An application-provided context, set by #mdb_set_relctx()."] 106 | #[doc = " @todo This feature is currently unimplemented."] 107 | pub type MDB_rel_func = ::std::option::Option< 108 | unsafe extern "C" fn( 109 | item: *mut MDB_val, 110 | oldptr: *mut ::libc::c_void, 111 | newptr: *mut ::libc::c_void, 112 | relctx: *mut ::libc::c_void, 113 | ), 114 | >; 115 | #[doc = "< Position at first key/data item"] 116 | pub const MDB_FIRST: MDB_cursor_op = 0; 117 | #[doc = "< Position at first data item of current key."] 118 | #[doc = "Only for #MDB_DUPSORT"] 119 | pub const MDB_FIRST_DUP: MDB_cursor_op = 1; 120 | #[doc = "< Position at key/data pair. Only for #MDB_DUPSORT"] 121 | pub const MDB_GET_BOTH: MDB_cursor_op = 2; 122 | #[doc = "< position at key, nearest data. Only for #MDB_DUPSORT"] 123 | pub const MDB_GET_BOTH_RANGE: MDB_cursor_op = 3; 124 | #[doc = "< Return key/data at current cursor position"] 125 | pub const MDB_GET_CURRENT: MDB_cursor_op = 4; 126 | #[doc = "< Return up to a page of duplicate data items"] 127 | #[doc = "from current cursor position. Move cursor to prepare"] 128 | #[doc = "for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED"] 129 | pub const MDB_GET_MULTIPLE: MDB_cursor_op = 5; 130 | #[doc = "< Position at last key/data item"] 131 | pub const MDB_LAST: MDB_cursor_op = 6; 132 | #[doc = "< Position at last data item of current key."] 133 | #[doc = "Only for #MDB_DUPSORT"] 134 | pub const MDB_LAST_DUP: MDB_cursor_op = 7; 135 | #[doc = "< Position at next data item"] 136 | pub const MDB_NEXT: MDB_cursor_op = 8; 137 | #[doc = "< Position at next data item of current key."] 138 | #[doc = "Only for #MDB_DUPSORT"] 139 | pub const MDB_NEXT_DUP: MDB_cursor_op = 9; 140 | #[doc = "< Return up to a page of duplicate data items"] 141 | #[doc = "from next cursor position. Move cursor to prepare"] 142 | #[doc = "for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED"] 143 | pub const MDB_NEXT_MULTIPLE: MDB_cursor_op = 10; 144 | #[doc = "< Position at first data item of next key"] 145 | pub const MDB_NEXT_NODUP: MDB_cursor_op = 11; 146 | #[doc = "< Position at previous data item"] 147 | pub const MDB_PREV: MDB_cursor_op = 12; 148 | #[doc = "< Position at previous data item of current key."] 149 | #[doc = "Only for #MDB_DUPSORT"] 150 | pub const MDB_PREV_DUP: MDB_cursor_op = 13; 151 | #[doc = "< Position at last data item of previous key"] 152 | pub const MDB_PREV_NODUP: MDB_cursor_op = 14; 153 | #[doc = "< Position at specified key"] 154 | pub const MDB_SET: MDB_cursor_op = 15; 155 | #[doc = "< Position at specified key, return key + data"] 156 | pub const MDB_SET_KEY: MDB_cursor_op = 16; 157 | #[doc = "< Position at first key greater than or equal to specified key."] 158 | pub const MDB_SET_RANGE: MDB_cursor_op = 17; 159 | #[doc = "< Position at previous page and return up to"] 160 | #[doc = "a page of duplicate data items. Only for #MDB_DUPFIXED"] 161 | pub const MDB_PREV_MULTIPLE: MDB_cursor_op = 18; 162 | #[doc = " @brief Cursor Get operations."] 163 | #[doc = ""] 164 | #[doc = "\tThis is the set of all operations for retrieving data"] 165 | #[doc = "\tusing a cursor."] 166 | pub type MDB_cursor_op = u32; 167 | #[doc = " @brief Statistics for a database in the environment"] 168 | #[repr(C)] 169 | #[derive(Debug, Copy, Clone)] 170 | pub struct MDB_stat { 171 | #[doc = "< Size of a database page."] 172 | #[doc = "This is currently the same for all databases."] 173 | pub ms_psize: ::libc::c_uint, 174 | #[doc = "< Depth (height) of the B-tree"] 175 | pub ms_depth: ::libc::c_uint, 176 | #[doc = "< Number of internal (non-leaf) pages"] 177 | pub ms_branch_pages: usize, 178 | #[doc = "< Number of leaf pages"] 179 | pub ms_leaf_pages: usize, 180 | #[doc = "< Number of overflow pages"] 181 | pub ms_overflow_pages: usize, 182 | #[doc = "< Number of data items"] 183 | pub ms_entries: usize, 184 | } 185 | #[doc = " @brief Information about the environment"] 186 | #[repr(C)] 187 | #[derive(Debug, Copy, Clone)] 188 | pub struct MDB_envinfo { 189 | #[doc = "< Address of map, if fixed"] 190 | pub me_mapaddr: *mut ::libc::c_void, 191 | #[doc = "< Size of the data memory map"] 192 | pub me_mapsize: usize, 193 | #[doc = "< ID of the last used page"] 194 | pub me_last_pgno: usize, 195 | #[doc = "< ID of the last committed transaction"] 196 | pub me_last_txnid: usize, 197 | #[doc = "< max reader slots in the environment"] 198 | pub me_maxreaders: ::libc::c_uint, 199 | #[doc = "< max reader slots used in the environment"] 200 | pub me_numreaders: ::libc::c_uint, 201 | } 202 | extern "C" { 203 | #[doc = " @brief Return the LMDB library version information."] 204 | #[doc = ""] 205 | #[doc = " @param[out] major if non-NULL, the library major version number is copied here"] 206 | #[doc = " @param[out] minor if non-NULL, the library minor version number is copied here"] 207 | #[doc = " @param[out] patch if non-NULL, the library patch version number is copied here"] 208 | #[doc = " @retval \"version string\" The library version as a string"] 209 | pub fn mdb_version( 210 | major: *mut ::libc::c_int, 211 | minor: *mut ::libc::c_int, 212 | patch: *mut ::libc::c_int, 213 | ) -> *mut ::libc::c_char; 214 | } 215 | extern "C" { 216 | #[doc = " @brief Return a string describing a given error code."] 217 | #[doc = ""] 218 | #[doc = " This function is a superset of the ANSI C X3.159-1989 (ANSI C) strerror(3)"] 219 | #[doc = " function. If the error code is greater than or equal to 0, then the string"] 220 | #[doc = " returned by the system function strerror(3) is returned. If the error code"] 221 | #[doc = " is less than 0, an error string corresponding to the LMDB library error is"] 222 | #[doc = " returned. See @ref errors for a list of LMDB-specific error codes."] 223 | #[doc = " @param[in] err The error code"] 224 | #[doc = " @retval \"error message\" The description of the error"] 225 | pub fn mdb_strerror(err: ::libc::c_int) -> *mut ::libc::c_char; 226 | } 227 | extern "C" { 228 | #[doc = " @brief Create an LMDB environment handle."] 229 | #[doc = ""] 230 | #[doc = " This function allocates memory for a #MDB_env structure. To release"] 231 | #[doc = " the allocated memory and discard the handle, call #mdb_env_close()."] 232 | #[doc = " Before the handle may be used, it must be opened using #mdb_env_open()."] 233 | #[doc = " Various other options may also need to be set before opening the handle,"] 234 | #[doc = " e.g. #mdb_env_set_mapsize(), #mdb_env_set_maxreaders(), #mdb_env_set_maxdbs(),"] 235 | #[doc = " depending on usage requirements."] 236 | #[doc = " @param[out] env The address where the new handle will be stored"] 237 | #[doc = " @return A non-zero error value on failure and 0 on success."] 238 | pub fn mdb_env_create(env: *mut *mut MDB_env) -> ::libc::c_int; 239 | } 240 | extern "C" { 241 | #[doc = " @brief Open an environment handle."] 242 | #[doc = ""] 243 | #[doc = " If this function fails, #mdb_env_close() must be called to discard the #MDB_env handle."] 244 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 245 | #[doc = " @param[in] path The directory in which the database files reside. This"] 246 | #[doc = " directory must already exist and be writable."] 247 | #[doc = " @param[in] flags Special options for this environment. This parameter"] 248 | #[doc = " must be set to 0 or by bitwise OR'ing together one or more of the"] 249 | #[doc = " values described here."] 250 | #[doc = " Flags set by mdb_env_set_flags() are also used."] 251 | #[doc = "
    "] 252 | #[doc = "\t
  • #MDB_FIXEDMAP"] 253 | #[doc = " use a fixed address for the mmap region. This flag must be specified"] 254 | #[doc = " when creating the environment, and is stored persistently in the environment."] 255 | #[doc = "\t\tIf successful, the memory map will always reside at the same virtual address"] 256 | #[doc = "\t\tand pointers used to reference data items in the database will be constant"] 257 | #[doc = "\t\tacross multiple invocations. This option may not always work, depending on"] 258 | #[doc = "\t\thow the operating system has allocated memory to shared libraries and other uses."] 259 | #[doc = "\t\tThe feature is highly experimental."] 260 | #[doc = "\t
  • #MDB_NOSUBDIR"] 261 | #[doc = "\t\tBy default, LMDB creates its environment in a directory whose"] 262 | #[doc = "\t\tpathname is given in \\b path, and creates its data and lock files"] 263 | #[doc = "\t\tunder that directory. With this option, \\b path is used as-is for"] 264 | #[doc = "\t\tthe database main data file. The database lock file is the \\b path"] 265 | #[doc = "\t\twith \"-lock\" appended."] 266 | #[doc = "\t
  • #MDB_RDONLY"] 267 | #[doc = "\t\tOpen the environment in read-only mode. No write operations will be"] 268 | #[doc = "\t\tallowed. LMDB will still modify the lock file - except on read-only"] 269 | #[doc = "\t\tfilesystems, where LMDB does not use locks."] 270 | #[doc = "\t
  • #MDB_WRITEMAP"] 271 | #[doc = "\t\tUse a writeable memory map unless MDB_RDONLY is set. This uses"] 272 | #[doc = "\t\tfewer mallocs but loses protection from application bugs"] 273 | #[doc = "\t\tlike wild pointer writes and other bad updates into the database."] 274 | #[doc = "\t\tThis may be slightly faster for DBs that fit entirely in RAM, but"] 275 | #[doc = "\t\tis slower for DBs larger than RAM."] 276 | #[doc = "\t\tIncompatible with nested transactions."] 277 | #[doc = "\t\tDo not mix processes with and without MDB_WRITEMAP on the same"] 278 | #[doc = "\t\tenvironment. This can defeat durability (#mdb_env_sync etc)."] 279 | #[doc = "\t
  • #MDB_NOMETASYNC"] 280 | #[doc = "\t\tFlush system buffers to disk only once per transaction, omit the"] 281 | #[doc = "\t\tmetadata flush. Defer that until the system flushes files to disk,"] 282 | #[doc = "\t\tor next non-MDB_RDONLY commit or #mdb_env_sync(). This optimization"] 283 | #[doc = "\t\tmaintains database integrity, but a system crash may undo the last"] 284 | #[doc = "\t\tcommitted transaction. I.e. it preserves the ACI (atomicity,"] 285 | #[doc = "\t\tconsistency, isolation) but not D (durability) database property."] 286 | #[doc = "\t\tThis flag may be changed at any time using #mdb_env_set_flags()."] 287 | #[doc = "\t
  • #MDB_NOSYNC"] 288 | #[doc = "\t\tDon't flush system buffers to disk when committing a transaction."] 289 | #[doc = "\t\tThis optimization means a system crash can corrupt the database or"] 290 | #[doc = "\t\tlose the last transactions if buffers are not yet flushed to disk."] 291 | #[doc = "\t\tThe risk is governed by how often the system flushes dirty buffers"] 292 | #[doc = "\t\tto disk and how often #mdb_env_sync() is called. However, if the"] 293 | #[doc = "\t\tfilesystem preserves write order and the #MDB_WRITEMAP flag is not"] 294 | #[doc = "\t\tused, transactions exhibit ACI (atomicity, consistency, isolation)"] 295 | #[doc = "\t\tproperties and only lose D (durability). I.e. database integrity"] 296 | #[doc = "\t\tis maintained, but a system crash may undo the final transactions."] 297 | #[doc = "\t\tNote that (#MDB_NOSYNC | #MDB_WRITEMAP) leaves the system with no"] 298 | #[doc = "\t\thint for when to write transactions to disk, unless #mdb_env_sync()"] 299 | #[doc = "\t\tis called. (#MDB_MAPASYNC | #MDB_WRITEMAP) may be preferable."] 300 | #[doc = "\t\tThis flag may be changed at any time using #mdb_env_set_flags()."] 301 | #[doc = "\t
  • #MDB_MAPASYNC"] 302 | #[doc = "\t\tWhen using #MDB_WRITEMAP, use asynchronous flushes to disk."] 303 | #[doc = "\t\tAs with #MDB_NOSYNC, a system crash can then corrupt the"] 304 | #[doc = "\t\tdatabase or lose the last transactions. Calling #mdb_env_sync()"] 305 | #[doc = "\t\tensures on-disk database integrity until next commit."] 306 | #[doc = "\t\tThis flag may be changed at any time using #mdb_env_set_flags()."] 307 | #[doc = "\t
  • #MDB_NOTLS"] 308 | #[doc = "\t\tDon't use Thread-Local Storage. Tie reader locktable slots to"] 309 | #[doc = "\t\t#MDB_txn objects instead of to threads. I.e. #mdb_txn_reset() keeps"] 310 | #[doc = "\t\tthe slot reseved for the #MDB_txn object. A thread may use parallel"] 311 | #[doc = "\t\tread-only transactions. A read-only transaction may span threads if"] 312 | #[doc = "\t\tthe user synchronizes its use. Applications that multiplex many"] 313 | #[doc = "\t\tuser threads over individual OS threads need this option. Such an"] 314 | #[doc = "\t\tapplication must also serialize the write transactions in an OS"] 315 | #[doc = "\t\tthread, since LMDB's write locking is unaware of the user threads."] 316 | #[doc = "\t
  • #MDB_NOLOCK"] 317 | #[doc = "\t\tDon't do any locking. If concurrent access is anticipated, the"] 318 | #[doc = "\t\tcaller must manage all concurrency itself. For proper operation"] 319 | #[doc = "\t\tthe caller must enforce single-writer semantics, and must ensure"] 320 | #[doc = "\t\tthat no readers are using old transactions while a writer is"] 321 | #[doc = "\t\tactive. The simplest approach is to use an exclusive lock so that"] 322 | #[doc = "\t\tno readers may be active at all when a writer begins."] 323 | #[doc = "\t
  • #MDB_NORDAHEAD"] 324 | #[doc = "\t\tTurn off readahead. Most operating systems perform readahead on"] 325 | #[doc = "\t\tread requests by default. This option turns it off if the OS"] 326 | #[doc = "\t\tsupports it. Turning it off may help random read performance"] 327 | #[doc = "\t\twhen the DB is larger than RAM and system RAM is full."] 328 | #[doc = "\t\tThe option is not implemented on Windows."] 329 | #[doc = "\t
  • #MDB_NOMEMINIT"] 330 | #[doc = "\t\tDon't initialize malloc'd memory before writing to unused spaces"] 331 | #[doc = "\t\tin the data file. By default, memory for pages written to the data"] 332 | #[doc = "\t\tfile is obtained using malloc. While these pages may be reused in"] 333 | #[doc = "\t\tsubsequent transactions, freshly malloc'd pages will be initialized"] 334 | #[doc = "\t\tto zeroes before use. This avoids persisting leftover data from other"] 335 | #[doc = "\t\tcode (that used the heap and subsequently freed the memory) into the"] 336 | #[doc = "\t\tdata file. Note that many other system libraries may allocate"] 337 | #[doc = "\t\tand free memory from the heap for arbitrary uses. E.g., stdio may"] 338 | #[doc = "\t\tuse the heap for file I/O buffers. This initialization step has a"] 339 | #[doc = "\t\tmodest performance cost so some applications may want to disable"] 340 | #[doc = "\t\tit using this flag. This option can be a problem for applications"] 341 | #[doc = "\t\twhich handle sensitive data like passwords, and it makes memory"] 342 | #[doc = "\t\tcheckers like Valgrind noisy. This flag is not needed with #MDB_WRITEMAP,"] 343 | #[doc = "\t\twhich writes directly to the mmap instead of using malloc for pages. The"] 344 | #[doc = "\t\tinitialization is also skipped if #MDB_RESERVE is used; the"] 345 | #[doc = "\t\tcaller is expected to overwrite all of the memory that was"] 346 | #[doc = "\t\treserved in that case."] 347 | #[doc = "\t\tThis flag may be changed at any time using #mdb_env_set_flags()."] 348 | #[doc = "
"] 349 | #[doc = " @param[in] mode The UNIX permissions to set on created files and semaphores."] 350 | #[doc = " This parameter is ignored on Windows."] 351 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 352 | #[doc = " errors are:"] 353 | #[doc = "
    "] 354 | #[doc = "\t
  • #MDB_VERSION_MISMATCH - the version of the LMDB library doesn't match the"] 355 | #[doc = "\tversion that created the database environment."] 356 | #[doc = "\t
  • #MDB_INVALID - the environment file headers are corrupted."] 357 | #[doc = "\t
  • ENOENT - the directory specified by the path parameter doesn't exist."] 358 | #[doc = "\t
  • EACCES - the user didn't have permission to access the environment files."] 359 | #[doc = "\t
  • EAGAIN - the environment was locked by another process."] 360 | #[doc = "
"] 361 | pub fn mdb_env_open( 362 | env: *mut MDB_env, 363 | path: *const ::libc::c_char, 364 | flags: ::libc::c_uint, 365 | mode: mdb_mode_t, 366 | ) -> ::libc::c_int; 367 | } 368 | extern "C" { 369 | #[doc = " @brief Copy an LMDB environment to the specified path."] 370 | #[doc = ""] 371 | #[doc = " This function may be used to make a backup of an existing environment."] 372 | #[doc = " No lockfile is created, since it gets recreated at need."] 373 | #[doc = " @note This call can trigger significant file size growth if run in"] 374 | #[doc = " parallel with write transactions, because it employs a read-only"] 375 | #[doc = " transaction. See long-lived transactions under @ref caveats_sec."] 376 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create(). It"] 377 | #[doc = " must have already been opened successfully."] 378 | #[doc = " @param[in] path The directory in which the copy will reside. This"] 379 | #[doc = " directory must already exist and be writable but must otherwise be"] 380 | #[doc = " empty."] 381 | #[doc = " @return A non-zero error value on failure and 0 on success."] 382 | pub fn mdb_env_copy(env: *mut MDB_env, path: *const ::libc::c_char) -> ::libc::c_int; 383 | } 384 | extern "C" { 385 | #[doc = " @brief Copy an LMDB environment to the specified file descriptor."] 386 | #[doc = ""] 387 | #[doc = " This function may be used to make a backup of an existing environment."] 388 | #[doc = " No lockfile is created, since it gets recreated at need."] 389 | #[doc = " @note This call can trigger significant file size growth if run in"] 390 | #[doc = " parallel with write transactions, because it employs a read-only"] 391 | #[doc = " transaction. See long-lived transactions under @ref caveats_sec."] 392 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create(). It"] 393 | #[doc = " must have already been opened successfully."] 394 | #[doc = " @param[in] fd The filedescriptor to write the copy to. It must"] 395 | #[doc = " have already been opened for Write access."] 396 | #[doc = " @return A non-zero error value on failure and 0 on success."] 397 | pub fn mdb_env_copyfd(env: *mut MDB_env, fd: mdb_filehandle_t) -> ::libc::c_int; 398 | } 399 | extern "C" { 400 | #[doc = " @brief Copy an LMDB environment to the specified path, with options."] 401 | #[doc = ""] 402 | #[doc = " This function may be used to make a backup of an existing environment."] 403 | #[doc = " No lockfile is created, since it gets recreated at need."] 404 | #[doc = " @note This call can trigger significant file size growth if run in"] 405 | #[doc = " parallel with write transactions, because it employs a read-only"] 406 | #[doc = " transaction. See long-lived transactions under @ref caveats_sec."] 407 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create(). It"] 408 | #[doc = " must have already been opened successfully."] 409 | #[doc = " @param[in] path The directory in which the copy will reside. This"] 410 | #[doc = " directory must already exist and be writable but must otherwise be"] 411 | #[doc = " empty."] 412 | #[doc = " @param[in] flags Special options for this operation. This parameter"] 413 | #[doc = " must be set to 0 or by bitwise OR'ing together one or more of the"] 414 | #[doc = " values described here."] 415 | #[doc = "
    "] 416 | #[doc = "\t
  • #MDB_CP_COMPACT - Perform compaction while copying: omit free"] 417 | #[doc = "\t\tpages and sequentially renumber all pages in output. This option"] 418 | #[doc = "\t\tconsumes more CPU and runs more slowly than the default."] 419 | #[doc = "\t\tCurrently it fails if the environment has suffered a page leak."] 420 | #[doc = "
"] 421 | #[doc = " @return A non-zero error value on failure and 0 on success."] 422 | pub fn mdb_env_copy2( 423 | env: *mut MDB_env, 424 | path: *const ::libc::c_char, 425 | flags: ::libc::c_uint, 426 | ) -> ::libc::c_int; 427 | } 428 | extern "C" { 429 | #[doc = " @brief Copy an LMDB environment to the specified file descriptor,"] 430 | #[doc = "\twith options."] 431 | #[doc = ""] 432 | #[doc = " This function may be used to make a backup of an existing environment."] 433 | #[doc = " No lockfile is created, since it gets recreated at need. See"] 434 | #[doc = " #mdb_env_copy2() for further details."] 435 | #[doc = " @note This call can trigger significant file size growth if run in"] 436 | #[doc = " parallel with write transactions, because it employs a read-only"] 437 | #[doc = " transaction. See long-lived transactions under @ref caveats_sec."] 438 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create(). It"] 439 | #[doc = " must have already been opened successfully."] 440 | #[doc = " @param[in] fd The filedescriptor to write the copy to. It must"] 441 | #[doc = " have already been opened for Write access."] 442 | #[doc = " @param[in] flags Special options for this operation."] 443 | #[doc = " See #mdb_env_copy2() for options."] 444 | #[doc = " @return A non-zero error value on failure and 0 on success."] 445 | pub fn mdb_env_copyfd2( 446 | env: *mut MDB_env, 447 | fd: mdb_filehandle_t, 448 | flags: ::libc::c_uint, 449 | ) -> ::libc::c_int; 450 | } 451 | extern "C" { 452 | #[doc = " @brief Return statistics about the LMDB environment."] 453 | #[doc = ""] 454 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 455 | #[doc = " @param[out] stat The address of an #MDB_stat structure"] 456 | #[doc = " \twhere the statistics will be copied"] 457 | pub fn mdb_env_stat(env: *mut MDB_env, stat: *mut MDB_stat) -> ::libc::c_int; 458 | } 459 | extern "C" { 460 | #[doc = " @brief Return information about the LMDB environment."] 461 | #[doc = ""] 462 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 463 | #[doc = " @param[out] stat The address of an #MDB_envinfo structure"] 464 | #[doc = " \twhere the information will be copied"] 465 | pub fn mdb_env_info(env: *mut MDB_env, stat: *mut MDB_envinfo) -> ::libc::c_int; 466 | } 467 | extern "C" { 468 | #[doc = " @brief Flush the data buffers to disk."] 469 | #[doc = ""] 470 | #[doc = " Data is always written to disk when #mdb_txn_commit() is called,"] 471 | #[doc = " but the operating system may keep it buffered. LMDB always flushes"] 472 | #[doc = " the OS buffers upon commit as well, unless the environment was"] 473 | #[doc = " opened with #MDB_NOSYNC or in part #MDB_NOMETASYNC. This call is"] 474 | #[doc = " not valid if the environment was opened with #MDB_RDONLY."] 475 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 476 | #[doc = " @param[in] force If non-zero, force a synchronous flush. Otherwise"] 477 | #[doc = " if the environment has the #MDB_NOSYNC flag set the flushes"] 478 | #[doc = "\twill be omitted, and with #MDB_MAPASYNC they will be asynchronous."] 479 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 480 | #[doc = " errors are:"] 481 | #[doc = "
    "] 482 | #[doc = "\t
  • EACCES - the environment is read-only."] 483 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 484 | #[doc = "\t
  • EIO - an error occurred during synchronization."] 485 | #[doc = "
"] 486 | pub fn mdb_env_sync(env: *mut MDB_env, force: ::libc::c_int) -> ::libc::c_int; 487 | } 488 | extern "C" { 489 | #[doc = " @brief Close the environment and release the memory map."] 490 | #[doc = ""] 491 | #[doc = " Only a single thread may call this function. All transactions, databases,"] 492 | #[doc = " and cursors must already be closed before calling this function. Attempts to"] 493 | #[doc = " use any such handles after calling this function will cause a SIGSEGV."] 494 | #[doc = " The environment handle will be freed and must not be used again after this call."] 495 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 496 | pub fn mdb_env_close(env: *mut MDB_env); 497 | } 498 | extern "C" { 499 | #[doc = " @brief Set environment flags."] 500 | #[doc = ""] 501 | #[doc = " This may be used to set some flags in addition to those from"] 502 | #[doc = " #mdb_env_open(), or to unset these flags. If several threads"] 503 | #[doc = " change the flags at the same time, the result is undefined."] 504 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 505 | #[doc = " @param[in] flags The flags to change, bitwise OR'ed together"] 506 | #[doc = " @param[in] onoff A non-zero value sets the flags, zero clears them."] 507 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 508 | #[doc = " errors are:"] 509 | #[doc = "
    "] 510 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 511 | #[doc = "
"] 512 | pub fn mdb_env_set_flags( 513 | env: *mut MDB_env, 514 | flags: ::libc::c_uint, 515 | onoff: ::libc::c_int, 516 | ) -> ::libc::c_int; 517 | } 518 | extern "C" { 519 | #[doc = " @brief Get environment flags."] 520 | #[doc = ""] 521 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 522 | #[doc = " @param[out] flags The address of an integer to store the flags"] 523 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 524 | #[doc = " errors are:"] 525 | #[doc = "
    "] 526 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 527 | #[doc = "
"] 528 | pub fn mdb_env_get_flags(env: *mut MDB_env, flags: *mut ::libc::c_uint) -> ::libc::c_int; 529 | } 530 | extern "C" { 531 | #[doc = " @brief Return the path that was used in #mdb_env_open()."] 532 | #[doc = ""] 533 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 534 | #[doc = " @param[out] path Address of a string pointer to contain the path. This"] 535 | #[doc = " is the actual string in the environment, not a copy. It should not be"] 536 | #[doc = " altered in any way."] 537 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 538 | #[doc = " errors are:"] 539 | #[doc = "
    "] 540 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 541 | #[doc = "
"] 542 | pub fn mdb_env_get_path(env: *mut MDB_env, path: *mut *const ::libc::c_char) -> ::libc::c_int; 543 | } 544 | extern "C" { 545 | #[doc = " @brief Return the filedescriptor for the given environment."] 546 | #[doc = ""] 547 | #[doc = " This function may be called after fork(), so the descriptor can be"] 548 | #[doc = " closed before exec*(). Other LMDB file descriptors have FD_CLOEXEC."] 549 | #[doc = " (Until LMDB 0.9.18, only the lockfile had that.)"] 550 | #[doc = ""] 551 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 552 | #[doc = " @param[out] fd Address of a mdb_filehandle_t to contain the descriptor."] 553 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 554 | #[doc = " errors are:"] 555 | #[doc = "
    "] 556 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 557 | #[doc = "
"] 558 | pub fn mdb_env_get_fd(env: *mut MDB_env, fd: *mut mdb_filehandle_t) -> ::libc::c_int; 559 | } 560 | extern "C" { 561 | #[doc = " @brief Set the size of the memory map to use for this environment."] 562 | #[doc = ""] 563 | #[doc = " The size should be a multiple of the OS page size. The default is"] 564 | #[doc = " 10485760 bytes. The size of the memory map is also the maximum size"] 565 | #[doc = " of the database. The value should be chosen as large as possible,"] 566 | #[doc = " to accommodate future growth of the database."] 567 | #[doc = " This function should be called after #mdb_env_create() and before #mdb_env_open()."] 568 | #[doc = " It may be called at later times if no transactions are active in"] 569 | #[doc = " this process. Note that the library does not check for this condition,"] 570 | #[doc = " the caller must ensure it explicitly."] 571 | #[doc = ""] 572 | #[doc = " The new size takes effect immediately for the current process but"] 573 | #[doc = " will not be persisted to any others until a write transaction has been"] 574 | #[doc = " committed by the current process. Also, only mapsize increases are"] 575 | #[doc = " persisted into the environment."] 576 | #[doc = ""] 577 | #[doc = " If the mapsize is increased by another process, and data has grown"] 578 | #[doc = " beyond the range of the current mapsize, #mdb_txn_begin() will"] 579 | #[doc = " return #MDB_MAP_RESIZED. This function may be called with a size"] 580 | #[doc = " of zero to adopt the new size."] 581 | #[doc = ""] 582 | #[doc = " Any attempt to set a size smaller than the space already consumed"] 583 | #[doc = " by the environment will be silently changed to the current size of the used space."] 584 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 585 | #[doc = " @param[in] size The size in bytes"] 586 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 587 | #[doc = " errors are:"] 588 | #[doc = "
    "] 589 | #[doc = "\t
  • EINVAL - an invalid parameter was specified, or the environment has"] 590 | #[doc = " \tan active write transaction."] 591 | #[doc = "
"] 592 | pub fn mdb_env_set_mapsize(env: *mut MDB_env, size: usize) -> ::libc::c_int; 593 | } 594 | extern "C" { 595 | #[doc = " @brief Set the maximum number of threads/reader slots for the environment."] 596 | #[doc = ""] 597 | #[doc = " This defines the number of slots in the lock table that is used to track readers in the"] 598 | #[doc = " the environment. The default is 126."] 599 | #[doc = " Starting a read-only transaction normally ties a lock table slot to the"] 600 | #[doc = " current thread until the environment closes or the thread exits. If"] 601 | #[doc = " MDB_NOTLS is in use, #mdb_txn_begin() instead ties the slot to the"] 602 | #[doc = " MDB_txn object until it or the #MDB_env object is destroyed."] 603 | #[doc = " This function may only be called after #mdb_env_create() and before #mdb_env_open()."] 604 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 605 | #[doc = " @param[in] readers The maximum number of reader lock table slots"] 606 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 607 | #[doc = " errors are:"] 608 | #[doc = "
    "] 609 | #[doc = "\t
  • EINVAL - an invalid parameter was specified, or the environment is already open."] 610 | #[doc = "
"] 611 | pub fn mdb_env_set_maxreaders(env: *mut MDB_env, readers: ::libc::c_uint) -> ::libc::c_int; 612 | } 613 | extern "C" { 614 | #[doc = " @brief Get the maximum number of threads/reader slots for the environment."] 615 | #[doc = ""] 616 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 617 | #[doc = " @param[out] readers Address of an integer to store the number of readers"] 618 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 619 | #[doc = " errors are:"] 620 | #[doc = "
    "] 621 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 622 | #[doc = "
"] 623 | pub fn mdb_env_get_maxreaders(env: *mut MDB_env, readers: *mut ::libc::c_uint) 624 | -> ::libc::c_int; 625 | } 626 | extern "C" { 627 | #[doc = " @brief Set the maximum number of named databases for the environment."] 628 | #[doc = ""] 629 | #[doc = " This function is only needed if multiple databases will be used in the"] 630 | #[doc = " environment. Simpler applications that use the environment as a single"] 631 | #[doc = " unnamed database can ignore this option."] 632 | #[doc = " This function may only be called after #mdb_env_create() and before #mdb_env_open()."] 633 | #[doc = ""] 634 | #[doc = " Currently a moderate number of slots are cheap but a huge number gets"] 635 | #[doc = " expensive: 7-120 words per transaction, and every #mdb_dbi_open()"] 636 | #[doc = " does a linear search of the opened slots."] 637 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 638 | #[doc = " @param[in] dbs The maximum number of databases"] 639 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 640 | #[doc = " errors are:"] 641 | #[doc = "
    "] 642 | #[doc = "\t
  • EINVAL - an invalid parameter was specified, or the environment is already open."] 643 | #[doc = "
"] 644 | pub fn mdb_env_set_maxdbs(env: *mut MDB_env, dbs: MDB_dbi) -> ::libc::c_int; 645 | } 646 | extern "C" { 647 | #[doc = " @brief Get the maximum size of keys and #MDB_DUPSORT data we can write."] 648 | #[doc = ""] 649 | #[doc = " Depends on the compile-time constant #MDB_MAXKEYSIZE. Default 511."] 650 | #[doc = " See @ref MDB_val."] 651 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 652 | #[doc = " @return The maximum size of a key we can write"] 653 | pub fn mdb_env_get_maxkeysize(env: *mut MDB_env) -> ::libc::c_int; 654 | } 655 | extern "C" { 656 | #[doc = " @brief Set application information associated with the #MDB_env."] 657 | #[doc = ""] 658 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 659 | #[doc = " @param[in] ctx An arbitrary pointer for whatever the application needs."] 660 | #[doc = " @return A non-zero error value on failure and 0 on success."] 661 | pub fn mdb_env_set_userctx(env: *mut MDB_env, ctx: *mut ::libc::c_void) -> ::libc::c_int; 662 | } 663 | extern "C" { 664 | #[doc = " @brief Get the application information associated with the #MDB_env."] 665 | #[doc = ""] 666 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 667 | #[doc = " @return The pointer set by #mdb_env_set_userctx()."] 668 | pub fn mdb_env_get_userctx(env: *mut MDB_env) -> *mut ::libc::c_void; 669 | } 670 | #[doc = " @brief A callback function for most LMDB assert() failures,"] 671 | #[doc = " called before printing the message and aborting."] 672 | #[doc = ""] 673 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()."] 674 | #[doc = " @param[in] msg The assertion message, not including newline."] 675 | pub type MDB_assert_func = 676 | ::std::option::Option; 677 | extern "C" { 678 | #[doc = " Set or reset the assert() callback of the environment."] 679 | #[doc = " Disabled if liblmdb is buillt with NDEBUG."] 680 | #[doc = " @note This hack should become obsolete as lmdb's error handling matures."] 681 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()."] 682 | #[doc = " @param[in] func An #MDB_assert_func function, or 0."] 683 | #[doc = " @return A non-zero error value on failure and 0 on success."] 684 | pub fn mdb_env_set_assert(env: *mut MDB_env, func: MDB_assert_func) -> ::libc::c_int; 685 | } 686 | extern "C" { 687 | #[doc = " @brief Create a transaction for use with the environment."] 688 | #[doc = ""] 689 | #[doc = " The transaction handle may be discarded using #mdb_txn_abort() or #mdb_txn_commit()."] 690 | #[doc = " @note A transaction and its cursors must only be used by a single"] 691 | #[doc = " thread, and a thread may only have a single transaction at a time."] 692 | #[doc = " If #MDB_NOTLS is in use, this does not apply to read-only transactions."] 693 | #[doc = " @note Cursors may not span transactions."] 694 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 695 | #[doc = " @param[in] parent If this parameter is non-NULL, the new transaction"] 696 | #[doc = " will be a nested transaction, with the transaction indicated by \\b parent"] 697 | #[doc = " as its parent. Transactions may be nested to any level. A parent"] 698 | #[doc = " transaction and its cursors may not issue any other operations than"] 699 | #[doc = " mdb_txn_commit and mdb_txn_abort while it has active child transactions."] 700 | #[doc = " @param[in] flags Special options for this transaction. This parameter"] 701 | #[doc = " must be set to 0 or by bitwise OR'ing together one or more of the"] 702 | #[doc = " values described here."] 703 | #[doc = "
    "] 704 | #[doc = "\t
  • #MDB_RDONLY"] 705 | #[doc = "\t\tThis transaction will not perform any write operations."] 706 | #[doc = "
"] 707 | #[doc = " @param[out] txn Address where the new #MDB_txn handle will be stored"] 708 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 709 | #[doc = " errors are:"] 710 | #[doc = "
    "] 711 | #[doc = "\t
  • #MDB_PANIC - a fatal error occurred earlier and the environment"] 712 | #[doc = "\t\tmust be shut down."] 713 | #[doc = "\t
  • #MDB_MAP_RESIZED - another process wrote data beyond this MDB_env's"] 714 | #[doc = "\t\tmapsize and this environment's map must be resized as well."] 715 | #[doc = "\t\tSee #mdb_env_set_mapsize()."] 716 | #[doc = "\t
  • #MDB_READERS_FULL - a read-only transaction was requested and"] 717 | #[doc = "\t\tthe reader lock table is full. See #mdb_env_set_maxreaders()."] 718 | #[doc = "\t
  • ENOMEM - out of memory."] 719 | #[doc = "
"] 720 | pub fn mdb_txn_begin( 721 | env: *mut MDB_env, 722 | parent: *mut MDB_txn, 723 | flags: ::libc::c_uint, 724 | txn: *mut *mut MDB_txn, 725 | ) -> ::libc::c_int; 726 | } 727 | extern "C" { 728 | #[doc = " @brief Returns the transaction's #MDB_env"] 729 | #[doc = ""] 730 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 731 | pub fn mdb_txn_env(txn: *mut MDB_txn) -> *mut MDB_env; 732 | } 733 | extern "C" { 734 | #[doc = " @brief Return the transaction's ID."] 735 | #[doc = ""] 736 | #[doc = " This returns the identifier associated with this transaction. For a"] 737 | #[doc = " read-only transaction, this corresponds to the snapshot being read;"] 738 | #[doc = " concurrent readers will frequently have the same transaction ID."] 739 | #[doc = ""] 740 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 741 | #[doc = " @return A transaction ID, valid if input is an active transaction."] 742 | pub fn mdb_txn_id(txn: *mut MDB_txn) -> usize; 743 | } 744 | extern "C" { 745 | #[doc = " @brief Commit all the operations of a transaction into the database."] 746 | #[doc = ""] 747 | #[doc = " The transaction handle is freed. It and its cursors must not be used"] 748 | #[doc = " again after this call, except with #mdb_cursor_renew()."] 749 | #[doc = " @note Earlier documentation incorrectly said all cursors would be freed."] 750 | #[doc = " Only write-transactions free cursors."] 751 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 752 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 753 | #[doc = " errors are:"] 754 | #[doc = "
    "] 755 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 756 | #[doc = "\t
  • ENOSPC - no more disk space."] 757 | #[doc = "\t
  • EIO - a low-level I/O error occurred while writing."] 758 | #[doc = "\t
  • ENOMEM - out of memory."] 759 | #[doc = "
"] 760 | pub fn mdb_txn_commit(txn: *mut MDB_txn) -> ::libc::c_int; 761 | } 762 | extern "C" { 763 | #[doc = " @brief Abandon all the operations of the transaction instead of saving them."] 764 | #[doc = ""] 765 | #[doc = " The transaction handle is freed. It and its cursors must not be used"] 766 | #[doc = " again after this call, except with #mdb_cursor_renew()."] 767 | #[doc = " @note Earlier documentation incorrectly said all cursors would be freed."] 768 | #[doc = " Only write-transactions free cursors."] 769 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 770 | pub fn mdb_txn_abort(txn: *mut MDB_txn); 771 | } 772 | extern "C" { 773 | #[doc = " @brief Reset a read-only transaction."] 774 | #[doc = ""] 775 | #[doc = " Abort the transaction like #mdb_txn_abort(), but keep the transaction"] 776 | #[doc = " handle. #mdb_txn_renew() may reuse the handle. This saves allocation"] 777 | #[doc = " overhead if the process will start a new read-only transaction soon,"] 778 | #[doc = " and also locking overhead if #MDB_NOTLS is in use. The reader table"] 779 | #[doc = " lock is released, but the table slot stays tied to its thread or"] 780 | #[doc = " #MDB_txn. Use mdb_txn_abort() to discard a reset handle, and to free"] 781 | #[doc = " its lock table slot if MDB_NOTLS is in use."] 782 | #[doc = " Cursors opened within the transaction must not be used"] 783 | #[doc = " again after this call, except with #mdb_cursor_renew()."] 784 | #[doc = " Reader locks generally don't interfere with writers, but they keep old"] 785 | #[doc = " versions of database pages allocated. Thus they prevent the old pages"] 786 | #[doc = " from being reused when writers commit new data, and so under heavy load"] 787 | #[doc = " the database size may grow much more rapidly than otherwise."] 788 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 789 | pub fn mdb_txn_reset(txn: *mut MDB_txn); 790 | } 791 | extern "C" { 792 | #[doc = " @brief Renew a read-only transaction."] 793 | #[doc = ""] 794 | #[doc = " This acquires a new reader lock for a transaction handle that had been"] 795 | #[doc = " released by #mdb_txn_reset(). It must be called before a reset transaction"] 796 | #[doc = " may be used again."] 797 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 798 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 799 | #[doc = " errors are:"] 800 | #[doc = "
    "] 801 | #[doc = "\t
  • #MDB_PANIC - a fatal error occurred earlier and the environment"] 802 | #[doc = "\t\tmust be shut down."] 803 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 804 | #[doc = "
"] 805 | pub fn mdb_txn_renew(txn: *mut MDB_txn) -> ::libc::c_int; 806 | } 807 | extern "C" { 808 | #[doc = " @brief Open a database in the environment."] 809 | #[doc = ""] 810 | #[doc = " A database handle denotes the name and parameters of a database,"] 811 | #[doc = " independently of whether such a database exists."] 812 | #[doc = " The database handle may be discarded by calling #mdb_dbi_close()."] 813 | #[doc = " The old database handle is returned if the database was already open."] 814 | #[doc = " The handle may only be closed once."] 815 | #[doc = ""] 816 | #[doc = " The database handle will be private to the current transaction until"] 817 | #[doc = " the transaction is successfully committed. If the transaction is"] 818 | #[doc = " aborted the handle will be closed automatically."] 819 | #[doc = " After a successful commit the handle will reside in the shared"] 820 | #[doc = " environment, and may be used by other transactions."] 821 | #[doc = ""] 822 | #[doc = " This function must not be called from multiple concurrent"] 823 | #[doc = " transactions in the same process. A transaction that uses"] 824 | #[doc = " this function must finish (either commit or abort) before"] 825 | #[doc = " any other transaction in the process may use this function."] 826 | #[doc = ""] 827 | #[doc = " To use named databases (with name != NULL), #mdb_env_set_maxdbs()"] 828 | #[doc = " must be called before opening the environment. Database names are"] 829 | #[doc = " keys in the unnamed database, and may be read but not written."] 830 | #[doc = ""] 831 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 832 | #[doc = " @param[in] name The name of the database to open. If only a single"] 833 | #[doc = " \tdatabase is needed in the environment, this value may be NULL."] 834 | #[doc = " @param[in] flags Special options for this database. This parameter"] 835 | #[doc = " must be set to 0 or by bitwise OR'ing together one or more of the"] 836 | #[doc = " values described here."] 837 | #[doc = "
    "] 838 | #[doc = "\t
  • #MDB_REVERSEKEY"] 839 | #[doc = "\t\tKeys are strings to be compared in reverse order, from the end"] 840 | #[doc = "\t\tof the strings to the beginning. By default, Keys are treated as strings and"] 841 | #[doc = "\t\tcompared from beginning to end."] 842 | #[doc = "\t
  • #MDB_DUPSORT"] 843 | #[doc = "\t\tDuplicate keys may be used in the database. (Or, from another perspective,"] 844 | #[doc = "\t\tkeys may have multiple data items, stored in sorted order.) By default"] 845 | #[doc = "\t\tkeys must be unique and may have only a single data item."] 846 | #[doc = "\t
  • #MDB_INTEGERKEY"] 847 | #[doc = "\t\tKeys are binary integers in native byte order, either unsigned int"] 848 | #[doc = "\t\tor size_t, and will be sorted as such."] 849 | #[doc = "\t\tThe keys must all be of the same size."] 850 | #[doc = "\t
  • #MDB_DUPFIXED"] 851 | #[doc = "\t\tThis flag may only be used in combination with #MDB_DUPSORT. This option"] 852 | #[doc = "\t\ttells the library that the data items for this database are all the same"] 853 | #[doc = "\t\tsize, which allows further optimizations in storage and retrieval. When"] 854 | #[doc = "\t\tall data items are the same size, the #MDB_GET_MULTIPLE, #MDB_NEXT_MULTIPLE"] 855 | #[doc = "\t\tand #MDB_PREV_MULTIPLE cursor operations may be used to retrieve multiple"] 856 | #[doc = "\t\titems at once."] 857 | #[doc = "\t
  • #MDB_INTEGERDUP"] 858 | #[doc = "\t\tThis option specifies that duplicate data items are binary integers,"] 859 | #[doc = "\t\tsimilar to #MDB_INTEGERKEY keys."] 860 | #[doc = "\t
  • #MDB_REVERSEDUP"] 861 | #[doc = "\t\tThis option specifies that duplicate data items should be compared as"] 862 | #[doc = "\t\tstrings in reverse order."] 863 | #[doc = "\t
  • #MDB_CREATE"] 864 | #[doc = "\t\tCreate the named database if it doesn't exist. This option is not"] 865 | #[doc = "\t\tallowed in a read-only transaction or a read-only environment."] 866 | #[doc = "
"] 867 | #[doc = " @param[out] dbi Address where the new #MDB_dbi handle will be stored"] 868 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 869 | #[doc = " errors are:"] 870 | #[doc = "
    "] 871 | #[doc = "\t
  • #MDB_NOTFOUND - the specified database doesn't exist in the environment"] 872 | #[doc = "\t\tand #MDB_CREATE was not specified."] 873 | #[doc = "\t
  • #MDB_DBS_FULL - too many databases have been opened. See #mdb_env_set_maxdbs()."] 874 | #[doc = "
"] 875 | pub fn mdb_dbi_open( 876 | txn: *mut MDB_txn, 877 | name: *const ::libc::c_char, 878 | flags: ::libc::c_uint, 879 | dbi: *mut MDB_dbi, 880 | ) -> ::libc::c_int; 881 | } 882 | extern "C" { 883 | #[doc = " @brief Retrieve statistics for a database."] 884 | #[doc = ""] 885 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 886 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 887 | #[doc = " @param[out] stat The address of an #MDB_stat structure"] 888 | #[doc = " \twhere the statistics will be copied"] 889 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 890 | #[doc = " errors are:"] 891 | #[doc = "
    "] 892 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 893 | #[doc = "
"] 894 | pub fn mdb_stat(txn: *mut MDB_txn, dbi: MDB_dbi, stat: *mut MDB_stat) -> ::libc::c_int; 895 | } 896 | extern "C" { 897 | #[doc = " @brief Retrieve the DB flags for a database handle."] 898 | #[doc = ""] 899 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 900 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 901 | #[doc = " @param[out] flags Address where the flags will be returned."] 902 | #[doc = " @return A non-zero error value on failure and 0 on success."] 903 | pub fn mdb_dbi_flags( 904 | txn: *mut MDB_txn, 905 | dbi: MDB_dbi, 906 | flags: *mut ::libc::c_uint, 907 | ) -> ::libc::c_int; 908 | } 909 | extern "C" { 910 | #[doc = " @brief Close a database handle. Normally unnecessary. Use with care:"] 911 | #[doc = ""] 912 | #[doc = " This call is not mutex protected. Handles should only be closed by"] 913 | #[doc = " a single thread, and only if no other threads are going to reference"] 914 | #[doc = " the database handle or one of its cursors any further. Do not close"] 915 | #[doc = " a handle if an existing transaction has modified its database."] 916 | #[doc = " Doing so can cause misbehavior from database corruption to errors"] 917 | #[doc = " like MDB_BAD_VALSIZE (since the DB name is gone)."] 918 | #[doc = ""] 919 | #[doc = " Closing a database handle is not necessary, but lets #mdb_dbi_open()"] 920 | #[doc = " reuse the handle value. Usually it's better to set a bigger"] 921 | #[doc = " #mdb_env_set_maxdbs(), unless that value would be large."] 922 | #[doc = ""] 923 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 924 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 925 | pub fn mdb_dbi_close(env: *mut MDB_env, dbi: MDB_dbi); 926 | } 927 | extern "C" { 928 | #[doc = " @brief Empty or delete+close a database."] 929 | #[doc = ""] 930 | #[doc = " See #mdb_dbi_close() for restrictions about closing the DB handle."] 931 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 932 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 933 | #[doc = " @param[in] del 0 to empty the DB, 1 to delete it from the"] 934 | #[doc = " environment and close the DB handle."] 935 | #[doc = " @return A non-zero error value on failure and 0 on success."] 936 | pub fn mdb_drop(txn: *mut MDB_txn, dbi: MDB_dbi, del: ::libc::c_int) -> ::libc::c_int; 937 | } 938 | extern "C" { 939 | #[doc = " @brief Set a custom key comparison function for a database."] 940 | #[doc = ""] 941 | #[doc = " The comparison function is called whenever it is necessary to compare a"] 942 | #[doc = " key specified by the application with a key currently stored in the database."] 943 | #[doc = " If no comparison function is specified, and no special key flags were specified"] 944 | #[doc = " with #mdb_dbi_open(), the keys are compared lexically, with shorter keys collating"] 945 | #[doc = " before longer keys."] 946 | #[doc = " @warning This function must be called before any data access functions are used,"] 947 | #[doc = " otherwise data corruption may occur. The same comparison function must be used by every"] 948 | #[doc = " program accessing the database, every time the database is used."] 949 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 950 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 951 | #[doc = " @param[in] cmp A #MDB_cmp_func function"] 952 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 953 | #[doc = " errors are:"] 954 | #[doc = "
    "] 955 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 956 | #[doc = "
"] 957 | pub fn mdb_set_compare(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: MDB_cmp_func) -> ::libc::c_int; 958 | } 959 | extern "C" { 960 | #[doc = " @brief Set a custom data comparison function for a #MDB_DUPSORT database."] 961 | #[doc = ""] 962 | #[doc = " This comparison function is called whenever it is necessary to compare a data"] 963 | #[doc = " item specified by the application with a data item currently stored in the database."] 964 | #[doc = " This function only takes effect if the database was opened with the #MDB_DUPSORT"] 965 | #[doc = " flag."] 966 | #[doc = " If no comparison function is specified, and no special key flags were specified"] 967 | #[doc = " with #mdb_dbi_open(), the data items are compared lexically, with shorter items collating"] 968 | #[doc = " before longer items."] 969 | #[doc = " @warning This function must be called before any data access functions are used,"] 970 | #[doc = " otherwise data corruption may occur. The same comparison function must be used by every"] 971 | #[doc = " program accessing the database, every time the database is used."] 972 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 973 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 974 | #[doc = " @param[in] cmp A #MDB_cmp_func function"] 975 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 976 | #[doc = " errors are:"] 977 | #[doc = "
    "] 978 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 979 | #[doc = "
"] 980 | pub fn mdb_set_dupsort(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: MDB_cmp_func) -> ::libc::c_int; 981 | } 982 | extern "C" { 983 | #[doc = " @brief Set a relocation function for a #MDB_FIXEDMAP database."] 984 | #[doc = ""] 985 | #[doc = " @todo The relocation function is called whenever it is necessary to move the data"] 986 | #[doc = " of an item to a different position in the database (e.g. through tree"] 987 | #[doc = " balancing operations, shifts as a result of adds or deletes, etc.). It is"] 988 | #[doc = " intended to allow address/position-dependent data items to be stored in"] 989 | #[doc = " a database in an environment opened with the #MDB_FIXEDMAP option."] 990 | #[doc = " Currently the relocation feature is unimplemented and setting"] 991 | #[doc = " this function has no effect."] 992 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 993 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 994 | #[doc = " @param[in] rel A #MDB_rel_func function"] 995 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 996 | #[doc = " errors are:"] 997 | #[doc = "
    "] 998 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 999 | #[doc = "
"] 1000 | pub fn mdb_set_relfunc(txn: *mut MDB_txn, dbi: MDB_dbi, rel: MDB_rel_func) -> ::libc::c_int; 1001 | } 1002 | extern "C" { 1003 | #[doc = " @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function."] 1004 | #[doc = ""] 1005 | #[doc = " See #mdb_set_relfunc and #MDB_rel_func for more details."] 1006 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1007 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1008 | #[doc = " @param[in] ctx An arbitrary pointer for whatever the application needs."] 1009 | #[doc = " It will be passed to the callback function set by #mdb_set_relfunc"] 1010 | #[doc = " as its \\b relctx parameter whenever the callback is invoked."] 1011 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1012 | #[doc = " errors are:"] 1013 | #[doc = "
    "] 1014 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1015 | #[doc = "
"] 1016 | pub fn mdb_set_relctx( 1017 | txn: *mut MDB_txn, 1018 | dbi: MDB_dbi, 1019 | ctx: *mut ::libc::c_void, 1020 | ) -> ::libc::c_int; 1021 | } 1022 | extern "C" { 1023 | #[doc = " @brief Get items from a database."] 1024 | #[doc = ""] 1025 | #[doc = " This function retrieves key/data pairs from the database. The address"] 1026 | #[doc = " and length of the data associated with the specified \\b key are returned"] 1027 | #[doc = " in the structure to which \\b data refers."] 1028 | #[doc = " If the database supports duplicate keys (#MDB_DUPSORT) then the"] 1029 | #[doc = " first data item for the key will be returned. Retrieval of other"] 1030 | #[doc = " items requires the use of #mdb_cursor_get()."] 1031 | #[doc = ""] 1032 | #[doc = " @note The memory pointed to by the returned values is owned by the"] 1033 | #[doc = " database. The caller need not dispose of the memory, and may not"] 1034 | #[doc = " modify it in any way. For values returned in a read-only transaction"] 1035 | #[doc = " any modification attempts will cause a SIGSEGV."] 1036 | #[doc = " @note Values returned from the database are valid only until a"] 1037 | #[doc = " subsequent update operation, or the end of the transaction."] 1038 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1039 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1040 | #[doc = " @param[in] key The key to search for in the database"] 1041 | #[doc = " @param[out] data The data corresponding to the key"] 1042 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1043 | #[doc = " errors are:"] 1044 | #[doc = "
    "] 1045 | #[doc = "\t
  • #MDB_NOTFOUND - the key was not in the database."] 1046 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1047 | #[doc = "
"] 1048 | pub fn mdb_get( 1049 | txn: *mut MDB_txn, 1050 | dbi: MDB_dbi, 1051 | key: *mut MDB_val, 1052 | data: *mut MDB_val, 1053 | ) -> ::libc::c_int; 1054 | } 1055 | extern "C" { 1056 | #[doc = " @brief Store items into a database."] 1057 | #[doc = ""] 1058 | #[doc = " This function stores key/data pairs in the database. The default behavior"] 1059 | #[doc = " is to enter the new key/data pair, replacing any previously existing key"] 1060 | #[doc = " if duplicates are disallowed, or adding a duplicate data item if"] 1061 | #[doc = " duplicates are allowed (#MDB_DUPSORT)."] 1062 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1063 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1064 | #[doc = " @param[in] key The key to store in the database"] 1065 | #[doc = " @param[in,out] data The data to store"] 1066 | #[doc = " @param[in] flags Special options for this operation. This parameter"] 1067 | #[doc = " must be set to 0 or by bitwise OR'ing together one or more of the"] 1068 | #[doc = " values described here."] 1069 | #[doc = "
    "] 1070 | #[doc = "\t
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not"] 1071 | #[doc = "\t\talready appear in the database. This flag may only be specified"] 1072 | #[doc = "\t\tif the database was opened with #MDB_DUPSORT. The function will"] 1073 | #[doc = "\t\treturn #MDB_KEYEXIST if the key/data pair already appears in the"] 1074 | #[doc = "\t\tdatabase."] 1075 | #[doc = "\t
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key"] 1076 | #[doc = "\t\tdoes not already appear in the database. The function will return"] 1077 | #[doc = "\t\t#MDB_KEYEXIST if the key already appears in the database, even if"] 1078 | #[doc = "\t\tthe database supports duplicates (#MDB_DUPSORT). The \\b data"] 1079 | #[doc = "\t\tparameter will be set to point to the existing item."] 1080 | #[doc = "\t
  • #MDB_RESERVE - reserve space for data of the given size, but"] 1081 | #[doc = "\t\tdon't copy the given data. Instead, return a pointer to the"] 1082 | #[doc = "\t\treserved space, which the caller can fill in later - before"] 1083 | #[doc = "\t\tthe next update operation or the transaction ends. This saves"] 1084 | #[doc = "\t\tan extra memcpy if the data is being generated later."] 1085 | #[doc = "\t\tLMDB does nothing else with this memory, the caller is expected"] 1086 | #[doc = "\t\tto modify all of the space requested. This flag must not be"] 1087 | #[doc = "\t\tspecified if the database was opened with #MDB_DUPSORT."] 1088 | #[doc = "\t
  • #MDB_APPEND - append the given key/data pair to the end of the"] 1089 | #[doc = "\t\tdatabase. This option allows fast bulk loading when keys are"] 1090 | #[doc = "\t\talready known to be in the correct order. Loading unsorted keys"] 1091 | #[doc = "\t\twith this flag will cause a #MDB_KEYEXIST error."] 1092 | #[doc = "\t
  • #MDB_APPENDDUP - as above, but for sorted dup data."] 1093 | #[doc = "
"] 1094 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1095 | #[doc = " errors are:"] 1096 | #[doc = "
    "] 1097 | #[doc = "\t
  • #MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize()."] 1098 | #[doc = "\t
  • #MDB_TXN_FULL - the transaction has too many dirty pages."] 1099 | #[doc = "\t
  • EACCES - an attempt was made to write in a read-only transaction."] 1100 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1101 | #[doc = "
"] 1102 | pub fn mdb_put( 1103 | txn: *mut MDB_txn, 1104 | dbi: MDB_dbi, 1105 | key: *mut MDB_val, 1106 | data: *mut MDB_val, 1107 | flags: ::libc::c_uint, 1108 | ) -> ::libc::c_int; 1109 | } 1110 | extern "C" { 1111 | #[doc = " @brief Delete items from a database."] 1112 | #[doc = ""] 1113 | #[doc = " This function removes key/data pairs from the database."] 1114 | #[doc = " If the database does not support sorted duplicate data items"] 1115 | #[doc = " (#MDB_DUPSORT) the data parameter is ignored."] 1116 | #[doc = " If the database supports sorted duplicates and the data parameter"] 1117 | #[doc = " is NULL, all of the duplicate data items for the key will be"] 1118 | #[doc = " deleted. Otherwise, if the data parameter is non-NULL"] 1119 | #[doc = " only the matching data item will be deleted."] 1120 | #[doc = " This function will return #MDB_NOTFOUND if the specified key/data"] 1121 | #[doc = " pair is not in the database."] 1122 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1123 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1124 | #[doc = " @param[in] key The key to delete from the database"] 1125 | #[doc = " @param[in] data The data to delete"] 1126 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1127 | #[doc = " errors are:"] 1128 | #[doc = "
    "] 1129 | #[doc = "\t
  • EACCES - an attempt was made to write in a read-only transaction."] 1130 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1131 | #[doc = "
"] 1132 | pub fn mdb_del( 1133 | txn: *mut MDB_txn, 1134 | dbi: MDB_dbi, 1135 | key: *mut MDB_val, 1136 | data: *mut MDB_val, 1137 | ) -> ::libc::c_int; 1138 | } 1139 | extern "C" { 1140 | #[doc = " @brief Create a cursor handle."] 1141 | #[doc = ""] 1142 | #[doc = " A cursor is associated with a specific transaction and database."] 1143 | #[doc = " A cursor cannot be used when its database handle is closed. Nor"] 1144 | #[doc = " when its transaction has ended, except with #mdb_cursor_renew()."] 1145 | #[doc = " It can be discarded with #mdb_cursor_close()."] 1146 | #[doc = " A cursor in a write-transaction can be closed before its transaction"] 1147 | #[doc = " ends, and will otherwise be closed when its transaction ends."] 1148 | #[doc = " A cursor in a read-only transaction must be closed explicitly, before"] 1149 | #[doc = " or after its transaction ends. It can be reused with"] 1150 | #[doc = " #mdb_cursor_renew() before finally closing it."] 1151 | #[doc = " @note Earlier documentation said that cursors in every transaction"] 1152 | #[doc = " were closed when the transaction committed or aborted."] 1153 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1154 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1155 | #[doc = " @param[out] cursor Address where the new #MDB_cursor handle will be stored"] 1156 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1157 | #[doc = " errors are:"] 1158 | #[doc = "
    "] 1159 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1160 | #[doc = "
"] 1161 | pub fn mdb_cursor_open( 1162 | txn: *mut MDB_txn, 1163 | dbi: MDB_dbi, 1164 | cursor: *mut *mut MDB_cursor, 1165 | ) -> ::libc::c_int; 1166 | } 1167 | extern "C" { 1168 | #[doc = " @brief Close a cursor handle."] 1169 | #[doc = ""] 1170 | #[doc = " The cursor handle will be freed and must not be used again after this call."] 1171 | #[doc = " Its transaction must still be live if it is a write-transaction."] 1172 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1173 | pub fn mdb_cursor_close(cursor: *mut MDB_cursor); 1174 | } 1175 | extern "C" { 1176 | #[doc = " @brief Renew a cursor handle."] 1177 | #[doc = ""] 1178 | #[doc = " A cursor is associated with a specific transaction and database."] 1179 | #[doc = " Cursors that are only used in read-only"] 1180 | #[doc = " transactions may be re-used, to avoid unnecessary malloc/free overhead."] 1181 | #[doc = " The cursor may be associated with a new read-only transaction, and"] 1182 | #[doc = " referencing the same database handle as it was created with."] 1183 | #[doc = " This may be done whether the previous transaction is live or dead."] 1184 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1185 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1186 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1187 | #[doc = " errors are:"] 1188 | #[doc = "
    "] 1189 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1190 | #[doc = "
"] 1191 | pub fn mdb_cursor_renew(txn: *mut MDB_txn, cursor: *mut MDB_cursor) -> ::libc::c_int; 1192 | } 1193 | extern "C" { 1194 | #[doc = " @brief Return the cursor's transaction handle."] 1195 | #[doc = ""] 1196 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1197 | pub fn mdb_cursor_txn(cursor: *mut MDB_cursor) -> *mut MDB_txn; 1198 | } 1199 | extern "C" { 1200 | #[doc = " @brief Return the cursor's database handle."] 1201 | #[doc = ""] 1202 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1203 | pub fn mdb_cursor_dbi(cursor: *mut MDB_cursor) -> MDB_dbi; 1204 | } 1205 | extern "C" { 1206 | #[doc = " @brief Retrieve by cursor."] 1207 | #[doc = ""] 1208 | #[doc = " This function retrieves key/data pairs from the database. The address and length"] 1209 | #[doc = " of the key are returned in the object to which \\b key refers (except for the"] 1210 | #[doc = " case of the #MDB_SET option, in which the \\b key object is unchanged), and"] 1211 | #[doc = " the address and length of the data are returned in the object to which \\b data"] 1212 | #[doc = " refers."] 1213 | #[doc = " See #mdb_get() for restrictions on using the output values."] 1214 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1215 | #[doc = " @param[in,out] key The key for a retrieved item"] 1216 | #[doc = " @param[in,out] data The data of a retrieved item"] 1217 | #[doc = " @param[in] op A cursor operation #MDB_cursor_op"] 1218 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1219 | #[doc = " errors are:"] 1220 | #[doc = "
    "] 1221 | #[doc = "\t
  • #MDB_NOTFOUND - no matching key found."] 1222 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1223 | #[doc = "
"] 1224 | pub fn mdb_cursor_get( 1225 | cursor: *mut MDB_cursor, 1226 | key: *mut MDB_val, 1227 | data: *mut MDB_val, 1228 | op: MDB_cursor_op, 1229 | ) -> ::libc::c_int; 1230 | } 1231 | extern "C" { 1232 | #[doc = " @brief Store by cursor."] 1233 | #[doc = ""] 1234 | #[doc = " This function stores key/data pairs into the database."] 1235 | #[doc = " The cursor is positioned at the new item, or on failure usually near it."] 1236 | #[doc = " @note Earlier documentation incorrectly said errors would leave the"] 1237 | #[doc = " state of the cursor unchanged."] 1238 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1239 | #[doc = " @param[in] key The key operated on."] 1240 | #[doc = " @param[in] data The data operated on."] 1241 | #[doc = " @param[in] flags Options for this operation. This parameter"] 1242 | #[doc = " must be set to 0 or one of the values described here."] 1243 | #[doc = "
    "] 1244 | #[doc = "\t
  • #MDB_CURRENT - replace the item at the current cursor position."] 1245 | #[doc = "\t\tThe \\b key parameter must still be provided, and must match it."] 1246 | #[doc = "\t\tIf using sorted duplicates (#MDB_DUPSORT) the data item must still"] 1247 | #[doc = "\t\tsort into the same place. This is intended to be used when the"] 1248 | #[doc = "\t\tnew data is the same size as the old. Otherwise it will simply"] 1249 | #[doc = "\t\tperform a delete of the old record followed by an insert."] 1250 | #[doc = "\t
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not"] 1251 | #[doc = "\t\talready appear in the database. This flag may only be specified"] 1252 | #[doc = "\t\tif the database was opened with #MDB_DUPSORT. The function will"] 1253 | #[doc = "\t\treturn #MDB_KEYEXIST if the key/data pair already appears in the"] 1254 | #[doc = "\t\tdatabase."] 1255 | #[doc = "\t
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key"] 1256 | #[doc = "\t\tdoes not already appear in the database. The function will return"] 1257 | #[doc = "\t\t#MDB_KEYEXIST if the key already appears in the database, even if"] 1258 | #[doc = "\t\tthe database supports duplicates (#MDB_DUPSORT)."] 1259 | #[doc = "\t
  • #MDB_RESERVE - reserve space for data of the given size, but"] 1260 | #[doc = "\t\tdon't copy the given data. Instead, return a pointer to the"] 1261 | #[doc = "\t\treserved space, which the caller can fill in later - before"] 1262 | #[doc = "\t\tthe next update operation or the transaction ends. This saves"] 1263 | #[doc = "\t\tan extra memcpy if the data is being generated later. This flag"] 1264 | #[doc = "\t\tmust not be specified if the database was opened with #MDB_DUPSORT."] 1265 | #[doc = "\t
  • #MDB_APPEND - append the given key/data pair to the end of the"] 1266 | #[doc = "\t\tdatabase. No key comparisons are performed. This option allows"] 1267 | #[doc = "\t\tfast bulk loading when keys are already known to be in the"] 1268 | #[doc = "\t\tcorrect order. Loading unsorted keys with this flag will cause"] 1269 | #[doc = "\t\ta #MDB_KEYEXIST error."] 1270 | #[doc = "\t
  • #MDB_APPENDDUP - as above, but for sorted dup data."] 1271 | #[doc = "\t
  • #MDB_MULTIPLE - store multiple contiguous data elements in a"] 1272 | #[doc = "\t\tsingle request. This flag may only be specified if the database"] 1273 | #[doc = "\t\twas opened with #MDB_DUPFIXED. The \\b data argument must be an"] 1274 | #[doc = "\t\tarray of two MDB_vals. The mv_size of the first MDB_val must be"] 1275 | #[doc = "\t\tthe size of a single data element. The mv_data of the first MDB_val"] 1276 | #[doc = "\t\tmust point to the beginning of the array of contiguous data elements."] 1277 | #[doc = "\t\tThe mv_size of the second MDB_val must be the count of the number"] 1278 | #[doc = "\t\tof data elements to store. On return this field will be set to"] 1279 | #[doc = "\t\tthe count of the number of elements actually written. The mv_data"] 1280 | #[doc = "\t\tof the second MDB_val is unused."] 1281 | #[doc = "
"] 1282 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1283 | #[doc = " errors are:"] 1284 | #[doc = "
    "] 1285 | #[doc = "\t
  • #MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize()."] 1286 | #[doc = "\t
  • #MDB_TXN_FULL - the transaction has too many dirty pages."] 1287 | #[doc = "\t
  • EACCES - an attempt was made to write in a read-only transaction."] 1288 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1289 | #[doc = "
"] 1290 | pub fn mdb_cursor_put( 1291 | cursor: *mut MDB_cursor, 1292 | key: *mut MDB_val, 1293 | data: *mut MDB_val, 1294 | flags: ::libc::c_uint, 1295 | ) -> ::libc::c_int; 1296 | } 1297 | extern "C" { 1298 | #[doc = " @brief Delete current key/data pair"] 1299 | #[doc = ""] 1300 | #[doc = " This function deletes the key/data pair to which the cursor refers."] 1301 | #[doc = " This does not invalidate the cursor, so operations such as MDB_NEXT"] 1302 | #[doc = " can still be used on it."] 1303 | #[doc = " Both MDB_NEXT and MDB_GET_CURRENT will return the same record after"] 1304 | #[doc = " this operation."] 1305 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1306 | #[doc = " @param[in] flags Options for this operation. This parameter"] 1307 | #[doc = " must be set to 0 or one of the values described here."] 1308 | #[doc = "
    "] 1309 | #[doc = "\t
  • #MDB_NODUPDATA - delete all of the data items for the current key."] 1310 | #[doc = "\t\tThis flag may only be specified if the database was opened with #MDB_DUPSORT."] 1311 | #[doc = "
"] 1312 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1313 | #[doc = " errors are:"] 1314 | #[doc = "
    "] 1315 | #[doc = "\t
  • EACCES - an attempt was made to write in a read-only transaction."] 1316 | #[doc = "\t
  • EINVAL - an invalid parameter was specified."] 1317 | #[doc = "
"] 1318 | pub fn mdb_cursor_del(cursor: *mut MDB_cursor, flags: ::libc::c_uint) -> ::libc::c_int; 1319 | } 1320 | extern "C" { 1321 | #[doc = " @brief Return count of duplicates for current key."] 1322 | #[doc = ""] 1323 | #[doc = " This call is only valid on databases that support sorted duplicate"] 1324 | #[doc = " data items #MDB_DUPSORT."] 1325 | #[doc = " @param[in] cursor A cursor handle returned by #mdb_cursor_open()"] 1326 | #[doc = " @param[out] countp Address where the count will be stored"] 1327 | #[doc = " @return A non-zero error value on failure and 0 on success. Some possible"] 1328 | #[doc = " errors are:"] 1329 | #[doc = "
    "] 1330 | #[doc = "\t
  • EINVAL - cursor is not initialized, or an invalid parameter was specified."] 1331 | #[doc = "
"] 1332 | pub fn mdb_cursor_count(cursor: *mut MDB_cursor, countp: *mut usize) -> ::libc::c_int; 1333 | } 1334 | extern "C" { 1335 | #[doc = " @brief Compare two data items according to a particular database."] 1336 | #[doc = ""] 1337 | #[doc = " This returns a comparison as if the two data items were keys in the"] 1338 | #[doc = " specified database."] 1339 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1340 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1341 | #[doc = " @param[in] a The first item to compare"] 1342 | #[doc = " @param[in] b The second item to compare"] 1343 | #[doc = " @return < 0 if a < b, 0 if a == b, > 0 if a > b"] 1344 | pub fn mdb_cmp( 1345 | txn: *mut MDB_txn, 1346 | dbi: MDB_dbi, 1347 | a: *const MDB_val, 1348 | b: *const MDB_val, 1349 | ) -> ::libc::c_int; 1350 | } 1351 | extern "C" { 1352 | #[doc = " @brief Compare two data items according to a particular database."] 1353 | #[doc = ""] 1354 | #[doc = " This returns a comparison as if the two items were data items of"] 1355 | #[doc = " the specified database. The database must have the #MDB_DUPSORT flag."] 1356 | #[doc = " @param[in] txn A transaction handle returned by #mdb_txn_begin()"] 1357 | #[doc = " @param[in] dbi A database handle returned by #mdb_dbi_open()"] 1358 | #[doc = " @param[in] a The first item to compare"] 1359 | #[doc = " @param[in] b The second item to compare"] 1360 | #[doc = " @return < 0 if a < b, 0 if a == b, > 0 if a > b"] 1361 | pub fn mdb_dcmp( 1362 | txn: *mut MDB_txn, 1363 | dbi: MDB_dbi, 1364 | a: *const MDB_val, 1365 | b: *const MDB_val, 1366 | ) -> ::libc::c_int; 1367 | } 1368 | #[doc = " @brief A callback function used to print a message from the library."] 1369 | #[doc = ""] 1370 | #[doc = " @param[in] msg The string to be printed."] 1371 | #[doc = " @param[in] ctx An arbitrary context pointer for the callback."] 1372 | #[doc = " @return < 0 on failure, >= 0 on success."] 1373 | pub type MDB_msg_func = ::std::option::Option< 1374 | unsafe extern "C" fn(msg: *const ::libc::c_char, ctx: *mut ::libc::c_void) -> ::libc::c_int, 1375 | >; 1376 | extern "C" { 1377 | #[doc = " @brief Dump the entries in the reader lock table."] 1378 | #[doc = ""] 1379 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 1380 | #[doc = " @param[in] func A #MDB_msg_func function"] 1381 | #[doc = " @param[in] ctx Anything the message function needs"] 1382 | #[doc = " @return < 0 on failure, >= 0 on success."] 1383 | pub fn mdb_reader_list( 1384 | env: *mut MDB_env, 1385 | func: MDB_msg_func, 1386 | ctx: *mut ::libc::c_void, 1387 | ) -> ::libc::c_int; 1388 | } 1389 | extern "C" { 1390 | #[doc = " @brief Check for stale entries in the reader lock table."] 1391 | #[doc = ""] 1392 | #[doc = " @param[in] env An environment handle returned by #mdb_env_create()"] 1393 | #[doc = " @param[out] dead Number of stale slots that were cleared"] 1394 | #[doc = " @return 0 on success, non-zero on failure."] 1395 | pub fn mdb_reader_check(env: *mut MDB_env, dead: *mut ::libc::c_int) -> ::libc::c_int; 1396 | } 1397 | -------------------------------------------------------------------------------- /lmdb-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(clippy::all)] 4 | #![doc(html_root_url = "https://docs.rs/lmdb-rkv-sys/0.11.2")] 5 | 6 | extern crate libc; 7 | 8 | #[cfg(unix)] 9 | #[allow(non_camel_case_types)] 10 | pub type mdb_mode_t = ::libc::mode_t; 11 | #[cfg(windows)] 12 | #[allow(non_camel_case_types)] 13 | pub type mdb_mode_t = ::libc::c_int; 14 | 15 | #[cfg(unix)] 16 | #[allow(non_camel_case_types)] 17 | pub type mdb_filehandle_t = ::libc::c_int; 18 | #[cfg(windows)] 19 | #[allow(non_camel_case_types)] 20 | pub type mdb_filehandle_t = *mut ::libc::c_void; 21 | 22 | include!("bindings.rs"); 23 | -------------------------------------------------------------------------------- /lmdb-sys/tests/fixtures/testdb-32/data.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/lmdb-rs/946167603dd6806f3733e18f01a89cee21888468/lmdb-sys/tests/fixtures/testdb-32/data.mdb -------------------------------------------------------------------------------- /lmdb-sys/tests/fixtures/testdb-32/lock.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/lmdb-rs/946167603dd6806f3733e18f01a89cee21888468/lmdb-sys/tests/fixtures/testdb-32/lock.mdb -------------------------------------------------------------------------------- /lmdb-sys/tests/fixtures/testdb/data.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/lmdb-rs/946167603dd6806f3733e18f01a89cee21888468/lmdb-sys/tests/fixtures/testdb/data.mdb -------------------------------------------------------------------------------- /lmdb-sys/tests/fixtures/testdb/lock.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/lmdb-rs/946167603dd6806f3733e18f01a89cee21888468/lmdb-sys/tests/fixtures/testdb/lock.mdb -------------------------------------------------------------------------------- /lmdb-sys/tests/lmdb.rs: -------------------------------------------------------------------------------- 1 | extern crate lmdb_sys; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | use std::process::Command; 6 | 7 | #[test] 8 | fn test_lmdb() { 9 | let mut lmdb = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); 10 | lmdb.push("lmdb"); 11 | lmdb.push("libraries"); 12 | lmdb.push("liblmdb"); 13 | 14 | let make_cmd = Command::new("make") 15 | .current_dir(&lmdb) 16 | .status() 17 | .expect("failed to execute process"); 18 | 19 | assert_eq!(make_cmd.success(), true); 20 | 21 | let make_test_cmd = Command::new("make") 22 | .arg("test") 23 | .current_dir(&lmdb) 24 | .status() 25 | .expect("failed to execute process"); 26 | 27 | assert_eq!(make_test_cmd.success(), true); 28 | } 29 | -------------------------------------------------------------------------------- /lmdb-sys/tests/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate lmdb_sys; 2 | 3 | use lmdb_sys::*; 4 | 5 | use std::ffi::{c_void, CString}; 6 | use std::fs::File; 7 | use std::ptr; 8 | 9 | // https://github.com/victorporof/lmdb/blob/mdb.master/libraries/liblmdb/moz-test.c 10 | 11 | macro_rules! E { 12 | ($expr:expr) => {{ 13 | match $expr { 14 | ::MDB_SUCCESS => (), 15 | err_code => assert!(false, "Failed with code {}", err_code), 16 | } 17 | }}; 18 | } 19 | 20 | macro_rules! str { 21 | ($expr:expr) => { 22 | ::CString::new($expr).unwrap().as_ptr() 23 | }; 24 | } 25 | 26 | #[test] 27 | #[cfg(target_pointer_width = "32")] 28 | fn test_simple_32() { 29 | test_simple("./tests/fixtures/testdb-32") 30 | } 31 | 32 | #[test] 33 | #[cfg(target_pointer_width = "64")] 34 | fn test_simple_64() { 35 | test_simple("./tests/fixtures/testdb") 36 | } 37 | 38 | #[cfg(windows)] 39 | fn get_file_fd(file: &File) -> std::os::windows::io::RawHandle { 40 | use std::os::windows::io::AsRawHandle; 41 | file.as_raw_handle() 42 | } 43 | 44 | #[cfg(unix)] 45 | fn get_file_fd(file: &File) -> std::os::unix::io::RawFd { 46 | use std::os::unix::io::AsRawFd; 47 | file.as_raw_fd() 48 | } 49 | 50 | fn test_simple(env_path: &str) { 51 | let mut env: *mut MDB_env = ptr::null_mut(); 52 | let mut dbi: MDB_dbi = 0; 53 | let mut key = MDB_val { 54 | mv_size: 0, 55 | mv_data: ptr::null_mut(), 56 | }; 57 | let mut data = MDB_val { 58 | mv_size: 0, 59 | mv_data: ptr::null_mut(), 60 | }; 61 | let mut txn: *mut MDB_txn = ptr::null_mut(); 62 | let sval = str!("foo") as *mut c_void; 63 | let dval = str!("bar") as *mut c_void; 64 | 65 | unsafe { 66 | E!(mdb_env_create(&mut env)); 67 | E!(mdb_env_set_maxdbs(env, 2)); 68 | E!(mdb_env_open(env, str!(env_path), 0, 0664)); 69 | 70 | E!(mdb_txn_begin(env, ptr::null_mut(), 0, &mut txn)); 71 | E!(mdb_dbi_open(txn, str!("subdb"), MDB_CREATE, &mut dbi)); 72 | E!(mdb_txn_commit(txn)); 73 | 74 | key.mv_size = 3; 75 | key.mv_data = sval; 76 | data.mv_size = 3; 77 | data.mv_data = dval; 78 | 79 | E!(mdb_txn_begin(env, ptr::null_mut(), 0, &mut txn)); 80 | E!(mdb_put(txn, dbi, &mut key, &mut data, 0)); 81 | E!(mdb_txn_commit(txn)); 82 | } 83 | 84 | let file = File::create("./tests/fixtures/copytestdb.mdb").unwrap(); 85 | 86 | unsafe { 87 | let fd = get_file_fd(&file); 88 | E!(mdb_env_copyfd(env, fd)); 89 | 90 | mdb_dbi_close(env, dbi); 91 | mdb_env_close(env); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/cursor.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::{ 3 | fmt, 4 | mem, 5 | ptr, 6 | result, 7 | slice, 8 | }; 9 | 10 | use libc::{ 11 | c_uint, 12 | c_void, 13 | size_t, 14 | EINVAL, 15 | }; 16 | 17 | use database::Database; 18 | use error::{ 19 | lmdb_result, 20 | Error, 21 | Result, 22 | }; 23 | use ffi; 24 | use flags::WriteFlags; 25 | use transaction::Transaction; 26 | 27 | /// An LMDB cursor. 28 | pub trait Cursor<'txn> { 29 | /// Returns a raw pointer to the underlying LMDB cursor. 30 | /// 31 | /// The caller **must** ensure that the pointer is not used after the 32 | /// lifetime of the cursor. 33 | fn cursor(&self) -> *mut ffi::MDB_cursor; 34 | 35 | /// Retrieves a key/data pair from the cursor. Depending on the cursor op, 36 | /// the current key may be returned. 37 | fn get(&self, key: Option<&[u8]>, data: Option<&[u8]>, op: c_uint) -> Result<(Option<&'txn [u8]>, &'txn [u8])> { 38 | unsafe { 39 | let mut key_val = slice_to_val(key); 40 | let mut data_val = slice_to_val(data); 41 | let key_ptr = key_val.mv_data; 42 | lmdb_result(ffi::mdb_cursor_get(self.cursor(), &mut key_val, &mut data_val, op))?; 43 | let key_out = if key_ptr != key_val.mv_data { 44 | Some(val_to_slice(key_val)) 45 | } else { 46 | None 47 | }; 48 | let data_out = val_to_slice(data_val); 49 | Ok((key_out, data_out)) 50 | } 51 | } 52 | 53 | /// Iterate over database items. The iterator will begin with item next 54 | /// after the cursor, and continue until the end of the database. For new 55 | /// cursors, the iterator will begin with the first item in the database. 56 | /// 57 | /// For databases with duplicate data items (`DatabaseFlags::DUP_SORT`), the 58 | /// duplicate data items of each key will be returned before moving on to 59 | /// the next key. 60 | fn iter(&mut self) -> Iter<'txn> { 61 | Iter::new(self.cursor(), ffi::MDB_NEXT, ffi::MDB_NEXT) 62 | } 63 | 64 | /// Iterate over database items starting from the beginning of the database. 65 | /// 66 | /// For databases with duplicate data items (`DatabaseFlags::DUP_SORT`), the 67 | /// duplicate data items of each key will be returned before moving on to 68 | /// the next key. 69 | fn iter_start(&mut self) -> Iter<'txn> { 70 | Iter::new(self.cursor(), ffi::MDB_FIRST, ffi::MDB_NEXT) 71 | } 72 | 73 | /// Iterate over database items starting from the given key. 74 | /// 75 | /// For databases with duplicate data items (`DatabaseFlags::DUP_SORT`), the 76 | /// duplicate data items of each key will be returned before moving on to 77 | /// the next key. 78 | fn iter_from(&mut self, key: K) -> Iter<'txn> 79 | where 80 | K: AsRef<[u8]>, 81 | { 82 | match self.get(Some(key.as_ref()), None, ffi::MDB_SET_RANGE) { 83 | Ok(_) | Err(Error::NotFound) => (), 84 | Err(error) => return Iter::Err(error), 85 | }; 86 | Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT) 87 | } 88 | 89 | /// Iterate over duplicate database items. The iterator will begin with the 90 | /// item next after the cursor, and continue until the end of the database. 91 | /// Each item will be returned as an iterator of its duplicates. 92 | fn iter_dup(&mut self) -> IterDup<'txn> { 93 | IterDup::new(self.cursor(), ffi::MDB_NEXT) 94 | } 95 | 96 | /// Iterate over duplicate database items starting from the beginning of the 97 | /// database. Each item will be returned as an iterator of its duplicates. 98 | fn iter_dup_start(&mut self) -> IterDup<'txn> { 99 | IterDup::new(self.cursor(), ffi::MDB_FIRST) 100 | } 101 | 102 | /// Iterate over duplicate items in the database starting from the given 103 | /// key. Each item will be returned as an iterator of its duplicates. 104 | fn iter_dup_from(&mut self, key: K) -> IterDup<'txn> 105 | where 106 | K: AsRef<[u8]>, 107 | { 108 | match self.get(Some(key.as_ref()), None, ffi::MDB_SET_RANGE) { 109 | Ok(_) | Err(Error::NotFound) => (), 110 | Err(error) => return IterDup::Err(error), 111 | }; 112 | IterDup::new(self.cursor(), ffi::MDB_GET_CURRENT) 113 | } 114 | 115 | /// Iterate over the duplicates of the item in the database with the given key. 116 | fn iter_dup_of(&mut self, key: K) -> Iter<'txn> 117 | where 118 | K: AsRef<[u8]>, 119 | { 120 | match self.get(Some(key.as_ref()), None, ffi::MDB_SET) { 121 | Ok(_) => (), 122 | Err(Error::NotFound) => { 123 | self.get(None, None, ffi::MDB_LAST).ok(); 124 | return Iter::new(self.cursor(), ffi::MDB_NEXT, ffi::MDB_NEXT); 125 | }, 126 | Err(error) => return Iter::Err(error), 127 | }; 128 | Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT_DUP) 129 | } 130 | } 131 | 132 | /// A read-only cursor for navigating the items within a database. 133 | pub struct RoCursor<'txn> { 134 | cursor: *mut ffi::MDB_cursor, 135 | _marker: PhantomData &'txn ()>, 136 | } 137 | 138 | impl<'txn> Cursor<'txn> for RoCursor<'txn> { 139 | fn cursor(&self) -> *mut ffi::MDB_cursor { 140 | self.cursor 141 | } 142 | } 143 | 144 | impl<'txn> fmt::Debug for RoCursor<'txn> { 145 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 146 | f.debug_struct("RoCursor").finish() 147 | } 148 | } 149 | 150 | impl<'txn> Drop for RoCursor<'txn> { 151 | fn drop(&mut self) { 152 | unsafe { ffi::mdb_cursor_close(self.cursor) } 153 | } 154 | } 155 | 156 | impl<'txn> RoCursor<'txn> { 157 | /// Creates a new read-only cursor in the given database and transaction. 158 | /// Prefer using `Transaction::open_cursor`. 159 | pub(crate) fn new(txn: &'txn T, db: Database) -> Result> 160 | where 161 | T: Transaction, 162 | { 163 | let mut cursor: *mut ffi::MDB_cursor = ptr::null_mut(); 164 | unsafe { 165 | lmdb_result(ffi::mdb_cursor_open(txn.txn(), db.dbi(), &mut cursor))?; 166 | } 167 | Ok(RoCursor { 168 | cursor, 169 | _marker: PhantomData, 170 | }) 171 | } 172 | } 173 | 174 | /// A read-write cursor for navigating items within a database. 175 | pub struct RwCursor<'txn> { 176 | cursor: *mut ffi::MDB_cursor, 177 | _marker: PhantomData &'txn ()>, 178 | } 179 | 180 | impl<'txn> Cursor<'txn> for RwCursor<'txn> { 181 | fn cursor(&self) -> *mut ffi::MDB_cursor { 182 | self.cursor 183 | } 184 | } 185 | 186 | impl<'txn> fmt::Debug for RwCursor<'txn> { 187 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 188 | f.debug_struct("RwCursor").finish() 189 | } 190 | } 191 | 192 | impl<'txn> Drop for RwCursor<'txn> { 193 | fn drop(&mut self) { 194 | unsafe { ffi::mdb_cursor_close(self.cursor) } 195 | } 196 | } 197 | 198 | impl<'txn> RwCursor<'txn> { 199 | /// Creates a new read-only cursor in the given database and transaction. 200 | /// Prefer using `RwTransaction::open_rw_cursor`. 201 | pub(crate) fn new(txn: &'txn T, db: Database) -> Result> 202 | where 203 | T: Transaction, 204 | { 205 | let mut cursor: *mut ffi::MDB_cursor = ptr::null_mut(); 206 | unsafe { 207 | lmdb_result(ffi::mdb_cursor_open(txn.txn(), db.dbi(), &mut cursor))?; 208 | } 209 | Ok(RwCursor { 210 | cursor, 211 | _marker: PhantomData, 212 | }) 213 | } 214 | 215 | /// Puts a key/data pair into the database. The cursor will be positioned at 216 | /// the new data item, or on failure usually near it. 217 | pub fn put(&mut self, key: &K, data: &D, flags: WriteFlags) -> Result<()> 218 | where 219 | K: AsRef<[u8]>, 220 | D: AsRef<[u8]>, 221 | { 222 | let key = key.as_ref(); 223 | let data = data.as_ref(); 224 | let mut key_val: ffi::MDB_val = ffi::MDB_val { 225 | mv_size: key.len() as size_t, 226 | mv_data: key.as_ptr() as *mut c_void, 227 | }; 228 | let mut data_val: ffi::MDB_val = ffi::MDB_val { 229 | mv_size: data.len() as size_t, 230 | mv_data: data.as_ptr() as *mut c_void, 231 | }; 232 | unsafe { lmdb_result(ffi::mdb_cursor_put(self.cursor(), &mut key_val, &mut data_val, flags.bits())) } 233 | } 234 | 235 | /// Deletes the current key/data pair. 236 | /// 237 | /// ### Flags 238 | /// 239 | /// `WriteFlags::NO_DUP_DATA` may be used to delete all data items for the 240 | /// current key, if the database was opened with `DatabaseFlags::DUP_SORT`. 241 | pub fn del(&mut self, flags: WriteFlags) -> Result<()> { 242 | unsafe { lmdb_result(ffi::mdb_cursor_del(self.cursor(), flags.bits())) } 243 | } 244 | } 245 | 246 | unsafe fn slice_to_val(slice: Option<&[u8]>) -> ffi::MDB_val { 247 | match slice { 248 | Some(slice) => ffi::MDB_val { 249 | mv_size: slice.len() as size_t, 250 | mv_data: slice.as_ptr() as *mut c_void, 251 | }, 252 | None => ffi::MDB_val { 253 | mv_size: 0, 254 | mv_data: ptr::null_mut(), 255 | }, 256 | } 257 | } 258 | 259 | unsafe fn val_to_slice<'a>(val: ffi::MDB_val) -> &'a [u8] { 260 | slice::from_raw_parts(val.mv_data as *const u8, val.mv_size as usize) 261 | } 262 | 263 | /// An iterator over the key/value pairs in an LMDB database. 264 | pub enum Iter<'txn> { 265 | /// An iterator that returns an error on every call to Iter.next(). 266 | /// Cursor.iter*() creates an Iter of this type when LMDB returns an error 267 | /// on retrieval of a cursor. Using this variant instead of returning 268 | /// an error makes Cursor.iter()* methods infallible, so consumers only 269 | /// need to check the result of Iter.next(). 270 | Err(Error), 271 | 272 | /// An iterator that returns an Item on calls to Iter.next(). 273 | /// The Item is a Result<(&'txn [u8], &'txn [u8])>, so this variant 274 | /// might still return an error, if retrieval of the key/value pair 275 | /// fails for some reason. 276 | Ok { 277 | /// The LMDB cursor with which to iterate. 278 | cursor: *mut ffi::MDB_cursor, 279 | 280 | /// The first operation to perform when the consumer calls Iter.next(). 281 | op: c_uint, 282 | 283 | /// The next and subsequent operations to perform. 284 | next_op: c_uint, 285 | 286 | /// A marker to ensure the iterator doesn't outlive the transaction. 287 | _marker: PhantomData, 288 | }, 289 | } 290 | 291 | impl<'txn> Iter<'txn> { 292 | /// Creates a new iterator backed by the given cursor. 293 | fn new<'t>(cursor: *mut ffi::MDB_cursor, op: c_uint, next_op: c_uint) -> Iter<'t> { 294 | Iter::Ok { 295 | cursor, 296 | op, 297 | next_op, 298 | _marker: PhantomData, 299 | } 300 | } 301 | } 302 | 303 | impl<'txn> fmt::Debug for Iter<'txn> { 304 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 305 | f.debug_struct("Iter").finish() 306 | } 307 | } 308 | 309 | impl<'txn> Iterator for Iter<'txn> { 310 | type Item = Result<(&'txn [u8], &'txn [u8])>; 311 | 312 | fn next(&mut self) -> Option> { 313 | match self { 314 | &mut Iter::Ok { 315 | cursor, 316 | ref mut op, 317 | next_op, 318 | _marker, 319 | } => { 320 | let mut key = ffi::MDB_val { 321 | mv_size: 0, 322 | mv_data: ptr::null_mut(), 323 | }; 324 | let mut data = ffi::MDB_val { 325 | mv_size: 0, 326 | mv_data: ptr::null_mut(), 327 | }; 328 | let op = mem::replace(op, next_op); 329 | unsafe { 330 | match ffi::mdb_cursor_get(cursor, &mut key, &mut data, op) { 331 | ffi::MDB_SUCCESS => Some(Ok((val_to_slice(key), val_to_slice(data)))), 332 | // EINVAL can occur when the cursor was previously seeked to a non-existent value, 333 | // e.g. iter_from with a key greater than all values in the database. 334 | ffi::MDB_NOTFOUND | EINVAL => None, 335 | error => Some(Err(Error::from_err_code(error))), 336 | } 337 | } 338 | }, 339 | &mut Iter::Err(err) => Some(Err(err)), 340 | } 341 | } 342 | } 343 | 344 | /// An iterator over the keys and duplicate values in an LMDB database. 345 | /// 346 | /// The yielded items of the iterator are themselves iterators over the duplicate values for a 347 | /// specific key. 348 | pub enum IterDup<'txn> { 349 | /// An iterator that returns an error on every call to Iter.next(). 350 | /// Cursor.iter*() creates an Iter of this type when LMDB returns an error 351 | /// on retrieval of a cursor. Using this variant instead of returning 352 | /// an error makes Cursor.iter()* methods infallible, so consumers only 353 | /// need to check the result of Iter.next(). 354 | Err(Error), 355 | 356 | /// An iterator that returns an Item on calls to Iter.next(). 357 | /// The Item is a Result<(&'txn [u8], &'txn [u8])>, so this variant 358 | /// might still return an error, if retrieval of the key/value pair 359 | /// fails for some reason. 360 | Ok { 361 | /// The LMDB cursor with which to iterate. 362 | cursor: *mut ffi::MDB_cursor, 363 | 364 | /// The first operation to perform when the consumer calls Iter.next(). 365 | op: c_uint, 366 | 367 | /// A marker to ensure the iterator doesn't outlive the transaction. 368 | _marker: PhantomData, 369 | }, 370 | } 371 | 372 | impl<'txn> IterDup<'txn> { 373 | /// Creates a new iterator backed by the given cursor. 374 | fn new<'t>(cursor: *mut ffi::MDB_cursor, op: c_uint) -> IterDup<'t> { 375 | IterDup::Ok { 376 | cursor, 377 | op, 378 | _marker: PhantomData, 379 | } 380 | } 381 | } 382 | 383 | impl<'txn> fmt::Debug for IterDup<'txn> { 384 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 385 | f.debug_struct("IterDup").finish() 386 | } 387 | } 388 | 389 | impl<'txn> Iterator for IterDup<'txn> { 390 | type Item = Iter<'txn>; 391 | 392 | fn next(&mut self) -> Option> { 393 | match self { 394 | &mut IterDup::Ok { 395 | cursor, 396 | ref mut op, 397 | _marker, 398 | } => { 399 | let mut key = ffi::MDB_val { 400 | mv_size: 0, 401 | mv_data: ptr::null_mut(), 402 | }; 403 | let mut data = ffi::MDB_val { 404 | mv_size: 0, 405 | mv_data: ptr::null_mut(), 406 | }; 407 | let op = mem::replace(op, ffi::MDB_NEXT_NODUP); 408 | let err_code = unsafe { ffi::mdb_cursor_get(cursor, &mut key, &mut data, op) }; 409 | 410 | if err_code == ffi::MDB_SUCCESS { 411 | Some(Iter::new(cursor, ffi::MDB_GET_CURRENT, ffi::MDB_NEXT_DUP)) 412 | } else { 413 | None 414 | } 415 | }, 416 | &mut IterDup::Err(err) => Some(Iter::Err(err)), 417 | } 418 | } 419 | } 420 | 421 | #[cfg(test)] 422 | mod test { 423 | use tempdir::TempDir; 424 | 425 | use super::*; 426 | use environment::*; 427 | use ffi::*; 428 | use flags::*; 429 | 430 | #[test] 431 | fn test_get() { 432 | let dir = TempDir::new("test").unwrap(); 433 | let env = Environment::new().open(dir.path()).unwrap(); 434 | let db = env.open_db(None).unwrap(); 435 | 436 | let mut txn = env.begin_rw_txn().unwrap(); 437 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 438 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 439 | txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); 440 | 441 | let cursor = txn.open_ro_cursor(db).unwrap(); 442 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_FIRST).unwrap()); 443 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_GET_CURRENT).unwrap()); 444 | assert_eq!((Some(&b"key2"[..]), &b"val2"[..]), cursor.get(None, None, MDB_NEXT).unwrap()); 445 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_PREV).unwrap()); 446 | assert_eq!((Some(&b"key3"[..]), &b"val3"[..]), cursor.get(None, None, MDB_LAST).unwrap()); 447 | assert_eq!((None, &b"val2"[..]), cursor.get(Some(b"key2"), None, MDB_SET).unwrap()); 448 | assert_eq!((Some(&b"key3"[..]), &b"val3"[..]), cursor.get(Some(&b"key3"[..]), None, MDB_SET_KEY).unwrap()); 449 | assert_eq!((Some(&b"key3"[..]), &b"val3"[..]), cursor.get(Some(&b"key2\0"[..]), None, MDB_SET_RANGE).unwrap()); 450 | } 451 | 452 | #[test] 453 | fn test_get_dup() { 454 | let dir = TempDir::new("test").unwrap(); 455 | let env = Environment::new().open(dir.path()).unwrap(); 456 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 457 | 458 | let mut txn = env.begin_rw_txn().unwrap(); 459 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 460 | txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); 461 | txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); 462 | txn.put(db, b"key2", b"val1", WriteFlags::empty()).unwrap(); 463 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 464 | txn.put(db, b"key2", b"val3", WriteFlags::empty()).unwrap(); 465 | 466 | let cursor = txn.open_ro_cursor(db).unwrap(); 467 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_FIRST).unwrap()); 468 | assert_eq!((None, &b"val1"[..]), cursor.get(None, None, MDB_FIRST_DUP).unwrap()); 469 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_GET_CURRENT).unwrap()); 470 | assert_eq!((Some(&b"key2"[..]), &b"val1"[..]), cursor.get(None, None, MDB_NEXT_NODUP).unwrap()); 471 | assert_eq!((Some(&b"key2"[..]), &b"val2"[..]), cursor.get(None, None, MDB_NEXT_DUP).unwrap()); 472 | assert_eq!((Some(&b"key2"[..]), &b"val3"[..]), cursor.get(None, None, MDB_NEXT_DUP).unwrap()); 473 | assert!(cursor.get(None, None, MDB_NEXT_DUP).is_err()); 474 | assert_eq!((Some(&b"key2"[..]), &b"val2"[..]), cursor.get(None, None, MDB_PREV_DUP).unwrap()); 475 | assert_eq!((None, &b"val3"[..]), cursor.get(None, None, MDB_LAST_DUP).unwrap()); 476 | assert_eq!((Some(&b"key1"[..]), &b"val3"[..]), cursor.get(None, None, MDB_PREV_NODUP).unwrap()); 477 | assert_eq!((None, &b"val1"[..]), cursor.get(Some(&b"key1"[..]), None, MDB_SET).unwrap()); 478 | assert_eq!((Some(&b"key2"[..]), &b"val1"[..]), cursor.get(Some(&b"key2"[..]), None, MDB_SET_KEY).unwrap()); 479 | assert_eq!((Some(&b"key2"[..]), &b"val1"[..]), cursor.get(Some(&b"key1\0"[..]), None, MDB_SET_RANGE).unwrap()); 480 | assert_eq!((None, &b"val3"[..]), cursor.get(Some(&b"key1"[..]), Some(&b"val3"[..]), MDB_GET_BOTH).unwrap()); 481 | assert_eq!( 482 | (None, &b"val1"[..]), 483 | cursor.get(Some(&b"key2"[..]), Some(&b"val"[..]), MDB_GET_BOTH_RANGE).unwrap() 484 | ); 485 | } 486 | 487 | #[test] 488 | fn test_get_dupfixed() { 489 | let dir = TempDir::new("test").unwrap(); 490 | let env = Environment::new().open(dir.path()).unwrap(); 491 | let db = env.create_db(None, DatabaseFlags::DUP_SORT | DatabaseFlags::DUP_FIXED).unwrap(); 492 | 493 | let mut txn = env.begin_rw_txn().unwrap(); 494 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 495 | txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); 496 | txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); 497 | txn.put(db, b"key2", b"val4", WriteFlags::empty()).unwrap(); 498 | txn.put(db, b"key2", b"val5", WriteFlags::empty()).unwrap(); 499 | txn.put(db, b"key2", b"val6", WriteFlags::empty()).unwrap(); 500 | 501 | let cursor = txn.open_ro_cursor(db).unwrap(); 502 | assert_eq!((Some(&b"key1"[..]), &b"val1"[..]), cursor.get(None, None, MDB_FIRST).unwrap()); 503 | assert_eq!((None, &b"val1val2val3"[..]), cursor.get(None, None, MDB_GET_MULTIPLE).unwrap()); 504 | assert!(cursor.get(None, None, MDB_NEXT_MULTIPLE).is_err()); 505 | } 506 | 507 | #[test] 508 | fn test_iter() { 509 | let dir = TempDir::new("test").unwrap(); 510 | let env = Environment::new().open(dir.path()).unwrap(); 511 | let db = env.open_db(None).unwrap(); 512 | 513 | let items: Vec<(&[u8], &[u8])> = 514 | vec![(b"key1", b"val1"), (b"key2", b"val2"), (b"key3", b"val3"), (b"key5", b"val5")]; 515 | 516 | { 517 | let mut txn = env.begin_rw_txn().unwrap(); 518 | for &(ref key, ref data) in &items { 519 | txn.put(db, key, data, WriteFlags::empty()).unwrap(); 520 | } 521 | txn.commit().unwrap(); 522 | } 523 | 524 | let txn = env.begin_ro_txn().unwrap(); 525 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 526 | 527 | // Because Result implements FromIterator, we can collect the iterator 528 | // of items of type Result<_, E> into a Result> by specifying 529 | // the collection type via the turbofish syntax. 530 | assert_eq!(items, cursor.iter().collect::>>().unwrap()); 531 | 532 | // Alternately, we can collect it into an appropriately typed variable. 533 | let retr: Result> = cursor.iter_start().collect(); 534 | assert_eq!(items, retr.unwrap()); 535 | 536 | cursor.get(Some(b"key2"), None, MDB_SET).unwrap(); 537 | assert_eq!( 538 | items.clone().into_iter().skip(2).collect::>(), 539 | cursor.iter().collect::>>().unwrap() 540 | ); 541 | 542 | assert_eq!(items, cursor.iter_start().collect::>>().unwrap()); 543 | 544 | assert_eq!( 545 | items.clone().into_iter().skip(1).collect::>(), 546 | cursor.iter_from(b"key2").collect::>>().unwrap() 547 | ); 548 | 549 | assert_eq!( 550 | items.clone().into_iter().skip(3).collect::>(), 551 | cursor.iter_from(b"key4").collect::>>().unwrap() 552 | ); 553 | 554 | assert_eq!( 555 | vec!().into_iter().collect::>(), 556 | cursor.iter_from(b"key6").collect::>>().unwrap() 557 | ); 558 | } 559 | 560 | #[test] 561 | fn test_iter_empty_database() { 562 | let dir = TempDir::new("test").unwrap(); 563 | let env = Environment::new().open(dir.path()).unwrap(); 564 | let db = env.open_db(None).unwrap(); 565 | let txn = env.begin_ro_txn().unwrap(); 566 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 567 | 568 | assert_eq!(0, cursor.iter().count()); 569 | assert_eq!(0, cursor.iter_start().count()); 570 | assert_eq!(0, cursor.iter_from(b"foo").count()); 571 | } 572 | 573 | #[test] 574 | fn test_iter_empty_dup_database() { 575 | let dir = TempDir::new("test").unwrap(); 576 | let env = Environment::new().open(dir.path()).unwrap(); 577 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 578 | let txn = env.begin_ro_txn().unwrap(); 579 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 580 | 581 | assert_eq!(0, cursor.iter().count()); 582 | assert_eq!(0, cursor.iter_start().count()); 583 | assert_eq!(0, cursor.iter_from(b"foo").count()); 584 | assert_eq!(0, cursor.iter_dup().count()); 585 | assert_eq!(0, cursor.iter_dup_start().count()); 586 | assert_eq!(0, cursor.iter_dup_from(b"foo").count()); 587 | assert_eq!(0, cursor.iter_dup_of(b"foo").count()); 588 | } 589 | 590 | #[test] 591 | fn test_iter_dup() { 592 | let dir = TempDir::new("test").unwrap(); 593 | let env = Environment::new().open(dir.path()).unwrap(); 594 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 595 | 596 | let items: Vec<(&[u8], &[u8])> = vec![ 597 | (b"a", b"1"), 598 | (b"a", b"2"), 599 | (b"a", b"3"), 600 | (b"b", b"1"), 601 | (b"b", b"2"), 602 | (b"b", b"3"), 603 | (b"c", b"1"), 604 | (b"c", b"2"), 605 | (b"c", b"3"), 606 | (b"e", b"1"), 607 | (b"e", b"2"), 608 | (b"e", b"3"), 609 | ]; 610 | 611 | { 612 | let mut txn = env.begin_rw_txn().unwrap(); 613 | for &(ref key, ref data) in &items { 614 | txn.put(db, key, data, WriteFlags::empty()).unwrap(); 615 | } 616 | txn.commit().unwrap(); 617 | } 618 | 619 | let txn = env.begin_ro_txn().unwrap(); 620 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 621 | assert_eq!(items, cursor.iter_dup().flatten().collect::>>().unwrap()); 622 | 623 | cursor.get(Some(b"b"), None, MDB_SET).unwrap(); 624 | assert_eq!( 625 | items.clone().into_iter().skip(4).collect::>(), 626 | cursor.iter_dup().flatten().collect::>>().unwrap() 627 | ); 628 | 629 | assert_eq!(items, cursor.iter_dup_start().flatten().collect::>>().unwrap()); 630 | 631 | assert_eq!( 632 | items.clone().into_iter().skip(3).collect::>(), 633 | cursor.iter_dup_from(b"b").flatten().collect::>>().unwrap() 634 | ); 635 | 636 | assert_eq!( 637 | items.clone().into_iter().skip(3).collect::>(), 638 | cursor.iter_dup_from(b"ab").flatten().collect::>>().unwrap() 639 | ); 640 | 641 | assert_eq!( 642 | items.clone().into_iter().skip(9).collect::>(), 643 | cursor.iter_dup_from(b"d").flatten().collect::>>().unwrap() 644 | ); 645 | 646 | assert_eq!( 647 | vec!().into_iter().collect::>(), 648 | cursor.iter_dup_from(b"f").flatten().collect::>>().unwrap() 649 | ); 650 | 651 | assert_eq!( 652 | items.clone().into_iter().skip(3).take(3).collect::>(), 653 | cursor.iter_dup_of(b"b").collect::>>().unwrap() 654 | ); 655 | 656 | assert_eq!(0, cursor.iter_dup_of(b"foo").count()); 657 | } 658 | 659 | #[test] 660 | fn test_iter_del_get() { 661 | let dir = TempDir::new("test").unwrap(); 662 | let env = Environment::new().open(dir.path()).unwrap(); 663 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 664 | 665 | let items: Vec<(&[u8], &[u8])> = vec![(b"a", b"1"), (b"b", b"2")]; 666 | let r: Vec<(&[u8], &[u8])> = Vec::new(); 667 | { 668 | let txn = env.begin_ro_txn().unwrap(); 669 | let mut cursor = txn.open_ro_cursor(db).unwrap(); 670 | assert_eq!(r, cursor.iter_dup_of(b"a").collect::>>().unwrap()); 671 | } 672 | 673 | { 674 | let mut txn = env.begin_rw_txn().unwrap(); 675 | for &(ref key, ref data) in &items { 676 | txn.put(db, key, data, WriteFlags::empty()).unwrap(); 677 | } 678 | txn.commit().unwrap(); 679 | } 680 | 681 | let mut txn = env.begin_rw_txn().unwrap(); 682 | let mut cursor = txn.open_rw_cursor(db).unwrap(); 683 | assert_eq!(items, cursor.iter_dup().flat_map(|x| x).collect::>>().unwrap()); 684 | 685 | assert_eq!( 686 | items.clone().into_iter().take(1).collect::>(), 687 | cursor.iter_dup_of(b"a").collect::>>().unwrap() 688 | ); 689 | 690 | assert_eq!((None, &b"1"[..]), cursor.get(Some(b"a"), Some(b"1"), MDB_SET).unwrap()); 691 | 692 | cursor.del(WriteFlags::empty()).unwrap(); 693 | 694 | assert_eq!(r, cursor.iter_dup_of(b"a").collect::>>().unwrap()); 695 | } 696 | 697 | #[test] 698 | fn test_put_del() { 699 | let dir = TempDir::new("test").unwrap(); 700 | let env = Environment::new().open(dir.path()).unwrap(); 701 | let db = env.open_db(None).unwrap(); 702 | 703 | let mut txn = env.begin_rw_txn().unwrap(); 704 | let mut cursor = txn.open_rw_cursor(db).unwrap(); 705 | 706 | cursor.put(b"key1", b"val1", WriteFlags::empty()).unwrap(); 707 | cursor.put(b"key2", b"val2", WriteFlags::empty()).unwrap(); 708 | cursor.put(b"key3", b"val3", WriteFlags::empty()).unwrap(); 709 | 710 | assert_eq!((Some(&b"key3"[..]), &b"val3"[..]), cursor.get(None, None, MDB_GET_CURRENT).unwrap()); 711 | 712 | cursor.del(WriteFlags::empty()).unwrap(); 713 | assert_eq!((Some(&b"key2"[..]), &b"val2"[..]), cursor.get(None, None, MDB_LAST).unwrap()); 714 | } 715 | } 716 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use libc::c_uint; 2 | use std::ffi::CString; 3 | use std::ptr; 4 | 5 | use ffi; 6 | 7 | use error::{ 8 | lmdb_result, 9 | Result, 10 | }; 11 | 12 | /// A handle to an individual database in an environment. 13 | /// 14 | /// A database handle denotes the name and parameters of a database in an environment. 15 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 16 | pub struct Database { 17 | dbi: ffi::MDB_dbi, 18 | } 19 | 20 | impl Database { 21 | /// Opens a new database handle in the given transaction. 22 | /// 23 | /// Prefer using `Environment::open_db`, `Environment::create_db`, `TransactionExt::open_db`, 24 | /// or `RwTransaction::create_db`. 25 | pub(crate) unsafe fn new(txn: *mut ffi::MDB_txn, name: Option<&str>, flags: c_uint) -> Result { 26 | let c_name = name.map(|n| CString::new(n).unwrap()); 27 | let name_ptr = if let Some(ref c_name) = c_name { 28 | c_name.as_ptr() 29 | } else { 30 | ptr::null() 31 | }; 32 | let mut dbi: ffi::MDB_dbi = 0; 33 | lmdb_result(ffi::mdb_dbi_open(txn, name_ptr, flags, &mut dbi))?; 34 | Ok(Database { 35 | dbi, 36 | }) 37 | } 38 | 39 | pub(crate) fn freelist_db() -> Database { 40 | Database { 41 | dbi: 0, 42 | } 43 | } 44 | 45 | /// Returns the underlying LMDB database handle. 46 | /// 47 | /// The caller **must** ensure that the handle is not used after the lifetime of the 48 | /// environment, or after the database has been closed. 49 | #[allow(clippy::trivially_copy_pass_by_ref)] 50 | pub fn dbi(&self) -> ffi::MDB_dbi { 51 | self.dbi 52 | } 53 | } 54 | 55 | unsafe impl Sync for Database {} 56 | unsafe impl Send for Database {} 57 | -------------------------------------------------------------------------------- /src/environment.rs: -------------------------------------------------------------------------------- 1 | use libc::{ 2 | c_uint, 3 | size_t, 4 | }; 5 | use std::ffi::CString; 6 | #[cfg(windows)] 7 | use std::ffi::OsStr; 8 | #[cfg(unix)] 9 | use std::os::unix::ffi::OsStrExt; 10 | use std::path::Path; 11 | use std::sync::Mutex; 12 | use std::{ 13 | fmt, 14 | mem, 15 | ptr, 16 | result, 17 | }; 18 | 19 | use ffi; 20 | 21 | use byteorder::{ 22 | ByteOrder, 23 | NativeEndian, 24 | }; 25 | 26 | use cursor::Cursor; 27 | use database::Database; 28 | use error::{ 29 | lmdb_result, 30 | Error, 31 | Result, 32 | }; 33 | use flags::{ 34 | DatabaseFlags, 35 | EnvironmentFlags, 36 | }; 37 | use transaction::{ 38 | RoTransaction, 39 | RwTransaction, 40 | Transaction, 41 | }; 42 | 43 | #[cfg(windows)] 44 | /// Adding a 'missing' trait from windows OsStrExt 45 | trait OsStrExtLmdb { 46 | fn as_bytes(&self) -> &[u8]; 47 | } 48 | #[cfg(windows)] 49 | impl OsStrExtLmdb for OsStr { 50 | fn as_bytes(&self) -> &[u8] { 51 | &self.to_str().unwrap().as_bytes() 52 | } 53 | } 54 | 55 | /// An LMDB environment. 56 | /// 57 | /// An environment supports multiple databases, all residing in the same shared-memory map. 58 | pub struct Environment { 59 | env: *mut ffi::MDB_env, 60 | dbi_open_mutex: Mutex<()>, 61 | } 62 | 63 | impl Environment { 64 | /// Creates a new builder for specifying options for opening an LMDB environment. 65 | #[allow(clippy::new_ret_no_self)] 66 | pub fn new() -> EnvironmentBuilder { 67 | EnvironmentBuilder { 68 | flags: EnvironmentFlags::empty(), 69 | max_readers: None, 70 | max_dbs: None, 71 | map_size: None, 72 | } 73 | } 74 | 75 | /// Returns a raw pointer to the underlying LMDB environment. 76 | /// 77 | /// The caller **must** ensure that the pointer is not dereferenced after the lifetime of the 78 | /// environment. 79 | pub fn env(&self) -> *mut ffi::MDB_env { 80 | self.env 81 | } 82 | 83 | /// Opens a handle to an LMDB database. 84 | /// 85 | /// If `name` is `None`, then the returned handle will be for the default database. 86 | /// 87 | /// If `name` is not `None`, then the returned handle will be for a named database. In this 88 | /// case the environment must be configured to allow named databases through 89 | /// `EnvironmentBuilder::set_max_dbs`. 90 | /// 91 | /// The returned database handle may be shared among any transaction in the environment. 92 | /// 93 | /// This function will fail with `Error::BadRslot` if called by a thread which has an ongoing 94 | /// transaction. 95 | /// 96 | /// The database name may not contain the null character. 97 | pub fn open_db<'env>(&'env self, name: Option<&str>) -> Result { 98 | let mutex = self.dbi_open_mutex.lock(); 99 | let txn = self.begin_ro_txn()?; 100 | let db = unsafe { txn.open_db(name)? }; 101 | txn.commit()?; 102 | drop(mutex); 103 | Ok(db) 104 | } 105 | 106 | /// Opens a handle to an LMDB database, creating the database if necessary. 107 | /// 108 | /// If the database is already created, the given option flags will be added to it. 109 | /// 110 | /// If `name` is `None`, then the returned handle will be for the default database. 111 | /// 112 | /// If `name` is not `None`, then the returned handle will be for a named database. In this 113 | /// case the environment must be configured to allow named databases through 114 | /// `EnvironmentBuilder::set_max_dbs`. 115 | /// 116 | /// The returned database handle may be shared among any transaction in the environment. 117 | /// 118 | /// This function will fail with `Error::BadRslot` if called by a thread with an open 119 | /// transaction. 120 | pub fn create_db<'env>(&'env self, name: Option<&str>, flags: DatabaseFlags) -> Result { 121 | let mutex = self.dbi_open_mutex.lock(); 122 | let txn = self.begin_rw_txn()?; 123 | let db = unsafe { txn.create_db(name, flags)? }; 124 | txn.commit()?; 125 | drop(mutex); 126 | Ok(db) 127 | } 128 | 129 | /// Retrieves the set of flags which the database is opened with. 130 | /// 131 | /// The database must belong to to this environment. 132 | pub fn get_db_flags(&self, db: Database) -> Result { 133 | let txn = self.begin_ro_txn()?; 134 | let mut flags: c_uint = 0; 135 | unsafe { 136 | lmdb_result(ffi::mdb_dbi_flags(txn.txn(), db.dbi(), &mut flags))?; 137 | } 138 | Ok(DatabaseFlags::from_bits(flags).unwrap()) 139 | } 140 | 141 | /// Create a read-only transaction for use with the environment. 142 | pub fn begin_ro_txn<'env>(&'env self) -> Result> { 143 | RoTransaction::new(self) 144 | } 145 | 146 | /// Create a read-write transaction for use with the environment. This method will block while 147 | /// there are any other read-write transactions open on the environment. 148 | pub fn begin_rw_txn<'env>(&'env self) -> Result> { 149 | RwTransaction::new(self) 150 | } 151 | 152 | /// Flush data buffers to disk. 153 | /// 154 | /// Data is always written to disk when `Transaction::commit` is called, but the operating 155 | /// system may keep it buffered. LMDB always flushes the OS buffers upon commit as well, unless 156 | /// the environment was opened with `MDB_NOSYNC` or in part `MDB_NOMETASYNC`. 157 | pub fn sync(&self, force: bool) -> Result<()> { 158 | unsafe { 159 | lmdb_result(ffi::mdb_env_sync( 160 | self.env(), 161 | if force { 162 | 1 163 | } else { 164 | 0 165 | }, 166 | )) 167 | } 168 | } 169 | 170 | /// Closes the database handle. Normally unnecessary. 171 | /// 172 | /// Closing a database handle is not necessary, but lets `Transaction::open_database` reuse the 173 | /// handle value. Usually it's better to set a bigger `EnvironmentBuilder::set_max_dbs`, unless 174 | /// that value would be large. 175 | /// 176 | /// ## Safety 177 | /// 178 | /// This call is not mutex protected. Databases should only be closed by a single thread, and 179 | /// only if no other threads are going to reference the database handle or one of its cursors 180 | /// any further. Do not close a handle if an existing transaction has modified its database. 181 | /// Doing so can cause misbehavior from database corruption to errors like 182 | /// `Error::BadValSize` (since the DB name is gone). 183 | pub unsafe fn close_db(&mut self, db: Database) { 184 | ffi::mdb_dbi_close(self.env, db.dbi()); 185 | } 186 | 187 | /// Retrieves statistics about this environment. 188 | pub fn stat(&self) -> Result { 189 | unsafe { 190 | let mut stat = Stat::new(); 191 | lmdb_try!(ffi::mdb_env_stat(self.env(), stat.mdb_stat())); 192 | Ok(stat) 193 | } 194 | } 195 | 196 | /// Retrieves info about this environment. 197 | pub fn info(&self) -> Result { 198 | unsafe { 199 | let mut info = Info(mem::zeroed()); 200 | lmdb_try!(ffi::mdb_env_info(self.env(), &mut info.0)); 201 | Ok(info) 202 | } 203 | } 204 | 205 | /// Retrieves the total number of pages on the freelist. 206 | /// 207 | /// Along with `Environment::info()`, this can be used to calculate the exact number 208 | /// of used pages as well as free pages in this environment. 209 | /// 210 | /// ```ignore 211 | /// let env = Environment::new().open("/tmp/test").unwrap(); 212 | /// let info = env.info().unwrap(); 213 | /// let stat = env.stat().unwrap(); 214 | /// let freelist = env.freelist().unwrap(); 215 | /// let last_pgno = info.last_pgno() + 1; // pgno is 0 based. 216 | /// let total_pgs = info.map_size() / stat.page_size() as usize; 217 | /// let pgs_in_use = last_pgno - freelist; 218 | /// let pgs_free = total_pgs - pgs_in_use; 219 | /// ``` 220 | /// 221 | /// Note: 222 | /// 223 | /// * LMDB stores all the freelists in the designated database 0 in each environment, 224 | /// and the freelist count is stored at the beginning of the value as `libc::size_t` 225 | /// in the native byte order. 226 | /// 227 | /// * It will create a read transaction to traverse the freelist database. 228 | pub fn freelist(&self) -> Result { 229 | let mut freelist: size_t = 0; 230 | let db = Database::freelist_db(); 231 | let txn = self.begin_ro_txn()?; 232 | let mut cursor = txn.open_ro_cursor(db)?; 233 | 234 | for result in cursor.iter() { 235 | let (_key, value) = result?; 236 | if value.len() < mem::size_of::() { 237 | return Err(Error::Corrupted); 238 | } 239 | 240 | let s = &value[..mem::size_of::()]; 241 | if cfg!(target_pointer_width = "64") { 242 | freelist += NativeEndian::read_u64(s) as size_t; 243 | } else { 244 | freelist += NativeEndian::read_u32(s) as size_t; 245 | } 246 | } 247 | 248 | Ok(freelist) 249 | } 250 | 251 | /// Sets the size of the memory map to use for the environment. 252 | /// 253 | /// This could be used to resize the map when the environment is already open. 254 | /// 255 | /// Note: 256 | /// 257 | /// * No active transactions allowed when performing resizing in this process. 258 | /// 259 | /// * The size should be a multiple of the OS page size. Any attempt to set 260 | /// a size smaller than the space already consumed by the environment will 261 | /// be silently changed to the current size of the used space. 262 | /// 263 | /// * In the multi-process case, once a process resizes the map, other 264 | /// processes need to either re-open the environment, or call set_map_size 265 | /// with size 0 to update the environment. Otherwise, new transaction creation 266 | /// will fail with `Error::MapResized`. 267 | pub fn set_map_size(&self, size: size_t) -> Result<()> { 268 | unsafe { lmdb_result(ffi::mdb_env_set_mapsize(self.env(), size)) } 269 | } 270 | } 271 | 272 | /// Environment statistics. 273 | /// 274 | /// Contains information about the size and layout of an LMDB environment or database. 275 | pub struct Stat(ffi::MDB_stat); 276 | 277 | impl Stat { 278 | /// Create a new Stat with zero'd inner struct `ffi::MDB_stat`. 279 | pub(crate) fn new() -> Stat { 280 | unsafe { Stat(mem::zeroed()) } 281 | } 282 | 283 | /// Returns a mut pointer to `ffi::MDB_stat`. 284 | pub(crate) fn mdb_stat(&mut self) -> *mut ffi::MDB_stat { 285 | &mut self.0 286 | } 287 | } 288 | 289 | impl Stat { 290 | /// Size of a database page. This is the same for all databases in the environment. 291 | #[inline] 292 | pub fn page_size(&self) -> u32 { 293 | self.0.ms_psize 294 | } 295 | 296 | /// Depth (height) of the B-tree. 297 | #[inline] 298 | pub fn depth(&self) -> u32 { 299 | self.0.ms_depth 300 | } 301 | 302 | /// Number of internal (non-leaf) pages. 303 | #[inline] 304 | pub fn branch_pages(&self) -> usize { 305 | self.0.ms_branch_pages 306 | } 307 | 308 | /// Number of leaf pages. 309 | #[inline] 310 | pub fn leaf_pages(&self) -> usize { 311 | self.0.ms_leaf_pages 312 | } 313 | 314 | /// Number of overflow pages. 315 | #[inline] 316 | pub fn overflow_pages(&self) -> usize { 317 | self.0.ms_overflow_pages 318 | } 319 | 320 | /// Number of data items. 321 | #[inline] 322 | pub fn entries(&self) -> usize { 323 | self.0.ms_entries 324 | } 325 | } 326 | 327 | /// Environment information. 328 | /// 329 | /// Contains environment information about the map size, readers, last txn id etc. 330 | pub struct Info(ffi::MDB_envinfo); 331 | 332 | impl Info { 333 | /// Size of memory map. 334 | #[inline] 335 | pub fn map_size(&self) -> usize { 336 | self.0.me_mapsize 337 | } 338 | 339 | /// Last used page number 340 | #[inline] 341 | pub fn last_pgno(&self) -> usize { 342 | self.0.me_last_pgno 343 | } 344 | 345 | /// Last transaction ID 346 | #[inline] 347 | pub fn last_txnid(&self) -> usize { 348 | self.0.me_last_txnid 349 | } 350 | 351 | /// Max reader slots in the environment 352 | #[inline] 353 | pub fn max_readers(&self) -> u32 { 354 | self.0.me_maxreaders 355 | } 356 | 357 | /// Max reader slots used in the environment 358 | #[inline] 359 | pub fn num_readers(&self) -> u32 { 360 | self.0.me_numreaders 361 | } 362 | } 363 | 364 | unsafe impl Send for Environment {} 365 | unsafe impl Sync for Environment {} 366 | 367 | impl fmt::Debug for Environment { 368 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 369 | f.debug_struct("Environment").finish() 370 | } 371 | } 372 | 373 | impl Drop for Environment { 374 | fn drop(&mut self) { 375 | unsafe { ffi::mdb_env_close(self.env) } 376 | } 377 | } 378 | 379 | /////////////////////////////////////////////////////////////////////////////////////////////////// 380 | //// Environment Builder 381 | /////////////////////////////////////////////////////////////////////////////////////////////////// 382 | 383 | /// Options for opening or creating an environment. 384 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 385 | pub struct EnvironmentBuilder { 386 | flags: EnvironmentFlags, 387 | max_readers: Option, 388 | max_dbs: Option, 389 | map_size: Option, 390 | } 391 | 392 | impl EnvironmentBuilder { 393 | /// Open an environment. 394 | /// 395 | /// On UNIX, the database files will be opened with 644 permissions. 396 | /// 397 | /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) 398 | /// paths are not supported either. 399 | pub fn open(&self, path: &Path) -> Result { 400 | self.open_with_permissions(path, 0o644) 401 | } 402 | 403 | /// Open an environment with the provided UNIX permissions. 404 | /// 405 | /// On Windows, the permissions will be ignored. 406 | /// 407 | /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) 408 | /// paths are not supported either. 409 | pub fn open_with_permissions(&self, path: &Path, mode: ffi::mdb_mode_t) -> Result { 410 | let mut env: *mut ffi::MDB_env = ptr::null_mut(); 411 | unsafe { 412 | lmdb_try!(ffi::mdb_env_create(&mut env)); 413 | if let Some(max_readers) = self.max_readers { 414 | lmdb_try_with_cleanup!(ffi::mdb_env_set_maxreaders(env, max_readers), ffi::mdb_env_close(env)) 415 | } 416 | if let Some(max_dbs) = self.max_dbs { 417 | lmdb_try_with_cleanup!(ffi::mdb_env_set_maxdbs(env, max_dbs), ffi::mdb_env_close(env)) 418 | } 419 | if let Some(map_size) = self.map_size { 420 | lmdb_try_with_cleanup!(ffi::mdb_env_set_mapsize(env, map_size), ffi::mdb_env_close(env)) 421 | } 422 | let path = match CString::new(path.as_os_str().as_bytes()) { 423 | Ok(path) => path, 424 | Err(..) => return Err(::Error::Invalid), 425 | }; 426 | lmdb_try_with_cleanup!( 427 | ffi::mdb_env_open(env, path.as_ptr(), self.flags.bits(), mode), 428 | ffi::mdb_env_close(env) 429 | ); 430 | } 431 | Ok(Environment { 432 | env, 433 | dbi_open_mutex: Mutex::new(()), 434 | }) 435 | } 436 | 437 | /// Sets the provided options in the environment. 438 | pub fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut EnvironmentBuilder { 439 | self.flags = flags; 440 | self 441 | } 442 | 443 | /// Sets the maximum number of threads or reader slots for the environment. 444 | /// 445 | /// This defines the number of slots in the lock table that is used to track readers in the 446 | /// the environment. The default is 126. Starting a read-only transaction normally ties a lock 447 | /// table slot to the current thread until the environment closes or the thread exits. If 448 | /// `MDB_NOTLS` is in use, `Environment::open_txn` instead ties the slot to the `Transaction` 449 | /// object until it or the `Environment` object is destroyed. 450 | pub fn set_max_readers(&mut self, max_readers: c_uint) -> &mut EnvironmentBuilder { 451 | self.max_readers = Some(max_readers); 452 | self 453 | } 454 | 455 | /// Sets the maximum number of named databases for the environment. 456 | /// 457 | /// This function is only needed if multiple databases will be used in the 458 | /// environment. Simpler applications that use the environment as a single 459 | /// unnamed database can ignore this option. 460 | /// 461 | /// Currently a moderate number of slots are cheap but a huge number gets 462 | /// expensive: 7-120 words per transaction, and every `Transaction::open_db` 463 | /// does a linear search of the opened slots. 464 | pub fn set_max_dbs(&mut self, max_dbs: c_uint) -> &mut EnvironmentBuilder { 465 | self.max_dbs = Some(max_dbs); 466 | self 467 | } 468 | 469 | /// Sets the size of the memory map to use for the environment. 470 | /// 471 | /// The size should be a multiple of the OS page size. The default is 472 | /// 1048576 bytes. The size of the memory map is also the maximum size 473 | /// of the database. The value should be chosen as large as possible, 474 | /// to accommodate future growth of the database. It may be increased at 475 | /// later times. 476 | /// 477 | /// Any attempt to set a size smaller than the space already consumed 478 | /// by the environment will be silently changed to the current size of the used space. 479 | pub fn set_map_size(&mut self, map_size: size_t) -> &mut EnvironmentBuilder { 480 | self.map_size = Some(map_size); 481 | self 482 | } 483 | } 484 | 485 | #[cfg(test)] 486 | mod test { 487 | 488 | extern crate byteorder; 489 | 490 | use self::byteorder::{ 491 | ByteOrder, 492 | LittleEndian, 493 | }; 494 | use tempdir::TempDir; 495 | 496 | use flags::*; 497 | 498 | use super::*; 499 | 500 | #[test] 501 | fn test_open() { 502 | let dir = TempDir::new("test").unwrap(); 503 | 504 | // opening non-existent env with read-only should fail 505 | assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_err()); 506 | 507 | // opening non-existent env should succeed 508 | assert!(Environment::new().open(dir.path()).is_ok()); 509 | 510 | // opening env with read-only should succeed 511 | assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_ok()); 512 | } 513 | 514 | #[test] 515 | fn test_begin_txn() { 516 | let dir = TempDir::new("test").unwrap(); 517 | 518 | { 519 | // writable environment 520 | let env = Environment::new().open(dir.path()).unwrap(); 521 | 522 | assert!(env.begin_rw_txn().is_ok()); 523 | assert!(env.begin_ro_txn().is_ok()); 524 | } 525 | 526 | { 527 | // read-only environment 528 | let env = Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).unwrap(); 529 | 530 | assert!(env.begin_rw_txn().is_err()); 531 | assert!(env.begin_ro_txn().is_ok()); 532 | } 533 | } 534 | 535 | #[test] 536 | fn test_open_db() { 537 | let dir = TempDir::new("test").unwrap(); 538 | let env = Environment::new().set_max_dbs(1).open(dir.path()).unwrap(); 539 | 540 | assert!(env.open_db(None).is_ok()); 541 | assert!(env.open_db(Some("testdb")).is_err()); 542 | } 543 | 544 | #[test] 545 | fn test_create_db() { 546 | let dir = TempDir::new("test").unwrap(); 547 | let env = Environment::new().set_max_dbs(11).open(dir.path()).unwrap(); 548 | assert!(env.open_db(Some("testdb")).is_err()); 549 | assert!(env.create_db(Some("testdb"), DatabaseFlags::empty()).is_ok()); 550 | assert!(env.open_db(Some("testdb")).is_ok()) 551 | } 552 | 553 | #[test] 554 | fn test_close_database() { 555 | let dir = TempDir::new("test").unwrap(); 556 | let mut env = Environment::new().set_max_dbs(10).open(dir.path()).unwrap(); 557 | 558 | let db = env.create_db(Some("db"), DatabaseFlags::empty()).unwrap(); 559 | unsafe { 560 | env.close_db(db); 561 | } 562 | assert!(env.open_db(Some("db")).is_ok()); 563 | } 564 | 565 | #[test] 566 | fn test_sync() { 567 | let dir = TempDir::new("test").unwrap(); 568 | { 569 | let env = Environment::new().open(dir.path()).unwrap(); 570 | assert!(env.sync(true).is_ok()); 571 | } 572 | { 573 | let env = Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).unwrap(); 574 | assert!(env.sync(true).is_err()); 575 | } 576 | } 577 | 578 | #[test] 579 | fn test_stat() { 580 | let dir = TempDir::new("test").unwrap(); 581 | let env = Environment::new().open(dir.path()).unwrap(); 582 | 583 | // Stats should be empty initially. 584 | let stat = env.stat().unwrap(); 585 | assert_eq!(stat.page_size(), 4096); 586 | assert_eq!(stat.depth(), 0); 587 | assert_eq!(stat.branch_pages(), 0); 588 | assert_eq!(stat.leaf_pages(), 0); 589 | assert_eq!(stat.overflow_pages(), 0); 590 | assert_eq!(stat.entries(), 0); 591 | 592 | let db = env.open_db(None).unwrap(); 593 | 594 | // Write a few small values. 595 | for i in 0..64 { 596 | let mut value = [0u8; 8]; 597 | LittleEndian::write_u64(&mut value, i); 598 | let mut tx = env.begin_rw_txn().expect("begin_rw_txn"); 599 | tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put"); 600 | tx.commit().expect("tx.commit") 601 | } 602 | 603 | // Stats should now reflect inserted values. 604 | let stat = env.stat().unwrap(); 605 | assert_eq!(stat.page_size(), 4096); 606 | assert_eq!(stat.depth(), 1); 607 | assert_eq!(stat.branch_pages(), 0); 608 | assert_eq!(stat.leaf_pages(), 1); 609 | assert_eq!(stat.overflow_pages(), 0); 610 | assert_eq!(stat.entries(), 64); 611 | } 612 | 613 | #[test] 614 | fn test_info() { 615 | let map_size = 1024 * 1024; 616 | let dir = TempDir::new("test").unwrap(); 617 | let env = Environment::new().set_map_size(map_size).open(dir.path()).unwrap(); 618 | 619 | let info = env.info().unwrap(); 620 | assert_eq!(info.map_size(), map_size); 621 | assert_eq!(info.last_pgno(), 1); 622 | assert_eq!(info.last_txnid(), 0); 623 | // The default max readers is 126. 624 | assert_eq!(info.max_readers(), 126); 625 | assert_eq!(info.num_readers(), 0); 626 | } 627 | 628 | #[test] 629 | fn test_freelist() { 630 | let dir = TempDir::new("test").unwrap(); 631 | let env = Environment::new().open(dir.path()).unwrap(); 632 | 633 | let db = env.open_db(None).unwrap(); 634 | let mut freelist = env.freelist().unwrap(); 635 | assert_eq!(freelist, 0); 636 | 637 | // Write a few small values. 638 | for i in 0..64 { 639 | let mut value = [0u8; 8]; 640 | LittleEndian::write_u64(&mut value, i); 641 | let mut tx = env.begin_rw_txn().expect("begin_rw_txn"); 642 | tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put"); 643 | tx.commit().expect("tx.commit") 644 | } 645 | let mut tx = env.begin_rw_txn().expect("begin_rw_txn"); 646 | tx.clear_db(db).expect("clear"); 647 | tx.commit().expect("tx.commit"); 648 | 649 | // Freelist should not be empty after clear_db. 650 | freelist = env.freelist().unwrap(); 651 | assert!(freelist > 0); 652 | } 653 | 654 | #[test] 655 | fn test_set_map_size() { 656 | let dir = TempDir::new("test").unwrap(); 657 | let env = Environment::new().open(dir.path()).unwrap(); 658 | 659 | let mut info = env.info().unwrap(); 660 | let default_size = info.map_size(); 661 | 662 | // Resizing to 0 merely reloads the map size 663 | env.set_map_size(0).unwrap(); 664 | info = env.info().unwrap(); 665 | assert_eq!(info.map_size(), default_size); 666 | 667 | env.set_map_size(2 * default_size).unwrap(); 668 | info = env.info().unwrap(); 669 | assert_eq!(info.map_size(), 2 * default_size); 670 | 671 | env.set_map_size(4 * default_size).unwrap(); 672 | info = env.info().unwrap(); 673 | assert_eq!(info.map_size(), 4 * default_size); 674 | 675 | // Decreasing is also fine if the space hasn't been consumed. 676 | env.set_map_size(2 * default_size).unwrap(); 677 | info = env.info().unwrap(); 678 | assert_eq!(info.map_size(), 2 * default_size); 679 | } 680 | } 681 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use libc::c_int; 2 | use std::error::Error as StdError; 3 | use std::ffi::CStr; 4 | use std::os::raw::c_char; 5 | use std::{ 6 | fmt, 7 | result, 8 | str, 9 | }; 10 | 11 | use ffi; 12 | 13 | /// An LMDB error kind. 14 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 15 | pub enum Error { 16 | /// key/data pair already exists. 17 | KeyExist, 18 | /// key/data pair not found (EOF). 19 | NotFound, 20 | /// Requested page not found - this usually indicates corruption. 21 | PageNotFound, 22 | /// Located page was wrong type. 23 | Corrupted, 24 | /// Update of meta page failed or environment had fatal error. 25 | Panic, 26 | /// Environment version mismatch. 27 | VersionMismatch, 28 | /// File is not a valid LMDB file. 29 | Invalid, 30 | /// Environment mapsize reached. 31 | MapFull, 32 | /// Environment maxdbs reached. 33 | DbsFull, 34 | /// Environment maxreaders reached. 35 | ReadersFull, 36 | /// Too many TLS keys in use - Windows only. 37 | TlsFull, 38 | /// Txn has too many dirty pages. 39 | TxnFull, 40 | /// Cursor stack too deep - internal error. 41 | CursorFull, 42 | /// Page has not enough space - internal error. 43 | PageFull, 44 | /// Database contents grew beyond environment mapsize. 45 | MapResized, 46 | /// MDB_Incompatible: Operation and DB incompatible, or DB flags changed. 47 | Incompatible, 48 | /// Invalid reuse of reader locktable slot. 49 | BadRslot, 50 | /// Transaction cannot recover - it must be aborted. 51 | BadTxn, 52 | /// Unsupported size of key/DB name/data, or wrong DUP_FIXED size. 53 | BadValSize, 54 | /// The specified DBI was changed unexpectedly. 55 | BadDbi, 56 | /// Other error. 57 | Other(c_int), 58 | } 59 | 60 | impl Error { 61 | /// Converts a raw error code to an `Error`. 62 | pub fn from_err_code(err_code: c_int) -> Error { 63 | match err_code { 64 | ffi::MDB_KEYEXIST => Error::KeyExist, 65 | ffi::MDB_NOTFOUND => Error::NotFound, 66 | ffi::MDB_PAGE_NOTFOUND => Error::PageNotFound, 67 | ffi::MDB_CORRUPTED => Error::Corrupted, 68 | ffi::MDB_PANIC => Error::Panic, 69 | ffi::MDB_VERSION_MISMATCH => Error::VersionMismatch, 70 | ffi::MDB_INVALID => Error::Invalid, 71 | ffi::MDB_MAP_FULL => Error::MapFull, 72 | ffi::MDB_DBS_FULL => Error::DbsFull, 73 | ffi::MDB_READERS_FULL => Error::ReadersFull, 74 | ffi::MDB_TLS_FULL => Error::TlsFull, 75 | ffi::MDB_TXN_FULL => Error::TxnFull, 76 | ffi::MDB_CURSOR_FULL => Error::CursorFull, 77 | ffi::MDB_PAGE_FULL => Error::PageFull, 78 | ffi::MDB_MAP_RESIZED => Error::MapResized, 79 | ffi::MDB_INCOMPATIBLE => Error::Incompatible, 80 | ffi::MDB_BAD_RSLOT => Error::BadRslot, 81 | ffi::MDB_BAD_TXN => Error::BadTxn, 82 | ffi::MDB_BAD_VALSIZE => Error::BadValSize, 83 | ffi::MDB_BAD_DBI => Error::BadDbi, 84 | other => Error::Other(other), 85 | } 86 | } 87 | 88 | /// Converts an `Error` to the raw error code. 89 | #[allow(clippy::trivially_copy_pass_by_ref)] 90 | pub fn to_err_code(&self) -> c_int { 91 | match *self { 92 | Error::KeyExist => ffi::MDB_KEYEXIST, 93 | Error::NotFound => ffi::MDB_NOTFOUND, 94 | Error::PageNotFound => ffi::MDB_PAGE_NOTFOUND, 95 | Error::Corrupted => ffi::MDB_CORRUPTED, 96 | Error::Panic => ffi::MDB_PANIC, 97 | Error::VersionMismatch => ffi::MDB_VERSION_MISMATCH, 98 | Error::Invalid => ffi::MDB_INVALID, 99 | Error::MapFull => ffi::MDB_MAP_FULL, 100 | Error::DbsFull => ffi::MDB_DBS_FULL, 101 | Error::ReadersFull => ffi::MDB_READERS_FULL, 102 | Error::TlsFull => ffi::MDB_TLS_FULL, 103 | Error::TxnFull => ffi::MDB_TXN_FULL, 104 | Error::CursorFull => ffi::MDB_CURSOR_FULL, 105 | Error::PageFull => ffi::MDB_PAGE_FULL, 106 | Error::MapResized => ffi::MDB_MAP_RESIZED, 107 | Error::Incompatible => ffi::MDB_INCOMPATIBLE, 108 | Error::BadRslot => ffi::MDB_BAD_RSLOT, 109 | Error::BadTxn => ffi::MDB_BAD_TXN, 110 | Error::BadValSize => ffi::MDB_BAD_VALSIZE, 111 | Error::BadDbi => ffi::MDB_BAD_DBI, 112 | Error::Other(err_code) => err_code, 113 | } 114 | } 115 | } 116 | 117 | impl fmt::Display for Error { 118 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 119 | write!(fmt, "{}", self.description()) 120 | } 121 | } 122 | 123 | impl StdError for Error { 124 | fn description(&self) -> &str { 125 | unsafe { 126 | // This is safe since the error messages returned from mdb_strerror are static. 127 | let err: *const c_char = ffi::mdb_strerror(self.to_err_code()) as *const c_char; 128 | str::from_utf8_unchecked(CStr::from_ptr(err).to_bytes()) 129 | } 130 | } 131 | } 132 | 133 | /// An LMDB result. 134 | pub type Result = result::Result; 135 | 136 | pub fn lmdb_result(err_code: c_int) -> Result<()> { 137 | if err_code == ffi::MDB_SUCCESS { 138 | Ok(()) 139 | } else { 140 | Err(Error::from_err_code(err_code)) 141 | } 142 | } 143 | 144 | #[cfg(test)] 145 | mod test { 146 | 147 | use std::error::Error as StdError; 148 | 149 | use super::*; 150 | 151 | #[test] 152 | fn test_description() { 153 | assert_eq!("Permission denied", Error::from_err_code(13).description()); 154 | assert_eq!("MDB_NOTFOUND: No matching key/data pair found", Error::NotFound.description()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | use libc::c_uint; 2 | 3 | use ffi::*; 4 | 5 | bitflags! { 6 | #[doc="Environment options."] 7 | #[derive(Default)] 8 | pub struct EnvironmentFlags: c_uint { 9 | 10 | #[doc="Use a fixed address for the mmap region. This flag must be specified"] 11 | #[doc="when creating the environment, and is stored persistently in the environment."] 12 | #[doc="If successful, the memory map will always reside at the same virtual address"] 13 | #[doc="and pointers used to reference data items in the database will be constant"] 14 | #[doc="across multiple invocations. This option may not always work, depending on"] 15 | #[doc="how the operating system has allocated memory to shared libraries and other uses."] 16 | #[doc="The feature is highly experimental."] 17 | const FIXED_MAP = MDB_FIXEDMAP; 18 | 19 | #[doc="By default, LMDB creates its environment in a directory whose pathname is given in"] 20 | #[doc="`path`, and creates its data and lock files under that directory. With this option,"] 21 | #[doc="`path` is used as-is for the database main data file. The database lock file is the"] 22 | #[doc="`path` with `-lock` appended."] 23 | const NO_SUB_DIR = MDB_NOSUBDIR; 24 | 25 | #[doc="Use a writeable memory map unless `READ_ONLY` is set. This is faster and uses"] 26 | #[doc="fewer mallocs, but loses protection from application bugs like wild pointer writes"] 27 | #[doc="and other bad updates into the database. Incompatible with nested transactions."] 28 | #[doc="Processes with and without `WRITE_MAP` on the same environment do not cooperate"] 29 | #[doc="well."] 30 | const WRITE_MAP = MDB_WRITEMAP; 31 | 32 | #[doc="Open the environment in read-only mode. No write operations will be allowed."] 33 | #[doc="When opening an environment, LMDB will still modify the lock file - except on"] 34 | #[doc="read-only filesystems, where LMDB does not use locks."] 35 | const READ_ONLY = MDB_RDONLY; 36 | 37 | #[doc="Flush system buffers to disk only once per transaction, omit the metadata flush."] 38 | #[doc="Defer that until the system flushes files to disk, or next non-`READ_ONLY` commit"] 39 | #[doc="or `Environment::sync`. This optimization maintains database integrity, but a"] 40 | #[doc="system crash may undo the last committed transaction. I.e. it preserves the ACI"] 41 | #[doc="(atomicity, consistency, isolation) but not D (durability) database property."] 42 | #[doc="\n\nThis flag may be changed at any time using `Environment::set_flags`."] 43 | const NO_META_SYNC = MDB_NOMETASYNC; 44 | 45 | #[doc="Don't flush system buffers to disk when committing a transaction. This optimization"] 46 | #[doc="means a system crash can corrupt the database or lose the last transactions if"] 47 | #[doc="buffers are not yet flushed to disk. The risk is governed by how often the system"] 48 | #[doc="flushes dirty buffers to disk and how often `Environment::sync` is called. However,"] 49 | #[doc="if the filesystem preserves write order and the `WRITE_MAP` flag is not used,"] 50 | #[doc="transactions exhibit ACI (atomicity, consistency, isolation) properties and only"] 51 | #[doc="lose D (durability). I.e. database integrity is maintained, but a system"] 52 | #[doc="crash may undo the final transactions. Note that (`NO_SYNC | WRITE_MAP`) leaves the"] 53 | #[doc="system with no hint for when to write transactions to disk, unless"] 54 | #[doc="`Environment::sync` is called. (`MAP_ASYNC | WRITE_MAP`) may be preferable."] 55 | #[doc="\n\nThis flag may be changed at any time using `Environment::set_flags`."] 56 | const NO_SYNC = MDB_NOSYNC; 57 | 58 | #[doc="When using `WRITE_MAP`, use asynchronous flushes to disk. As with `NO_SYNC`, a"] 59 | #[doc="system crash can then corrupt the database or lose the last transactions. Calling"] 60 | #[doc="`Environment::sync` ensures on-disk database integrity until next commit."] 61 | #[doc="\n\nThis flag may be changed at any time using `Environment::set_flags`."] 62 | const MAP_ASYNC = MDB_MAPASYNC; 63 | 64 | #[doc="Don't use thread-local storage. Tie reader locktable slots to transaction objects"] 65 | #[doc="instead of to threads. I.e. `RoTransaction::reset` keeps the slot reserved for the"] 66 | #[doc="transaction object. A thread may use parallel read-only transactions. A read-only"] 67 | #[doc="transaction may span threads if the user synchronizes its use. Applications that"] 68 | #[doc="multiplex many the user synchronizes its use. Applications that multiplex many user"] 69 | #[doc="threads over individual OS threads need this option. Such an application must also"] 70 | #[doc="serialize the write transactions in an OS thread, since LMDB's write locking is"] 71 | #[doc="unaware of the user threads."] 72 | const NO_TLS = MDB_NOTLS; 73 | 74 | #[doc="Do not do any locking. If concurrent access is anticipated, the caller must manage"] 75 | #[doc="all concurrency themself. For proper operation the caller must enforce"] 76 | #[doc="single-writer semantics, and must ensure that no readers are using old"] 77 | #[doc="transactions while a writer is active. The simplest approach is to use an exclusive"] 78 | #[doc="lock so that no readers may be active at all when a writer begins."] 79 | const NO_LOCK = MDB_NOLOCK; 80 | 81 | #[doc="Turn off readahead. Most operating systems perform readahead on read requests by"] 82 | #[doc="default. This option turns it off if the OS supports it. Turning it off may help"] 83 | #[doc="random read performance when the DB is larger than RAM and system RAM is full."] 84 | #[doc="The option is not implemented on Windows."] 85 | const NO_READAHEAD = MDB_NORDAHEAD; 86 | 87 | #[doc="Do not initialize malloc'd memory before writing to unused spaces in the data file."] 88 | #[doc="By default, memory for pages written to the data file is obtained using malloc."] 89 | #[doc="While these pages may be reused in subsequent transactions, freshly malloc'd pages"] 90 | #[doc="will be initialized to zeroes before use. This avoids persisting leftover data from"] 91 | #[doc="other code (that used the heap and subsequently freed the memory) into the data"] 92 | #[doc="file. Note that many other system libraries may allocate and free memory from the"] 93 | #[doc="heap for arbitrary uses. E.g., stdio may use the heap for file I/O buffers. This"] 94 | #[doc="initialization step has a modest performance cost so some applications may want to"] 95 | #[doc="disable it using this flag. This option can be a problem for applications which"] 96 | #[doc="handle sensitive data like passwords, and it makes memory checkers like Valgrind"] 97 | #[doc="noisy. This flag is not needed with `WRITE_MAP`, which writes directly to the mmap"] 98 | #[doc="instead of using malloc for pages. The initialization is also skipped if writing"] 99 | #[doc="with reserve; the caller is expected to overwrite all of the memory that was"] 100 | #[doc="reserved in that case."] 101 | #[doc="\n\nThis flag may be changed at any time using `Environment::set_flags`."] 102 | const NO_MEM_INIT = MDB_NOMEMINIT; 103 | } 104 | } 105 | 106 | bitflags! { 107 | #[doc="Database options."] 108 | #[derive(Default)] 109 | pub struct DatabaseFlags: c_uint { 110 | 111 | #[doc="Keys are strings to be compared in reverse order, from the end of the strings"] 112 | #[doc="to the beginning. By default, Keys are treated as strings and compared from"] 113 | #[doc="beginning to end."] 114 | const REVERSE_KEY = MDB_REVERSEKEY; 115 | 116 | #[doc="Duplicate keys may be used in the database. (Or, from another perspective,"] 117 | #[doc="keys may have multiple data items, stored in sorted order.) By default"] 118 | #[doc="keys must be unique and may have only a single data item."] 119 | const DUP_SORT = MDB_DUPSORT; 120 | 121 | #[doc="Keys are binary integers in native byte order. Setting this option requires all"] 122 | #[doc="keys to be the same size, typically 32 or 64 bits."] 123 | const INTEGER_KEY = MDB_INTEGERKEY; 124 | 125 | #[doc="This flag may only be used in combination with `DUP_SORT`. This option tells"] 126 | #[doc="the library that the data items for this database are all the same size, which"] 127 | #[doc="allows further optimizations in storage and retrieval. When all data items are"] 128 | #[doc="the same size, the `GET_MULTIPLE` and `NEXT_MULTIPLE` cursor operations may be"] 129 | #[doc="used to retrieve multiple items at once."] 130 | const DUP_FIXED = MDB_DUPFIXED; 131 | 132 | #[doc="This option specifies that duplicate data items are also integers, and"] 133 | #[doc="should be sorted as such."] 134 | const INTEGER_DUP = MDB_INTEGERDUP; 135 | 136 | #[doc="This option specifies that duplicate data items should be compared as strings"] 137 | #[doc="in reverse order."] 138 | const REVERSE_DUP = MDB_REVERSEDUP; 139 | } 140 | } 141 | 142 | bitflags! { 143 | #[doc="Write options."] 144 | #[derive(Default)] 145 | pub struct WriteFlags: c_uint { 146 | 147 | #[doc="Insert the new item only if the key does not already appear in the database."] 148 | #[doc="The function will return `LmdbError::KeyExist` if the key already appears in the"] 149 | #[doc="database, even if the database supports duplicates (`DUP_SORT`)."] 150 | const NO_OVERWRITE = MDB_NOOVERWRITE; 151 | 152 | #[doc="Insert the new item only if it does not already appear in the database."] 153 | #[doc="This flag may only be specified if the database was opened with `DUP_SORT`."] 154 | #[doc="The function will return `LmdbError::KeyExist` if the item already appears in the"] 155 | #[doc="database."] 156 | const NO_DUP_DATA = MDB_NODUPDATA; 157 | 158 | #[doc="For `Cursor::put`. Replace the item at the current cursor position. The key"] 159 | #[doc="parameter must match the current position. If using sorted duplicates (`DUP_SORT`)"] 160 | #[doc="the data item must still sort into the same position. This is intended to be used"] 161 | #[doc="when the new data is the same size as the old. Otherwise it will simply perform a"] 162 | #[doc="delete of the old record followed by an insert."] 163 | const CURRENT = MDB_CURRENT; 164 | 165 | #[doc="Append the given item to the end of the database. No key comparisons are performed."] 166 | #[doc="This option allows fast bulk loading when keys are already known to be in the"] 167 | #[doc="correct order. Loading unsorted keys with this flag will cause data corruption."] 168 | const APPEND = MDB_APPEND; 169 | 170 | #[doc="Same as `APPEND`, but for sorted dup data."] 171 | const APPEND_DUP = MDB_APPENDDUP; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Idiomatic and safe APIs for interacting with the 2 | //! [Lightning Memory-mapped Database (LMDB)](https://symas.com/lmdb). 3 | 4 | #![deny(missing_docs)] 5 | #![doc(html_root_url = "https://docs.rs/lmdb-rkv/0.14.0")] 6 | 7 | extern crate byteorder; 8 | extern crate libc; 9 | extern crate lmdb_sys as ffi; 10 | 11 | #[cfg(test)] 12 | extern crate tempdir; 13 | #[macro_use] 14 | extern crate bitflags; 15 | 16 | pub use cursor::{ 17 | Cursor, 18 | Iter, 19 | IterDup, 20 | RoCursor, 21 | RwCursor, 22 | }; 23 | pub use database::Database; 24 | pub use environment::{ 25 | Environment, 26 | EnvironmentBuilder, 27 | Info, 28 | Stat, 29 | }; 30 | pub use error::{ 31 | Error, 32 | Result, 33 | }; 34 | pub use flags::*; 35 | pub use transaction::{ 36 | InactiveTransaction, 37 | RoTransaction, 38 | RwTransaction, 39 | Transaction, 40 | }; 41 | 42 | macro_rules! lmdb_try { 43 | ($expr:expr) => {{ 44 | match $expr { 45 | ::ffi::MDB_SUCCESS => (), 46 | err_code => return Err(::Error::from_err_code(err_code)), 47 | } 48 | }}; 49 | } 50 | 51 | macro_rules! lmdb_try_with_cleanup { 52 | ($expr:expr, $cleanup:expr) => {{ 53 | match $expr { 54 | ::ffi::MDB_SUCCESS => (), 55 | err_code => { 56 | let _ = $cleanup; 57 | return Err(::Error::from_err_code(err_code)); 58 | }, 59 | } 60 | }}; 61 | } 62 | 63 | mod cursor; 64 | mod database; 65 | mod environment; 66 | mod error; 67 | mod flags; 68 | mod transaction; 69 | 70 | #[cfg(test)] 71 | mod test_utils { 72 | 73 | use byteorder::{ 74 | ByteOrder, 75 | LittleEndian, 76 | }; 77 | use tempdir::TempDir; 78 | 79 | use super::*; 80 | 81 | /// Regression test for https://github.com/danburkert/lmdb-rs/issues/21. 82 | /// This test reliably segfaults when run against lmbdb compiled with opt level -O3 and newer 83 | /// GCC compilers. 84 | #[test] 85 | fn issue_21_regression() { 86 | const HEIGHT_KEY: [u8; 1] = [0]; 87 | 88 | let dir = TempDir::new("test").unwrap(); 89 | 90 | let env = { 91 | let mut builder = Environment::new(); 92 | builder.set_max_dbs(2); 93 | builder.set_map_size(1_000_000); 94 | builder.open(dir.path()).expect("open lmdb env") 95 | }; 96 | let index = env.create_db(None, DatabaseFlags::DUP_SORT).expect("open index db"); 97 | 98 | for height in 0..1000 { 99 | let mut value = [0u8; 8]; 100 | LittleEndian::write_u64(&mut value, height); 101 | let mut tx = env.begin_rw_txn().expect("begin_rw_txn"); 102 | tx.put(index, &HEIGHT_KEY, &value, WriteFlags::empty()).expect("tx.put"); 103 | tx.commit().expect("tx.commit") 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/transaction.rs: -------------------------------------------------------------------------------- 1 | use libc::{ 2 | c_uint, 3 | c_void, 4 | size_t, 5 | }; 6 | use std::marker::PhantomData; 7 | use std::{ 8 | fmt, 9 | mem, 10 | ptr, 11 | result, 12 | slice, 13 | }; 14 | 15 | use ffi; 16 | 17 | use cursor::{ 18 | RoCursor, 19 | RwCursor, 20 | }; 21 | use database::Database; 22 | use environment::{ 23 | Environment, 24 | Stat, 25 | }; 26 | use error::{ 27 | lmdb_result, 28 | Error, 29 | Result, 30 | }; 31 | use flags::{ 32 | DatabaseFlags, 33 | EnvironmentFlags, 34 | WriteFlags, 35 | }; 36 | 37 | /// An LMDB transaction. 38 | /// 39 | /// All database operations require a transaction. 40 | pub trait Transaction: Sized { 41 | /// Returns a raw pointer to the underlying LMDB transaction. 42 | /// 43 | /// The caller **must** ensure that the pointer is not used after the 44 | /// lifetime of the transaction. 45 | fn txn(&self) -> *mut ffi::MDB_txn; 46 | 47 | /// Commits the transaction. 48 | /// 49 | /// Any pending operations will be saved. 50 | fn commit(self) -> Result<()> { 51 | unsafe { 52 | let result = lmdb_result(ffi::mdb_txn_commit(self.txn())); 53 | mem::forget(self); 54 | result 55 | } 56 | } 57 | 58 | /// Aborts the transaction. 59 | /// 60 | /// Any pending operations will not be saved. 61 | fn abort(self) { 62 | // Abort should be performed in transaction destructors. 63 | } 64 | 65 | /// Opens a database in the transaction. 66 | /// 67 | /// If `name` is `None`, then the default database will be opened, otherwise 68 | /// a named database will be opened. The database handle will be private to 69 | /// the transaction until the transaction is successfully committed. If the 70 | /// transaction is aborted the returned database handle should no longer be 71 | /// used. 72 | /// 73 | /// Prefer using `Environment::open_db`. 74 | /// 75 | /// ## Safety 76 | /// 77 | /// This function (as well as `Environment::open_db`, 78 | /// `Environment::create_db`, and `Database::create`) **must not** be called 79 | /// from multiple concurrent transactions in the same environment. A 80 | /// transaction which uses this function must finish (either commit or 81 | /// abort) before any other transaction may use this function. 82 | unsafe fn open_db(&self, name: Option<&str>) -> Result { 83 | Database::new(self.txn(), name, 0) 84 | } 85 | 86 | /// Gets an item from a database. 87 | /// 88 | /// This function retrieves the data associated with the given key in the 89 | /// database. If the database supports duplicate keys 90 | /// (`DatabaseFlags::DUP_SORT`) then the first data item for the key will be 91 | /// returned. Retrieval of other items requires the use of 92 | /// `Transaction::cursor_get`. If the item is not in the database, then 93 | /// `Error::NotFound` will be returned. 94 | fn get<'txn, K>(&'txn self, database: Database, key: &K) -> Result<&'txn [u8]> 95 | where 96 | K: AsRef<[u8]>, 97 | { 98 | let key = key.as_ref(); 99 | let mut key_val: ffi::MDB_val = ffi::MDB_val { 100 | mv_size: key.len() as size_t, 101 | mv_data: key.as_ptr() as *mut c_void, 102 | }; 103 | let mut data_val: ffi::MDB_val = ffi::MDB_val { 104 | mv_size: 0, 105 | mv_data: ptr::null_mut(), 106 | }; 107 | unsafe { 108 | match ffi::mdb_get(self.txn(), database.dbi(), &mut key_val, &mut data_val) { 109 | ffi::MDB_SUCCESS => Ok(slice::from_raw_parts(data_val.mv_data as *const u8, data_val.mv_size as usize)), 110 | err_code => Err(Error::from_err_code(err_code)), 111 | } 112 | } 113 | } 114 | 115 | /// Open a new read-only cursor on the given database. 116 | fn open_ro_cursor<'txn>(&'txn self, db: Database) -> Result> { 117 | RoCursor::new(self, db) 118 | } 119 | 120 | /// Gets the option flags for the given database in the transaction. 121 | fn db_flags(&self, db: Database) -> Result { 122 | let mut flags: c_uint = 0; 123 | unsafe { 124 | lmdb_result(ffi::mdb_dbi_flags(self.txn(), db.dbi(), &mut flags))?; 125 | } 126 | Ok(DatabaseFlags::from_bits_truncate(flags)) 127 | } 128 | 129 | /// Retrieves database statistics. 130 | fn stat(&self, db: Database) -> Result { 131 | unsafe { 132 | let mut stat = Stat::new(); 133 | lmdb_try!(ffi::mdb_stat(self.txn(), db.dbi(), stat.mdb_stat())); 134 | Ok(stat) 135 | } 136 | } 137 | } 138 | 139 | /// An LMDB read-only transaction. 140 | pub struct RoTransaction<'env> { 141 | txn: *mut ffi::MDB_txn, 142 | _marker: PhantomData<&'env ()>, 143 | } 144 | 145 | impl<'env> fmt::Debug for RoTransaction<'env> { 146 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 147 | f.debug_struct("RoTransaction").finish() 148 | } 149 | } 150 | 151 | impl<'env> Drop for RoTransaction<'env> { 152 | fn drop(&mut self) { 153 | unsafe { ffi::mdb_txn_abort(self.txn) } 154 | } 155 | } 156 | 157 | impl<'env> RoTransaction<'env> { 158 | /// Creates a new read-only transaction in the given environment. Prefer 159 | /// using `Environment::begin_ro_txn`. 160 | pub(crate) fn new(env: &'env Environment) -> Result> { 161 | let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); 162 | unsafe { 163 | lmdb_result(ffi::mdb_txn_begin(env.env(), ptr::null_mut(), ffi::MDB_RDONLY, &mut txn))?; 164 | Ok(RoTransaction { 165 | txn, 166 | _marker: PhantomData, 167 | }) 168 | } 169 | } 170 | 171 | /// Resets the read-only transaction. 172 | /// 173 | /// Abort the transaction like `Transaction::abort`, but keep the 174 | /// transaction handle. `InactiveTransaction::renew` may reuse the handle. 175 | /// This saves allocation overhead if the process will start a new read-only 176 | /// transaction soon, and also locking overhead if 177 | /// `EnvironmentFlags::NO_TLS` is in use. The reader table lock is released, 178 | /// but the table slot stays tied to its thread or transaction. Reader locks 179 | /// generally don't interfere with writers, but they keep old versions of 180 | /// database pages allocated. Thus they prevent the old pages from being 181 | /// reused when writers commit new data, and so under heavy load the 182 | /// database size may grow much more rapidly than otherwise. 183 | pub fn reset(self) -> InactiveTransaction<'env> { 184 | let txn = self.txn; 185 | unsafe { 186 | mem::forget(self); 187 | ffi::mdb_txn_reset(txn) 188 | }; 189 | InactiveTransaction { 190 | txn, 191 | _marker: PhantomData, 192 | } 193 | } 194 | } 195 | 196 | impl<'env> Transaction for RoTransaction<'env> { 197 | fn txn(&self) -> *mut ffi::MDB_txn { 198 | self.txn 199 | } 200 | } 201 | 202 | /// An inactive read-only transaction. 203 | pub struct InactiveTransaction<'env> { 204 | txn: *mut ffi::MDB_txn, 205 | _marker: PhantomData<&'env ()>, 206 | } 207 | 208 | impl<'env> fmt::Debug for InactiveTransaction<'env> { 209 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 210 | f.debug_struct("InactiveTransaction").finish() 211 | } 212 | } 213 | 214 | impl<'env> Drop for InactiveTransaction<'env> { 215 | fn drop(&mut self) { 216 | unsafe { ffi::mdb_txn_abort(self.txn) } 217 | } 218 | } 219 | 220 | impl<'env> InactiveTransaction<'env> { 221 | /// Renews the inactive transaction, returning an active read-only 222 | /// transaction. 223 | /// 224 | /// This acquires a new reader lock for a transaction handle that had been 225 | /// released by `RoTransaction::reset`. 226 | pub fn renew(self) -> Result> { 227 | let txn = self.txn; 228 | unsafe { 229 | mem::forget(self); 230 | lmdb_result(ffi::mdb_txn_renew(txn))? 231 | }; 232 | Ok(RoTransaction { 233 | txn, 234 | _marker: PhantomData, 235 | }) 236 | } 237 | } 238 | 239 | /// An LMDB read-write transaction. 240 | pub struct RwTransaction<'env> { 241 | txn: *mut ffi::MDB_txn, 242 | _marker: PhantomData<&'env ()>, 243 | } 244 | 245 | impl<'env> fmt::Debug for RwTransaction<'env> { 246 | fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { 247 | f.debug_struct("RwTransaction").finish() 248 | } 249 | } 250 | 251 | impl<'env> Drop for RwTransaction<'env> { 252 | fn drop(&mut self) { 253 | unsafe { ffi::mdb_txn_abort(self.txn) } 254 | } 255 | } 256 | 257 | impl<'env> RwTransaction<'env> { 258 | /// Creates a new read-write transaction in the given environment. Prefer 259 | /// using `Environment::begin_ro_txn`. 260 | pub(crate) fn new(env: &'env Environment) -> Result> { 261 | let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); 262 | unsafe { 263 | lmdb_result(ffi::mdb_txn_begin(env.env(), ptr::null_mut(), EnvironmentFlags::empty().bits(), &mut txn))?; 264 | Ok(RwTransaction { 265 | txn, 266 | _marker: PhantomData, 267 | }) 268 | } 269 | } 270 | 271 | /// Opens a database in the provided transaction, creating it if necessary. 272 | /// 273 | /// If `name` is `None`, then the default database will be opened, otherwise 274 | /// a named database will be opened. The database handle will be private to 275 | /// the transaction until the transaction is successfully committed. If the 276 | /// transaction is aborted the returned database handle should no longer be 277 | /// used. 278 | /// 279 | /// Prefer using `Environment::create_db`. 280 | /// 281 | /// ## Safety 282 | /// 283 | /// This function (as well as `Environment::open_db`, 284 | /// `Environment::create_db`, and `Database::open`) **must not** be called 285 | /// from multiple concurrent transactions in the same environment. A 286 | /// transaction which uses this function must finish (either commit or 287 | /// abort) before any other transaction may use this function. 288 | pub unsafe fn create_db(&self, name: Option<&str>, flags: DatabaseFlags) -> Result { 289 | Database::new(self.txn(), name, flags.bits() | ffi::MDB_CREATE) 290 | } 291 | 292 | /// Opens a new read-write cursor on the given database and transaction. 293 | pub fn open_rw_cursor<'txn>(&'txn mut self, db: Database) -> Result> { 294 | RwCursor::new(self, db) 295 | } 296 | 297 | /// Stores an item into a database. 298 | /// 299 | /// This function stores key/data pairs in the database. The default 300 | /// behavior is to enter the new key/data pair, replacing any previously 301 | /// existing key if duplicates are disallowed, or adding a duplicate data 302 | /// item if duplicates are allowed (`DatabaseFlags::DUP_SORT`). 303 | pub fn put(&mut self, database: Database, key: &K, data: &D, flags: WriteFlags) -> Result<()> 304 | where 305 | K: AsRef<[u8]>, 306 | D: AsRef<[u8]>, 307 | { 308 | let key = key.as_ref(); 309 | let data = data.as_ref(); 310 | let mut key_val: ffi::MDB_val = ffi::MDB_val { 311 | mv_size: key.len() as size_t, 312 | mv_data: key.as_ptr() as *mut c_void, 313 | }; 314 | let mut data_val: ffi::MDB_val = ffi::MDB_val { 315 | mv_size: data.len() as size_t, 316 | mv_data: data.as_ptr() as *mut c_void, 317 | }; 318 | unsafe { lmdb_result(ffi::mdb_put(self.txn(), database.dbi(), &mut key_val, &mut data_val, flags.bits())) } 319 | } 320 | 321 | /// Returns a buffer which can be used to write a value into the item at the 322 | /// given key and with the given length. The buffer must be completely 323 | /// filled by the caller. 324 | pub fn reserve<'txn, K>( 325 | &'txn mut self, 326 | database: Database, 327 | key: &K, 328 | len: size_t, 329 | flags: WriteFlags, 330 | ) -> Result<&'txn mut [u8]> 331 | where 332 | K: AsRef<[u8]>, 333 | { 334 | let key = key.as_ref(); 335 | let mut key_val: ffi::MDB_val = ffi::MDB_val { 336 | mv_size: key.len() as size_t, 337 | mv_data: key.as_ptr() as *mut c_void, 338 | }; 339 | let mut data_val: ffi::MDB_val = ffi::MDB_val { 340 | mv_size: len, 341 | mv_data: ptr::null_mut::(), 342 | }; 343 | unsafe { 344 | lmdb_result(ffi::mdb_put( 345 | self.txn(), 346 | database.dbi(), 347 | &mut key_val, 348 | &mut data_val, 349 | flags.bits() | ffi::MDB_RESERVE, 350 | ))?; 351 | Ok(slice::from_raw_parts_mut(data_val.mv_data as *mut u8, data_val.mv_size as usize)) 352 | } 353 | } 354 | 355 | /// Deletes an item from a database. 356 | /// 357 | /// This function removes key/data pairs from the database. If the database 358 | /// does not support sorted duplicate data items (`DatabaseFlags::DUP_SORT`) 359 | /// the data parameter is ignored. If the database supports sorted 360 | /// duplicates and the data parameter is `None`, all of the duplicate data 361 | /// items for the key will be deleted. Otherwise, if the data parameter is 362 | /// `Some` only the matching data item will be deleted. This function will 363 | /// return `Error::NotFound` if the specified key/data pair is not in the 364 | /// database. 365 | pub fn del(&mut self, database: Database, key: &K, data: Option<&[u8]>) -> Result<()> 366 | where 367 | K: AsRef<[u8]>, 368 | { 369 | let key = key.as_ref(); 370 | let mut key_val: ffi::MDB_val = ffi::MDB_val { 371 | mv_size: key.len() as size_t, 372 | mv_data: key.as_ptr() as *mut c_void, 373 | }; 374 | let data_val: Option = data.map(|data| ffi::MDB_val { 375 | mv_size: data.len() as size_t, 376 | mv_data: data.as_ptr() as *mut c_void, 377 | }); 378 | 379 | if let Some(mut d) = data_val { 380 | unsafe { lmdb_result(ffi::mdb_del(self.txn(), database.dbi(), &mut key_val, &mut d)) } 381 | } else { 382 | unsafe { lmdb_result(ffi::mdb_del(self.txn(), database.dbi(), &mut key_val, ptr::null_mut())) } 383 | } 384 | } 385 | 386 | /// Empties the given database. All items will be removed. 387 | pub fn clear_db(&mut self, db: Database) -> Result<()> { 388 | unsafe { lmdb_result(ffi::mdb_drop(self.txn(), db.dbi(), 0)) } 389 | } 390 | 391 | /// Drops the database from the environment. 392 | /// 393 | /// ## Safety 394 | /// 395 | /// This method is unsafe in the same ways as `Environment::close_db`, and 396 | /// should be used accordingly. 397 | pub unsafe fn drop_db(&mut self, db: Database) -> Result<()> { 398 | lmdb_result(ffi::mdb_drop(self.txn, db.dbi(), 1)) 399 | } 400 | 401 | /// Begins a new nested transaction inside of this transaction. 402 | pub fn begin_nested_txn<'txn>(&'txn mut self) -> Result> { 403 | let mut nested: *mut ffi::MDB_txn = ptr::null_mut(); 404 | unsafe { 405 | let env: *mut ffi::MDB_env = ffi::mdb_txn_env(self.txn()); 406 | ffi::mdb_txn_begin(env, self.txn(), 0, &mut nested); 407 | } 408 | Ok(RwTransaction { 409 | txn: nested, 410 | _marker: PhantomData, 411 | }) 412 | } 413 | } 414 | 415 | impl<'env> Transaction for RwTransaction<'env> { 416 | fn txn(&self) -> *mut ffi::MDB_txn { 417 | self.txn 418 | } 419 | } 420 | 421 | #[cfg(test)] 422 | mod test { 423 | 424 | use std::io::Write; 425 | use std::sync::{ 426 | Arc, 427 | Barrier, 428 | }; 429 | use std::thread::{ 430 | self, 431 | JoinHandle, 432 | }; 433 | 434 | use tempdir::TempDir; 435 | 436 | use super::*; 437 | use cursor::Cursor; 438 | use error::*; 439 | use flags::*; 440 | 441 | #[test] 442 | fn test_put_get_del() { 443 | let dir = TempDir::new("test").unwrap(); 444 | let env = Environment::new().open(dir.path()).unwrap(); 445 | let db = env.open_db(None).unwrap(); 446 | 447 | let mut txn = env.begin_rw_txn().unwrap(); 448 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 449 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 450 | txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); 451 | txn.commit().unwrap(); 452 | 453 | let mut txn = env.begin_rw_txn().unwrap(); 454 | assert_eq!(b"val1", txn.get(db, b"key1").unwrap()); 455 | assert_eq!(b"val2", txn.get(db, b"key2").unwrap()); 456 | assert_eq!(b"val3", txn.get(db, b"key3").unwrap()); 457 | assert_eq!(txn.get(db, b"key"), Err(Error::NotFound)); 458 | 459 | txn.del(db, b"key1", None).unwrap(); 460 | assert_eq!(txn.get(db, b"key1"), Err(Error::NotFound)); 461 | } 462 | 463 | #[test] 464 | fn test_put_get_del_multi() { 465 | let dir = TempDir::new("test").unwrap(); 466 | let env = Environment::new().open(dir.path()).unwrap(); 467 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 468 | 469 | let mut txn = env.begin_rw_txn().unwrap(); 470 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 471 | txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); 472 | txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); 473 | txn.put(db, b"key2", b"val1", WriteFlags::empty()).unwrap(); 474 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 475 | txn.put(db, b"key2", b"val3", WriteFlags::empty()).unwrap(); 476 | txn.put(db, b"key3", b"val1", WriteFlags::empty()).unwrap(); 477 | txn.put(db, b"key3", b"val2", WriteFlags::empty()).unwrap(); 478 | txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); 479 | txn.commit().unwrap(); 480 | 481 | let txn = env.begin_rw_txn().unwrap(); 482 | { 483 | let mut cur = txn.open_ro_cursor(db).unwrap(); 484 | let iter = cur.iter_dup_of(b"key1"); 485 | let vals = iter.map(|x| x.unwrap()).map(|(_, x)| x).collect::>(); 486 | assert_eq!(vals, vec![b"val1", b"val2", b"val3"]); 487 | } 488 | txn.commit().unwrap(); 489 | 490 | let mut txn = env.begin_rw_txn().unwrap(); 491 | txn.del(db, b"key1", Some(b"val2")).unwrap(); 492 | txn.del(db, b"key2", None).unwrap(); 493 | txn.commit().unwrap(); 494 | 495 | let txn = env.begin_rw_txn().unwrap(); 496 | { 497 | let mut cur = txn.open_ro_cursor(db).unwrap(); 498 | let iter = cur.iter_dup_of(b"key1"); 499 | let vals = iter.map(|x| x.unwrap()).map(|(_, x)| x).collect::>(); 500 | assert_eq!(vals, vec![b"val1", b"val3"]); 501 | 502 | let iter = cur.iter_dup_of(b"key2"); 503 | assert_eq!(0, iter.count()); 504 | } 505 | txn.commit().unwrap(); 506 | } 507 | 508 | #[test] 509 | fn test_reserve() { 510 | let dir = TempDir::new("test").unwrap(); 511 | let env = Environment::new().open(dir.path()).unwrap(); 512 | let db = env.open_db(None).unwrap(); 513 | 514 | let mut txn = env.begin_rw_txn().unwrap(); 515 | { 516 | let mut writer = txn.reserve(db, b"key1", 4, WriteFlags::empty()).unwrap(); 517 | writer.write_all(b"val1").unwrap(); 518 | } 519 | txn.commit().unwrap(); 520 | 521 | let mut txn = env.begin_rw_txn().unwrap(); 522 | assert_eq!(b"val1", txn.get(db, b"key1").unwrap()); 523 | assert_eq!(txn.get(db, b"key"), Err(Error::NotFound)); 524 | 525 | txn.del(db, b"key1", None).unwrap(); 526 | assert_eq!(txn.get(db, b"key1"), Err(Error::NotFound)); 527 | } 528 | 529 | #[test] 530 | fn test_inactive_txn() { 531 | let dir = TempDir::new("test").unwrap(); 532 | let env = Environment::new().open(dir.path()).unwrap(); 533 | let db = env.open_db(None).unwrap(); 534 | 535 | { 536 | let mut txn = env.begin_rw_txn().unwrap(); 537 | txn.put(db, b"key", b"val", WriteFlags::empty()).unwrap(); 538 | txn.commit().unwrap(); 539 | } 540 | 541 | let txn = env.begin_ro_txn().unwrap(); 542 | let inactive = txn.reset(); 543 | let active = inactive.renew().unwrap(); 544 | assert!(active.get(db, b"key").is_ok()); 545 | } 546 | 547 | #[test] 548 | fn test_nested_txn() { 549 | let dir = TempDir::new("test").unwrap(); 550 | let env = Environment::new().open(dir.path()).unwrap(); 551 | let db = env.open_db(None).unwrap(); 552 | 553 | let mut txn = env.begin_rw_txn().unwrap(); 554 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 555 | 556 | { 557 | let mut nested = txn.begin_nested_txn().unwrap(); 558 | nested.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 559 | assert_eq!(nested.get(db, b"key1").unwrap(), b"val1"); 560 | assert_eq!(nested.get(db, b"key2").unwrap(), b"val2"); 561 | } 562 | 563 | assert_eq!(txn.get(db, b"key1").unwrap(), b"val1"); 564 | assert_eq!(txn.get(db, b"key2"), Err(Error::NotFound)); 565 | } 566 | 567 | #[test] 568 | fn test_clear_db() { 569 | let dir = TempDir::new("test").unwrap(); 570 | let env = Environment::new().open(dir.path()).unwrap(); 571 | let db = env.open_db(None).unwrap(); 572 | 573 | { 574 | let mut txn = env.begin_rw_txn().unwrap(); 575 | txn.put(db, b"key", b"val", WriteFlags::empty()).unwrap(); 576 | txn.commit().unwrap(); 577 | } 578 | 579 | { 580 | let mut txn = env.begin_rw_txn().unwrap(); 581 | txn.clear_db(db).unwrap(); 582 | txn.commit().unwrap(); 583 | } 584 | 585 | let txn = env.begin_ro_txn().unwrap(); 586 | assert_eq!(txn.get(db, b"key"), Err(Error::NotFound)); 587 | } 588 | 589 | #[test] 590 | fn test_drop_db() { 591 | let dir = TempDir::new("test").unwrap(); 592 | let env = Environment::new().set_max_dbs(2).open(dir.path()).unwrap(); 593 | let db = env.create_db(Some("test"), DatabaseFlags::empty()).unwrap(); 594 | 595 | { 596 | let mut txn = env.begin_rw_txn().unwrap(); 597 | txn.put(db, b"key", b"val", WriteFlags::empty()).unwrap(); 598 | txn.commit().unwrap(); 599 | } 600 | { 601 | let mut txn = env.begin_rw_txn().unwrap(); 602 | unsafe { 603 | txn.drop_db(db).unwrap(); 604 | } 605 | txn.commit().unwrap(); 606 | } 607 | 608 | assert_eq!(env.open_db(Some("test")), Err(Error::NotFound)); 609 | } 610 | 611 | #[test] 612 | fn test_concurrent_readers_single_writer() { 613 | let dir = TempDir::new("test").unwrap(); 614 | let env: Arc = Arc::new(Environment::new().open(dir.path()).unwrap()); 615 | 616 | let n = 10usize; // Number of concurrent readers 617 | let barrier = Arc::new(Barrier::new(n + 1)); 618 | let mut threads: Vec> = Vec::with_capacity(n); 619 | 620 | let key = b"key"; 621 | let val = b"val"; 622 | 623 | for _ in 0..n { 624 | let reader_env = env.clone(); 625 | let reader_barrier = barrier.clone(); 626 | 627 | threads.push(thread::spawn(move || { 628 | let db = reader_env.open_db(None).unwrap(); 629 | { 630 | let txn = reader_env.begin_ro_txn().unwrap(); 631 | assert_eq!(txn.get(db, key), Err(Error::NotFound)); 632 | txn.abort(); 633 | } 634 | reader_barrier.wait(); 635 | reader_barrier.wait(); 636 | { 637 | let txn = reader_env.begin_ro_txn().unwrap(); 638 | txn.get(db, key).unwrap() == val 639 | } 640 | })); 641 | } 642 | 643 | let db = env.open_db(None).unwrap(); 644 | let mut txn = env.begin_rw_txn().unwrap(); 645 | barrier.wait(); 646 | txn.put(db, key, val, WriteFlags::empty()).unwrap(); 647 | txn.commit().unwrap(); 648 | barrier.wait(); 649 | 650 | assert!(threads.into_iter().all(|b| b.join().unwrap())) 651 | } 652 | 653 | #[test] 654 | fn test_concurrent_writers() { 655 | let dir = TempDir::new("test").unwrap(); 656 | let env = Arc::new(Environment::new().open(dir.path()).unwrap()); 657 | 658 | let n = 10usize; // Number of concurrent writers 659 | let mut threads: Vec> = Vec::with_capacity(n); 660 | 661 | let key = "key"; 662 | let val = "val"; 663 | 664 | for i in 0..n { 665 | let writer_env = env.clone(); 666 | 667 | threads.push(thread::spawn(move || { 668 | let db = writer_env.open_db(None).unwrap(); 669 | let mut txn = writer_env.begin_rw_txn().unwrap(); 670 | txn.put(db, &format!("{}{}", key, i), &format!("{}{}", val, i), WriteFlags::empty()).unwrap(); 671 | txn.commit().is_ok() 672 | })); 673 | } 674 | assert!(threads.into_iter().all(|b| b.join().unwrap())); 675 | 676 | let db = env.open_db(None).unwrap(); 677 | let txn = env.begin_ro_txn().unwrap(); 678 | 679 | for i in 0..n { 680 | assert_eq!(format!("{}{}", val, i).as_bytes(), txn.get(db, &format!("{}{}", key, i)).unwrap()); 681 | } 682 | } 683 | 684 | #[test] 685 | fn test_stat() { 686 | let dir = TempDir::new("test").unwrap(); 687 | let env = Environment::new().open(dir.path()).unwrap(); 688 | let db = env.create_db(None, DatabaseFlags::empty()).unwrap(); 689 | 690 | let mut txn = env.begin_rw_txn().unwrap(); 691 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 692 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 693 | txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); 694 | txn.commit().unwrap(); 695 | 696 | { 697 | let txn = env.begin_ro_txn().unwrap(); 698 | let stat = txn.stat(db).unwrap(); 699 | assert_eq!(stat.entries(), 3); 700 | } 701 | 702 | let mut txn = env.begin_rw_txn().unwrap(); 703 | txn.del(db, b"key1", None).unwrap(); 704 | txn.del(db, b"key2", None).unwrap(); 705 | txn.commit().unwrap(); 706 | 707 | { 708 | let txn = env.begin_ro_txn().unwrap(); 709 | let stat = txn.stat(db).unwrap(); 710 | assert_eq!(stat.entries(), 1); 711 | } 712 | 713 | let mut txn = env.begin_rw_txn().unwrap(); 714 | txn.put(db, b"key4", b"val4", WriteFlags::empty()).unwrap(); 715 | txn.put(db, b"key5", b"val5", WriteFlags::empty()).unwrap(); 716 | txn.put(db, b"key6", b"val6", WriteFlags::empty()).unwrap(); 717 | txn.commit().unwrap(); 718 | 719 | { 720 | let txn = env.begin_ro_txn().unwrap(); 721 | let stat = txn.stat(db).unwrap(); 722 | assert_eq!(stat.entries(), 4); 723 | } 724 | } 725 | 726 | #[test] 727 | fn test_stat_dupsort() { 728 | let dir = TempDir::new("test").unwrap(); 729 | let env = Environment::new().open(dir.path()).unwrap(); 730 | let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); 731 | 732 | let mut txn = env.begin_rw_txn().unwrap(); 733 | txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); 734 | txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); 735 | txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); 736 | txn.put(db, b"key2", b"val1", WriteFlags::empty()).unwrap(); 737 | txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); 738 | txn.put(db, b"key2", b"val3", WriteFlags::empty()).unwrap(); 739 | txn.put(db, b"key3", b"val1", WriteFlags::empty()).unwrap(); 740 | txn.put(db, b"key3", b"val2", WriteFlags::empty()).unwrap(); 741 | txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); 742 | txn.commit().unwrap(); 743 | 744 | { 745 | let txn = env.begin_ro_txn().unwrap(); 746 | let stat = txn.stat(db).unwrap(); 747 | assert_eq!(stat.entries(), 9); 748 | } 749 | 750 | let mut txn = env.begin_rw_txn().unwrap(); 751 | txn.del(db, b"key1", Some(b"val2")).unwrap(); 752 | txn.del(db, b"key2", None).unwrap(); 753 | txn.commit().unwrap(); 754 | 755 | { 756 | let txn = env.begin_ro_txn().unwrap(); 757 | let stat = txn.stat(db).unwrap(); 758 | assert_eq!(stat.entries(), 5); 759 | } 760 | 761 | let mut txn = env.begin_rw_txn().unwrap(); 762 | txn.put(db, b"key4", b"val1", WriteFlags::empty()).unwrap(); 763 | txn.put(db, b"key4", b"val2", WriteFlags::empty()).unwrap(); 764 | txn.put(db, b"key4", b"val3", WriteFlags::empty()).unwrap(); 765 | txn.commit().unwrap(); 766 | 767 | { 768 | let txn = env.begin_ro_txn().unwrap(); 769 | let stat = txn.stat(db).unwrap(); 770 | assert_eq!(stat.entries(), 8); 771 | } 772 | } 773 | } 774 | --------------------------------------------------------------------------------