heed-0.20.5/.cargo_vcs_info.json0000644000000001420000000000100120220ustar { "git": { "sha1": "947c3aa814b9a5eab764a95f84242fe1756cf61a" }, "path_in_vcs": "heed" }heed-0.20.5/Cargo.lock0000644000000364460000000000100100150ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossbeam-queue" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] [[package]] name = "doxygen-rs" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" dependencies = [ "phf", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heed" version = "0.20.5" dependencies = [ "bitflags", "byteorder", "heed-traits", "heed-types", "libc", "lmdb-master-sys", "once_cell", "page_size", "serde", "synchronoise", "tempfile", "url", ] [[package]] name = "heed-traits" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" [[package]] name = "heed-types" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d3f528b053a6d700b2734eabcd0fd49cb8230647aa72958467527b0b7917114" dependencies = [ "bincode", "byteorder", "heed-traits", "rmp-serde", "serde", "serde_json", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lmdb-master-sys" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "472c3760e2a8d0f61f322fb36788021bb36d573c502b50fa3e2bcaac3ec326c9" dependencies = [ "cc", "doxygen-rs", "libc", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "page_size" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", ] [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 1.0.107", ] [[package]] name = "phf_shared" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" dependencies = [ "siphasher", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rmp" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder", "num-traits", "paste", ] [[package]] name = "rmp-serde" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder", "rmp", "serde", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "serde" version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", "syn 2.0.74", ] [[package]] name = "serde_json" version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "indexmap", "itoa", "memchr", "ryu", "serde", ] [[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synchronoise" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" dependencies = [ "crossbeam-queue", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" heed-0.20.5/Cargo.toml0000644000000062660000000000100100350ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "heed" version = "0.20.5" authors = ["Kerollmops "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "A fully typed LMDB wrapper with minimum overhead" readme = "README.md" keywords = [ "lmdb", "database", "storage", "typed", ] categories = [ "database", "data-structures", ] license = "MIT" repository = "https://github.com/Kerollmops/heed" [lib] name = "heed" path = "src/lib.rs" [[example]] name = "all-types" path = "examples/all-types.rs" [[example]] name = "clear-database" path = "examples/clear-database.rs" [[example]] name = "cursor-append" path = "examples/cursor-append.rs" [[example]] name = "custom-comparator" path = "examples/custom-comparator.rs" [[example]] name = "multi-env" path = "examples/multi-env.rs" [[example]] name = "nested" path = "examples/nested.rs" [[example]] name = "rmp-serde" path = "examples/rmp-serde.rs" required-features = ["serde-rmp"] [dependencies.bitflags] version = "2.6.0" features = ["serde"] [dependencies.byteorder] version = "1.5.0" default-features = false [dependencies.heed-traits] version = "0.20.0" [dependencies.heed-types] version = "0.20.1" default-features = false [dependencies.libc] version = "0.2.155" [dependencies.lmdb-master-sys] version = "0.2.4" [dependencies.once_cell] version = "1.19.0" [dependencies.page_size] version = "0.6.0" [dependencies.serde] version = "1.0.203" features = ["derive"] optional = true [dependencies.synchronoise] version = "1.0.1" [dev-dependencies.serde] version = "1.0.203" features = ["derive"] [dev-dependencies.tempfile] version = "3.10.1" [features] arbitrary_precision = ["heed-types/arbitrary_precision"] default = [ "serde", "serde-bincode", "serde-json", ] longer-keys = ["lmdb-master-sys/longer-keys"] mdb_idl_logn_10 = ["lmdb-master-sys/mdb_idl_logn_10"] mdb_idl_logn_11 = ["lmdb-master-sys/mdb_idl_logn_11"] mdb_idl_logn_12 = ["lmdb-master-sys/mdb_idl_logn_12"] mdb_idl_logn_13 = ["lmdb-master-sys/mdb_idl_logn_13"] mdb_idl_logn_14 = ["lmdb-master-sys/mdb_idl_logn_14"] mdb_idl_logn_15 = ["lmdb-master-sys/mdb_idl_logn_15"] mdb_idl_logn_16 = ["lmdb-master-sys/mdb_idl_logn_16"] mdb_idl_logn_8 = ["lmdb-master-sys/mdb_idl_logn_8"] mdb_idl_logn_9 = ["lmdb-master-sys/mdb_idl_logn_9"] posix-sem = ["lmdb-master-sys/posix-sem"] preserve_order = ["heed-types/preserve_order"] raw_value = ["heed-types/raw_value"] read-txn-no-tls = [] serde = [ "bitflags/serde", "dep:serde", ] serde-bincode = ["heed-types/serde-bincode"] serde-json = ["heed-types/serde-json"] serde-rmp = ["heed-types/serde-rmp"] unbounded_depth = ["heed-types/unbounded_depth"] [target."cfg(windows)".dependencies.url] version = "2.5.2" heed-0.20.5/Cargo.toml.orig000064400000000000000000000114311046102023000135040ustar 00000000000000[package] name = "heed" version = "0.20.5" authors = ["Kerollmops "] description = "A fully typed LMDB wrapper with minimum overhead" license = "MIT" repository = "https://github.com/Kerollmops/heed" keywords = ["lmdb", "database", "storage", "typed"] categories = ["database", "data-structures"] readme = "../README.md" edition = "2021" [dependencies] bitflags = { version = "2.6.0", features = ["serde"] } byteorder = { version = "1.5.0", default-features = false } heed-traits = { version = "0.20.0", path = "../heed-traits" } heed-types = { version = "0.20.1", default-features = false, path = "../heed-types" } libc = "0.2.155" lmdb-master-sys = { version = "0.2.4", path = "../lmdb-master-sys" } once_cell = "1.19.0" page_size = "0.6.0" serde = { version = "1.0.203", features = ["derive"], optional = true } synchronoise = "1.0.1" [dev-dependencies] serde = { version = "1.0.203", features = ["derive"] } tempfile = "3.10.1" [target.'cfg(windows)'.dependencies] url = "2.5.2" [features] # The `serde` feature makes some types serializable, # like the `EnvOpenOptions` struct. default = ["serde", "serde-bincode", "serde-json"] serde = ["bitflags/serde", "dep:serde"] # The #MDB_NOTLS flag is automatically set on Env opening, # RoTxn and RoCursors implements the Send trait. This allows the # user to move RoTxns and RoCursors between threads as read transactions # will no more use thread local storage and will tie reader locktable # slots to #MDB_txn objects instead of to threads. # # According to the LMDB documentation, when this feature is not enabled: # A thread can only use one transaction at a time, plus any child # transactions. Each transaction belongs to one thread. [...] # The #MDB_NOTLS flag changes this for read-only transactions. # # And a #MDB_BAD_RSLOT error will be thrown when multiple read # transactions exists on the same thread read-txn-no-tls = [] # Enable the serde en/decoders for bincode, serde_json, or rmp_serde serde-bincode = ["heed-types/serde-bincode"] serde-json = ["heed-types/serde-json"] serde-rmp = ["heed-types/serde-rmp"] # serde_json features preserve_order = ["heed-types/preserve_order"] arbitrary_precision = ["heed-types/arbitrary_precision"] raw_value = ["heed-types/raw_value"] unbounded_depth = ["heed-types/unbounded_depth"] # Whether to tell LMDB to use POSIX semaphores during compilation # (instead of the default, which are System V semaphores). # POSIX semaphores are required for Apple's App Sandbox on iOS & macOS, # and are possibly faster and more appropriate for single-process use. # There are tradeoffs for both POSIX and SysV semaphores; which you # should look into before enabling this feature. Also, see here: # posix-sem = ["lmdb-master-sys/posix-sem"] # These features configure the MDB_IDL_LOGN macro, which determines # the size of the free and dirty page lists (and thus the amount of memory # allocated when opening an LMDB environment in read-write mode). # # Each feature defines MDB_IDL_LOGN as the value in the name of the feature. # That means these features are mutually exclusive, and you must not specify # more than one at the same time (or the crate will fail to compile). # # For more information on the motivation for these features (and their effect), # see https://github.com/mozilla/lmdb/pull/2. mdb_idl_logn_8 = ["lmdb-master-sys/mdb_idl_logn_8"] mdb_idl_logn_9 = ["lmdb-master-sys/mdb_idl_logn_9"] mdb_idl_logn_10 = ["lmdb-master-sys/mdb_idl_logn_10"] mdb_idl_logn_11 = ["lmdb-master-sys/mdb_idl_logn_11"] mdb_idl_logn_12 = ["lmdb-master-sys/mdb_idl_logn_12"] mdb_idl_logn_13 = ["lmdb-master-sys/mdb_idl_logn_13"] mdb_idl_logn_14 = ["lmdb-master-sys/mdb_idl_logn_14"] mdb_idl_logn_15 = ["lmdb-master-sys/mdb_idl_logn_15"] mdb_idl_logn_16 = ["lmdb-master-sys/mdb_idl_logn_16"] # Setting this enables you to use keys longer than 511 bytes. The exact limit # is computed by LMDB at compile time. You can find the exact value by calling # Env::max_key_size(). This value varies by architecture. # # Example max key sizes: # - Apple M1 (ARM64): 8126 bytes # - Apple Intel (AMD64): 1982 bytes # - Linux Intel (AMD64): 1982 bytes # # Setting this also enables you to use values larger than 511 bytes when using # a Database with the DatabaseFlags::DUP_SORT flag. # # This builds LMDB with the -DMDB_MAXKEYSIZE=0 option. # # Note: If you are moving database files between architectures then your longest # stored key must fit within the smallest limit of all architectures used. For # example, if you are moving databases between Apple M1 and Apple Intel # computers then you need to keep your keys within the smaller 1982 byte limit. longer-keys = ["lmdb-master-sys/longer-keys"] [[example]] name = "rmp-serde" required-features = ["serde-rmp"] heed-0.20.5/README.md000064400000000000000000000045221046102023000120770ustar 00000000000000

heed

[![License](https://img.shields.io/badge/license-MIT-green)](#LICENSE) [![Crates.io](https://img.shields.io/crates/v/heed)](https://crates.io/crates/heed) [![Docs](https://docs.rs/heed/badge.svg)](https://docs.rs/heed) [![dependency status](https://deps.rs/repo/github/meilisearch/heed/status.svg)](https://deps.rs/repo/github/meilisearch/heed) [![Build](https://github.com/meilisearch/heed/actions/workflows/rust.yml/badge.svg)](https://github.com/meilisearch/heed/actions/workflows/rust.yml) A Rust-centric [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) abstraction with minimal overhead. This library enables the storage of various Rust types within LMDB, extending support to include Serde-compatible types. ## Simple Example Usage Here is an example on how to store and read entries into LMDB in a safe and ACID way. For usage examples, see [heed/examples/](heed/examples/). To see more advanced usage techniques go check our [Cookbook](https://docs.rs/heed/latest/heed/cookbook/index.html). ```rust use std::fs; use std::path::Path; use heed::{EnvOpenOptions, Database}; use heed::types::*; fn main() -> Result<(), Box> { let env = unsafe { EnvOpenOptions::new().open("my-first-db")? }; // We open the default unnamed database let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, None)?; // We open a write transaction db.put(&mut wtxn, "seven", &7)?; db.put(&mut wtxn, "zero", &0)?; db.put(&mut wtxn, "five", &5)?; db.put(&mut wtxn, "three", &3)?; wtxn.commit()?; // We open a read transaction to check if those values are now available let mut rtxn = env.read_txn()?; let ret = db.get(&rtxn, "zero")?; assert_eq!(ret, Some(0)); let ret = db.get(&rtxn, "five")?; assert_eq!(ret, Some(5)); Ok(()) } ``` ## Building from Source You can use this command to clone the repository: ```bash git clone --recursive https://github.com/meilisearch/heed.git cd heed cargo build ``` However, if you already cloned it and forgot to initialize the submodules, execute the following command: ```bash git submodule update --init ``` heed-0.20.5/examples/all-types.rs000064400000000000000000000060471046102023000147220ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use heed::byteorder::BE; use heed::types::*; use heed::{Database, EnvOpenOptions}; use serde::{Deserialize, Serialize}; fn main() -> Result<(), Box> { let path = Path::new("target").join("heed.mdb"); fs::create_dir_all(&path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // here the key will be an str and the data will be a slice of u8 let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("kiki"))?; db.put(&mut wtxn, "hello", &[2, 3][..])?; let ret: Option<&[u8]> = db.get(&wtxn, "hello")?; println!("{:?}", ret); wtxn.commit()?; // serde types are also supported!!! #[derive(Debug, Serialize, Deserialize)] struct Hello<'a> { string: &'a str, } let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-bincode"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-bincode:\t{:?}", ret); wtxn.commit()?; let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-json"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-json:\t{:?}", ret); wtxn.commit()?; // you can ignore the data let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?; db.put(&mut wtxn, "hello", &())?; let ret: Option<()> = db.get(&wtxn, "hello")?; println!("{:?}", ret); let ret: Option<()> = db.get(&wtxn, "non-existant")?; println!("{:?}", ret); wtxn.commit()?; // database opening and types are tested in a safe way // // we try to open a database twice with the same types let mut wtxn = env.write_txn()?; let _db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?; // you can iterate over keys in order type BEI64 = I64; let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; db.put(&mut wtxn, &0, &())?; db.put(&mut wtxn, &68, &())?; db.put(&mut wtxn, &35, &())?; db.put(&mut wtxn, &42, &())?; let rets: Result, _> = db.iter(&wtxn)?.collect(); println!("{:?}", rets); // or iterate over ranges too!!! let range = 35..=42; let rets: Result, _> = db.range(&wtxn, &range)?.collect(); println!("{:?}", rets); // delete a range of key let range = 35..=42; let deleted: usize = db.delete_range(&mut wtxn, &range)?; let rets: Result, _> = db.iter(&wtxn)?.collect(); println!("deleted: {:?}, {:?}", deleted, rets); wtxn.commit()?; Ok(()) } heed-0.20.5/examples/clear-database.rs000064400000000000000000000026751046102023000156430ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use heed::types::*; use heed::{Database, EnvOpenOptions}; // In this test we are checking that we can clear database entries and // write just after in the same transaction without loosing the writes. fn main() -> Result<(), Box> { let env_path = Path::new("target").join("clear-database.mdb"); let _ = fs::remove_dir_all(&env_path); fs::create_dir_all(&env_path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("first"))?; // We fill the db database with entries. db.put(&mut wtxn, "I am here", "to test things")?; db.put(&mut wtxn, "I am here too", "for the same purpose")?; wtxn.commit()?; let mut wtxn = env.write_txn()?; db.clear(&mut wtxn)?; db.put(&mut wtxn, "And I come back", "to test things")?; let mut iter = db.iter(&wtxn)?; assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things"))); assert_eq!(iter.next().transpose()?, None); drop(iter); wtxn.commit()?; let rtxn = env.read_txn()?; let mut iter = db.iter(&rtxn)?; assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things"))); assert_eq!(iter.next().transpose()?, None); Ok(()) } heed-0.20.5/examples/cursor-append.rs000064400000000000000000000026401046102023000155650ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use heed::types::*; use heed::{Database, EnvOpenOptions, PutFlags}; // In this test we are checking that we can append ordered entries in one // database even if there is multiple databases which already contain entries. fn main() -> Result<(), Box> { let env_path = Path::new("target").join("cursor-append.mdb"); let _ = fs::remove_dir_all(&env_path); fs::create_dir_all(&env_path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let first: Database = env.create_database(&mut wtxn, Some("first"))?; let second: Database = env.create_database(&mut wtxn, Some("second"))?; // We fill the first database with entries. first.put(&mut wtxn, "I am here", "to test things")?; first.put(&mut wtxn, "I am here too", "for the same purpose")?; // We try to append ordered entries in the second database. let mut iter = second.iter_mut(&mut wtxn)?; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "aaaa", "lol")? }; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "abcd", "lol")? }; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "bcde", "lol")? }; drop(iter); wtxn.commit()?; Ok(()) } heed-0.20.5/examples/custom-comparator.rs000064400000000000000000000036131046102023000164630ustar 00000000000000use std::cmp::Ordering; use std::error::Error; use std::path::Path; use std::{fs, str}; use heed::EnvOpenOptions; use heed_traits::Comparator; use heed_types::{Str, Unit}; enum StringAsIntCmp {} // This function takes two strings which represent positive numbers, // parses them into i32s and compare the parsed value. // Therefore "-1000" < "-100" must be true even without '0' padding. impl Comparator for StringAsIntCmp { fn compare(a: &[u8], b: &[u8]) -> Ordering { let a: i32 = str::from_utf8(a).unwrap().parse().unwrap(); let b: i32 = str::from_utf8(b).unwrap().parse().unwrap(); a.cmp(&b) } } fn main() -> Result<(), Box> { let env_path = Path::new("target").join("custom-key-cmp.mdb"); let _ = fs::remove_dir_all(&env_path); fs::create_dir_all(&env_path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let db = env .database_options() .types::() .key_comparator::() .create(&mut wtxn)?; wtxn.commit()?; let mut wtxn = env.write_txn()?; // We fill our database with entries. db.put(&mut wtxn, "-100000", &())?; db.put(&mut wtxn, "-10000", &())?; db.put(&mut wtxn, "-1000", &())?; db.put(&mut wtxn, "-100", &())?; db.put(&mut wtxn, "100", &())?; // We check that the key are in the right order ("-100" < "-1000" < "-10000"...) let mut iter = db.iter(&wtxn)?; assert_eq!(iter.next().transpose()?, Some(("-100000", ()))); assert_eq!(iter.next().transpose()?, Some(("-10000", ()))); assert_eq!(iter.next().transpose()?, Some(("-1000", ()))); assert_eq!(iter.next().transpose()?, Some(("-100", ()))); assert_eq!(iter.next().transpose()?, Some(("100", ()))); drop(iter); Ok(()) } heed-0.20.5/examples/multi-env.rs000064400000000000000000000025341046102023000147250ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use byteorder::BE; use heed::types::*; use heed::{Database, EnvOpenOptions}; type BEU32 = U32; fn main() -> Result<(), Box> { let env1_path = Path::new("target").join("env1.mdb"); let env2_path = Path::new("target").join("env2.mdb"); fs::create_dir_all(&env1_path)?; let env1 = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(env1_path)? }; fs::create_dir_all(&env2_path)?; let env2 = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(env2_path)? }; let mut wtxn1 = env1.write_txn()?; let mut wtxn2 = env2.write_txn()?; let db1: Database = env1.create_database(&mut wtxn1, Some("hello"))?; let db2: Database = env2.create_database(&mut wtxn2, Some("hello"))?; // clear db db1.clear(&mut wtxn1)?; wtxn1.commit()?; // clear db db2.clear(&mut wtxn2)?; wtxn2.commit()?; // ----- let mut wtxn1 = env1.write_txn()?; db1.put(&mut wtxn1, "what", &[4, 5][..])?; db1.get(&wtxn1, "what")?; wtxn1.commit()?; let rtxn2 = env2.read_txn()?; let ret = db2.last(&rtxn2)?; assert_eq!(ret, None); Ok(()) } heed-0.20.5/examples/nested.rs000064400000000000000000000036751046102023000142760ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use heed::types::*; use heed::{Database, EnvOpenOptions}; fn main() -> Result<(), Box> { let path = Path::new("target").join("heed.mdb"); fs::create_dir_all(&path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // here the key will be an str and the data will be a slice of u8 let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, None)?; // clear db db.clear(&mut wtxn)?; wtxn.commit()?; // ----- let mut wtxn = env.write_txn()?; let mut nwtxn = env.nested_write_txn(&mut wtxn)?; db.put(&mut nwtxn, "what", &[4, 5][..])?; let ret = db.get(&nwtxn, "what")?; println!("nested(1) \"what\": {:?}", ret); println!("nested(1) abort"); nwtxn.abort(); let ret = db.get(&wtxn, "what")?; println!("parent \"what\": {:?}", ret); // ------ println!(); // also try with multiple levels of nesting let mut nwtxn = env.nested_write_txn(&mut wtxn)?; let mut nnwtxn = env.nested_write_txn(&mut nwtxn)?; db.put(&mut nnwtxn, "humm...", &[6, 7][..])?; let ret = db.get(&nnwtxn, "humm...")?; println!("nested(2) \"humm...\": {:?}", ret); println!("nested(2) commit"); nnwtxn.commit()?; nwtxn.commit()?; let ret = db.get(&wtxn, "humm...")?; println!("parent \"humm...\": {:?}", ret); db.put(&mut wtxn, "hello", &[2, 3][..])?; let ret = db.get(&wtxn, "hello")?; println!("parent \"hello\": {:?}", ret); println!("parent commit"); wtxn.commit()?; // ------ println!(); let rtxn = env.read_txn()?; let ret = db.get(&rtxn, "hello")?; println!("parent (reader) \"hello\": {:?}", ret); let ret = db.get(&rtxn, "humm...")?; println!("parent (reader) \"humm...\": {:?}", ret); Ok(()) } heed-0.20.5/examples/rmp-serde.rs000064400000000000000000000017701046102023000147040ustar 00000000000000use std::error::Error; use std::fs; use std::path::Path; use heed::types::{SerdeRmp, Str}; use heed::{Database, EnvOpenOptions}; use serde::{Deserialize, Serialize}; fn main() -> Result<(), Box> { let path = Path::new("target").join("heed.mdb"); fs::create_dir_all(&path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // you can specify that a database will support some typed key/data // serde types are also supported!!! #[derive(Debug, Serialize, Deserialize)] struct Hello<'a> { string: &'a str, } let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-rmp"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-rmp:\t{:?}", ret); wtxn.commit()?; Ok(()) } heed-0.20.5/src/cookbook.rs000064400000000000000000000374351046102023000135740ustar 00000000000000//! A cookbook of examples on how to use heed. Here is the list of the different topics you can learn about: //! //! - [Decode Values on Demand](#decode-values-on-demand) //! - [Listing and Opening the Named Databases](#listing-and-opening-the-named-databases) //! - [Create Custom and Prefix Codecs](#create-custom-and-prefix-codecs) //! - [Change the Environment Size Dynamically](#change-the-environment-size-dynamically) //! - [Advanced Multithreaded Access of Entries](#advanced-multithreaded-access-of-entries) //! //! # Decode Values on Demand //! //! Sometimes, you need to iterate on the content of a database and //! conditionnaly decode the value depending on the key. You can use the //! [`Database::lazily_decode_data`] method to indicate this to heed. //! //! ``` //! use std::collections::HashMap; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! pub type StringMap = HashMap; //! //! fn main() -> Result<(), Box> { //! let path = Path::new("target").join("heed.mdb"); //! //! fs::create_dir_all(&path)?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(1024 * 1024 * 100) // 100 MiB //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database> = env.create_database(&mut wtxn, None)?; //! //! fill_with_data(&mut wtxn, db)?; //! //! // We make sure that iterating over this database will //! // not deserialize the values. We just want to decode //! // the value corresponding to 43th key. //! for (i, result) in db.lazily_decode_data().iter(&wtxn)?.enumerate() { //! let (_key, lazy_value) = result?; //! if i == 43 { //! // This is where the magic happens. We receive a Lazy type //! // that wraps a slice of bytes. We can decode on purpose. //! let value = lazy_value.decode()?; //! assert_eq!(value.get("secret"), Some(&String::from("434343"))); //! break; //! } //! } //! //! Ok(()) //! } //! //! fn fill_with_data( //! wtxn: &mut heed::RwTxn, //! db: Database>, //! ) -> heed::Result<()> { //! // This represents a very big value that we only want to decode when necessary. //! let mut big_string_map = HashMap::new(); //! big_string_map.insert("key1".into(), "I am a very long string".into()); //! big_string_map.insert("key2".into(), "I am a also very long string".into()); //! //! for i in 0..100 { //! let key = format!("{i:5}"); //! big_string_map.insert("secret".into(), format!("{i}{i}{i}")); //! db.put(wtxn, &key, &big_string_map)?; //! } //! Ok(()) //! } //! ``` //! //! # Listing and Opening the Named Databases //! //! Sometimes it is useful to list the databases available in an environment. //! LMDB automatically stores their names in the unnamed database, a database that doesn't //! need to be created in which you can write. //! //! Once you create new databases, after defining the [`EnvOpenOptions::max_dbs`] //! parameter, the names of those databases are automatically stored in the unnamed one. //! //! ``` //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! fn main() -> Result<(), Box> { //! let env_path = Path::new("target").join("heed.mdb"); //! //! fs::create_dir_all(&env_path)?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3) // Number of opened databases //! .open(env_path)? //! }; //! //! let rtxn = env.read_txn()?; //! // The database names are mixed with the user entries therefore we prefer //! // ignoring the values and try to open the databases one by one using the keys. //! let unnamed: Database = //! env.open_database(&rtxn, None)?.expect("the unnamed database always exists"); //! //! // The unnamed (or main) database contains the other //! // database names associated to empty values. //! for result in unnamed.iter(&rtxn)? { //! let (name, ()) = result?; //! //! if let Ok(Some(_db)) = env.open_database::(&rtxn, Some(name)) { //! // We succeeded into opening a new database that //! // contains strings associated to raw bytes. //! } //! } //! //! // When opening databases in a read-only transaction //! // you must commit your read transaction to make your //! // freshly opened databases globally available. //! rtxn.commit()?; //! //! // If you abort (or drop) your read-only transaction //! // the database handle will be invalid outside //! // the transaction scope. //! //! Ok(()) //! } //! ``` //! //! # Create Custom and Prefix Codecs //! //! With heed you can store any kind of data and serialize it the way you want. //! To do so you'll need to create a codec by using the [`BytesEncode`] and [`BytesDecode`] traits. //! //! Now imagine that your data is lexicographically well ordered. You can now leverage //! the use of prefix codecs. Those are classic codecs but are only used to encode key prefixes. //! //! In this example we will store logs associated to a timestamp. By encoding the timestamp //! in big endian we can create a prefix codec that restricts a subset of the data. It is recommended //! to create codecs to encode prefixes when possible instead of using a slice of bytes. //! //! ``` //! use std::borrow::Cow; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{BoxedError, BytesDecode, BytesEncode, Database, EnvOpenOptions}; //! //! #[derive(Debug, PartialEq, Eq)] //! pub enum Level { //! Debug, //! Warn, //! Error, //! } //! //! #[derive(Debug, PartialEq, Eq)] //! pub struct LogKey { //! timestamp: u32, //! level: Level, //! } //! //! pub struct LogKeyCodec; //! //! impl<'a> BytesEncode<'a> for LogKeyCodec { //! type EItem = LogKey; //! //! /// Encodes the u32 timestamp in big endian followed by the log level with a single byte. //! fn bytes_encode(log: &Self::EItem) -> Result, BoxedError> { //! let (timestamp_bytes, level_byte) = match log { //! LogKey { timestamp, level: Level::Debug } => (timestamp.to_be_bytes(), 0), //! LogKey { timestamp, level: Level::Warn } => (timestamp.to_be_bytes(), 1), //! LogKey { timestamp, level: Level::Error } => (timestamp.to_be_bytes(), 2), //! }; //! //! let mut output = Vec::new(); //! output.extend_from_slice(×tamp_bytes); //! output.push(level_byte); //! Ok(Cow::Owned(output)) //! } //! } //! //! impl<'a> BytesDecode<'a> for LogKeyCodec { //! type DItem = LogKey; //! //! fn bytes_decode(bytes: &'a [u8]) -> Result { //! use std::mem::size_of; //! //! let timestamp = match bytes.get(..size_of::()) { //! Some(bytes) => bytes.try_into().map(u32::from_be_bytes).unwrap(), //! None => return Err("invalid log key: cannot extract timestamp".into()), //! }; //! //! let level = match bytes.get(size_of::()) { //! Some(&0) => Level::Debug, //! Some(&1) => Level::Warn, //! Some(&2) => Level::Error, //! Some(_) => return Err("invalid log key: invalid log level".into()), //! None => return Err("invalid log key: cannot extract log level".into()), //! }; //! //! Ok(LogKey { timestamp, level }) //! } //! } //! //! /// Encodes the high part of a timestamp. As it is located //! /// at the start of the key it can be used to only return //! /// the logs that appeared during a, rather long, period. //! pub struct LogAtHalfTimestampCodec; //! //! impl<'a> BytesEncode<'a> for LogAtHalfTimestampCodec { //! type EItem = u32; //! //! /// This method encodes only the prefix of the keys in this particular case, the timestamp. //! fn bytes_encode(half_timestamp: &Self::EItem) -> Result, BoxedError> { //! Ok(Cow::Owned(half_timestamp.to_be_bytes()[..2].to_vec())) //! } //! } //! //! impl<'a> BytesDecode<'a> for LogAtHalfTimestampCodec { //! type DItem = LogKey; //! //! fn bytes_decode(bytes: &'a [u8]) -> Result { //! LogKeyCodec::bytes_decode(bytes) //! } //! } //! //! fn main() -> Result<(), Box> { //! let path = Path::new("target").join("heed.mdb"); //! //! fs::create_dir_all(&path)?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3000) //! .open(path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1608326232, level: Level::Debug }, //! "this is a very old log", //! )?; //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1708326232, level: Level::Debug }, //! "fibonacci was executed in 21ms", //! )?; //! db.put(&mut wtxn, &LogKey { timestamp: 1708326242, level: Level::Error }, "fibonacci crashed")?; //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1708326272, level: Level::Warn }, //! "fibonacci is running since 12s", //! )?; //! //! // We change the way we want to read our database by changing the key codec. //! // In this example we can prefix search only for the logs between a period of time //! // (the two high bytes of the u32 timestamp). //! let iter = db.remap_key_type::().prefix_iter(&wtxn, &1708326232)?; //! //! // As we filtered the log for a specific //! // period of time we must not see the very old log. //! for result in iter { //! let (LogKey { timestamp: _, level: _ }, content) = result?; //! assert_ne!(content, "this is a very old log"); //! } //! //! Ok(()) //! } //! ``` //! //! # Change the Environment Size Dynamically //! //! You must specify the maximum size of an LMDB environment when you open it. //! Environment do not dynamically increase there size for performance reasons and also to //! have more control on it. //! //! Here is a simple example on the way to go to dynamically increase the size //! of an environment when you detect that it is going out of space. //! //! ``` //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! fn main() -> Result<(), Box> { //! let path = Path::new("target").join("small-space.mdb"); //! //! fs::create_dir_all(&path)?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(16384) // one page //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! // Ho! Crap! We don't have enough space in this environment... //! assert!(matches!( //! fill_with_data(&mut wtxn, db), //! Err(heed::Error::Mdb(heed::MdbError::MapFull)) //! )); //! //! drop(wtxn); //! //! // We need to increase the page size and we can only do that //! // when no transaction are running so closing the env is easier. //! env.prepare_for_closing().wait(); //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 16384) // 10 pages //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! // We now have enough space in the env to store all of our entries. //! assert!(matches!(fill_with_data(&mut wtxn, db), Ok(()))); //! //! Ok(()) //! } //! //! fn fill_with_data(wtxn: &mut heed::RwTxn, db: Database) -> heed::Result<()> { //! for i in 0..1000 { //! let key = i.to_string(); //! db.put(wtxn, &key, "I am a very long string")?; //! } //! Ok(()) //! } //! ``` //! //! # Advanced Multithreaded Access of Entries //! //! LMDB disallow sharing cursors amongs threads. It is only possible to send //! them between threads when the heed `read-txn-no-tls` feature is enabled. //! //! This limits some usecases that require a parallel access to the content of the databases //! to process stuff faster. This is the case of arroy, a multithreads fast approximate //! neighbors search library. I wrote [an article explaining how //! to read entries in parallel][arroy article]. //! //! It is forbidden to write in an environement while reading in it. However, it is possible //! to keep pointers to the values of the entries returned by LMDB. Those pointers are valid //! until the end of the transaction. //! //! Here is a small example on how to declare a datastructure to be used in parallel across thread, //! safely. The unsafe part declare that the datastructure can be shared between thread despite //! the write transaction not being `Send` nor `Sync`. //! //! [arroy article]: https://blog.kerollmops.com/multithreading-and-memory-mapping-refining-ann-performance-with-arroy //! //! ``` //! use std::collections::HashMap; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions, RoTxn}; //! //! fn main() -> Result<(), Box> { //! let path = Path::new("target").join("heed.mdb"); //! //! fs::create_dir_all(&path)?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(1024 * 1024 * 100) // 100 MiB //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! fill_with_data(&mut wtxn, db)?; //! //! let immutable_map = ImmutableMap::from_db(&wtxn, db)?; //! //! // We can share the immutable map over multiple threads because it is Sync. //! // It is safe because we keep the write transaction lifetime in this type. //! std::thread::scope(|s| { //! s.spawn(|| { //! let value = immutable_map.get("10"); //! assert_eq!(value, Some("I am a very long string")); //! }); //! s.spawn(|| { //! let value = immutable_map.get("20"); //! assert_eq!(value, Some("I am a very long string")); //! }); //! }); //! //! // You can see that we always have it on the main thread. //! // We didn't sent it over threads. //! let value = immutable_map.get("50"); //! assert_eq!(value, Some("I am a very long string")); //! //! Ok(()) //! } //! //! fn fill_with_data(wtxn: &mut heed::RwTxn, db: Database) -> heed::Result<()> { //! for i in 0..100 { //! let key = i.to_string(); //! db.put(wtxn, &key, "I am a very long string")?; //! } //! Ok(()) //! } //! //! struct ImmutableMap<'a> { //! map: HashMap<&'a str, &'a str>, //! } //! //! impl<'t> ImmutableMap<'t> { //! fn from_db(rtxn: &'t RoTxn, db: Database) -> heed::Result { //! let mut map = HashMap::new(); //! for result in db.iter(rtxn)? { //! let (k, v) = result?; //! map.insert(k, v); //! } //! Ok(ImmutableMap { map }) //! } //! //! fn get(&self, key: &str) -> Option<&'t str> { //! self.map.get(key).copied() //! } //! } //! //! unsafe impl Sync for ImmutableMap<'_> {} //! ``` //! // To let cargo generate doc links #![allow(unused_imports)] use crate::{BytesDecode, BytesEncode, Database, EnvOpenOptions}; heed-0.20.5/src/cursor.rs000064400000000000000000000344651046102023000133030ustar 00000000000000use std::ops::{Deref, DerefMut}; use std::{marker, mem, ptr}; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::*; pub struct RoCursor<'txn> { cursor: *mut ffi::MDB_cursor, _marker: marker::PhantomData<&'txn ()>, } impl<'txn> RoCursor<'txn> { pub(crate) fn new(txn: &'txn RoTxn, dbi: ffi::MDB_dbi) -> Result> { let mut cursor: *mut ffi::MDB_cursor = ptr::null_mut(); unsafe { mdb_result(ffi::mdb_cursor_open(txn.txn, dbi, &mut cursor))? } Ok(RoCursor { cursor, _marker: marker::PhantomData }) } pub fn current(&mut self) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), ffi::cursor_op::MDB_GET_CURRENT, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_first(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_FIRST, MoveOperation::Dup => { unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, ptr::null_mut(), &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_FIRST_DUP, ))? }; ffi::cursor_op::MDB_GET_CURRENT } MoveOperation::NoDup => ffi::cursor_op::MDB_FIRST, }; // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_last(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_LAST, MoveOperation::Dup => { unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, ptr::null_mut(), &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_LAST_DUP, ))? }; ffi::cursor_op::MDB_GET_CURRENT } MoveOperation::NoDup => ffi::cursor_op::MDB_LAST, }; // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_key(&mut self, key: &[u8]) -> Result { let mut key_val = unsafe { crate::into_val(key) }; // Move the cursor to the specified key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, &mut key_val, &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_SET, )) }; match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } pub fn move_on_key_greater_than_or_equal_to( &mut self, key: &[u8], ) -> Result> { let mut key_val = unsafe { crate::into_val(key) }; let mut data_val = mem::MaybeUninit::uninit(); // Move the cursor to the specified key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, &mut key_val, data_val.as_mut_ptr(), ffi::cursor_op::MDB_SET_RANGE, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_prev(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_PREV, MoveOperation::Dup => ffi::cursor_op::MDB_PREV_DUP, MoveOperation::NoDup => ffi::cursor_op::MDB_PREV_NODUP, }; // Move the cursor to the previous non-dup key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_next(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_NEXT, MoveOperation::Dup => ffi::cursor_op::MDB_NEXT_DUP, MoveOperation::NoDup => ffi::cursor_op::MDB_NEXT_NODUP, }; // Move the cursor to the next non-dup key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } } impl Drop for RoCursor<'_> { fn drop(&mut self) { unsafe { ffi::mdb_cursor_close(self.cursor) } } } pub struct RwCursor<'txn> { cursor: RoCursor<'txn>, } impl<'txn> RwCursor<'txn> { pub(crate) fn new(txn: &'txn RwTxn, dbi: ffi::MDB_dbi) -> Result> { Ok(RwCursor { cursor: RoCursor::new(txn, dbi)? }) } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { // Delete the current entry let result = mdb_result(ffi::mdb_cursor_del(self.cursor.cursor, 0)); match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current(&mut self, key: &[u8], data: &[u8]) -> Result { let mut key_val = crate::into_val(key); let mut data_val = crate::into_val(data); // Modify the pointed data let result = mdb_result(ffi::mdb_cursor_put( self.cursor.cursor, &mut key_val, &mut data_val, ffi::MDB_CURRENT, )); match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`Self::put_current`] method. pub unsafe fn put_current_reserved_with_flags( &mut self, flags: PutFlags, key: &[u8], data_size: usize, write_func: F, ) -> Result where F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let mut key_val = crate::into_val(key); let mut reserved = ffi::reserve_size_val(data_size); let flags = ffi::MDB_RESERVE | flags.bits(); let result = mdb_result(ffi::mdb_cursor_put(self.cursor.cursor, &mut key_val, &mut reserved, flags)); let found = match result { Ok(()) => true, Err(e) if e.not_found() => false, Err(e) => return Err(e.into()), }; let mut reserved = ReservedSpace::from_val(reserved); write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(found) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } /// Append the given key/value pair to the end of the database. /// /// If a key is inserted that is less than any previous key a `KeyExist` error /// is returned and the key is not inserted into the database. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_flags( &mut self, flags: PutFlags, key: &[u8], data: &[u8], ) -> Result<()> { let mut key_val = crate::into_val(key); let mut data_val = crate::into_val(data); // Modify the pointed data let result = mdb_result(ffi::mdb_cursor_put( self.cursor.cursor, &mut key_val, &mut data_val, flags.bits(), )); result.map_err(Into::into) } } impl<'txn> Deref for RwCursor<'txn> { type Target = RoCursor<'txn>; fn deref(&self) -> &Self::Target { &self.cursor } } impl DerefMut for RwCursor<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cursor } } /// The way the `Iterator::next/prev` method behaves towards DUP data. #[derive(Debug, Clone, Copy)] pub enum MoveOperation { /// Move on the next/prev entry, wether it's the same key or not. Any, /// Move on the next/prev data of the current key. Dup, /// Move on the next/prev entry which is the next/prev key. /// Skip the multiple values of the current key. NoDup, } heed-0.20.5/src/database.rs000064400000000000000000002706451046102023000135340ustar 00000000000000use std::borrow::Cow; use std::ops::{Bound, RangeBounds}; use std::{any, fmt, marker, mem, ptr}; use heed_traits::{Comparator, LexicographicComparator}; use types::{DecodeIgnore, LazyDecode}; use crate::cursor::MoveOperation; use crate::env::DefaultComparator; use crate::iteration_method::MoveOnCurrentKeyDuplicates; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::mdb::lmdb_flags::{AllDatabaseFlags, DatabaseFlags}; use crate::*; /// Options and flags which can be used to configure how a [`Database`] is opened. /// /// # Examples /// /// Opening a file to read: /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// // Imagine you have an optional name /// let conditional_name = Some("big-endian-iter"); /// /// let mut wtxn = env.write_txn()?; /// let mut options = env.database_options().types::(); /// if let Some(name) = conditional_name { /// options.name(name); /// } /// let db = options.create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` #[derive(Debug)] pub struct DatabaseOpenOptions<'e, 'n, KC, DC, C = DefaultComparator> { env: &'e Env, types: marker::PhantomData<(KC, DC, C)>, name: Option<&'n str>, flags: AllDatabaseFlags, } impl<'e> DatabaseOpenOptions<'e, 'static, Unspecified, Unspecified> { /// Create an options struct to open/create a database with specific flags. pub fn new(env: &'e Env) -> Self { DatabaseOpenOptions { env, types: Default::default(), name: None, flags: AllDatabaseFlags::empty(), } } } impl<'e, 'n, KC, DC, C> DatabaseOpenOptions<'e, 'n, KC, DC, C> { /// Change the type of the database. /// /// The default types are [`Unspecified`] and require a call to [`Database::remap_types`] /// to use the [`Database`]. pub fn types(self) -> DatabaseOpenOptions<'e, 'n, NKC, NDC> { DatabaseOpenOptions { env: self.env, types: Default::default(), name: self.name, flags: self.flags, } } /// Change the customized key compare function of the database. /// /// By default no customized compare function will be set when opening a database. pub fn key_comparator(self) -> DatabaseOpenOptions<'e, 'n, KC, DC, NC> { DatabaseOpenOptions { env: self.env, types: Default::default(), name: self.name, flags: self.flags, } } /// Change the name of the database. /// /// By default the database is unnamed and there only is a single unnamed database. pub fn name(&mut self, name: &'n str) -> &mut Self { self.name = Some(name); self } /// Specify the set of flags used to open the database. pub fn flags(&mut self, flags: DatabaseFlags) -> &mut Self { self.flags = AllDatabaseFlags::from_bits(flags.bits()).unwrap(); self } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open(&self, rtxn: &RoTxn) -> Result>> where KC: 'static, DC: 'static, C: Comparator + 'static, { assert_eq_env_txn!(self.env, rtxn); match self.env.raw_init_database::(rtxn.txn, self.name, self.flags) { Ok(dbi) => Ok(Some(Database::new(self.env.env_mut_ptr() as _, dbi))), Err(Error::Mdb(e)) if e.not_found() => Ok(None), Err(e) => Err(e), } } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create(&self, wtxn: &mut RwTxn) -> Result> where KC: 'static, DC: 'static, C: Comparator + 'static, { assert_eq_env_txn!(self.env, wtxn); let flags = self.flags | AllDatabaseFlags::CREATE; match self.env.raw_init_database::(wtxn.txn.txn, self.name, flags) { Ok(dbi) => Ok(Database::new(self.env.env_mut_ptr() as _, dbi)), Err(e) => Err(e), } } } impl Clone for DatabaseOpenOptions<'_, '_, KC, DC, C> { fn clone(&self) -> Self { *self } } impl Copy for DatabaseOpenOptions<'_, '_, KC, DC, C> {} /// A typed database that accepts only the types it was created with. /// /// # Example: Iterate over databases entries /// /// In this example we store numbers in big endian this way those are ordered. /// Thanks to their bytes representation, heed is able to iterate over them /// from the lowest to the highest. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over database entries in order /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (35, ()), /// (42, ()), /// (68, ()), /// ]; /// /// assert_eq!(rets, expected); /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// # Example: Iterate over and delete ranges of entries /// /// Discern also support ranges and ranges deletions. /// Same configuration as above, numbers are ordered, therefore it is safe to specify /// a range and be able to iterate over and/or delete it. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over ranges too!!! /// let range = 35..=42; /// let rets: Result<_, _> = db.range(&wtxn, &range)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (35, ()), /// (42, ()), /// ]; /// /// assert_eq!(rets, expected); /// /// // even delete a range of keys /// let range = 35..=42; /// let deleted: usize = db.delete_range(&mut wtxn, &range)?; /// /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (68, ()), /// ]; /// /// assert_eq!(deleted, 2); /// assert_eq!(rets, expected); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub struct Database { pub(crate) env_ident: usize, pub(crate) dbi: ffi::MDB_dbi, marker: marker::PhantomData<(KC, DC, C)>, } impl Database { pub(crate) fn new(env_ident: usize, dbi: ffi::MDB_dbi) -> Database { Database { env_ident, dbi, marker: std::marker::PhantomData } } /// Retrieves the value associated with a key. /// /// If the key does not exist, then `None` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32= U32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("get-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// /// let ret = db.get(&wtxn, "i-am-forty-two")?; /// assert_eq!(ret, Some(42)); /// /// let ret = db.get(&wtxn, "i-am-twenty-one")?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get<'a, 'txn>(&self, txn: &'txn RoTxn, key: &'a KC::EItem) -> Result> where KC: BytesEncode<'a>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = mem::MaybeUninit::uninit(); let result = unsafe { mdb_result(ffi::mdb_get(txn.txn, self.dbi, &mut key_val, data_val.as_mut_ptr())) }; match result { Ok(()) => { let data = unsafe { crate::from_val(data_val.assume_init()) }; let data = DC::bytes_decode(data).map_err(Error::Decoding)?; Ok(Some(data)) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } /// Returns an iterator over all of the values of a single key. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_duplicates<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result>> where KC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; if cursor.move_on_key(&key_bytes)? { Ok(Some(RoIter::new(cursor))) } else { Ok(None) } } /// Retrieves the key/value pair lower than the given one in this database. /// /// If the database if empty or there is no key lower than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than(&wtxn, &43)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_lower_than(&wtxn, &27)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; cursor.move_on_key_greater_than_or_equal_to(&key_bytes)?; match cursor.move_on_prev(MoveOperation::NoDup) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the key/value pair lower than or equal to the given one in this database. /// /// If the database if empty or there is no key lower than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &43)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &26)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than_or_equal_to<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let result = match cursor.move_on_key_greater_than_or_equal_to(&key_bytes) { Ok(Some((key, data))) if key == &key_bytes[..] => Ok(Some((key, data))), Ok(_) => cursor.move_on_prev(MoveOperation::NoDup), Err(e) => Err(e), }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the key/value pair greater than the given one in this database. /// /// If the database if empty or there is no key greater than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than(&wtxn, &42)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_greater_than(&wtxn, &43)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let entry = match cursor.move_on_key_greater_than_or_equal_to(&key_bytes)? { Some((key, data)) if key > &key_bytes[..] => Some((key, data)), Some((_key, _data)) => cursor.move_on_next(MoveOperation::NoDup)?, None => None, }; match entry { Some((key, data)) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, None => Ok(None), } } /// Retrieves the key/value pair greater than or equal to the given one in this database. /// /// If the database if empty or there is no key greater than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &42)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &44)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than_or_equal_to<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; match cursor.move_on_key_greater_than_or_equal_to(&key_bytes) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the first key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("first-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.first(&wtxn)?; /// assert_eq!(ret, Some((27, "i-am-twenty-seven"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn first<'txn>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; match cursor.move_on_first(MoveOperation::Any) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the last key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("last-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.last(&wtxn)?; /// assert_eq!(ret, Some((42, "i-am-forty-two"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn last<'txn>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; match cursor.move_on_last(MoveOperation::Any) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Returns the number of elements in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 4); /// /// db.delete(&mut wtxn, &27)?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 3); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn len(&self, txn: &RoTxn) -> Result { self.stat(txn).map(|stat| stat.entries as u64) } /// Returns `true` if and only if this database is empty. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, false); /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, true); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn is_empty(&self, txn: &RoTxn) -> Result { self.len(txn).map(|l| l == 0) } /// Returns some statistics for this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let stat = db.stat(&wtxn)?; /// assert_eq!(stat.depth, 1); /// assert_eq!(stat.branch_pages, 0); /// assert_eq!(stat.leaf_pages, 1); /// assert_eq!(stat.overflow_pages, 0); /// assert_eq!(stat.entries, 4); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn stat(&self, txn: &RoTxn) -> Result { assert_eq_env_db_txn!(self, txn); let mut db_stat = mem::MaybeUninit::uninit(); let result = unsafe { mdb_result(ffi::mdb_stat(txn.txn, self.dbi, db_stat.as_mut_ptr())) }; match result { Ok(()) => { let stats = unsafe { db_stat.assume_init() }; Ok(DatabaseStat { page_size: stats.ms_psize, depth: stats.ms_depth, branch_pages: stats.ms_branch_pages, leaf_pages: stats.ms_leaf_pages, overflow_pages: stats.ms_overflow_pages, entries: stats.ms_entries, }) } Err(e) => Err(e.into()), } } /// Return a lexicographically ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoIter::new(cursor)) } /// Return a mutable lexicographically ordered iterator of all key-value pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, Some("i-am-the-new-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwIter::new(cursor)) } /// Return a reversed lexicographically ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoRevIter::new(cursor)) } /// Return a mutable reversed lexicographically ordered iterator of all key-value\ /// pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.put_current(&13, "i-am-the-new-thirteen")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, Some("i-am-the-new-thirteen")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwRevIter::new(cursor)) } /// Return a lexicographically ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut iter = db.range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn range<'a, 'txn, R>( &self, txn: &'txn RoTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RoCursor::new(txn, self.dbi).map(|cursor| RoRange::new(cursor, start_bound, end_bound)) } /// Return a mutable lexicographically ordered iterator of a range of /// key-value pairs in this database. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-new-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RwCursor::new(txn, self.dbi).map(|cursor| RwRange::new(cursor, start_bound, end_bound)) } /// Return a reversed lexicographically ordered iterator of a range of key-value /// pairs in this database. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=43; /// let mut iter = db.rev_range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range<'a, 'txn, R>( &self, txn: &'txn RoTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RoCursor::new(txn, self.dbi).map(|cursor| RoRevRange::new(cursor, start_bound, end_bound)) } /// Return a mutable reversed lexicographically ordered iterator of a range of /// key-value pairs in this database. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.rev_range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.put_current(&27, "i-am-the-new-twenty-seven")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-the-new-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RwCursor::new(txn, self.dbi).map(|cursor| RwRevRange::new(cursor, start_bound, end_bound)) } /// Return a lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter<'a, 'txn>( &self, txn: &'txn RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RoCursor::new(txn, self.dbi).map(|cursor| RoPrefix::new(cursor, prefix_bytes)) } /// Return a mutable lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.put_current("i-am-twenty-seven", &27000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, Some(27000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter_mut<'a, 'txn>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RwCursor::new(txn, self.dbi).map(|cursor| RwPrefix::new(cursor, prefix_bytes)) } /// Return a reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter<'a, 'txn>( &self, txn: &'txn RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RoCursor::new(txn, self.dbi).map(|cursor| RoRevPrefix::new(cursor, prefix_bytes)) } /// Return a mutable reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.put_current("i-am-twenty-eight", &28000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, Some(28000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter_mut<'a, 'txn>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RwCursor::new(txn, self.dbi).map(|cursor| RwRevPrefix::new(cursor, prefix_bytes)) } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with no specific flag. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, Some("i-am-twenty-seven")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = 0; unsafe { mdb_result(ffi::mdb_put(txn.txn.txn, self.dbi, &mut key_val, &mut data_val, flags))? } Ok(()) } /// Insert a key-value pair where the value can directly be written to disk, replacing any /// previous value. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let value = "I am a long long long value"; /// db.put_reserved(&mut wtxn, &42, value.len(), |reserved| { /// reserved.write_all(value.as_bytes()) /// })?; /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(value)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_reserved<'a, F>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result<()> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut reserved = ffi::reserve_size_val(data_size); let flags = ffi::MDB_RESERVE; unsafe { mdb_result(ffi::mdb_put(txn.txn.txn, self.dbi, &mut key_val, &mut reserved, flags))? } let mut reserved = unsafe { ReservedSpace::from_val(reserved) }; write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(()) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with the specified flags. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags, DatabaseFlags, Error, MdbError}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .name("dup-i32") /// .flags(DatabaseFlags::DUP_SORT) /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &42, "i-am-so-cool")?; /// db.put(&mut wtxn, &42, "i-am-the-king")?; /// db.put(&mut wtxn, &42, "i-am-fun")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND, &54, "i-am-older-than-you")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP, &54, "ok-but-i-am-better-than-you")?; /// // You can compose flags by OR'ing them /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP | PutFlags::NO_OVERWRITE, &55, "welcome")?; /// /// // The NO_DUP_DATA flag will return KeyExist if we try to insert the exact same key/value pair. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_DUP_DATA, &54, "ok-but-i-am-better-than-you"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// // The NO_OVERWRITE flag will return KeyExist if we try to insert something with an already existing key. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_OVERWRITE, &54, "there-can-be-only-one-data"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-fun"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-so-cool"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-king"))); /// assert_eq!(iter.next().transpose()?, Some((54, "i-am-older-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((54, "ok-but-i-am-better-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((55, "welcome"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_with_flags<'a>( &self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = flags.bits(); unsafe { mdb_result(ffi::mdb_put(txn.txn.txn, self.dbi, &mut key_val, &mut data_val, flags))? } Ok(()) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) flag. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put(&mut wtxn, &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put(&mut wtxn, &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put<'a, 'txn>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { self.get_or_put_with_flags(txn, PutFlags::empty(), key, data) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) which is always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_with_flags<'a, 'txn>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = (flags | PutFlags::NO_OVERWRITE).bits(); let result = unsafe { mdb_result(ffi::mdb_put(txn.txn.txn, self.dbi, &mut key_val, &mut data_val, flags)) }; match result { // the value was successfully inserted Ok(()) => Ok(None), // the key already exists: the previous value is stored in the data parameter Err(MdbError::KeyExist) => { let bytes = unsafe { crate::from_val(data_val) }; let data = DC::bytes_decode(bytes).map_err(Error::Decoding)?; Ok(Some(data)) } Err(error) => Err(error.into()), } } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and /// [`MDB_RESERVE`](lmdb_master_sys::MDB_RESERVE) flags. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { self.get_or_put_reserved_with_flags(txn, PutFlags::empty(), key, data_size, write_func) } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and [`MDB_RESERVE`](lmdb_master_sys::MDB_RESERVE) /// which are always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved_with_flags<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut reserved = ffi::reserve_size_val(data_size); let flags = (flags | PutFlags::NO_OVERWRITE).bits() | lmdb_master_sys::MDB_RESERVE; let result = unsafe { mdb_result(ffi::mdb_put(txn.txn.txn, self.dbi, &mut key_val, &mut reserved, flags)) }; match result { // value was inserted: fill the reserved space Ok(()) => { let mut reserved = unsafe { ReservedSpace::from_val(reserved) }; write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(None) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } // the key already exists: the previous value is stored in the data parameter Err(MdbError::KeyExist) => { let bytes = unsafe { crate::from_val(reserved) }; let data = DC::bytes_decode(bytes).map_err(Error::Decoding)?; Ok(Some(data)) } Err(error) => Err(error.into()), } } /// Deletes an entry or every duplicate data items of a key /// if the database supports duplicate data items. /// /// If the entry does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.delete(&mut wtxn, &27)?; /// assert_eq!(ret, true); /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, None); /// /// let ret = db.delete(&mut wtxn, &467)?; /// assert_eq!(ret, false); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem) -> Result where KC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let result = unsafe { mdb_result(ffi::mdb_del(txn.txn.txn, self.dbi, &mut key_val, ptr::null_mut())) }; match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Deletes a single key-value pair in this database. /// /// If the database doesn't support duplicate data items the data is ignored. /// If the key does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_one_duplicate<'a>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let result = unsafe { mdb_result(ffi::mdb_del(txn.txn.txn, self.dbi, &mut key_val, &mut data_val)) }; match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Deletes a range of key-value pairs in this database. /// /// Prefer using [`clear`] instead of a call to this method with a full range ([`..`]). /// /// Comparisons are made by using the bytes representation of the key. /// /// [`clear`]: crate::Database::clear /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let ret = db.delete_range(&mut wtxn, &range)?; /// assert_eq!(ret, 2); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_range<'a, 'txn, R>(&self, txn: &'txn mut RwTxn, range: &'a R) -> Result where KC: BytesEncode<'a> + BytesDecode<'txn>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let mut count = 0; let mut iter = self.remap_data_type::().range_mut(txn, range)?; while iter.next().is_some() { // safety: We do not keep any reference from the database while using `del_current`. // The user can't keep any reference inside of the database as we ask for a // mutable reference to the `txn`. unsafe { iter.del_current()? }; count += 1; } Ok(count) } /// Deletes all key/value pairs in this database. /// /// Prefer using this method instead of a call to [`delete_range`] with a full range ([`..`]). /// /// [`delete_range`]: crate::Database::delete_range /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert!(ret); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn clear(&self, txn: &mut RwTxn) -> Result<()> { assert_eq_env_db_txn!(self, txn); unsafe { mdb_result(ffi::mdb_drop(txn.txn.txn, self.dbi, 0)).map_err(Into::into) } } /// Change the codec types of this database, specifying the codecs. /// /// # Safety /// /// It is up to you to ensure that the data read and written using the polymorphic /// handle correspond to the the typed, uniform one. If an invalid write is made, /// it can corrupt the database from the eyes of heed. /// /// # Example /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// // We remap the types for ease of use. /// let db = db.remap_types::(); /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn remap_types(&self) -> Database { Database::new(self.env_ident, self.dbi) } /// Change the key codec type of this database, specifying the new codec. pub fn remap_key_type(&self) -> Database { self.remap_types::() } /// Change the data codec type of this database, specifying the new codec. pub fn remap_data_type(&self) -> Database { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(&self) -> Database, C> { self.remap_types::>() } } impl Clone for Database { fn clone(&self) -> Database { *self } } impl Copy for Database {} impl fmt::Debug for Database { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Database") .field("key_codec", &any::type_name::()) .field("data_codec", &any::type_name::()) .field("comparator", &any::type_name::()) .finish() } } /// Statistics for a database in the environment. #[derive(Debug, Clone, Copy)] pub struct DatabaseStat { /// Size of a database page. /// This is currently the same for all databases. pub page_size: u32, /// Depth (height) of the B-tree. pub depth: u32, /// Number of internal (non-leaf) pages pub branch_pages: usize, /// Number of leaf pages. pub leaf_pages: usize, /// Number of overflow pages. pub overflow_pages: usize, /// Number of data items. pub entries: usize, } #[cfg(test)] mod tests { use heed_types::*; use super::*; #[test] fn put_overwrite() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env.create_database::(&mut txn, None)?; assert_eq!(db.get(&txn, b"hello").unwrap(), None); db.put(&mut txn, b"hello", b"hi").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"hi"[..])); db.put(&mut txn, b"hello", b"bye").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"bye"[..])); Ok(()) } #[test] #[cfg(feature = "longer-keys")] fn longer_keys() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env.create_database::(&mut txn, None)?; // Try storing a key larger than 511 bytes (the default if MDB_MAXKEYSIZE is not set) let long_key = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut pharetra sit amet aliquam. Sit amet nisl purus in mollis nunc. Eget egestas purus viverra accumsan in nisl nisi scelerisque. Duis ultricies lacus sed turpis tincidunt. Sem nulla pharetra diam sit. Leo vel orci porta non pulvinar. Erat pellentesque adipiscing commodo elit at imperdiet dui. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Diam donec adipiscing tristique risus nec feugiat. In fermentum et sollicitudin ac orci. Ut sem nulla pharetra diam sit amet. Aliquam purus sit amet luctus venenatis lectus. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan. Urna duis convallis convallis tellus id interdum velit laoreet id. Ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Tincidunt arcu non sodales neque. Habitant morbi tristique senectus et netus et malesuada fames."; assert_eq!(db.get(&txn, long_key).unwrap(), None); db.put(&mut txn, long_key, b"hi").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"hi"[..])); db.put(&mut txn, long_key, b"bye").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"bye"[..])); Ok(()) } } heed-0.20.5/src/env.rs000064400000000000000000001351211046102023000125450ustar 00000000000000use std::any::TypeId; use std::cmp::Ordering; use std::collections::hash_map::{Entry, HashMap}; use std::ffi::{c_void, CString}; use std::fs::{File, Metadata}; use std::io::ErrorKind::NotFound; #[cfg(unix)] use std::os::unix::{ ffi::OsStrExt, io::{AsRawFd, BorrowedFd, RawFd}, }; use std::panic::catch_unwind; use std::path::{Path, PathBuf}; use std::process::abort; use std::sync::{Arc, RwLock}; use std::time::Duration; #[cfg(windows)] use std::{ ffi::OsStr, os::windows::io::{AsRawHandle, BorrowedHandle, RawHandle}, }; use std::{fmt, io, mem, ptr}; use heed_traits::{Comparator, LexicographicComparator}; use once_cell::sync::Lazy; use synchronoise::event::SignalEvent; use crate::cursor::MoveOperation; use crate::database::DatabaseOpenOptions; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::mdb::lmdb_flags::AllDatabaseFlags; use crate::{Database, EnvFlags, Error, Result, RoCursor, RoTxn, RwTxn, Unspecified}; /// The list of opened environments, the value is an optional environment, it is None /// when someone asks to close the environment, closing is a two-phase step, to make sure /// noone tries to open the same environment between these two phases. /// /// Trying to open a None marked environment returns an error to the user trying to open it. static OPENED_ENV: Lazy>> = Lazy::new(RwLock::default); struct EnvEntry { env: Option, signal_event: Arc, options: EnvOpenOptions, } // Thanks to the mozilla/rkv project // Workaround the UNC path on Windows, see https://github.com/rust-lang/rust/issues/42869. // Otherwise, `Env::from_env()` will panic with error_no(123). #[cfg(not(windows))] fn canonicalize_path(path: &Path) -> io::Result { path.canonicalize() } #[cfg(windows)] fn canonicalize_path(path: &Path) -> io::Result { let canonical = path.canonicalize()?; let url = url::Url::from_file_path(&canonical) .map_err(|_e| io::Error::new(io::ErrorKind::Other, "URL passing error"))?; url.to_file_path() .map_err(|_e| io::Error::new(io::ErrorKind::Other, "path canonicalization error")) } #[cfg(windows)] /// Adding a 'missing' trait from windows OsStrExt trait OsStrExtLmdb { fn as_bytes(&self) -> &[u8]; } #[cfg(windows)] impl OsStrExtLmdb for OsStr { fn as_bytes(&self) -> &[u8] { &self.to_str().unwrap().as_bytes() } } #[cfg(unix)] fn get_file_fd(file: &File) -> RawFd { file.as_raw_fd() } #[cfg(windows)] fn get_file_fd(file: &File) -> RawHandle { file.as_raw_handle() } #[cfg(unix)] /// Get metadata from a file descriptor. unsafe fn metadata_from_fd(raw_fd: RawFd) -> io::Result { let fd = BorrowedFd::borrow_raw(raw_fd); let owned = fd.try_clone_to_owned()?; File::from(owned).metadata() } #[cfg(windows)] /// Get metadata from a file descriptor. unsafe fn metadata_from_fd(raw_fd: RawHandle) -> io::Result { let fd = BorrowedHandle::borrow_raw(raw_fd); let owned = fd.try_clone_to_owned()?; File::from(owned).metadata() } /// Options and flags which can be used to configure how an environment is opened. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EnvOpenOptions { map_size: Option, max_readers: Option, max_dbs: Option, flags: EnvFlags, } impl Default for EnvOpenOptions { fn default() -> Self { Self::new() } } impl EnvOpenOptions { /// Creates a blank new set of options ready for configuration. pub fn new() -> EnvOpenOptions { EnvOpenOptions { map_size: None, max_readers: None, max_dbs: None, flags: EnvFlags::empty(), } } /// Set the size of the memory map to use for this environment. pub fn map_size(&mut self, size: usize) -> &mut Self { self.map_size = Some(size); self } /// Set the maximum number of threads/reader slots for the environment. pub fn max_readers(&mut self, readers: u32) -> &mut Self { self.max_readers = Some(readers); self } /// Set the maximum number of named databases for the environment. pub fn max_dbs(&mut self, dbs: u32) -> &mut Self { self.max_dbs = Some(dbs); self } /// Set one or more [LMDB flags](http://www.lmdb.tech/doc/group__mdb__env.html). /// ``` /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags}; /// use heed::types::*; /// /// # fn main() -> Result<(), Box> { /// fs::create_dir_all(Path::new("target").join("database.mdb"))?; /// let mut env_builder = EnvOpenOptions::new(); /// unsafe { env_builder.flags(EnvFlags::NO_TLS | EnvFlags::NO_META_SYNC); } /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// // we will open the default unnamed database /// let mut wtxn = env.write_txn()?; /// let db: Database> = env.create_database(&mut wtxn, None)?; /// /// // opening a write transaction /// db.put(&mut wtxn, "seven", &7)?; /// db.put(&mut wtxn, "zero", &0)?; /// db.put(&mut wtxn, "five", &5)?; /// db.put(&mut wtxn, "three", &3)?; /// wtxn.commit()?; /// /// // force the OS to flush the buffers (see Flag::NoSync and Flag::NoMetaSync). /// env.force_sync(); /// /// // opening a read transaction /// // to check if those values are now available /// let mut rtxn = env.read_txn()?; /// /// let ret = db.get(&rtxn, "zero")?; /// assert_eq!(ret, Some(0)); /// /// let ret = db.get(&rtxn, "five")?; /// assert_eq!(ret, Some(5)); /// # Ok(()) } /// ``` /// /// # Safety /// /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`. pub unsafe fn flags(&mut self, flags: EnvFlags) -> &mut Self { self.flags |= flags; self } /// Open an environment that will be located at the specified path. /// /// # Safety /// LMDB is backed by a memory map [^1] which comes with some safety precautions. /// /// Memory map constructors are marked `unsafe` because of the potential /// for Undefined Behavior (UB) using the map if the underlying file is /// subsequently modified, in or out of process. /// /// LMDB itself has a locking system that solves this problem, /// but it will not save you from making mistakes yourself. /// /// These are some things to take note of: /// /// - Avoid long-lived transactions, they will cause the database to grow quickly [^2] /// - Avoid aborting your process with an active transaction [^3] /// - Do not use LMDB on remote filesystems, even between processes on the same host [^4] /// - You must manage concurrent accesses yourself if using [`EnvFlags::NO_LOCK`] [^5] /// - Anything that causes LMDB's lock file to be broken will cause synchronization issues and may introduce UB [^6] /// /// `heed` itself upholds some safety invariants, including but not limited to: /// - Calling [`EnvOpenOptions::open`] twice in the same process, at the same time is OK [^7] /// /// For more details, it is highly recommended to read LMDB's official documentation. [^8] /// /// [^1]: /// /// [^2]: /// /// [^3]: /// /// [^4]: /// /// [^5]: /// /// [^6]: /// /// [^7]: /// /// [^8]: pub unsafe fn open>(&self, path: P) -> Result { let mut lock = OPENED_ENV.write().unwrap(); let path = match canonicalize_path(path.as_ref()) { Err(err) => { if err.kind() == NotFound && self.flags.contains(EnvFlags::NO_SUB_DIR) { let path = path.as_ref(); match path.parent().zip(path.file_name()) { Some((dir, file_name)) => canonicalize_path(dir)?.join(file_name), None => return Err(err.into()), } } else { return Err(err.into()); } } Ok(path) => path, }; match lock.entry(path) { Entry::Occupied(entry) => { let env = entry.get().env.clone().ok_or(Error::DatabaseClosing)?; let options = entry.get().options.clone(); if &options == self { Ok(env) } else { Err(Error::BadOpenOptions { env, options }) } } Entry::Vacant(entry) => { let path = entry.key(); let path_str = CString::new(path.as_os_str().as_bytes()).unwrap(); unsafe { let mut env: *mut ffi::MDB_env = ptr::null_mut(); mdb_result(ffi::mdb_env_create(&mut env))?; if let Some(size) = self.map_size { if size % page_size::get() != 0 { let msg = format!( "map size ({}) must be a multiple of the system page size ({})", size, page_size::get() ); return Err(Error::Io(io::Error::new( io::ErrorKind::InvalidInput, msg, ))); } mdb_result(ffi::mdb_env_set_mapsize(env, size))?; } if let Some(readers) = self.max_readers { mdb_result(ffi::mdb_env_set_maxreaders(env, readers))?; } if let Some(dbs) = self.max_dbs { mdb_result(ffi::mdb_env_set_maxdbs(env, dbs))?; } // When the `read-txn-no-tls` feature is enabled, we must force LMDB // to avoid using the thread local storage, this way we allow users // to use references of RoTxn between threads safely. let flags = if cfg!(feature = "read-txn-no-tls") { self.flags | EnvFlags::NO_TLS } else { self.flags }; let result = mdb_result(ffi::mdb_env_open(env, path_str.as_ptr(), flags.bits(), 0o600)); match result { Ok(()) => { let signal_event = Arc::new(SignalEvent::manual(false)); let inner = EnvInner { env, path: path.clone() }; let env = Env(Arc::new(inner)); let cache_entry = EnvEntry { env: Some(env.clone()), options: self.clone(), signal_event, }; entry.insert(cache_entry); Ok(env) } Err(e) => { ffi::mdb_env_close(env); Err(e.into()) } } } } } } } /// Returns a struct that allows to wait for the effective closing of an environment. pub fn env_closing_event>(path: P) -> Option { let lock = OPENED_ENV.read().unwrap(); lock.get(path.as_ref()).map(|e| EnvClosingEvent(e.signal_event.clone())) } /// An environment handle constructed by using [`EnvOpenOptions`]. #[derive(Clone)] pub struct Env(Arc); impl fmt::Debug for Env { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let EnvInner { env: _, path } = self.0.as_ref(); f.debug_struct("Env").field("path", &path.display()).finish_non_exhaustive() } } struct EnvInner { env: *mut ffi::MDB_env, path: PathBuf, } unsafe impl Send for EnvInner {} unsafe impl Sync for EnvInner {} impl Drop for EnvInner { fn drop(&mut self) { let mut lock = OPENED_ENV.write().unwrap(); match lock.remove(&self.path) { None => panic!("It seems another env closed this env before"), Some(EnvEntry { signal_event, .. }) => { unsafe { ffi::mdb_env_close(self.env); } // We signal to all the waiters that the env is closed now. signal_event.signal(); } } } } /// A helper function that transforms the LMDB types into Rust types (`MDB_val` into slices) /// and vice versa, the Rust types into C types (`Ordering` into an integer). /// /// # Safety /// /// `a` and `b` should both properly aligned, valid for reads and should point to a valid /// [`MDB_val`][ffi::MDB_val]. An [`MDB_val`][ffi::MDB_val] (consists of a pointer and size) is /// valid when its pointer (`mv_data`) is valid for reads of `mv_size` bytes and is not null. unsafe extern "C" fn custom_key_cmp_wrapper( a: *const ffi::MDB_val, b: *const ffi::MDB_val, ) -> i32 { let a = unsafe { ffi::from_val(*a) }; let b = unsafe { ffi::from_val(*b) }; match catch_unwind(|| C::compare(a, b)) { Ok(Ordering::Less) => -1, Ok(Ordering::Equal) => 0, Ok(Ordering::Greater) => 1, Err(_) => abort(), } } /// A representation of LMDB's default comparator behavior. /// /// This enum is used to indicate the absence of a custom comparator for an LMDB /// database instance. When a [`Database`] is created or opened with /// [`DefaultComparator`], it signifies that the comparator should not be explicitly /// set via [`ffi::mdb_set_compare`]. Consequently, the database /// instance utilizes LMDB's built-in default comparator, which inherently performs /// lexicographic comparison of keys. /// /// This comparator's lexicographic implementation is employed in scenarios involving /// prefix iterators. Specifically, methods other than [`Comparator::compare`] are utilized /// to determine the lexicographic successors and predecessors of byte sequences, which /// is essential for these iterators' operation. /// /// When a custom comparator is provided, the wrapper is responsible for setting /// it with the [`ffi::mdb_set_compare`] function, which overrides the default comparison /// behavior of LMDB with the user-defined logic. pub enum DefaultComparator {} impl LexicographicComparator for DefaultComparator { #[inline] fn compare_elem(a: u8, b: u8) -> Ordering { a.cmp(&b) } #[inline] fn successor(elem: u8) -> Option { match elem { u8::MAX => None, elem => Some(elem + 1), } } #[inline] fn predecessor(elem: u8) -> Option { match elem { u8::MIN => None, elem => Some(elem - 1), } } #[inline] fn max_elem() -> u8 { u8::MAX } #[inline] fn min_elem() -> u8 { u8::MIN } } /// Whether to perform compaction while copying an environment. #[derive(Debug, Copy, Clone)] pub enum CompactionOption { /// Omit free pages and sequentially renumber all pages in output. /// /// This option consumes more CPU and runs more slowly than the default. /// Currently it fails if the environment has suffered a page leak. Enabled, /// Copy everything without taking any special action about free pages. Disabled, } /// Whether to enable or disable flags in [`Env::set_flags`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum FlagSetMode { /// Enable the flags. Enable, /// Disable the flags. Disable, } impl FlagSetMode { /// Convert the enum into the `i32` required by LMDB. /// "A non-zero value sets the flags, zero clears them." /// fn as_mdb_env_set_flags_input(self) -> i32 { match self { Self::Enable => 1, Self::Disable => 0, } } } impl Env { pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env { self.0.env } /// The size of the data file on disk. /// /// # Example /// /// ``` /// use heed::EnvOpenOptions; /// /// # fn main() -> Result<(), Box> { /// let dir = tempfile::tempdir()?; /// let size_in_bytes = 1024 * 1024; /// let env = unsafe { EnvOpenOptions::new().map_size(size_in_bytes).open(dir.path())? }; /// /// let actual_size = env.real_disk_size()? as usize; /// assert!(actual_size < size_in_bytes); /// # Ok(()) } /// ``` pub fn real_disk_size(&self) -> Result { let mut fd = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_get_fd(self.env_mut_ptr(), fd.as_mut_ptr()))? }; let fd = unsafe { fd.assume_init() }; let metadata = unsafe { metadata_from_fd(fd)? }; Ok(metadata.len()) } /// Return the raw flags the environment was opened with. /// /// Returns `None` if the environment flags are different from the [`EnvFlags`] set. pub fn flags(&self) -> Result> { self.get_flags().map(EnvFlags::from_bits) } /// Enable or disable the environment's currently active [`EnvFlags`]. /// /// ``` /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode}; /// use heed::types::*; /// /// # fn main() -> Result<(), Box> { /// fs::create_dir_all(Path::new("target").join("database.mdb"))?; /// let mut env_builder = EnvOpenOptions::new(); /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// // Env was opened without flags. /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// /// // Enable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Enable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::NO_SYNC.bits()); /// /// // Disable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Disable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// # Ok(()) } /// ``` /// /// # Safety /// /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`. /// /// LMDB also requires that only 1 thread calls this function at any given moment. /// Neither `heed` or LMDB check for this condition, so the caller must ensure it explicitly. pub unsafe fn set_flags(&self, flags: EnvFlags, mode: FlagSetMode) -> Result<()> { // safety: caller must ensure no other thread is calling this function. // mdb_result(unsafe { ffi::mdb_env_set_flags( self.env_mut_ptr(), flags.bits(), mode.as_mdb_env_set_flags_input(), ) }) .map_err(Into::into) } /// Return the raw flags the environment is currently set with. pub fn get_flags(&self) -> Result { let mut flags = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_get_flags(self.env_mut_ptr(), flags.as_mut_ptr()))? }; let flags = unsafe { flags.assume_init() }; Ok(flags) } /// Returns some basic informations about this environment. pub fn info(&self) -> EnvInfo { let mut raw_info = mem::MaybeUninit::uninit(); unsafe { ffi::mdb_env_info(self.0.env, raw_info.as_mut_ptr()) }; let raw_info = unsafe { raw_info.assume_init() }; EnvInfo { map_addr: raw_info.me_mapaddr, map_size: raw_info.me_mapsize, last_page_number: raw_info.me_last_pgno, last_txn_id: raw_info.me_last_txnid, maximum_number_of_readers: raw_info.me_maxreaders, number_of_readers: raw_info.me_numreaders, } } /// Returns the size used by all the databases in the environment without the free pages. /// /// It is crucial to configure [`EnvOpenOptions::max_dbs`] with a sufficiently large value /// before invoking this function. All databases within the environment will be opened /// and remain so. pub fn non_free_pages_size(&self) -> Result { let compute_size = |stat: ffi::MDB_stat| { (stat.ms_leaf_pages + stat.ms_branch_pages + stat.ms_overflow_pages) as u64 * stat.ms_psize as u64 }; let mut size = 0; let mut stat = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_stat(self.env_mut_ptr(), stat.as_mut_ptr()))? }; let stat = unsafe { stat.assume_init() }; size += compute_size(stat); let rtxn = self.read_txn()?; // Open the main database let dbi = self.raw_open_dbi::(rtxn.txn, None, 0)?; // We're going to iterate on the unnamed database let mut cursor = RoCursor::new(&rtxn, dbi)?; while let Some((key, _value)) = cursor.move_on_next(MoveOperation::NoDup)? { if key.contains(&0) { continue; } let key = String::from_utf8(key.to_vec()).unwrap(); // Calling `ffi::db_stat` on a database instance does not involve key comparison // in LMDB, so it's safe to specify a noop key compare function for it. if let Ok(dbi) = self.raw_open_dbi::(rtxn.txn, Some(&key), 0) { let mut stat = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_stat(rtxn.txn, dbi, stat.as_mut_ptr()))? }; let stat = unsafe { stat.assume_init() }; size += compute_size(stat); } } Ok(size) } /// Options and flags which can be used to configure how a [`Database`] is opened. pub fn database_options(&self) -> DatabaseOpenOptions { DatabaseOpenOptions::new(self) } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open_database( &self, rtxn: &RoTxn, name: Option<&str>, ) -> Result>> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.open(rtxn) } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened during this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create_database( &self, wtxn: &mut RwTxn, name: Option<&str>, ) -> Result> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.create(wtxn) } pub(crate) fn raw_init_database( &self, raw_txn: *mut ffi::MDB_txn, name: Option<&str>, flags: AllDatabaseFlags, ) -> Result { match self.raw_open_dbi::(raw_txn, name, flags.bits()) { Ok(dbi) => Ok(dbi), Err(e) => Err(e.into()), } } fn raw_open_dbi( &self, raw_txn: *mut ffi::MDB_txn, name: Option<&str>, flags: u32, ) -> std::result::Result { let mut dbi = 0; let name = name.map(|n| CString::new(n).unwrap()); let name_ptr = match name { Some(ref name) => name.as_bytes_with_nul().as_ptr() as *const _, None => ptr::null(), }; // safety: The name cstring is cloned by LMDB, we can drop it after. // If a read-only is used with the MDB_CREATE flag, LMDB will throw an error. unsafe { mdb_result(ffi::mdb_dbi_open(raw_txn, name_ptr, flags, &mut dbi))?; if TypeId::of::() != TypeId::of::() { mdb_result(ffi::mdb_set_compare(raw_txn, dbi, Some(custom_key_cmp_wrapper::)))?; } }; Ok(dbi) } /// Create a transaction with read and write access for use with the environment. /// /// ## LMDB Limitations /// /// Only one [`RwTxn`] may exist simultaneously in the current environment. /// If another write transaction is initiated, while another write transaction exists /// the thread initiating the new one will wait on a mutex upon completion of the previous /// transaction. pub fn write_txn(&self) -> Result { RwTxn::new(self) } /// Create a nested transaction with read and write access for use with the environment. /// /// The new transaction will be a nested transaction, with the transaction indicated by parent /// as its parent. Transactions may be nested to any level. /// /// A parent transaction and its cursors may not issue any other operations than _commit_ and /// _abort_ while it has active child transactions. pub fn nested_write_txn<'p>(&'p self, parent: &'p mut RwTxn) -> Result> { RwTxn::nested(self, parent) } /// Create a transaction with read-only access for use with the environment. /// /// You can make this transaction `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// See [`Self::static_read_txn`] if you want the txn to own the environment. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn read_txn(&self) -> Result { RoTxn::new(self) } /// Create a transaction with read-only access for use with the environment. /// Contrary to [`Self::read_txn`], this version **owns** the environment, which /// means you won't be able to close the environment while this transaction is alive. /// /// You can make this transaction `Send`able between threads by /// using the `read-txn-no-tls` crate feature. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn static_read_txn(self) -> Result> { RoTxn::static_read_txn(self) } /// Copy an LMDB environment to the specified path, with options. /// /// This function may be used to make a backup of an existing environment. /// No lockfile is created, since it gets recreated at need. pub fn copy_to_file>(&self, path: P, option: CompactionOption) -> Result { let file = File::options().create_new(true).write(true).open(&path)?; let fd = get_file_fd(&file); unsafe { self.copy_to_fd(fd, option)? }; // We reopen the file to make sure the cursor is at the start, // even a seek to start doesn't work properly. let file = File::open(path)?; Ok(file) } /// Copy an LMDB environment to the specified file descriptor, with compaction option. /// /// This function may be used to make a backup of an existing environment. /// No lockfile is created, since it gets recreated at need. /// /// # Safety /// /// The [`ffi::mdb_filehandle_t`] must have already been opened for Write access. pub unsafe fn copy_to_fd( &self, fd: ffi::mdb_filehandle_t, option: CompactionOption, ) -> Result<()> { let flags = if let CompactionOption::Enabled = option { ffi::MDB_CP_COMPACT } else { 0 }; mdb_result(ffi::mdb_env_copyfd2(self.0.env, fd, flags))?; Ok(()) } /// Flush the data buffers to disk. pub fn force_sync(&self) -> Result<()> { unsafe { mdb_result(ffi::mdb_env_sync(self.0.env, 1))? } Ok(()) } /// Returns the canonicalized path where this env lives. pub fn path(&self) -> &Path { &self.0.path } /// Returns an `EnvClosingEvent` that can be used to wait for the closing event, /// multiple threads can wait on this event. /// /// Make sure that you drop all the copies of `Env`s you have, env closing are triggered /// when all references are dropped, the last one will eventually close the environment. pub fn prepare_for_closing(self) -> EnvClosingEvent { let mut lock = OPENED_ENV.write().unwrap(); match lock.get_mut(self.path()) { None => panic!("cannot find the env that we are trying to close"), Some(EnvEntry { env, signal_event, .. }) => { // We remove the env from the global list and replace it with a None. let _env = env.take(); let signal_event = signal_event.clone(); // we must make sure we release the lock before we drop the env // as the drop of the EnvInner also tries to lock the OPENED_ENV // global and we don't want to trigger a dead-lock. drop(lock); EnvClosingEvent(signal_event) } } } /// Check for stale entries in the reader lock table and clear them. /// /// Returns the number of stale readers cleared. pub fn clear_stale_readers(&self) -> Result { let mut dead: i32 = 0; unsafe { mdb_result(ffi::mdb_reader_check(self.0.env, &mut dead))? } // safety: The reader_check function asks for an i32, initialize it to zero // and never decrements it. It is safe to use either an u32 or u64 (usize). Ok(dead as usize) } /// Resize the memory map to a new size. /// /// # Safety /// /// According to the [LMDB documentation](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5), /// it is okay to call `mdb_env_set_mapsize` for an open environment as long as no transactions are active, /// but the library does not check for this condition, so the caller must ensure it explicitly. pub unsafe fn resize(&self, new_size: usize) -> Result<()> { if new_size % page_size::get() != 0 { let msg = format!( "map size ({}) must be a multiple of the system page size ({})", new_size, page_size::get() ); return Err(Error::Io(io::Error::new(io::ErrorKind::InvalidInput, msg))); } mdb_result(unsafe { ffi::mdb_env_set_mapsize(self.env_mut_ptr(), new_size) }) .map_err(Into::into) } /// Get the maximum size of keys and MDB_DUPSORT data we can write. /// /// Depends on the compile-time constant MDB_MAXKEYSIZE. Default 511 pub fn max_key_size(&self) -> usize { let maxsize: i32 = unsafe { ffi::mdb_env_get_maxkeysize(self.env_mut_ptr()) }; maxsize as usize } } /// Contains information about the environment. #[derive(Debug, Clone, Copy)] pub struct EnvInfo { /// Address of the map, if fixed. pub map_addr: *mut c_void, /// Size of the data memory map. pub map_size: usize, /// ID of the last used page. pub last_page_number: usize, /// ID of the last committed transaction. pub last_txn_id: usize, /// Maximum number of reader slots in the environment. pub maximum_number_of_readers: u32, /// Number of reader slots used in the environment. pub number_of_readers: u32, } /// A structure that can be used to wait for the closing event. /// Multiple threads can wait on this event. #[derive(Clone)] pub struct EnvClosingEvent(Arc); impl EnvClosingEvent { /// Blocks this thread until the environment is effectively closed. /// /// # Safety /// /// Make sure that you don't have any copy of the environment in the thread /// that is waiting for a close event. If you do, you will have a deadlock. pub fn wait(&self) { self.0.wait() } /// Blocks this thread until either the environment has been closed /// or until the timeout elapses. Returns `true` if the environment /// has been effectively closed. pub fn wait_timeout(&self, timeout: Duration) -> bool { self.0.wait_timeout(timeout) } } impl fmt::Debug for EnvClosingEvent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnvClosingEvent").finish() } } #[cfg(test)] mod tests { use std::io::ErrorKind; use std::time::Duration; use std::{fs, thread}; use crate::types::*; use crate::{env_closing_event, EnvOpenOptions, Error}; #[test] fn close_env() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(30) .open(dir.path()) .unwrap() }; // Force a thread to keep the env for 1 second. let env_cloned = env.clone(); thread::spawn(move || { let _env = env_cloned; thread::sleep(Duration::from_secs(1)); }); let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, "hello", "hello").unwrap(); db.put(&mut wtxn, "world", "world").unwrap(); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some(("hello", "hello"))); assert_eq!(iter.next().transpose().unwrap(), Some(("world", "world"))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.commit().unwrap(); let signal_event = env.prepare_for_closing(); eprintln!("waiting for the env to be closed"); signal_event.wait(); eprintln!("env closed successfully"); // Make sure we don't have a reference to the env assert!(env_closing_event(dir.path()).is_none()); } #[test] fn reopen_env_with_different_options_is_err() { let dir = tempfile::tempdir().unwrap(); let _env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path()) .unwrap() }; let result = unsafe { EnvOpenOptions::new() .map_size(12 * 1024 * 1024) // 12MB .open(dir.path()) }; assert!(matches!(result, Err(Error::BadOpenOptions { .. }))); } #[test] fn open_env_with_named_path() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join("babar.mdb")).unwrap(); let _env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path().join("babar.mdb")) .unwrap() }; let _env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path().join("babar.mdb")) .unwrap() }; } #[test] #[cfg(not(windows))] fn open_database_with_writemap_flag() { let dir = tempfile::tempdir().unwrap(); let mut envbuilder = EnvOpenOptions::new(); envbuilder.map_size(10 * 1024 * 1024); // 10MB envbuilder.max_dbs(10); unsafe { envbuilder.flags(crate::EnvFlags::WRITE_MAP) }; let env = unsafe { envbuilder.open(dir.path()).unwrap() }; let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); } #[test] fn open_database_with_nosubdir() { let dir = tempfile::tempdir().unwrap(); let mut envbuilder = EnvOpenOptions::new(); unsafe { envbuilder.flags(crate::EnvFlags::NO_SUB_DIR) }; let _env = unsafe { envbuilder.open(dir.path().join("data.mdb")).unwrap() }; } #[test] fn create_database_without_commit() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.abort(); let rtxn = env.read_txn().unwrap(); let option = env.open_database::(&rtxn, Some("my-super-db")).unwrap(); assert!(option.is_none()); } #[test] fn open_already_existing_database() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; // we first create a database let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); // Close the environement and reopen it, databases must not be loaded in memory. env.prepare_for_closing().wait(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; let rtxn = env.read_txn().unwrap(); let option = env.open_database::(&rtxn, Some("my-super-db")).unwrap(); assert!(option.is_some()); } #[test] fn resize_database() { let dir = tempfile::tempdir().unwrap(); let page_size = page_size::get(); let env = unsafe { EnvOpenOptions::new().map_size(9 * page_size).max_dbs(1).open(dir.path()).unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); for i in 0..64 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); for i in 64..128 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().expect_err("cannot commit a transaction that would reach the map size limit"); unsafe { env.resize(10 * page_size).unwrap(); } let mut wtxn = env.write_txn().unwrap(); for i in 64..128 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().expect("transaction should commit after resizing the map size"); assert_eq!(10 * page_size, env.info().map_size); } /// Non-regression test for /// /// /// We should be able to open database Read-Only Env with /// no prior Read-Write Env opening. And query data. #[test] fn open_read_only_without_no_env_opened_before() { let expected_data0 = "Data Expected db0"; let dir = tempfile::tempdir().unwrap(); { // We really need this env to be dropped before the read-only access. let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let database0 = env.create_database::(&mut wtxn, Some("shared0")).unwrap(); wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); database0.put(&mut wtxn, "shared0", expected_data0).unwrap(); wtxn.commit().unwrap(); // We also really need that no other env reside in memory in other thread doing tests. env.prepare_for_closing().wait(); } { // Open now we do a read-only opening let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let database0 = { let rtxn = env.read_txn().unwrap(); let database0 = env.open_database::(&rtxn, Some("shared0")).unwrap().unwrap(); // This commit is mandatory if not committed you might get // Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" }) rtxn.commit().unwrap(); database0 }; { // If we didn't committed the opening it might fail with EINVAL. let rtxn = env.read_txn().unwrap(); let value = database0.get(&rtxn, "shared0").unwrap().unwrap(); assert_eq!(value, expected_data0); } env.prepare_for_closing().wait(); } // To avoid reintroducing the bug let's try to open again but without the commit { // Open now we do a read-only opening let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let database0 = { let rtxn = env.read_txn().unwrap(); let database0 = env.open_database::(&rtxn, Some("shared0")).unwrap().unwrap(); // No commit it's important, dropping explicitly drop(rtxn); database0 }; { // We didn't committed the opening we will get EINVAL. let rtxn = env.read_txn().unwrap(); // The dbg!() is intentional in case of a change in rust-std or in lmdb related // to the windows error. let err = dbg!(database0.get(&rtxn, "shared0")); // The error kind is still ErrorKind Uncategorized on windows. // Behind it's a ERROR_BAD_COMMAND code 22 like EINVAL. if cfg!(windows) { assert!(err.is_err()); } else { assert!( matches!(err, Err(Error::Io(ref e)) if e.kind() == ErrorKind::InvalidInput) ); } } env.prepare_for_closing().wait(); } } #[test] fn max_key_size() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new().open(dir.path().join(dir.path())).unwrap() }; let maxkeysize = env.max_key_size(); eprintln!("maxkeysize: {}", maxkeysize); if cfg!(feature = "longer-keys") { // Should be larger than the default of 511 assert!(maxkeysize > 511); } else { // Should be the default of 511 assert_eq!(maxkeysize, 511); } } } heed-0.20.5/src/iteration_method.rs000064400000000000000000000021051046102023000153060ustar 00000000000000//! The set of possible iteration methods for the different iterators. use crate::cursor::MoveOperation; /// The trait used to define the way iterators behave. pub trait IterationMethod { /// The internal operation to move the cursor through entries. const MOVE_OPERATION: MoveOperation; } /// Moves to the next or previous key if there /// are no more values associated with the current key. #[derive(Debug, Clone, Copy)] pub enum MoveThroughDuplicateValues {} impl IterationMethod for MoveThroughDuplicateValues { const MOVE_OPERATION: MoveOperation = MoveOperation::Any; } /// Moves between keys and ignores the duplicate values of keys. #[derive(Debug, Clone, Copy)] pub enum MoveBetweenKeys {} impl IterationMethod for MoveBetweenKeys { const MOVE_OPERATION: MoveOperation = MoveOperation::NoDup; } /// Moves only on the duplicate values of a given key and ignores other keys. #[derive(Debug, Clone, Copy)] pub enum MoveOnCurrentKeyDuplicates {} impl IterationMethod for MoveOnCurrentKeyDuplicates { const MOVE_OPERATION: MoveOperation = MoveOperation::Dup; } heed-0.20.5/src/iterator/iter.rs000064400000000000000000000703741046102023000145610ustar 00000000000000use std::borrow::Cow; use std::marker; use types::LazyDecode; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; /// A read-only iterator structure. pub struct RoIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RoCursor<'txn>) -> RoIter<'txn, KC, DC, IM> { RoIter { cursor, move_on_first: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?.move_between_keys(); /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn move_between_keys(self) -> RoIter<'txn, KC, DC, MoveBetweenKeys> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?.move_through_duplicate_values(); /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn move_through_duplicate_values(self) -> RoIter<'txn, KC, DC, MoveThroughDuplicateValues> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoIter<'txn, KC2, DC2, IM> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_first(IM::MOVE_OPERATION) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { self.cursor.move_on_last(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_last(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoIter").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoIter<'_, KC, DC, IM> {} /// A read-write iterator structure. pub struct RwIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RwCursor<'txn>) -> RwIter<'txn, KC, DC, IM> { RwIter { cursor, move_on_first: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwIter::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwIter<'txn, KC, DC, MoveBetweenKeys> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values(self) -> RwIter<'txn, KC, DC, MoveThroughDuplicateValues> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwIter<'txn, KC2, DC2, IM> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_first(IM::MOVE_OPERATION) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { self.cursor.move_on_last(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_last(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwIter").finish() } } /// A reverse read-only iterator structure. pub struct RoRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RoCursor<'txn>) -> RoRevIter<'txn, KC, DC, IM> { RoRevIter { cursor, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRevIter<'txn, KC, DC, MoveBetweenKeys> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRevIter<'txn, KC, DC, MoveThroughDuplicateValues> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRevIter<'txn, KC2, DC2, IM> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRevIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRevIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoRevIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; self.cursor.move_on_last(IM::MOVE_OPERATION) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_first(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_first(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRevIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRevIter").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoRevIter<'_, KC, DC, IM> {} /// A reverse read-write iterator structure. pub struct RwRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwRevIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RwCursor<'txn>) -> RwRevIter<'txn, KC, DC, IM> { RwRevIter { cursor, move_on_last: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevIter::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevIter<'txn, KC, DC, MoveBetweenKeys> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRevIter<'txn, KC, DC, MoveThroughDuplicateValues> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRevIter<'txn, KC2, DC2, IM> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRevIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRevIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwRevIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; self.cursor.move_on_last(IM::MOVE_OPERATION) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_first(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_first(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRevIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevIter").finish() } } heed-0.20.5/src/iterator/mod.rs000064400000000000000000000577541046102023000144040ustar 00000000000000mod iter; mod prefix; mod range; pub use self::iter::{RoIter, RoRevIter, RwIter, RwRevIter}; pub use self::prefix::{RoPrefix, RoRevPrefix, RwPrefix, RwRevPrefix}; pub use self::range::{RoRange, RoRevRange, RwRange, RwRevRange}; #[cfg(test)] mod tests { use std::ops; #[test] fn prefix_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], "hello").unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 254, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 104, 101, 108, 108, 111], "hello").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 1, 0, 119, 111, 114, 108, 100], "world").unwrap(); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); // Lets check that we can prefix_iter on that sequence with the key "255". let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0u8, 0, 0, 255, 104, 101, 108, 108, 111][..], "hello")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], "world")) ); // Lets check that we can prefix_iter on that sequence with the key "255". let mut iter = db.prefix_iter(&wtxn, &[255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 254, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 104, 101, 108, 108, 111][..], "hello")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } #[test] fn iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), None); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn range_iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), None); assert_eq!(iter.last().transpose().unwrap(), None); let range = 2..=4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let range = 2..4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((3, ()))); let range = 2..4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let range = 2..2; let iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.last().transpose().unwrap(), None); #[allow(clippy::reversed_empty_ranges)] let range = 2..=1; let iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn range_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 1], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 2], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db .range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); // Lets check that we can range_iter on that sequence with the key "255". let mut iter = db .range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 2][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } #[test] fn prefix_iter_last() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_prefix_iter_last() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_prefix_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we can get last entry on that sequence ending with the key "255". let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); // Lets check that we can prefix_iter on that sequence ending with the key "255". let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.rev_prefix_iter(&wtxn, &[255, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_range_iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.rev_range(&wtxn, &(1..=3)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(0..4)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(0..=5)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.rev_range(&wtxn, &(0..=5)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(4..=4)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_range_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 1], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 2], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db .rev_range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); // Lets check that we can range_iter on that sequence with the key "255". let mut iter = db .rev_range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 2][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } } heed-0.20.5/src/iterator/prefix.rs000064400000000000000000000743121046102023000151070ustar 00000000000000use std::borrow::Cow; use std::marker; use heed_traits::LexicographicComparator; use types::LazyDecode; use crate::cursor::MoveOperation; use crate::env::DefaultComparator; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; /// Advances `bytes` to the immediate lexicographic successor of equal length, as /// defined by the `C` comparator. If no successor exists (i.e. `bytes` is the maximal /// value), it remains unchanged and the function returns `false`. Otherwise, updates /// `bytes` and returns `true`. fn advance_prefix(bytes: &mut [u8]) -> bool { let mut idx = bytes.len(); while idx > 0 && bytes[idx - 1] == C::max_elem() { idx -= 1; } if idx == 0 { return false; } bytes[idx - 1] = C::successor(bytes[idx - 1]).expect("Cannot advance byte; this is a bug."); for i in (idx + 1)..=bytes.len() { bytes[i - 1] = C::min_elem(); } true } /// Retreats `bytes` to the immediate lexicographic predecessor of equal length, as /// defined by the `C` comparator. If no predecessor exists (i.e. `bytes` is the minimum /// value), it remains unchanged and the function returns `false`. Otherwise, updates /// `bytes` and returns `true`. fn retreat_prefix(bytes: &mut [u8]) -> bool { let mut idx = bytes.len(); while idx > 0 && bytes[idx - 1] == C::min_elem() { idx -= 1; } if idx == 0 { return false; } bytes[idx - 1] = C::predecessor(bytes[idx - 1]).expect("Cannot retreat byte; this is a bug."); for i in (idx + 1)..=bytes.len() { bytes[i - 1] = C::max_elem(); } true } fn move_on_prefix_end<'txn, C: LexicographicComparator>( cursor: &mut RoCursor<'txn>, prefix: &mut [u8], ) -> Result> { if advance_prefix::(prefix) { let result = cursor .move_on_key_greater_than_or_equal_to(prefix) .and_then(|_| cursor.move_on_prev(MoveOperation::NoDup)); retreat_prefix::(prefix); result } else { // `prefix` is the maximum among all bytes sequence of the same length. cursor.move_on_last(MoveOperation::NoDup) } } /// A read-only prefix iterator structure. pub struct RoPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, prefix: Vec, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoPrefix<'txn, KC, DC, C, IM> { RoPrefix { cursor, prefix, move_on_first: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoPrefix<'txn, KC2, DC2, C, IM> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoPrefix").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoPrefix<'_, KC, DC, IM> {} /// A read-write prefix iterator structure. pub struct RwPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, prefix: Vec, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RwCursor<'txn>, prefix: Vec) -> RwPrefix<'txn, KC, DC, C, IM> { RwPrefix { cursor, prefix, move_on_first: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwPrefix::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwPrefix<'txn, KC2, DC2, C, IM> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwPrefix").finish() } } /// A reverse read-only prefix iterator structure. pub struct RoRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, prefix: Vec, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoRevPrefix<'txn, KC, DC, C, IM> { RoRevPrefix { cursor, prefix, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRevPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRevPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRevPrefix<'txn, KC2, DC2, C, IM> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRevPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRevPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoRevPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { let current = self.cursor.current(); let start = self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRevPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRevPrefix").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoRevPrefix<'_, KC, DC, IM> {} /// A reverse read-write prefix iterator structure. pub struct RwRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, prefix: Vec, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwRevPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RwCursor<'txn>, prefix: Vec) -> RwRevPrefix<'txn, KC, DC, C, IM> { RwRevPrefix { cursor, prefix, move_on_last: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevPrefix::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRevPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRevPrefix<'txn, KC2, DC2, C, IM> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRevPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRevPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwRevPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { let current = self.cursor.current(); let start = self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRevPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevPrefix").finish() } } heed-0.20.5/src/iterator/range.rs000064400000000000000000000777511046102023000147200ustar 00000000000000use std::borrow::Cow; use std::marker; use std::ops::Bound; use types::LazyDecode; use crate::cursor::MoveOperation; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; fn move_on_range_end<'txn>( cursor: &mut RoCursor<'txn>, end_bound: &Bound>, ) -> Result> { match end_bound { Bound::Included(end) => match cursor.move_on_key_greater_than_or_equal_to(end) { Ok(Some((key, data))) if key == &end[..] => Ok(Some((key, data))), Ok(_) => cursor.move_on_prev(MoveOperation::NoDup), Err(e) => Err(e), }, Bound::Excluded(end) => cursor .move_on_key_greater_than_or_equal_to(end) .and_then(|_| cursor.move_on_prev(MoveOperation::NoDup)), Bound::Unbounded => cursor.move_on_last(MoveOperation::NoDup), } } fn move_on_range_start<'txn>( cursor: &mut RoCursor<'txn>, start_bound: &mut Bound>, ) -> Result> { match start_bound { Bound::Included(start) => cursor.move_on_key_greater_than_or_equal_to(start), Bound::Excluded(start) => match cursor.move_on_key_greater_than_or_equal_to(start)? { Some((key, _)) if key == start => cursor.move_on_next(MoveOperation::NoDup), result => Ok(result), }, Bound::Unbounded => cursor.move_on_first(MoveOperation::NoDup), } } /// A read-only range iterator structure. pub struct RoRange<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_start: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoRange<'txn, KC, DC, IM> { pub(crate) fn new( cursor: RoCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RoRange<'txn, KC, DC, IM> { RoRange { cursor, move_on_start: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRange<'txn, KC, DC, MoveBetweenKeys> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRange<'txn, KC, DC, MoveThroughDuplicateValues> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRange<'txn, KC2, DC2, IM> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRange<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRange<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRange<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoRange<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_start { self.move_on_start = false; move_on_range_start(&mut self.cursor, &mut self.start_bound) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.end_bound { Bound::Included(end) => key <= end, Bound::Excluded(end) => key < end, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_start { move_on_range_end(&mut self.cursor, &self.end_bound) } else { match (self.cursor.current(), move_on_range_end(&mut self.cursor, &self.end_bound)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.start_bound { Bound::Included(start) => key >= start, Bound::Excluded(start) => key > start, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRange<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRange").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoRange<'_, KC, DC, IM> {} /// A read-write range iterator structure. pub struct RwRange<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_start: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwRange<'txn, KC, DC, IM> { pub(crate) fn new( cursor: RwCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RwRange<'txn, KC, DC, IM> { RwRange { cursor, move_on_start: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRange::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRange<'txn, KC, DC, MoveBetweenKeys> { RwRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRange<'txn, KC, DC, MoveThroughDuplicateValues> { RwRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRange<'txn, KC2, DC2, IM> { RwRange { cursor: self.cursor, move_on_start: self.move_on_start, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRange<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRange<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRange<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwRange<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_start { self.move_on_start = false; move_on_range_start(&mut self.cursor, &mut self.start_bound) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { let must_be_returned = match self.end_bound { Bound::Included(ref end) => key <= end, Bound::Excluded(ref end) => key < end, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_start { move_on_range_end(&mut self.cursor, &self.end_bound) } else { match (self.cursor.current(), move_on_range_end(&mut self.cursor, &self.end_bound)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.start_bound { Bound::Included(start) => key >= start, Bound::Excluded(start) => key > start, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRange<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRange").finish() } } /// A reverse read-only range iterator structure. pub struct RoRevRange<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_end: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoRevRange<'txn, KC, DC, IM> { pub(crate) fn new( cursor: RoCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RoRevRange<'txn, KC, DC, IM> { RoRevRange { cursor, move_on_end: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRevRange<'txn, KC, DC, MoveBetweenKeys> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRevRange<'txn, KC, DC, MoveThroughDuplicateValues> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRevRange<'txn, KC2, DC2, IM> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRevRange<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRevRange<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevRange<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoRevRange<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_end { self.move_on_end = false; move_on_range_end(&mut self.cursor, &self.end_bound) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.start_bound { Bound::Included(start) => key >= start, Bound::Excluded(start) => key > start, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_end { move_on_range_start(&mut self.cursor, &mut self.start_bound) } else { let current = self.cursor.current(); let start = move_on_range_start(&mut self.cursor, &mut self.start_bound); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.end_bound { Bound::Included(end) => key <= end, Bound::Excluded(end) => key < end, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRevRange<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRevRange").finish() } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoRevRange<'_, KC, DC, IM> {} /// A reverse read-write range iterator structure. pub struct RwRevRange<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_end: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwRevRange<'txn, KC, DC, IM> { pub(crate) fn new( cursor: RwCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RwRevRange<'txn, KC, DC, IM> { RwRevRange { cursor, move_on_end: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevRange::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevRange<'txn, KC, DC, MoveBetweenKeys> { RwRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRevRange<'txn, KC, DC, MoveThroughDuplicateValues> { RwRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRevRange<'txn, KC2, DC2, IM> { RwRevRange { cursor: self.cursor, move_on_end: self.move_on_end, start_bound: self.start_bound, end_bound: self.end_bound, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRevRange<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRevRange<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevRange<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwRevRange<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_end { self.move_on_end = false; move_on_range_end(&mut self.cursor, &self.end_bound) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.start_bound { Bound::Included(start) => key >= start, Bound::Excluded(start) => key > start, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_end { move_on_range_start(&mut self.cursor, &mut self.start_bound) } else { let current = self.cursor.current(); let start = move_on_range_start(&mut self.cursor, &mut self.start_bound); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { let must_be_returned = match &self.end_bound { Bound::Included(end) => key <= end, Bound::Excluded(end) => key < end, Bound::Unbounded => true, }; if must_be_returned { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRevRange<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevRange").finish() } } heed-0.20.5/src/lib.rs000064400000000000000000000164771046102023000125370ustar 00000000000000#![doc( html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon.ico?raw=true" )] #![doc( html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true" )] //! `heed` is a high-level wrapper of [LMDB]. //! //! The [cookbook] will give you a variety of complete Rust programs to use with heed. //! //! ---- //! //! This crate simply facilitates the use of LMDB by providing a mechanism to store and //! retrieve Rust types. It abstracts away some of the complexities of the raw LMDB usage //! while retaining its performance characteristics. The functionality is achieved with the help //! of the serde library for data serialization concerns. //! //! LMDB stands for Lightning Memory-Mapped Database, which utilizes memory-mapped files //! for efficient data storage and retrieval by mapping file content directly into the virtual //! address space. `heed` derives its efficiency from the underlying LMDB without imposing //! additional runtime costs. //! //! [LMDB]: https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database //! //! # Examples //! //! Open a database that will support some typed key/data and ensure, at compile time, //! that you'll write those types and not others. //! //! ``` //! use std::fs; //! use std::path::Path; //! use heed::{EnvOpenOptions, Database}; //! use heed::types::*; //! //! # fn main() -> Result<(), Box> { //! let dir = tempfile::tempdir()?; //! let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; //! //! // we will open the default unnamed database //! let mut wtxn = env.write_txn()?; //! let db: Database> = env.create_database(&mut wtxn, None)?; //! //! // opening a write transaction //! db.put(&mut wtxn, "seven", &7)?; //! db.put(&mut wtxn, "zero", &0)?; //! db.put(&mut wtxn, "five", &5)?; //! db.put(&mut wtxn, "three", &3)?; //! wtxn.commit()?; //! //! // opening a read transaction //! // to check if those values are now available //! let mut rtxn = env.read_txn()?; //! //! let ret = db.get(&rtxn, "zero")?; //! assert_eq!(ret, Some(0)); //! //! let ret = db.get(&rtxn, "five")?; //! assert_eq!(ret, Some(5)); //! # Ok(()) } //! ``` #![warn(missing_docs)] pub mod cookbook; mod cursor; mod database; mod env; pub mod iteration_method; mod iterator; mod mdb; mod reserved_space; mod txn; use std::ffi::CStr; use std::{error, fmt, io, mem, result}; use heed_traits as traits; pub use {byteorder, heed_types as types}; use self::cursor::{RoCursor, RwCursor}; pub use self::database::{Database, DatabaseOpenOptions, DatabaseStat}; pub use self::env::{ env_closing_event, CompactionOption, DefaultComparator, Env, EnvClosingEvent, EnvInfo, EnvOpenOptions, FlagSetMode, }; pub use self::iterator::{ RoIter, RoPrefix, RoRange, RoRevIter, RoRevPrefix, RoRevRange, RwIter, RwPrefix, RwRange, RwRevIter, RwRevPrefix, RwRevRange, }; pub use self::mdb::error::Error as MdbError; use self::mdb::ffi::{from_val, into_val}; pub use self::mdb::flags::{DatabaseFlags, EnvFlags, PutFlags}; pub use self::reserved_space::ReservedSpace; pub use self::traits::{BoxedError, BytesDecode, BytesEncode, Comparator, LexicographicComparator}; pub use self::txn::{RoTxn, RwTxn}; /// The underlying LMDB library version information. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct LmdbVersion { /// The library version as a string. pub string: &'static str, /// The library major version number. pub major: i32, /// The library minor version number. pub minor: i32, /// The library patch version number. pub patch: i32, } /// Return the LMDB library version information. /// /// ``` /// use heed::{lmdb_version, LmdbVersion}; /// /// let expected = LmdbVersion { /// string: "LMDB 0.9.70: (December 19, 2015)", /// major: 0, /// minor: 9, /// patch: 70, /// }; /// assert_eq!(lmdb_version(), expected); /// ``` pub fn lmdb_version() -> LmdbVersion { let mut major = mem::MaybeUninit::uninit(); let mut minor = mem::MaybeUninit::uninit(); let mut patch = mem::MaybeUninit::uninit(); unsafe { let string_ptr = mdb::ffi::mdb_version(major.as_mut_ptr(), minor.as_mut_ptr(), patch.as_mut_ptr()); LmdbVersion { string: CStr::from_ptr(string_ptr).to_str().unwrap(), major: major.assume_init(), minor: minor.assume_init(), patch: patch.assume_init(), } } } /// An error that encapsulates all possible errors in this crate. #[derive(Debug)] pub enum Error { /// I/O error: can come from the standard library or be a rewrapped [`MdbError`]. Io(io::Error), /// LMDB error. Mdb(MdbError), /// Encoding error. Encoding(BoxedError), /// Decoding error. Decoding(BoxedError), /// Database closing in progress. DatabaseClosing, /// Attempt to open [`Env`] with different options. BadOpenOptions { /// The options that were used to originally open this env. options: EnvOpenOptions, /// The env opened with the original options. env: Env, }, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Io(error) => write!(f, "{}", error), Error::Mdb(error) => write!(f, "{}", error), Error::Encoding(error) => write!(f, "error while encoding: {}", error), Error::Decoding(error) => write!(f, "error while decoding: {}", error), Error::DatabaseClosing => { f.write_str("database is in a closing phase, you can't open it at the same time") } Error::BadOpenOptions { .. } => { f.write_str("an environment is already opened with different options") } } } } impl error::Error for Error {} impl From for Error { fn from(error: MdbError) -> Error { match error { MdbError::Other(e) => Error::Io(io::Error::from_raw_os_error(e)), _ => Error::Mdb(error), } } } impl From for Error { fn from(error: io::Error) -> Error { Error::Io(error) } } /// Either a success or an [`Error`]. pub type Result = result::Result; /// An unspecified type. /// /// It is used as placeholders when creating a database. /// It does not implement the [`BytesEncode`] and [`BytesDecode`] traits /// and therefore can't be used as codecs. You must use the [`Database::remap_types`] /// to properly define them. pub enum Unspecified {} macro_rules! assert_eq_env_db_txn { ($database:ident, $txn:ident) => { assert!( $database.env_ident == $txn.env_mut_ptr() as usize, "The database environment doesn't match the transaction's environment" ); }; } macro_rules! assert_eq_env_txn { ($env:expr, $txn:ident) => { assert!( $env.env_mut_ptr() == $txn.env_mut_ptr(), "The environment doesn't match the transaction's environment" ); }; } pub(crate) use {assert_eq_env_db_txn, assert_eq_env_txn}; #[cfg(test)] mod tests { use super::*; #[test] fn error_is_send_sync() { fn give_me_send_sync(_: T) {} let error = Error::Encoding(Box::from("There is an issue, you know?")); give_me_send_sync(error); } } heed-0.20.5/src/mdb/lmdb_error.rs000064400000000000000000000140441046102023000146460ustar 00000000000000use std::error::Error as StdError; use std::ffi::CStr; use std::os::raw::c_char; use std::{fmt, str}; use libc::c_int; use lmdb_master_sys as ffi; /// An LMDB error kind. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Error { /// A key/data pair already exists. /// /// May be also returned by append functions. Data passed /// doesn't respect the database ordering. KeyExist, /// A key/data pair was not found (EOF). NotFound, /// Requested page not found - this usually indicates corruption. PageNotFound, /// Located page was wrong type. Corrupted, /// Update of meta page failed or environment had fatal error. Panic, /// Environment version mismatch. VersionMismatch, /// File is not a valid LMDB file. Invalid, /// Environment mapsize reached. MapFull, /// Environment maxdbs reached. DbsFull, /// Environment maxreaders reached. ReadersFull, /// Too many TLS keys in use - Windows only. TlsFull, /// Txn has too many dirty pages. TxnFull, /// Cursor stack too deep - internal error. CursorFull, /// Page has not enough space - internal error. PageFull, /// Database contents grew beyond environment mapsize. MapResized, /// Operation and DB incompatible, or DB type changed. This can mean: /// - The operation expects an MDB_DUPSORT / MDB_DUPFIXED database. /// - Opening a named DB when the unnamed DB has MDB_DUPSORT / MDB_INTEGERKEY. /// - Accessing a data record as a database, or vice versa. /// - The database was dropped and recreated with different flags. Incompatible, /// Invalid reuse of reader locktable slot. BadRslot, /// Transaction cannot recover - it must be aborted. BadTxn, /// Unsupported size of key/DB name/data, or wrong DUP_FIXED size. /// /// Common causes of this error: /// - You tried to store a zero-length key /// - You tried to store a key longer than the max allowed key (511 bytes by default) /// - You are using [DUP_SORT](crate::DatabaseFlags::DUP_SORT) and trying to store a /// value longer than the max allowed key size (511 bytes by default) /// /// In the last two cases you can enable the `longer-keys` feature to increase the max allowed key size. BadValSize, /// The specified DBI was changed unexpectedly. BadDbi, /// Unexpected problem - transaction should abort. Problem, /// Other error. Other(c_int), } impl Error { /// Returns `true` if the given error is [`Error::NotFound`]. pub fn not_found(&self) -> bool { *self == Error::NotFound } /// Converts a raw error code to an `Error`. pub fn from_err_code(err_code: c_int) -> Error { match err_code { ffi::MDB_KEYEXIST => Error::KeyExist, ffi::MDB_NOTFOUND => Error::NotFound, ffi::MDB_PAGE_NOTFOUND => Error::PageNotFound, ffi::MDB_CORRUPTED => Error::Corrupted, ffi::MDB_PANIC => Error::Panic, ffi::MDB_VERSION_MISMATCH => Error::VersionMismatch, ffi::MDB_INVALID => Error::Invalid, ffi::MDB_MAP_FULL => Error::MapFull, ffi::MDB_DBS_FULL => Error::DbsFull, ffi::MDB_READERS_FULL => Error::ReadersFull, ffi::MDB_TLS_FULL => Error::TlsFull, ffi::MDB_TXN_FULL => Error::TxnFull, ffi::MDB_CURSOR_FULL => Error::CursorFull, ffi::MDB_PAGE_FULL => Error::PageFull, ffi::MDB_MAP_RESIZED => Error::MapResized, ffi::MDB_INCOMPATIBLE => Error::Incompatible, ffi::MDB_BAD_RSLOT => Error::BadRslot, ffi::MDB_BAD_TXN => Error::BadTxn, ffi::MDB_BAD_VALSIZE => Error::BadValSize, ffi::MDB_BAD_DBI => Error::BadDbi, ffi::MDB_PROBLEM => Error::Problem, other => Error::Other(other), } } /// Converts an `Error` to the raw error code. #[allow(clippy::trivially_copy_pass_by_ref)] pub fn to_err_code(&self) -> c_int { match *self { Error::KeyExist => ffi::MDB_KEYEXIST, Error::NotFound => ffi::MDB_NOTFOUND, Error::PageNotFound => ffi::MDB_PAGE_NOTFOUND, Error::Corrupted => ffi::MDB_CORRUPTED, Error::Panic => ffi::MDB_PANIC, Error::VersionMismatch => ffi::MDB_VERSION_MISMATCH, Error::Invalid => ffi::MDB_INVALID, Error::MapFull => ffi::MDB_MAP_FULL, Error::DbsFull => ffi::MDB_DBS_FULL, Error::ReadersFull => ffi::MDB_READERS_FULL, Error::TlsFull => ffi::MDB_TLS_FULL, Error::TxnFull => ffi::MDB_TXN_FULL, Error::CursorFull => ffi::MDB_CURSOR_FULL, Error::PageFull => ffi::MDB_PAGE_FULL, Error::MapResized => ffi::MDB_MAP_RESIZED, Error::Incompatible => ffi::MDB_INCOMPATIBLE, Error::BadRslot => ffi::MDB_BAD_RSLOT, Error::BadTxn => ffi::MDB_BAD_TXN, Error::BadValSize => ffi::MDB_BAD_VALSIZE, Error::BadDbi => ffi::MDB_BAD_DBI, Error::Problem => ffi::MDB_PROBLEM, Error::Other(err_code) => err_code, } } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let description = unsafe { // This is safe since the error messages returned from mdb_strerror are static. let err: *const c_char = ffi::mdb_strerror(self.to_err_code()) as *const c_char; str::from_utf8_unchecked(CStr::from_ptr(err).to_bytes()) }; fmt.write_str(description) } } impl StdError for Error {} pub fn mdb_result(err_code: c_int) -> Result<(), Error> { if err_code == ffi::MDB_SUCCESS { Ok(()) } else { Err(Error::from_err_code(err_code)) } } #[cfg(test)] mod test { use super::*; #[test] fn test_description() { assert_eq!("Permission denied", Error::from_err_code(13).to_string()); assert_eq!("MDB_NOTFOUND: No matching key/data pair found", Error::NotFound.to_string()); } } heed-0.20.5/src/mdb/lmdb_ffi.rs000064400000000000000000000036701046102023000142640ustar 00000000000000use std::ptr; pub use ffi::{ mdb_cursor_close, mdb_cursor_del, mdb_cursor_get, mdb_cursor_open, mdb_cursor_put, mdb_dbi_open, mdb_del, mdb_drop, mdb_env_close, mdb_env_copyfd2, mdb_env_create, mdb_env_get_fd, mdb_env_get_flags, mdb_env_get_maxkeysize, mdb_env_info, mdb_env_open, mdb_env_set_flags, mdb_env_set_mapsize, mdb_env_set_maxdbs, mdb_env_set_maxreaders, mdb_env_stat, mdb_env_sync, mdb_filehandle_t, mdb_get, mdb_put, mdb_reader_check, mdb_set_compare, mdb_stat, mdb_txn_abort, mdb_txn_begin, mdb_txn_commit, mdb_version, MDB_cursor, MDB_dbi, MDB_env, MDB_stat, MDB_txn, MDB_val, MDB_CP_COMPACT, MDB_CURRENT, MDB_RDONLY, MDB_RESERVE, }; use lmdb_master_sys as ffi; pub mod cursor_op { use super::ffi::{self, MDB_cursor_op}; pub const MDB_FIRST: MDB_cursor_op = ffi::MDB_FIRST; pub const MDB_FIRST_DUP: MDB_cursor_op = ffi::MDB_FIRST_DUP; pub const MDB_LAST: MDB_cursor_op = ffi::MDB_LAST; pub const MDB_LAST_DUP: MDB_cursor_op = ffi::MDB_LAST_DUP; pub const MDB_SET_RANGE: MDB_cursor_op = ffi::MDB_SET_RANGE; pub const MDB_SET: MDB_cursor_op = ffi::MDB_SET; pub const MDB_PREV: MDB_cursor_op = ffi::MDB_PREV; pub const MDB_PREV_NODUP: MDB_cursor_op = ffi::MDB_PREV_NODUP; pub const MDB_PREV_DUP: MDB_cursor_op = ffi::MDB_PREV_DUP; pub const MDB_NEXT: MDB_cursor_op = ffi::MDB_NEXT; pub const MDB_NEXT_NODUP: MDB_cursor_op = ffi::MDB_NEXT_NODUP; pub const MDB_NEXT_DUP: MDB_cursor_op = ffi::MDB_NEXT_DUP; pub const MDB_GET_CURRENT: MDB_cursor_op = ffi::MDB_GET_CURRENT; } pub fn reserve_size_val(size: usize) -> ffi::MDB_val { ffi::MDB_val { mv_size: size, mv_data: ptr::null_mut() } } pub unsafe fn into_val(value: &[u8]) -> ffi::MDB_val { ffi::MDB_val { mv_data: value.as_ptr() as *mut libc::c_void, mv_size: value.len() } } pub unsafe fn from_val<'a>(value: ffi::MDB_val) -> &'a [u8] { std::slice::from_raw_parts(value.mv_data as *const u8, value.mv_size) } heed-0.20.5/src/mdb/lmdb_flags.rs000064400000000000000000000427461046102023000146230ustar 00000000000000use bitflags::bitflags; use lmdb_master_sys as ffi; bitflags! { /// LMDB environment flags (see for more details). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] pub struct EnvFlags: u32 { /// mmap at a fixed address (experimental). const FIXEDMAP = ffi::MDB_FIXEDMAP; /// No environment directory. const NO_SUB_DIR = ffi::MDB_NOSUBDIR; /// Don't fsync after commit. const NO_SYNC = ffi::MDB_NOSYNC; /// Read only. const READ_ONLY = ffi::MDB_RDONLY; /// Don't fsync metapage after commit. const NO_META_SYNC = ffi::MDB_NOMETASYNC; /// Use writable mmap. const WRITE_MAP = ffi::MDB_WRITEMAP; /// Use asynchronous msync when MDB_WRITEMAP is used. const MAP_ASYNC = ffi::MDB_MAPASYNC; /// Tie reader locktable slots to MDB_txn objects instead of to threads. const NO_TLS = ffi::MDB_NOTLS; /// Don't do any locking, caller must manage their own locks. const NO_LOCK = ffi::MDB_NOLOCK; /// Don't do readahead (no effect on Windows). const NO_READ_AHEAD = ffi::MDB_NORDAHEAD; /// Don't initialize malloc'd memory before writing to datafile. const NO_MEM_INIT = ffi::MDB_NOMEMINIT; } } bitflags! { /// LMDB database flags (see for more details). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AllDatabaseFlags: u32 { /// Use reverse string keys. const REVERSE_KEY = ffi::MDB_REVERSEKEY; /// Use sorted duplicates. const DUP_SORT = ffi::MDB_DUPSORT; /// Numeric keys in native byte order: either `u32` or `usize`. /// The keys must all be of the same size. const INTEGER_KEY = ffi::MDB_INTEGERKEY; /// With [`DatabaseFlags::DUP_SORT`], sorted dup items have fixed size. const DUP_FIXED = ffi::MDB_DUPFIXED; /// With [`DatabaseKey::DUP_SORT`], dups are [`DatabaseKey::INTEGER_KEY`]-style integers. const INTEGER_DUP = ffi::MDB_INTEGERDUP; /// With [`DatabaseKey::DUP_SORT`], use reverse string dups. const REVERSE_DUP = ffi::MDB_REVERSEDUP; /// Create DB if not already existing. const CREATE = ffi::MDB_CREATE; } } bitflags! { /// LMDB database flags (see for more details). // It is a subset of the whole list of possible flags LMDB exposes but // we only want users to be able to specify these with the DUP flags. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct DatabaseFlags: u32 { /// Use reverse string keys. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::REVERSE_KEY) /// .name("reverse-key") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &"bonjour", &())?; /// db.put(&mut wtxn, &"hello", &())?; /// db.put(&mut wtxn, &"holla", &())?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some(("holla", ()))); /// assert_eq!(iter.next().transpose()?, Some(("hello", ()))); /// assert_eq!(iter.next().transpose()?, Some(("bonjour", ()))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.rev_iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some(("bonjour", ()))); /// assert_eq!(iter.next().transpose()?, Some(("hello", ()))); /// assert_eq!(iter.next().transpose()?, Some(("holla", ()))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const REVERSE_KEY = ffi::MDB_REVERSEKEY; /// Use sorted duplicates. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const DUP_SORT = ffi::MDB_DUPSORT; /// Numeric keys in native byte order: either `u32` or `usize`. /// The keys must all be of the same size. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::INTEGER_KEY) /// .name("integer-key") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((92, 32))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const INTEGER_KEY = ffi::MDB_INTEGERKEY; /// With [`DatabaseFlags::DUP_SORT`], sorted dup items have fixed size. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::DUP_FIXED) /// .name("dup-sort-fixed") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const DUP_FIXED = ffi::MDB_DUPFIXED; /// With [`DatabaseKey::DUP_SORT`], dups are [`DatabaseKey::INTEGER_KEY`]-style integers. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::INTEGER_DUP) /// .name("dup-sort-integer-dup") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const INTEGER_DUP = ffi::MDB_INTEGERDUP; /// With [`DatabaseKey::DUP_SORT`], use reverse string dups. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::REVERSE_DUP) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &"bonjour")?; /// db.put(&mut wtxn, &68, &"holla")?; /// db.put(&mut wtxn, &68, &"hello")?; /// db.put(&mut wtxn, &92, &"hallo")?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, "holla"))); /// assert_eq!(iter.next().transpose()?, Some((68, "hello"))); /// assert_eq!(iter.next().transpose()?, Some((68, "bonjour"))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const REVERSE_DUP = ffi::MDB_REVERSEDUP; } } bitflags! { /// LMDB put flags (see /// or for more details). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct PutFlags: u32 { /// Enter the new key/data pair only if it does not already appear in the database. /// /// This flag may only be specified if the database was opened with MDB_DUPSORT. /// The function will return MDB_KEYEXIST if the key/data pair already appears in the database. const NO_DUP_DATA = ffi::MDB_NODUPDATA; /// Enter the new key/data pair only if the key does not already appear in the database. /// /// The function will return MDB_KEYEXIST if the key already appears in the database, /// even if the database supports duplicates (MDB_DUPSORT). /// The data parameter will be set to point to the existing item. const NO_OVERWRITE = ffi::MDB_NOOVERWRITE; /// Append the given key/data pair to the end of the database. /// /// This option allows fast bulk loading when keys are already known to be in the correct order. /// Loading unsorted keys with this flag will cause a MDB_KEYEXIST error. const APPEND = ffi::MDB_APPEND; /// Append the given key/data pair to the end of the database but for sorted dup data. /// /// This option allows fast bulk loading when keys and dup data are already known to be in the correct order. /// Loading unsorted key/values with this flag will cause a MDB_KEYEXIST error. const APPEND_DUP = ffi::MDB_APPENDDUP; } } heed-0.20.5/src/mdb/mod.rs000064400000000000000000000002061046102023000132710ustar 00000000000000pub mod lmdb_error; pub mod lmdb_ffi; pub mod lmdb_flags; pub use self::{lmdb_error as error, lmdb_ffi as ffi, lmdb_flags as flags}; heed-0.20.5/src/reserved_space.rs000064400000000000000000000135241046102023000147510ustar 00000000000000use std::mem::MaybeUninit; use std::{fmt, io}; use crate::mdb::ffi; /// A structure that is used to improve the write speed in LMDB. /// /// You must write the exact amount of bytes, no less, no more. pub struct ReservedSpace<'a> { bytes: &'a mut [MaybeUninit], /// Number of bytes which have been written: all bytes in `0..written`. written: usize, /// Index of the byte which should be written next. /// /// # Safety /// /// To ensure there are no unwritten gaps in the buffer this should be kept in the range /// `0..=written` at all times. write_head: usize, } impl ReservedSpace<'_> { pub(crate) unsafe fn from_val<'a>(val: ffi::MDB_val) -> ReservedSpace<'a> { let len = val.mv_size; let ptr = val.mv_data; ReservedSpace { bytes: std::slice::from_raw_parts_mut(ptr.cast(), len), written: 0, write_head: 0, } } /// The total number of bytes that this memory buffer has. #[inline] pub fn size(&self) -> usize { self.bytes.len() } /// The remaining number of bytes that this memory buffer has. #[inline] pub fn remaining(&self) -> usize { self.bytes.len() - self.write_head } /// Get a slice of all the bytes that have previously been written. /// /// This can be used to write information which cannot be known until the very end of /// serialization. For example, this method can be used to serialize a value, then compute a /// checksum over the bytes, and then write that checksum to a header at the start of the /// reserved space. #[inline] pub fn written_mut(&mut self) -> &mut [u8] { let ptr = self.bytes.as_mut_ptr(); let len = self.written; unsafe { std::slice::from_raw_parts_mut(ptr.cast(), len) } } /// Fills the remaining reserved space with zeroes. /// /// This can be used together with [`written_mut`](Self::written_mut) to get a mutable view of /// the entire reserved space. /// /// ### Note /// /// After calling this function, the entire space is considered to be filled and any /// further attempt to [`write`](std::io::Write::write) anything else will fail. #[inline] pub fn fill_zeroes(&mut self) { self.bytes[self.write_head..].fill(MaybeUninit::new(0)); self.written = self.bytes.len(); self.write_head = self.bytes.len(); } /// Get a slice of bytes corresponding to the *entire* reserved space. /// /// It is safe to write to any byte within the slice. However, for a write past the end of the /// prevously written bytes to take effect, [`assume_written`](Self::assume_written) has to be /// called to mark those bytes as initialized. /// /// # Safety /// /// As the memory comes from within the database itself, the bytes may not yet be /// initialized. Thus, it is up to the caller to ensure that only initialized memory is read /// (ensured by the [`MaybeUninit`] API). #[inline] pub fn as_uninit_mut(&mut self) -> &mut [MaybeUninit] { self.bytes } /// Marks the bytes in the range `0..len` as being initialized by advancing the internal write /// pointer. /// /// # Safety /// /// The caller guarantees that all bytes in the range have been initialized. #[inline] pub unsafe fn assume_written(&mut self, len: usize) { debug_assert!(len <= self.bytes.len()); self.written = len; self.write_head = len; } } impl io::Write for ReservedSpace<'_> { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.write_all(buf)?; Ok(buf.len()) } #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { let remaining = unsafe { self.bytes.get_unchecked_mut(self.write_head..) }; if buf.len() > remaining.len() { return Err(io::Error::from(io::ErrorKind::WriteZero)); } unsafe { // SAFETY: we can always cast `T` -> `MaybeUninit` as it's a transparent wrapper let buf_uninit = std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len()); remaining.as_mut_ptr().copy_from_nonoverlapping(buf_uninit.as_ptr(), buf.len()); } self.write_head += buf.len(); self.written = usize::max(self.written, self.write_head); Ok(()) } #[inline(always)] fn flush(&mut self) -> io::Result<()> { Ok(()) } } /// ## Note /// /// May only seek within the previously written space. /// Attempts to do otherwise will result in an error. impl io::Seek for ReservedSpace<'_> { #[inline] fn seek(&mut self, pos: io::SeekFrom) -> io::Result { let (base, offset) = match pos { io::SeekFrom::Start(start) => (start, 0), io::SeekFrom::End(offset) => (self.written as u64, offset), io::SeekFrom::Current(offset) => (self.write_head as u64, offset), }; let Some(new_pos) = base.checked_add_signed(offset) else { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "cannot seek before start of reserved space", )); }; if new_pos > self.written as u64 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "cannot seek past end of reserved space", )); } self.write_head = new_pos as usize; Ok(new_pos) } #[inline] fn rewind(&mut self) -> io::Result<()> { self.write_head = 0; Ok(()) } #[inline] fn stream_position(&mut self) -> io::Result { Ok(self.write_head as u64) } } impl fmt::Debug for ReservedSpace<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ReservedSpace").finish() } } heed-0.20.5/src/txn.rs000064400000000000000000000123361046102023000125700ustar 00000000000000use std::borrow::Cow; use std::ops::Deref; use std::ptr; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::{Env, Result}; /// A read-only transaction. /// /// ## LMDB Limitations /// /// It's a must to keep read transactions short-lived. /// /// Active Read transactions prevent the reuse of pages freed /// by newer write transactions, thus the database can grow quickly. /// /// ## OSX/Darwin Limitation /// /// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can /// be active at the same time for a process. Threads are in the same process space. /// /// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB! /// /// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist` pub struct RoTxn<'e> { pub(crate) txn: *mut ffi::MDB_txn, env: Cow<'e, Env>, } impl<'e> RoTxn<'e> { pub(crate) fn new(env: &'e Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { mdb_result(ffi::mdb_txn_begin( env.env_mut_ptr(), ptr::null_mut(), ffi::MDB_RDONLY, &mut txn, ))? }; Ok(RoTxn { txn, env: Cow::Borrowed(env) }) } pub(crate) fn static_read_txn(env: Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { mdb_result(ffi::mdb_txn_begin( env.env_mut_ptr(), ptr::null_mut(), ffi::MDB_RDONLY, &mut txn, ))? }; Ok(RoTxn { txn, env: Cow::Owned(env) }) } pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env { self.env.env_mut_ptr() } /// Commit a read transaction. /// /// Synchronizing some [`Env`] metadata with the global handle. /// /// ## LMDB /// /// It's mandatory in a multi-process setup to call [`RoTxn::commit`] upon read-only database opening. /// After the transaction opening, the database is dropped. The next transaction might return /// `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` known as `EINVAL`. pub fn commit(mut self) -> Result<()> { let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn)) }; self.txn = ptr::null_mut(); result.map_err(Into::into) } } impl Drop for RoTxn<'_> { fn drop(&mut self) { if !self.txn.is_null() { abort_txn(self.txn); } } } #[cfg(feature = "read-txn-no-tls")] unsafe impl Send for RoTxn<'_> {} fn abort_txn(txn: *mut ffi::MDB_txn) { // Asserts that the transaction hasn't been already committed. assert!(!txn.is_null()); unsafe { ffi::mdb_txn_abort(txn) } } /// A read-write transaction. /// /// ## LMDB Limitations /// /// Only one [`RwTxn`] may exist in the same environment at the same time. /// If two exist, the new one may wait on a mutex for [`RwTxn::commit`] or [`RwTxn::abort`] to /// be called for the first one. /// /// ## OSX/Darwin Limitation /// /// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can /// be active at the same time for a process. Threads are in the same process space. /// /// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB! /// /// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist` pub struct RwTxn<'p> { pub(crate) txn: RoTxn<'p>, } impl<'p> RwTxn<'p> { pub(crate) fn new(env: &'p Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr(), ptr::null_mut(), 0, &mut txn))? }; Ok(RwTxn { txn: RoTxn { txn, env: Cow::Borrowed(env) } }) } pub(crate) fn nested(env: &'p Env, parent: &'p mut RwTxn) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); let parent_ptr: *mut ffi::MDB_txn = parent.txn.txn; unsafe { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr(), parent_ptr, 0, &mut txn))? }; Ok(RwTxn { txn: RoTxn { txn, env: Cow::Borrowed(env) } }) } pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env { self.txn.env.env_mut_ptr() } /// Commit all the operations of a transaction into the database. /// The transaction is reset. pub fn commit(mut self) -> Result<()> { let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn.txn)) }; self.txn.txn = ptr::null_mut(); result.map_err(Into::into) } /// Abandon all the operations of the transaction instead of saving them. /// The transaction is reset. pub fn abort(mut self) { abort_txn(self.txn.txn); self.txn.txn = ptr::null_mut(); } } impl<'p> Deref for RwTxn<'p> { type Target = RoTxn<'p>; fn deref(&self) -> &Self::Target { &self.txn } }