├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── .gitmodules
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── heed-pigeon-logo.png
└── heed-pigeon.ico
├── convert-to-heed3.sh
├── heed-traits
├── Cargo.toml
└── src
│ └── lib.rs
├── heed-types
├── Cargo.toml
└── src
│ ├── bytes.rs
│ ├── decode_ignore.rs
│ ├── integer.rs
│ ├── lazy_decode.rs
│ ├── lib.rs
│ ├── serde_bincode.rs
│ ├── serde_json.rs
│ ├── serde_rmp.rs
│ ├── str.rs
│ └── unit.rs
├── heed
├── Cargo.toml
├── build.rs
├── examples
│ ├── all-types.rs
│ ├── clear-database.rs
│ ├── cursor-append.rs
│ ├── custom-comparator.rs
│ ├── custom-dupsort-comparator.rs
│ ├── multi-env.rs
│ ├── nested.rs
│ ├── prev-snapshot.rs
│ └── rmp-serde.rs
└── src
│ ├── cookbook.rs
│ ├── cursor.rs
│ ├── databases
│ ├── database.rs
│ ├── encrypted_database.rs
│ └── mod.rs
│ ├── envs
│ ├── encrypted_env.rs
│ ├── env.rs
│ ├── env_open_options.rs
│ └── mod.rs
│ ├── iteration_method.rs
│ ├── iterator
│ ├── iter.rs
│ ├── mod.rs
│ ├── prefix.rs
│ └── range.rs
│ ├── lib.rs
│ ├── mdb
│ ├── lmdb_error.rs
│ ├── lmdb_ffi.rs
│ ├── lmdb_flags.rs
│ └── mod.rs
│ ├── reserved_space.rs
│ └── txn.rs
├── heed3
├── Cargo.toml
└── examples
│ ├── heed3-all-types.rs
│ ├── heed3-encrypted.rs
│ └── prev-snapshot.rs
├── lmdb-master-sys
├── .rustfmt.toml
├── Cargo.toml
├── README.md
├── bindgen.rs
├── build.rs
├── src
│ ├── bindings.rs
│ └── lib.rs
└── tests
│ ├── fixtures
│ └── testdb-32
│ │ ├── data.mdb
│ │ └── lock.mdb
│ ├── lmdb.rs
│ └── simple.rs
└── lmdb-master3-sys
├── .rustfmt.toml
├── Cargo.toml
├── README.md
├── bindgen.rs
├── build.rs
└── src
├── bindings.rs
└── lib.rs
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | merge_group:
4 |
5 | name: Rust
6 | jobs:
7 | heed-test:
8 | name: Test the heed project
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | os: [ubuntu-latest, windows-latest, macos-latest]
13 | include:
14 | - os: ubuntu-latest
15 | - os: windows-latest
16 | - os: macos-latest
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | submodules: recursive
21 | - uses: actions-rs/toolchain@v1
22 | with:
23 | profile: minimal
24 | toolchain: stable
25 | override: true
26 | - name: Run cargo test
27 | run: |
28 | cargo clean
29 | cargo test
30 |
31 | heed3-test:
32 | name: Test the heed3 project
33 | runs-on: ${{ matrix.os }}
34 | strategy:
35 | matrix:
36 | os: [ubuntu-latest, windows-latest, macos-latest]
37 | include:
38 | - os: ubuntu-latest
39 | - os: windows-latest
40 | - os: macos-latest
41 | steps:
42 | - uses: actions/checkout@v2
43 | with:
44 | submodules: recursive
45 | - uses: actions-rs/toolchain@v1
46 | with:
47 | profile: minimal
48 | toolchain: stable
49 | override: true
50 | - name: Run cargo test
51 | run: |
52 | cargo clean
53 | bash convert-to-heed3.sh
54 | cargo test
55 |
56 | check-heed3:
57 | name: Check the heed3 project
58 | runs-on: ${{ matrix.os }}
59 | strategy:
60 | matrix:
61 | os: [ubuntu-latest, windows-latest, macos-latest]
62 | include:
63 | - os: ubuntu-latest
64 | - os: windows-latest
65 | - os: macos-latest
66 | steps:
67 | - uses: actions/checkout@v2
68 | with:
69 | submodules: recursive
70 | - uses: actions-rs/toolchain@v1
71 | with:
72 | profile: minimal
73 | toolchain: stable
74 | override: true
75 | - name: Run cargo check
76 | run: |
77 | cargo clean
78 | bash convert-to-heed3.sh
79 | cargo check -p heed3
80 |
81 | check-all-features:
82 | name: Check all the features of the heed project
83 | runs-on: ubuntu-latest
84 | env:
85 | RUSTFLAGS: -D warnings
86 | steps:
87 | - uses: actions/checkout@v2
88 | with:
89 | submodules: recursive
90 | - uses: actions-rs/toolchain@v1
91 | with:
92 | profile: minimal
93 | toolchain: stable
94 | override: true
95 | - id: check_toml
96 | run: |
97 | if grep -q 'name = "heed3"' heed/Cargo.toml; then
98 | echo "should_skip=true" >> $GITHUB_OUTPUT
99 | else
100 | echo "should_skip=false" >> $GITHUB_OUTPUT
101 | fi
102 | - name: Run cargo test
103 | # Skip it if the CI is running with the heed3 Cargo.toml
104 | if: ${{ steps.check_toml.outputs.should_skip == 'false' }}
105 | run: |
106 | sudo apt install -y valgrind
107 | cargo clean
108 | cargo check --all-features -p heed
109 |
110 | check-all-features-heed3:
111 | name: Check all the features of the heed3 project
112 | runs-on: ubuntu-latest
113 | env:
114 | RUSTFLAGS: -D warnings
115 | steps:
116 | - uses: actions/checkout@v2
117 | with:
118 | submodules: recursive
119 | - uses: actions-rs/toolchain@v1
120 | with:
121 | profile: minimal
122 | toolchain: stable
123 | override: true
124 | - name: Run cargo test
125 | run: |
126 | sudo apt install -y valgrind
127 | cargo clean
128 | bash convert-to-heed3.sh
129 | cargo check --all-features -p heed3
130 |
131 | examples:
132 | name: Run the heed examples
133 | runs-on: ${{ matrix.os }}
134 | strategy:
135 | matrix:
136 | os: [ubuntu-latest, macos-latest]
137 | include:
138 | - os: ubuntu-latest
139 | - os: macos-latest
140 | steps:
141 | - uses: actions/checkout@v2
142 | with:
143 | submodules: recursive
144 | - uses: actions-rs/toolchain@v1
145 | with:
146 | profile: minimal
147 | toolchain: stable
148 | override: true
149 | - id: check_toml
150 | run: |
151 | if grep -q 'name = "heed3"' heed/Cargo.toml; then
152 | echo "should_skip=true" >> $GITHUB_OUTPUT
153 | else
154 | echo "should_skip=false" >> $GITHUB_OUTPUT
155 | fi
156 | - name: Run the examples
157 | # Skip it if the CI is running with the heed3 Cargo.toml
158 | if: ${{ steps.check_toml.outputs.should_skip == 'false' }}
159 | run: |
160 | cargo clean
161 | # rmp-serde needs a feature activated, so we'll just run it separately.
162 | cargo run --example 2>&1 | grep -E '^ ' | awk '!/rmp-serde/' | xargs -n1 cargo run --example
163 | cargo run --example rmp-serde --features serde-rmp
164 |
165 | heed3-examples:
166 | name: Run the heed3 examples
167 | runs-on: ${{ matrix.os }}
168 | strategy:
169 | matrix:
170 | os: [ubuntu-latest, macos-latest]
171 | include:
172 | - os: ubuntu-latest
173 | - os: macos-latest
174 | steps:
175 | - uses: actions/checkout@v2
176 | with:
177 | submodules: recursive
178 | - uses: actions-rs/toolchain@v1
179 | with:
180 | profile: minimal
181 | toolchain: stable
182 | override: true
183 | - name: Run the examples
184 | run: |
185 | cargo clean
186 | bash convert-to-heed3.sh
187 | cargo run --example 2>&1 | grep -E '^ '| xargs -n1 cargo run --example
188 |
189 | clippy:
190 | name: Ensure clippy is happy on heed and heed3
191 | runs-on: ubuntu-latest
192 | steps:
193 | - uses: actions/checkout@v2
194 | with:
195 | submodules: recursive
196 | - uses: actions-rs/toolchain@v1
197 | with:
198 | profile: minimal
199 | toolchain: stable
200 | override: true
201 | - name: Run the examples
202 | run: |
203 | cargo clippy --all-targets -- --deny warnings
204 | bash convert-to-heed3.sh
205 | cargo clippy --all-targets -- --deny warnings
206 |
207 | fmt:
208 | name: Ensure the heed and heed3 project are formatted
209 | runs-on: ubuntu-latest
210 | steps:
211 | - uses: actions/checkout@v2
212 | - uses: actions-rs/toolchain@v1
213 | with:
214 | profile: minimal
215 | toolchain: nightly
216 | override: true
217 | components: rustfmt
218 | - name: Run cargo fmt
219 | run: |
220 | cargo fmt --check
221 | bash convert-to-heed3.sh
222 | cargo fmt --check
223 |
224 | no-heed3-in-heed-folder:
225 | name: Ensure heed3 is not erasing heed
226 | runs-on: ubuntu-latest
227 | steps:
228 | - uses: actions/checkout@v2
229 | - name: Check name is heed with grep
230 | run: grep -q 'name = "heed"' heed/Cargo.toml
231 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | heed/target
3 | **/*.rs.bk
4 | Cargo.lock
5 | *.mdb
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lmdb-master-sys/lmdb"]
2 | path = lmdb-master-sys/lmdb
3 | url = https://git.openldap.org/openldap/openldap
4 | branch = mdb.master
5 | [submodule "lmdb-master3-sys/lmdb"]
6 | path = lmdb-master3-sys/lmdb
7 | url = https://git.openldap.org/openldap/openldap
8 | branch = mdb.master3
9 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | unstable_features = true
2 |
3 | use_small_heuristics = "max"
4 | imports_granularity = "Module"
5 | group_imports = "StdExternalCrate"
6 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["lmdb-master-sys", "lmdb-master3-sys", "heed", "heed-traits", "heed-types"]
3 | resolver = "2"
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Clément Renault
4 | Copyright (c) 2019-2025 Meili SAS
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 | heed & heed3
3 |
4 | [](#LICENSE)
5 | [](https://crates.io/crates/heed)
6 | [](https://docs.rs/heed)
7 | [](https://deps.rs/repo/github/meilisearch/heed)
8 | [](https://github.com/meilisearch/heed/actions/workflows/rust.yml)
9 | [](https://discord.com/channels/1006923006964154428/1347203493106024528)
10 |
11 | Rust-centric [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) abstractions with minimal overhead. These libraries enable the storage of various Rust types within LMDB, extending support to include Serde-compatible types. It supports not only the LMDB `mdb.master` branch but also the `mdb.master3` branch, which features encryption-at-rest.
12 |
13 | ## Simple Example Usage
14 |
15 | Here is an example on how to store and read entries into LMDB in a safe and ACID way. For usage examples, see [examples/](examples/). To see more advanced usage techniques go check our [Cookbook](https://docs.rs/heed/latest/heed/cookbook/index.html).
16 |
17 | ```rust
18 | use std::fs;
19 | use std::path::Path;
20 | use heed::{EnvOpenOptions, Database};
21 | use heed::types::*;
22 |
23 | fn main() -> Result<(), Box> {
24 | let env = unsafe { EnvOpenOptions::new().open("my-first-db")? };
25 |
26 | // We open the default unnamed database
27 | let mut wtxn = env.write_txn()?;
28 | let db: Database> = env.create_database(&mut wtxn, None)?;
29 |
30 | // We open a write transaction
31 | db.put(&mut wtxn, "seven", &7)?;
32 | db.put(&mut wtxn, "zero", &0)?;
33 | db.put(&mut wtxn, "five", &5)?;
34 | db.put(&mut wtxn, "three", &3)?;
35 | wtxn.commit()?;
36 |
37 | // We open a read transaction to check if those values are now available
38 | let mut rtxn = env.read_txn()?;
39 |
40 | let ret = db.get(&rtxn, "zero")?;
41 | assert_eq!(ret, Some(0));
42 |
43 | let ret = db.get(&rtxn, "five")?;
44 | assert_eq!(ret, Some(5));
45 |
46 | Ok(())
47 | }
48 | ```
49 |
50 | ## Working with two Crates: heed and heed3
51 |
52 | The heed and heed3 crates manage a shared codebase. Within the heed3 folder, you can find the Cargo.toml specific to the heed3 crate.
53 | To facilitate work on heed3, utilize the `convert-to-heed3.sh` script.
54 |
55 | This script conveniently moves the `heed3/Cargo.toml` file to the `heed/` folder, updates the `heed::` references to `heed3::`, and generates a commit for easy rollback if needed.
56 |
57 | ## Building from Source
58 |
59 | You can use this command to clone the repository:
60 |
61 | ```bash
62 | git clone --recursive https://github.com/meilisearch/heed.git
63 | cd heed
64 | cargo build
65 | ```
66 |
67 | However, if you already cloned it and forgot to initialize the submodules, execute the following command:
68 |
69 | ```bash
70 | git submodule update --init
71 | ```
72 |
--------------------------------------------------------------------------------
/assets/heed-pigeon-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meilisearch/heed/5a10a00e02669d28edd6d11d35b617a9128189ca/assets/heed-pigeon-logo.png
--------------------------------------------------------------------------------
/assets/heed-pigeon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meilisearch/heed/5a10a00e02669d28edd6d11d35b617a9128189ca/assets/heed-pigeon.ico
--------------------------------------------------------------------------------
/convert-to-heed3.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This script is meant to setup the heed3 crate.
4 | #
5 |
6 | if [[ -n $(git status -s) ]]; then
7 | echo "Error: Repository is git dirty, please commit or stash changes before running this script."
8 | exit 1
9 | fi
10 |
11 | set -e
12 |
13 | # It basically copy the heed3/Cargo.toml file into
14 | # the heed folder...
15 | if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
16 | cp heed3\\Cargo.toml heed\\Cargo.toml
17 | rm -rf heed\\examples
18 | cp -R heed3\\examples heed\\examples
19 | else
20 | cp heed3/Cargo.toml heed/Cargo.toml
21 | rm -rf heed/examples
22 | cp -R heed3/examples heed/examples
23 | fi
24 |
25 | # ...and replaces the `heed::` string by the `heed3::` one.
26 | for file in $(find heed/src -type f -name "*.rs"); do
27 | if [[ "$OSTYPE" == "darwin"* ]]; then
28 | sed -i '' 's/heed::/heed3::/g' "$file"
29 | else
30 | sed -i 's/heed::/heed3::/g' "$file"
31 | fi
32 | done
33 |
34 | # Make it easier to rollback by doing a commit
35 | git config --local user.email "ci@github.com"
36 | git config --local user.name "The CI"
37 |
38 | # Adds the new examples to the changes
39 | if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
40 | git add heed\\examples
41 | else
42 | git add heed/examples
43 | fi
44 |
45 | # But only commit if there is something to commit
46 | git diff -q --exit-code || git commit -am 'remove-me: heed3 changes generated by the convert-to-heed3.sh script'
47 |
48 | git config --local --unset user.email
49 | git config --local --unset user.name
50 |
51 | echo "Heed3 crate setup completed successfully. Configurations for the heed crate have been copied and modified."
52 | echo "A commit (starting with remove-me) has been generated and must be deleted before merging into the main branch."
53 |
--------------------------------------------------------------------------------
/heed-traits/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "heed-traits"
3 | version = "0.20.0"
4 | authors = ["Kerollmops "]
5 | description = "The traits used inside of the fully typed LMDB wrapper, heed"
6 | license = "MIT"
7 | repository = "https://github.com/Kerollmops/heed"
8 | readme = "../README.md"
9 | edition = "2021"
10 |
11 | [dependencies]
12 |
--------------------------------------------------------------------------------
/heed-traits/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc(
2 | html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets//heed-pigeon.ico?raw=true"
3 | )]
4 | #![doc(
5 | html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true"
6 | )]
7 |
8 | //! Contains the traits used to encode and decode database content.
9 |
10 | #![warn(missing_docs)]
11 |
12 | use std::borrow::Cow;
13 | use std::cmp::{Ord, Ordering};
14 | use std::error::Error as StdError;
15 |
16 | /// A boxed `Send + Sync + 'static` error.
17 | pub type BoxedError = Box;
18 |
19 | /// A trait that represents an encoding structure.
20 | pub trait BytesEncode<'a> {
21 | /// The type to encode.
22 | type EItem: ?Sized + 'a;
23 |
24 | /// Encode the given item as bytes.
25 | fn bytes_encode(item: &'a Self::EItem) -> Result, BoxedError>;
26 | }
27 |
28 | /// A trait that represents a decoding structure.
29 | pub trait BytesDecode<'a> {
30 | /// The type to decode.
31 | type DItem: 'a;
32 |
33 | /// Decode the given bytes as `DItem`.
34 | fn bytes_decode(bytes: &'a [u8]) -> Result;
35 | }
36 |
37 | /// Define a custom key comparison function for a database.
38 | ///
39 | /// The comparison function is called whenever it is necessary to compare a key specified
40 | /// by the application with a key currently stored in the database. If no comparison function
41 | /// is specified, and no special key flags were specified, the keys are compared lexically,
42 | /// with shorter keys collating before longer keys.
43 | pub trait Comparator {
44 | /// Compares the raw bytes representation of two keys.
45 | fn compare(a: &[u8], b: &[u8]) -> Ordering;
46 | }
47 |
48 | /// Define a lexicographic comparator, which is a special case of [`Comparator`].
49 | ///
50 | /// Types that implements [`LexicographicComparator`] will automatically have [`Comparator`]
51 | /// implemented as well, where the [`Comparator::compare`] is implemented per the definition
52 | /// of lexicographic ordering with [`LexicographicComparator::compare_elem`].
53 | ///
54 | /// This trait is introduced to support prefix iterators, which implicit assumes that the
55 | /// underlying key comparator is lexicographic.
56 | pub trait LexicographicComparator: Comparator {
57 | /// Compare a single byte; this function is used to implement [`Comparator::compare`]
58 | /// by definition of lexicographic ordering.
59 | fn compare_elem(a: u8, b: u8) -> Ordering;
60 |
61 | /// Advances the given `elem` to its immediate lexicographic successor, if possible.
62 | /// Returns `None` if `elem` is already at its maximum value with respect to the
63 | /// lexicographic order defined by this comparator.
64 | fn successor(elem: u8) -> Option;
65 |
66 | /// Moves the given `elem` to its immediate lexicographic predecessor, if possible.
67 | /// Returns `None` if `elem` is already at its minimum value with respect to the
68 | /// lexicographic order defined by this comparator.
69 | fn predecessor(elem: u8) -> Option;
70 |
71 | /// Returns the maximum byte value per the comparator's lexicographic order.
72 | fn max_elem() -> u8;
73 |
74 | /// Returns the minimum byte value per the comparator's lexicographic order.
75 | fn min_elem() -> u8;
76 | }
77 |
78 | impl Comparator for C {
79 | fn compare(a: &[u8], b: &[u8]) -> Ordering {
80 | for idx in 0..std::cmp::min(a.len(), b.len()) {
81 | if a[idx] != b[idx] {
82 | return C::compare_elem(a[idx], b[idx]);
83 | }
84 | }
85 | Ord::cmp(&a.len(), &b.len())
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/heed-types/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "heed-types"
3 | version = "0.21.0"
4 | authors = ["Kerollmops "]
5 | description = "The types used with the fully typed LMDB wrapper, heed"
6 | license = "MIT"
7 | repository = "https://github.com/Kerollmops/heed"
8 | readme = "../README.md"
9 | edition = "2021"
10 |
11 | [dependencies]
12 | bincode = { version = "1.3.3", optional = true }
13 | byteorder = "1.5.0"
14 | heed-traits = { version = "0.20.0", path = "../heed-traits" }
15 | serde = { version = "1.0.218", optional = true }
16 | serde_json = { version = "1.0.140", optional = true }
17 | rmp-serde = { version = "1.3.0", optional = true }
18 |
19 | [features]
20 | default = ["serde-bincode", "serde-json"]
21 | serde-bincode = ["serde", "bincode"]
22 | serde-json = ["serde", "serde_json"]
23 | serde-rmp = ["serde", "rmp-serde"]
24 | # serde_json features
25 | preserve_order = ["serde_json/preserve_order"]
26 | arbitrary_precision = ["serde_json/arbitrary_precision"]
27 | raw_value = ["serde_json/raw_value"]
28 | unbounded_depth = ["serde_json/unbounded_depth"]
29 |
--------------------------------------------------------------------------------
/heed-types/src/bytes.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 |
3 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
4 |
5 | /// Describes a byte slice `[u8]` that is totally borrowed and doesn't depend on
6 | /// any [memory alignment].
7 | ///
8 | /// [memory alignment]: std::mem::align_of()
9 | pub enum Bytes {}
10 |
11 | impl<'a> BytesEncode<'a> for Bytes {
12 | type EItem = [u8];
13 |
14 | fn bytes_encode(item: &'a Self::EItem) -> Result, BoxedError> {
15 | Ok(Cow::Borrowed(item))
16 | }
17 | }
18 |
19 | impl<'a> BytesDecode<'a> for Bytes {
20 | type DItem = &'a [u8];
21 |
22 | fn bytes_decode(bytes: &'a [u8]) -> Result {
23 | Ok(bytes)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/heed-types/src/decode_ignore.rs:
--------------------------------------------------------------------------------
1 | use heed_traits::BoxedError;
2 |
3 | /// A convenient struct made to ignore the type when decoding it.
4 | ///
5 | /// For example, it is appropriate to be used to count keys or to ensure that an
6 | /// entry exists.
7 | pub enum DecodeIgnore {}
8 |
9 | impl heed_traits::BytesDecode<'_> for DecodeIgnore {
10 | type DItem = ();
11 |
12 | fn bytes_decode(_bytes: &[u8]) -> Result {
13 | Ok(())
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/heed-types/src/integer.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::marker::PhantomData;
3 | use std::mem::size_of;
4 |
5 | use byteorder::{ByteOrder, ReadBytesExt};
6 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
7 |
8 | /// Encodable version of [`u8`].
9 | pub struct U8;
10 |
11 | impl BytesEncode<'_> for U8 {
12 | type EItem = u8;
13 |
14 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
15 | Ok(Cow::from([*item].to_vec()))
16 | }
17 | }
18 |
19 | impl BytesDecode<'_> for U8 {
20 | type DItem = u8;
21 |
22 | fn bytes_decode(mut bytes: &'_ [u8]) -> Result {
23 | bytes.read_u8().map_err(Into::into)
24 | }
25 | }
26 |
27 | /// Encodable version of [`i8`].
28 | pub struct I8;
29 |
30 | impl BytesEncode<'_> for I8 {
31 | type EItem = i8;
32 |
33 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
34 | Ok(Cow::from([*item as u8].to_vec()))
35 | }
36 | }
37 |
38 | impl BytesDecode<'_> for I8 {
39 | type DItem = i8;
40 |
41 | fn bytes_decode(mut bytes: &'_ [u8]) -> Result {
42 | bytes.read_i8().map_err(Into::into)
43 | }
44 | }
45 |
46 | macro_rules! define_type {
47 | ($name:ident, $native:ident, $read_method:ident, $write_method:ident) => {
48 | #[doc = "Encodable version of [`"]
49 | #[doc = stringify!($native)]
50 | #[doc = "`]."]
51 |
52 | pub struct $name(PhantomData);
53 |
54 | impl BytesEncode<'_> for $name {
55 | type EItem = $native;
56 |
57 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
58 | let mut buf = vec![0; size_of::()];
59 | O::$write_method(&mut buf, *item);
60 | Ok(Cow::from(buf))
61 | }
62 | }
63 |
64 | impl BytesDecode<'_> for $name {
65 | type DItem = $native;
66 |
67 | fn bytes_decode(mut bytes: &'_ [u8]) -> Result {
68 | bytes.$read_method::().map_err(Into::into)
69 | }
70 | }
71 | };
72 | }
73 |
74 | define_type!(U16, u16, read_u16, write_u16);
75 | define_type!(U32, u32, read_u32, write_u32);
76 | define_type!(U64, u64, read_u64, write_u64);
77 | define_type!(U128, u128, read_u128, write_u128);
78 | define_type!(I16, i16, read_i16, write_i16);
79 | define_type!(I32, i32, read_i32, write_i32);
80 | define_type!(I64, i64, read_i64, write_i64);
81 | define_type!(I128, i128, read_i128, write_i128);
82 |
--------------------------------------------------------------------------------
/heed-types/src/lazy_decode.rs:
--------------------------------------------------------------------------------
1 | use std::marker;
2 |
3 | use heed_traits::BoxedError;
4 |
5 | /// Lazily decodes the data bytes.
6 | ///
7 | /// It can be used to avoid CPU-intensive decoding before making sure that it
8 | /// actually needs to be decoded (e.g. based on the key).
9 | #[derive(Default)]
10 | pub struct LazyDecode(marker::PhantomData);
11 |
12 | impl<'a, C: 'static> heed_traits::BytesDecode<'a> for LazyDecode {
13 | type DItem = Lazy<'a, C>;
14 |
15 | fn bytes_decode(bytes: &'a [u8]) -> Result {
16 | Ok(Lazy { data: bytes, _phantom: marker::PhantomData })
17 | }
18 | }
19 |
20 | /// Owns bytes that can be decoded on demand.
21 | #[derive(Copy, Clone)]
22 | pub struct Lazy<'a, C> {
23 | data: &'a [u8],
24 | _phantom: marker::PhantomData,
25 | }
26 |
27 | impl<'a, C> Lazy<'a, C> {
28 | /// Change the codec type of the given bytes, specifying the new codec.
29 | pub fn remap(&self) -> Lazy<'a, NC> {
30 | Lazy { data: self.data, _phantom: marker::PhantomData }
31 | }
32 | }
33 |
34 | impl<'a, C: heed_traits::BytesDecode<'a>> Lazy<'a, C> {
35 | /// Decode the given bytes as `DItem`.
36 | pub fn decode(&self) -> Result {
37 | C::bytes_decode(self.data)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/heed-types/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc(
2 | html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon.ico?raw=true"
3 | )]
4 | #![doc(
5 | html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true"
6 | )]
7 |
8 | //! Types that can be used to serialize and deserialize types inside databases.
9 |
10 | #![warn(missing_docs)]
11 |
12 | mod bytes;
13 | mod decode_ignore;
14 | mod integer;
15 | mod lazy_decode;
16 | mod str;
17 | mod unit;
18 |
19 | #[cfg(feature = "serde-bincode")]
20 | mod serde_bincode;
21 |
22 | #[cfg(feature = "serde-json")]
23 | mod serde_json;
24 |
25 | #[cfg(feature = "serde-rmp")]
26 | mod serde_rmp;
27 |
28 | pub use self::bytes::Bytes;
29 | pub use self::decode_ignore::DecodeIgnore;
30 | pub use self::integer::*;
31 | pub use self::lazy_decode::{Lazy, LazyDecode};
32 | #[cfg(feature = "serde-bincode")]
33 | pub use self::serde_bincode::SerdeBincode;
34 | #[cfg(feature = "serde-json")]
35 | pub use self::serde_json::SerdeJson;
36 | #[cfg(feature = "serde-rmp")]
37 | pub use self::serde_rmp::SerdeRmp;
38 | pub use self::str::Str;
39 | pub use self::unit::Unit;
40 |
--------------------------------------------------------------------------------
/heed-types/src/serde_bincode.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 |
3 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
4 | use serde::{Deserialize, Serialize};
5 |
6 | /// Describes a type that is [`Serialize`]/[`Deserialize`] and uses `bincode` to do so.
7 | ///
8 | /// It can borrow bytes from the original slice.
9 | pub struct SerdeBincode(std::marker::PhantomData);
10 |
11 | impl<'a, T: 'a> BytesEncode<'a> for SerdeBincode
12 | where
13 | T: Serialize,
14 | {
15 | type EItem = T;
16 |
17 | fn bytes_encode(item: &'a Self::EItem) -> Result, BoxedError> {
18 | bincode::serialize(item).map(Cow::Owned).map_err(Into::into)
19 | }
20 | }
21 |
22 | impl<'a, T: 'a> BytesDecode<'a> for SerdeBincode
23 | where
24 | T: Deserialize<'a>,
25 | {
26 | type DItem = T;
27 |
28 | fn bytes_decode(bytes: &'a [u8]) -> Result {
29 | bincode::deserialize(bytes).map_err(Into::into)
30 | }
31 | }
32 |
33 | unsafe impl Send for SerdeBincode {}
34 |
35 | unsafe impl Sync for SerdeBincode {}
36 |
--------------------------------------------------------------------------------
/heed-types/src/serde_json.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 |
3 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
4 | use serde::{Deserialize, Serialize};
5 |
6 | /// Describes a type that is [`Serialize`]/[`Deserialize`] and uses `serde_json` to do so.
7 | ///
8 | /// It can borrow bytes from the original slice.
9 | pub struct SerdeJson(std::marker::PhantomData);
10 |
11 | impl<'a, T: 'a> BytesEncode<'a> for SerdeJson
12 | where
13 | T: Serialize,
14 | {
15 | type EItem = T;
16 |
17 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
18 | serde_json::to_vec(item).map(Cow::Owned).map_err(Into::into)
19 | }
20 | }
21 |
22 | impl<'a, T: 'a> BytesDecode<'a> for SerdeJson
23 | where
24 | T: Deserialize<'a>,
25 | {
26 | type DItem = T;
27 |
28 | fn bytes_decode(bytes: &'a [u8]) -> Result {
29 | serde_json::from_slice(bytes).map_err(Into::into)
30 | }
31 | }
32 |
33 | unsafe impl Send for SerdeJson {}
34 |
35 | unsafe impl Sync for SerdeJson {}
36 |
--------------------------------------------------------------------------------
/heed-types/src/serde_rmp.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 |
3 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
4 | use serde::{Deserialize, Serialize};
5 |
6 | /// Describes a type that is [`Serialize`]/[`Deserialize`] and uses `rmp_serde` to do so.
7 | ///
8 | /// It can borrow bytes from the original slice.
9 | pub struct SerdeRmp(std::marker::PhantomData);
10 |
11 | impl<'a, T: 'a> BytesEncode<'a> for SerdeRmp
12 | where
13 | T: Serialize,
14 | {
15 | type EItem = T;
16 |
17 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
18 | rmp_serde::to_vec(item).map(Cow::Owned).map_err(Into::into)
19 | }
20 | }
21 |
22 | impl<'a, T: 'a> BytesDecode<'a> for SerdeRmp
23 | where
24 | T: Deserialize<'a>,
25 | {
26 | type DItem = T;
27 |
28 | fn bytes_decode(bytes: &'a [u8]) -> Result {
29 | rmp_serde::from_slice(bytes).map_err(Into::into)
30 | }
31 | }
32 |
33 | unsafe impl Send for SerdeRmp {}
34 |
35 | unsafe impl Sync for SerdeRmp {}
36 |
--------------------------------------------------------------------------------
/heed-types/src/str.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::str;
3 |
4 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
5 |
6 | /// Describes a [`prim@str`].
7 | pub enum Str {}
8 |
9 | impl BytesEncode<'_> for Str {
10 | type EItem = str;
11 |
12 | fn bytes_encode(item: &Self::EItem) -> Result, BoxedError> {
13 | Ok(Cow::Borrowed(item.as_bytes()))
14 | }
15 | }
16 |
17 | impl<'a> BytesDecode<'a> for Str {
18 | type DItem = &'a str;
19 |
20 | fn bytes_decode(bytes: &'a [u8]) -> Result {
21 | str::from_utf8(bytes).map_err(Into::into)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/heed-types/src/unit.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::{error, fmt};
3 |
4 | use heed_traits::{BoxedError, BytesDecode, BytesEncode};
5 |
6 | /// Describes the unit `()` type.
7 | pub enum Unit {}
8 |
9 | impl BytesEncode<'_> for Unit {
10 | type EItem = ();
11 |
12 | fn bytes_encode(_item: &Self::EItem) -> Result, BoxedError> {
13 | Ok(Cow::Borrowed(&[]))
14 | }
15 | }
16 |
17 | impl BytesDecode<'_> for Unit {
18 | type DItem = ();
19 |
20 | fn bytes_decode(bytes: &[u8]) -> Result {
21 | if bytes.is_empty() {
22 | Ok(())
23 | } else {
24 | Err(NonEmptyError.into())
25 | }
26 | }
27 | }
28 |
29 | /// The slice of bytes is non-empty and therefore is not a unit `()` type.
30 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31 | pub struct NonEmptyError;
32 |
33 | impl fmt::Display for NonEmptyError {
34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 | f.write_str("the slice of bytes is non-empty and therefore is not a unit `()` type")
36 | }
37 | }
38 |
39 | impl error::Error for NonEmptyError {}
40 |
--------------------------------------------------------------------------------
/heed/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "heed"
3 | version = "0.22.0"
4 | authors = ["Kerollmops "]
5 | description = "A fully typed LMDB (mdb.master) wrapper with minimum overhead"
6 | license = "MIT"
7 | repository = "https://github.com/Kerollmops/heed"
8 | keywords = ["lmdb", "database", "storage", "typed"]
9 | categories = ["database", "data-structures"]
10 | readme = "../README.md"
11 | edition = "2021"
12 |
13 | [dependencies]
14 | bitflags = { version = "2.9.0", features = ["serde"] }
15 | byteorder = { version = "1.5.0", default-features = false }
16 | heed-traits = { version = "0.20.0", path = "../heed-traits" }
17 | heed-types = { version = "0.21.0", default-features = false, path = "../heed-types" }
18 | libc = "0.2.170"
19 | lmdb-master-sys = { version = "0.2.5", path = "../lmdb-master-sys" }
20 | once_cell = "1.20.3"
21 | page_size = "0.6.0"
22 | serde = { version = "1.0.218", features = ["derive"], optional = true }
23 | synchronoise = "1.0.1"
24 |
25 | [dev-dependencies]
26 | memchr = "2.7.4"
27 | serde = { version = "1.0.218", features = ["derive"] }
28 | tempfile = "3.18.0"
29 |
30 | [target.'cfg(windows)'.dependencies]
31 | url = "2.5.4"
32 |
33 | [features]
34 | # The `serde` feature makes some types serializable,
35 | # like the `EnvOpenOptions` struct.
36 | default = ["serde", "serde-bincode", "serde-json"]
37 | serde = ["bitflags/serde", "dep:serde"]
38 |
39 | # Enable the serde en/decoders for bincode, serde_json, or rmp_serde
40 | serde-bincode = ["heed-types/serde-bincode"]
41 | serde-json = ["heed-types/serde-json"]
42 | serde-rmp = ["heed-types/serde-rmp"]
43 |
44 | # serde_json features
45 | preserve_order = ["heed-types/preserve_order"]
46 | arbitrary_precision = ["heed-types/arbitrary_precision"]
47 | raw_value = ["heed-types/raw_value"]
48 | unbounded_depth = ["heed-types/unbounded_depth"]
49 |
50 | # Whether to tell LMDB to use POSIX semaphores during compilation
51 | # (instead of the default, which are System V semaphores).
52 | # POSIX semaphores are required for Apple's App Sandbox on iOS & macOS,
53 | # and are possibly faster and more appropriate for single-process use.
54 | # There are tradeoffs for both POSIX and SysV semaphores; which you
55 | # should look into before enabling this feature. Also, see here:
56 | #
57 | posix-sem = ["lmdb-master-sys/posix-sem"]
58 |
59 | # These features configure the MDB_IDL_LOGN macro, which determines
60 | # the size of the free and dirty page lists (and thus the amount of memory
61 | # allocated when opening an LMDB environment in read-write mode).
62 | #
63 | # Each feature defines MDB_IDL_LOGN as the value in the name of the feature.
64 | # That means these features are mutually exclusive, and you must not specify
65 | # more than one at the same time (or the crate will fail to compile).
66 | #
67 | # For more information on the motivation for these features (and their effect),
68 | # see https://github.com/mozilla/lmdb/pull/2.
69 | mdb_idl_logn_8 = ["lmdb-master-sys/mdb_idl_logn_8"]
70 | mdb_idl_logn_9 = ["lmdb-master-sys/mdb_idl_logn_9"]
71 | mdb_idl_logn_10 = ["lmdb-master-sys/mdb_idl_logn_10"]
72 | mdb_idl_logn_11 = ["lmdb-master-sys/mdb_idl_logn_11"]
73 | mdb_idl_logn_12 = ["lmdb-master-sys/mdb_idl_logn_12"]
74 | mdb_idl_logn_13 = ["lmdb-master-sys/mdb_idl_logn_13"]
75 | mdb_idl_logn_14 = ["lmdb-master-sys/mdb_idl_logn_14"]
76 | mdb_idl_logn_15 = ["lmdb-master-sys/mdb_idl_logn_15"]
77 | mdb_idl_logn_16 = ["lmdb-master-sys/mdb_idl_logn_16"]
78 |
79 | # Setting this enables you to use keys longer than 511 bytes. The exact limit
80 | # is computed by LMDB at compile time. You can find the exact value by calling
81 | # Env::max_key_size(). This value varies by architecture.
82 | #
83 | # Example max key sizes:
84 | # - Apple M1 (ARM64): 8126 bytes
85 | # - Apple Intel (AMD64): 1982 bytes
86 | # - Linux Intel (AMD64): 1982 bytes
87 | #
88 | # Setting this also enables you to use values larger than 511 bytes when using
89 | # a Database with the DatabaseFlags::DUP_SORT flag.
90 | #
91 | # This builds LMDB with the -DMDB_MAXKEYSIZE=0 option.
92 | #
93 | # Note: If you are moving database files between architectures then your longest
94 | # stored key must fit within the smallest limit of all architectures used. For
95 | # example, if you are moving databases between Apple M1 and Apple Intel
96 | # computers then you need to keep your keys within the smaller 1982 byte limit.
97 | longer-keys = ["lmdb-master-sys/longer-keys"]
98 |
99 | # Enable a better Valgrind support. This builds LMDB with the -DUSE_VALGRIND=1 option.
100 | #
101 | # You have to install the RPM valgrind-devel which contains memcheck.h.
102 | #
103 | # More information can be found at:
104 | #
105 | use-valgrind = ["lmdb-master-sys/use-valgrind"]
106 |
107 | [[example]]
108 | name = "all-types"
109 |
110 | [[example]]
111 | name = "clear-database"
112 |
113 | [[example]]
114 | name = "cursor-append"
115 |
116 | [[example]]
117 | name = "custom-comparator"
118 |
119 | [[example]]
120 | name = "custom-dupsort-comparator"
121 |
122 | [[example]]
123 | name = "multi-env"
124 |
125 | [[example]]
126 | name = "nested"
127 |
128 | [[example]]
129 | name = "prev-snapshot"
130 |
131 | [[example]]
132 | name = "rmp-serde"
133 | required-features = ["serde-rmp"]
134 |
--------------------------------------------------------------------------------
/heed/build.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | fn main() {
4 | println!("cargo:rerun-if-changed=build.rs");
5 | println!("cargo::rustc-check-cfg=cfg(master3)");
6 |
7 | let pkgname = env::var("CARGO_PKG_NAME").expect("Cargo didn't set the CARGO_PKG_NAME env var!");
8 | match pkgname.as_str() {
9 | "heed" => (),
10 | "heed3" => println!("cargo:rustc-cfg=master3"),
11 | _ => panic!("unexpected package name!"),
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/heed/examples/all-types.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::byteorder::BE;
4 | use heed::types::*;
5 | use heed::{Database, EnvOpenOptions};
6 | use serde::{Deserialize, Serialize};
7 |
8 | fn main() -> Result<(), Box> {
9 | let path = tempfile::tempdir()?;
10 |
11 | let env = unsafe {
12 | EnvOpenOptions::new()
13 | .map_size(10 * 1024 * 1024) // 10MB
14 | .max_dbs(3000)
15 | .open(path)?
16 | };
17 |
18 | // here the key will be an str and the data will be a slice of u8
19 | let mut wtxn = env.write_txn()?;
20 | let db: Database = env.create_database(&mut wtxn, Some("kiki"))?;
21 |
22 | db.put(&mut wtxn, "hello", &[2, 3][..])?;
23 | let ret: Option<&[u8]> = db.get(&wtxn, "hello")?;
24 |
25 | println!("{:?}", ret);
26 | wtxn.commit()?;
27 |
28 | // serde types are also supported!!!
29 | #[derive(Debug, Serialize, Deserialize)]
30 | struct Hello<'a> {
31 | string: &'a str,
32 | }
33 |
34 | let mut wtxn = env.write_txn()?;
35 | let db: Database> =
36 | env.create_database(&mut wtxn, Some("serde-bincode"))?;
37 |
38 | let hello = Hello { string: "hi" };
39 | db.put(&mut wtxn, "hello", &hello)?;
40 |
41 | let ret: Option = db.get(&wtxn, "hello")?;
42 | println!("serde-bincode:\t{:?}", ret);
43 |
44 | wtxn.commit()?;
45 |
46 | let mut wtxn = env.write_txn()?;
47 | let db: Database> = env.create_database(&mut wtxn, Some("serde-json"))?;
48 |
49 | let hello = Hello { string: "hi" };
50 | db.put(&mut wtxn, "hello", &hello)?;
51 |
52 | let ret: Option = db.get(&wtxn, "hello")?;
53 | println!("serde-json:\t{:?}", ret);
54 |
55 | wtxn.commit()?;
56 |
57 | // you can ignore the data
58 | let mut wtxn = env.write_txn()?;
59 | let db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?;
60 |
61 | db.put(&mut wtxn, "hello", &())?;
62 | let ret: Option<()> = db.get(&wtxn, "hello")?;
63 |
64 | println!("{:?}", ret);
65 |
66 | let ret: Option<()> = db.get(&wtxn, "non-existant")?;
67 |
68 | println!("{:?}", ret);
69 | wtxn.commit()?;
70 |
71 | // database opening and types are tested in a safe way
72 | //
73 | // we try to open a database twice with the same types
74 | let mut wtxn = env.write_txn()?;
75 | let _db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?;
76 |
77 | // you can iterate over keys in order
78 | type BEI64 = I64;
79 |
80 | let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?;
81 |
82 | db.put(&mut wtxn, &0, &())?;
83 | db.put(&mut wtxn, &68, &())?;
84 | db.put(&mut wtxn, &35, &())?;
85 | db.put(&mut wtxn, &42, &())?;
86 |
87 | let rets: Result, _> = db.iter(&wtxn)?.collect();
88 |
89 | println!("{:?}", rets);
90 |
91 | // or iterate over ranges too!!!
92 | let range = 35..=42;
93 | let rets: Result, _> = db.range(&wtxn, &range)?.collect();
94 |
95 | println!("{:?}", rets);
96 |
97 | // delete a range of key
98 | let range = 35..=42;
99 | let deleted: usize = db.delete_range(&mut wtxn, &range)?;
100 |
101 | let rets: Result, _> = db.iter(&wtxn)?.collect();
102 |
103 | println!("deleted: {:?}, {:?}", deleted, rets);
104 | wtxn.commit()?;
105 |
106 | Ok(())
107 | }
108 |
--------------------------------------------------------------------------------
/heed/examples/clear-database.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::types::*;
4 | use heed::{Database, EnvOpenOptions};
5 |
6 | // In this test we are checking that we can clear database entries and
7 | // write just after in the same transaction without loosing the writes.
8 | fn main() -> Result<(), Box> {
9 | let env_path = tempfile::tempdir()?;
10 |
11 | let env = unsafe {
12 | EnvOpenOptions::new()
13 | .map_size(10 * 1024 * 1024) // 10MB
14 | .max_dbs(3)
15 | .open(env_path)?
16 | };
17 |
18 | let mut wtxn = env.write_txn()?;
19 | let db: Database = env.create_database(&mut wtxn, Some("first"))?;
20 |
21 | // We fill the db database with entries.
22 | db.put(&mut wtxn, "I am here", "to test things")?;
23 | db.put(&mut wtxn, "I am here too", "for the same purpose")?;
24 |
25 | wtxn.commit()?;
26 |
27 | let mut wtxn = env.write_txn()?;
28 | db.clear(&mut wtxn)?;
29 | db.put(&mut wtxn, "And I come back", "to test things")?;
30 |
31 | let mut iter = db.iter(&wtxn)?;
32 | assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things")));
33 | assert_eq!(iter.next().transpose()?, None);
34 |
35 | drop(iter);
36 | wtxn.commit()?;
37 |
38 | let rtxn = env.read_txn()?;
39 | let mut iter = db.iter(&rtxn)?;
40 | assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things")));
41 | assert_eq!(iter.next().transpose()?, None);
42 |
43 | Ok(())
44 | }
45 |
--------------------------------------------------------------------------------
/heed/examples/cursor-append.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::types::*;
4 | use heed::{Database, EnvOpenOptions, PutFlags};
5 |
6 | // In this test we are checking that we can append ordered entries in one
7 | // database even if there is multiple databases which already contain entries.
8 | fn main() -> Result<(), Box> {
9 | let env_path = tempfile::tempdir()?;
10 |
11 | let env = unsafe {
12 | EnvOpenOptions::new()
13 | .map_size(10 * 1024 * 1024) // 10MB
14 | .max_dbs(3)
15 | .open(env_path)?
16 | };
17 |
18 | let mut wtxn = env.write_txn()?;
19 | let first: Database = env.create_database(&mut wtxn, Some("first"))?;
20 | let second: Database = env.create_database(&mut wtxn, Some("second"))?;
21 |
22 | // We fill the first database with entries.
23 | first.put(&mut wtxn, "I am here", "to test things")?;
24 | first.put(&mut wtxn, "I am here too", "for the same purpose")?;
25 |
26 | // We try to append ordered entries in the second database.
27 | let mut iter = second.iter_mut(&mut wtxn)?;
28 |
29 | unsafe { iter.put_current_with_options::(PutFlags::APPEND, "aaaa", "lol")? };
30 | unsafe { iter.put_current_with_options::(PutFlags::APPEND, "abcd", "lol")? };
31 | unsafe { iter.put_current_with_options::(PutFlags::APPEND, "bcde", "lol")? };
32 |
33 | drop(iter);
34 |
35 | wtxn.commit()?;
36 |
37 | Ok(())
38 | }
39 |
--------------------------------------------------------------------------------
/heed/examples/custom-comparator.rs:
--------------------------------------------------------------------------------
1 | use std::cmp::Ordering;
2 | use std::error::Error;
3 | use std::str;
4 |
5 | use heed::EnvOpenOptions;
6 | use heed_traits::Comparator;
7 | use heed_types::{Str, Unit};
8 |
9 | enum StringAsIntCmp {}
10 |
11 | // This function takes two strings which represent positive numbers,
12 | // parses them into i32s and compare the parsed value.
13 | // Therefore "-1000" < "-100" must be true even without '0' padding.
14 | impl Comparator for StringAsIntCmp {
15 | fn compare(a: &[u8], b: &[u8]) -> Ordering {
16 | let a: i32 = str::from_utf8(a).unwrap().parse().unwrap();
17 | let b: i32 = str::from_utf8(b).unwrap().parse().unwrap();
18 | a.cmp(&b)
19 | }
20 | }
21 |
22 | fn main() -> Result<(), Box> {
23 | let env_path = tempfile::tempdir()?;
24 |
25 | let env = unsafe {
26 | EnvOpenOptions::new()
27 | .map_size(10 * 1024 * 1024) // 10MB
28 | .max_dbs(3)
29 | .open(env_path)?
30 | };
31 |
32 | let mut wtxn = env.write_txn()?;
33 | let db = env
34 | .database_options()
35 | .types::()
36 | .key_comparator::()
37 | .create(&mut wtxn)?;
38 | wtxn.commit()?;
39 |
40 | let mut wtxn = env.write_txn()?;
41 |
42 | // We fill our database with entries.
43 | db.put(&mut wtxn, "-100000", &())?;
44 | db.put(&mut wtxn, "-10000", &())?;
45 | db.put(&mut wtxn, "-1000", &())?;
46 | db.put(&mut wtxn, "-100", &())?;
47 | db.put(&mut wtxn, "100", &())?;
48 |
49 | // We check that the key are in the right order ("-100" < "-1000" < "-10000"...)
50 | let mut iter = db.iter(&wtxn)?;
51 | assert_eq!(iter.next().transpose()?, Some(("-100000", ())));
52 | assert_eq!(iter.next().transpose()?, Some(("-10000", ())));
53 | assert_eq!(iter.next().transpose()?, Some(("-1000", ())));
54 | assert_eq!(iter.next().transpose()?, Some(("-100", ())));
55 | assert_eq!(iter.next().transpose()?, Some(("100", ())));
56 | drop(iter);
57 |
58 | Ok(())
59 | }
60 |
--------------------------------------------------------------------------------
/heed/examples/custom-dupsort-comparator.rs:
--------------------------------------------------------------------------------
1 | use std::cmp::Ordering;
2 | use std::error::Error;
3 | use std::fs;
4 | use std::path::Path;
5 |
6 | use byteorder::BigEndian;
7 | use heed::{DatabaseFlags, EnvOpenOptions};
8 | use heed_traits::Comparator;
9 | use heed_types::{Str, U128};
10 |
11 | enum DescendingIntCmp {}
12 |
13 | impl Comparator for DescendingIntCmp {
14 | fn compare(a: &[u8], b: &[u8]) -> Ordering {
15 | a.cmp(b).reverse()
16 | }
17 | }
18 |
19 | fn main() -> Result<(), Box> {
20 | let env_path = Path::new("target").join("custom-dupsort-cmp.mdb");
21 |
22 | let _ = fs::remove_dir_all(&env_path);
23 |
24 | fs::create_dir_all(&env_path)?;
25 | let env = unsafe {
26 | EnvOpenOptions::new()
27 | .map_size(10 * 1024 * 1024) // 10MB
28 | .max_dbs(3)
29 | .open(env_path)?
30 | };
31 |
32 | let mut wtxn = env.write_txn()?;
33 | let db = env
34 | .database_options()
35 | .types::>()
36 | .flags(DatabaseFlags::DUP_SORT)
37 | .dup_sort_comparator::()
38 | .create(&mut wtxn)?;
39 | wtxn.commit()?;
40 |
41 | let mut wtxn = env.write_txn()?;
42 |
43 | // We fill our database with entries.
44 | db.put(&mut wtxn, "1", &1)?;
45 | db.put(&mut wtxn, "1", &2)?;
46 | db.put(&mut wtxn, "1", &3)?;
47 | db.put(&mut wtxn, "2", &4)?;
48 | db.put(&mut wtxn, "1", &5)?;
49 | db.put(&mut wtxn, "0", &0)?;
50 |
51 | // We check that the keys are in lexicographic and values in descending order.
52 | let mut iter = db.iter(&wtxn)?;
53 | assert_eq!(iter.next().transpose()?, Some(("0", 0)));
54 | assert_eq!(iter.next().transpose()?, Some(("1", 5)));
55 | assert_eq!(iter.next().transpose()?, Some(("1", 3)));
56 | assert_eq!(iter.next().transpose()?, Some(("1", 2)));
57 | assert_eq!(iter.next().transpose()?, Some(("1", 1)));
58 | assert_eq!(iter.next().transpose()?, Some(("2", 4)));
59 | drop(iter);
60 |
61 | Ok(())
62 | }
63 |
--------------------------------------------------------------------------------
/heed/examples/multi-env.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use byteorder::BE;
4 | use heed::types::*;
5 | use heed::{Database, EnvOpenOptions};
6 |
7 | type BEU32 = U32;
8 |
9 | fn main() -> Result<(), Box> {
10 | let env1_path = tempfile::tempdir()?;
11 | let env2_path = tempfile::tempdir()?;
12 | let env1 = unsafe {
13 | EnvOpenOptions::new()
14 | .map_size(10 * 1024 * 1024) // 10MB
15 | .max_dbs(3000)
16 | .open(env1_path)?
17 | };
18 |
19 | let env2 = unsafe {
20 | EnvOpenOptions::new()
21 | .map_size(10 * 1024 * 1024) // 10MB
22 | .max_dbs(3000)
23 | .open(env2_path)?
24 | };
25 |
26 | let mut wtxn1 = env1.write_txn()?;
27 | let mut wtxn2 = env2.write_txn()?;
28 | let db1: Database = env1.create_database(&mut wtxn1, Some("hello"))?;
29 | let db2: Database = env2.create_database(&mut wtxn2, Some("hello"))?;
30 |
31 | // clear db
32 | db1.clear(&mut wtxn1)?;
33 | wtxn1.commit()?;
34 |
35 | // clear db
36 | db2.clear(&mut wtxn2)?;
37 | wtxn2.commit()?;
38 |
39 | // -----
40 |
41 | let mut wtxn1 = env1.write_txn()?;
42 |
43 | db1.put(&mut wtxn1, "what", &[4, 5][..])?;
44 | db1.get(&wtxn1, "what")?;
45 | wtxn1.commit()?;
46 |
47 | let rtxn2 = env2.read_txn()?;
48 | let ret = db2.last(&rtxn2)?;
49 | assert_eq!(ret, None);
50 |
51 | Ok(())
52 | }
53 |
--------------------------------------------------------------------------------
/heed/examples/nested.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::types::*;
4 | use heed::{Database, EnvOpenOptions};
5 |
6 | fn main() -> Result<(), Box> {
7 | let path = tempfile::tempdir()?;
8 |
9 | let env = unsafe {
10 | EnvOpenOptions::new()
11 | .map_size(10 * 1024 * 1024) // 10MB
12 | .max_dbs(3000)
13 | .open(path)?
14 | };
15 |
16 | // here the key will be an str and the data will be a slice of u8
17 | let mut wtxn = env.write_txn()?;
18 | let db: Database = env.create_database(&mut wtxn, None)?;
19 |
20 | // clear db
21 | db.clear(&mut wtxn)?;
22 | wtxn.commit()?;
23 |
24 | // -----
25 |
26 | let mut wtxn = env.write_txn()?;
27 | let mut nwtxn = env.nested_write_txn(&mut wtxn)?;
28 |
29 | db.put(&mut nwtxn, "what", &[4, 5][..])?;
30 | let ret = db.get(&nwtxn, "what")?;
31 | println!("nested(1) \"what\": {:?}", ret);
32 |
33 | println!("nested(1) abort");
34 | nwtxn.abort();
35 |
36 | let ret = db.get(&wtxn, "what")?;
37 | println!("parent \"what\": {:?}", ret);
38 |
39 | // ------
40 | println!();
41 |
42 | // also try with multiple levels of nesting
43 | let mut nwtxn = env.nested_write_txn(&mut wtxn)?;
44 | let mut nnwtxn = env.nested_write_txn(&mut nwtxn)?;
45 |
46 | db.put(&mut nnwtxn, "humm...", &[6, 7][..])?;
47 | let ret = db.get(&nnwtxn, "humm...")?;
48 | println!("nested(2) \"humm...\": {:?}", ret);
49 |
50 | println!("nested(2) commit");
51 | nnwtxn.commit()?;
52 | nwtxn.commit()?;
53 |
54 | let ret = db.get(&wtxn, "humm...")?;
55 | println!("parent \"humm...\": {:?}", ret);
56 |
57 | db.put(&mut wtxn, "hello", &[2, 3][..])?;
58 |
59 | let ret = db.get(&wtxn, "hello")?;
60 | println!("parent \"hello\": {:?}", ret);
61 |
62 | println!("parent commit");
63 | wtxn.commit()?;
64 |
65 | // ------
66 | println!();
67 |
68 | let rtxn = env.read_txn()?;
69 |
70 | let ret = db.get(&rtxn, "hello")?;
71 | println!("parent (reader) \"hello\": {:?}", ret);
72 |
73 | let ret = db.get(&rtxn, "humm...")?;
74 | println!("parent (reader) \"humm...\": {:?}", ret);
75 |
76 | Ok(())
77 | }
78 |
--------------------------------------------------------------------------------
/heed/examples/prev-snapshot.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::types::*;
4 | use heed::{Database, EnvFlags, EnvOpenOptions};
5 |
6 | // In this test we are checking that we can move to a previous environement snapshot.
7 | fn main() -> Result<(), Box> {
8 | let env_path = tempfile::tempdir()?;
9 |
10 | let env = unsafe {
11 | EnvOpenOptions::new()
12 | .map_size(10 * 1024 * 1024) // 10MB
13 | .max_dbs(3)
14 | .open(&env_path)?
15 | };
16 |
17 | let mut wtxn = env.write_txn()?;
18 | let db: Database = env.create_database(&mut wtxn, None)?;
19 |
20 | // We fill the db database with entries.
21 | db.put(&mut wtxn, "I am here", "to test things")?;
22 | db.put(&mut wtxn, "I am here too", "for the same purpose")?;
23 |
24 | wtxn.commit()?;
25 |
26 | env.prepare_for_closing().wait();
27 |
28 | // We can get the env state from before the last commit
29 | // and therefore see an empty env.
30 | let env = unsafe {
31 | EnvOpenOptions::new()
32 | .map_size(10 * 1024 * 1024) // 10MB
33 | .max_dbs(3)
34 | .flags(EnvFlags::PREV_SNAPSHOT)
35 | .open(&env_path)?
36 | };
37 |
38 | let mut wtxn = env.write_txn()?;
39 | let db: Database = env.create_database(&mut wtxn, None)?;
40 |
41 | assert!(db.is_empty(&wtxn)?);
42 |
43 | wtxn.abort();
44 | env.prepare_for_closing().wait();
45 |
46 | // However, if we don't commit we can still get
47 | // back the latest version of the env.
48 | let env = unsafe {
49 | EnvOpenOptions::new()
50 | .map_size(10 * 1024 * 1024) // 10MB
51 | .max_dbs(3)
52 | .open(&env_path)?
53 | };
54 |
55 | let mut wtxn = env.write_txn()?;
56 | let db: Database = env.create_database(&mut wtxn, None)?;
57 |
58 | assert_eq!(db.get(&wtxn, "I am here")?, Some("to test things"));
59 | assert_eq!(db.get(&wtxn, "I am here too")?, Some("for the same purpose"));
60 |
61 | // And write new stuff in the env.
62 | db.put(&mut wtxn, "I will fade away", "I am so sad")?;
63 |
64 | wtxn.commit()?;
65 | env.prepare_for_closing().wait();
66 |
67 | // Once again we can get back the previous version
68 | // of the env and see some entries disappear.
69 | let env = unsafe {
70 | EnvOpenOptions::new()
71 | .map_size(10 * 1024 * 1024) // 10MB
72 | .max_dbs(3)
73 | .flags(EnvFlags::PREV_SNAPSHOT)
74 | .open(&env_path)?
75 | };
76 |
77 | let rtxn = env.read_txn()?;
78 | let db: Database = env.open_database(&rtxn, None)?.unwrap();
79 |
80 | assert_eq!(db.get(&rtxn, "I am here")?, Some("to test things"));
81 | assert_eq!(db.get(&rtxn, "I am here too")?, Some("for the same purpose"));
82 | assert_eq!(db.get(&rtxn, "I will fade away")?, None);
83 |
84 | Ok(())
85 | }
86 |
--------------------------------------------------------------------------------
/heed/examples/rmp-serde.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use heed::types::{SerdeRmp, Str};
4 | use heed::{Database, EnvOpenOptions};
5 | use serde::{Deserialize, Serialize};
6 |
7 | fn main() -> Result<(), Box> {
8 | let path = tempfile::tempdir()?;
9 |
10 | let env = unsafe {
11 | EnvOpenOptions::new()
12 | .map_size(10 * 1024 * 1024) // 10MB
13 | .max_dbs(3000)
14 | .open(path)?
15 | };
16 |
17 | // you can specify that a database will support some typed key/data
18 | // serde types are also supported!!!
19 | #[derive(Debug, Serialize, Deserialize)]
20 | struct Hello<'a> {
21 | string: &'a str,
22 | }
23 |
24 | let mut wtxn = env.write_txn()?;
25 | let db: Database> = env.create_database(&mut wtxn, Some("serde-rmp"))?;
26 |
27 | let hello = Hello { string: "hi" };
28 | db.put(&mut wtxn, "hello", &hello)?;
29 |
30 | let ret: Option = db.get(&wtxn, "hello")?;
31 | println!("serde-rmp:\t{:?}", ret);
32 |
33 | wtxn.commit()?;
34 |
35 | Ok(())
36 | }
37 |
--------------------------------------------------------------------------------
/heed/src/cursor.rs:
--------------------------------------------------------------------------------
1 | use std::ops::{Deref, DerefMut};
2 | use std::{marker, mem, ptr};
3 |
4 | use crate::mdb::error::mdb_result;
5 | use crate::mdb::ffi;
6 | use crate::*;
7 |
8 | pub struct RoCursor<'txn> {
9 | cursor: *mut ffi::MDB_cursor,
10 | _marker: marker::PhantomData<&'txn ()>,
11 | }
12 |
13 | impl<'txn> RoCursor<'txn> {
14 | pub(crate) fn new(txn: &'txn RoTxn, dbi: ffi::MDB_dbi) -> Result> {
15 | let mut cursor: *mut ffi::MDB_cursor = ptr::null_mut();
16 | let mut txn = txn.txn_ptr();
17 | unsafe { mdb_result(ffi::mdb_cursor_open(txn.as_mut(), dbi, &mut cursor))? }
18 | Ok(RoCursor { cursor, _marker: marker::PhantomData })
19 | }
20 |
21 | pub fn current(&mut self) -> Result