heed-0.20.0-alpha.9/.cargo_vcs_info.json0000644000000001420000000000100132470ustar { "git": { "sha1": "22f441733683086d918532dffa6d0d2c4fa34587" }, "path_in_vcs": "heed" }heed-0.20.0-alpha.9/Cargo.lock0000644000000373760000000000100112450ustar # 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 = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" dependencies = [ "serde", ] [[package]] name = "bytemuck" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[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.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "doxygen-rs" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff670ea0c9bbb8414e7efa6e23ebde2b8f520a7eef78273a3918cf1903e7505" 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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "heed" version = "0.20.0-alpha.9" dependencies = [ "bitflags 2.3.3", "bytemuck", "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-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ab0b7d9cde969ad36dde692e487dc89d97f7168bf6a7bd3b894ad4bf7278298" [[package]] name = "heed-types" version = "0.20.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0cb3567a7363f28b597bf6e9897b9466397951dd0e52df2c8196dd8a71af44a" dependencies = [ "bincode", "byteorder", "heed-traits", "rmp-serde", "serde", "serde_json", ] [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lmdb-master-sys" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629c123f5321b48fa4f8f4d3b868165b748d9ba79c7103fb58e3a94f736bcedd" dependencies = [ "cc", "doxygen-rs", "libc", "pkg-config", ] [[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.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 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 = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "rmp" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", "paste", ] [[package]] name = "rmp-serde" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" dependencies = [ "byteorder", "rmp", "serde", ] [[package]] name = "rustix" version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "indexmap", "itoa", "ryu", "serde", ] [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "syn" version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" 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.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[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.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" 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.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" heed-0.20.0-alpha.9/Cargo.toml0000644000000043040000000000100112510ustar # 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.0-alpha.9" authors = ["Kerollmops "] 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" [[example]] name = "rmp-serde" required-features = ["serde-rmp"] [dependencies.bitflags] version = "2.3.3" features = ["serde"] [dependencies.bytemuck] version = "1.12.3" [dependencies.byteorder] version = "1.4.3" default-features = false [dependencies.heed-traits] version = "0.20.0-alpha.9" [dependencies.heed-types] version = "0.20.0-alpha.9" default-features = false [dependencies.libc] version = "0.2.139" [dependencies.lmdb-master-sys] version = "0.1.0" [dependencies.once_cell] version = "1.16.0" [dependencies.page_size] version = "0.6.0" [dependencies.serde] version = "1.0.151" features = ["derive"] optional = true [dependencies.synchronoise] version = "1.0.1" [dev-dependencies.bytemuck] version = "1.12.3" features = ["derive"] [dev-dependencies.serde] version = "1.0.151" features = ["derive"] [dev-dependencies.tempfile] version = "3.3.0" [features] arbitrary_precision = ["heed-types/arbitrary_precision"] default = [ "serde", "serde-bincode", "serde-json", ] posix-sem = ["lmdb-master-sys/posix-sem"] preserve_order = ["heed-types/preserve_order"] raw_value = ["heed-types/raw_value"] read-txn-no-tls = [] 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.3.1" heed-0.20.0-alpha.9/Cargo.toml.orig000064400000000000000000000056371046102023000147440ustar 00000000000000[package] name = "heed" version = "0.20.0-alpha.9" 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.3.3", features = ["serde"] } bytemuck = "1.12.3" byteorder = { version = "1.4.3", default-features = false } heed-traits = { version = "0.20.0-alpha.9", path = "../heed-traits" } heed-types = { version = "0.20.0-alpha.9", default-features = false, path = "../heed-types" } libc = "0.2.139" lmdb-master-sys = { version = "0.1.0", path = "../lmdb-master-sys" } once_cell = "1.16.0" page_size = "0.6.0" serde = { version = "1.0.151", features = ["derive"], optional = true } synchronoise = "1.0.1" [dev-dependencies] serde = { version = "1.0.151", features = ["derive"] } bytemuck = { version = "1.12.3", features = ["derive"] } tempfile = "3.3.0" [target.'cfg(windows)'.dependencies] url = "2.3.1" [features] # The `serde` feature makes some types serializable, # like the `EnvOpenOptions` struct. default = ["serde", "serde-bincode", "serde-json"] # The #MDB_NOTLS flag is automatically set on Env opening and # RoTxn implements the Send trait. This allows the user to move # a RoTxn 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"] [[example]] name = "rmp-serde" required-features = ["serde-rmp"] heed-0.20.0-alpha.9/README.md000064400000000000000000000024151046102023000133230ustar 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 fully typed [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) wrapper with minimum overhead, uses bytemuck internally. This library is able to serialize all kind of types, not just bytes slices, even _Serde_ types are supported. Go check out [the examples](heed/examples/). ## Building from Source If you don't already cloned the repository you can use this command: ```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 follwing command: ```bash git submodule update --init ``` heed-0.20.0-alpha.9/examples/all-types.rs000064400000000000000000000066441046102023000161520ustar 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 = 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"))?; // and here we try to open it with other types // asserting that it correctly returns an error // // NOTE that those types are not saved upon runs and // therefore types cannot be checked upon different runs, // the first database opening fix the types for this run. let result = env.create_database::>(&mut wtxn, Some("ignored-data")); assert!(result.is_err()); // 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.0-alpha.9/examples/clear-database.rs000064400000000000000000000026321046102023000170610ustar 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 = 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.0-alpha.9/examples/cursor-append.rs000064400000000000000000000025751046102023000170210ustar 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 = 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.0-alpha.9/examples/custom-comparator.rs000064400000000000000000000035501046102023000177100ustar 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 = 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.0-alpha.9/examples/multi-env.rs000064400000000000000000000024261046102023000161520ustar 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 = EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(env1_path)?; fs::create_dir_all(&env2_path)?; let env2 = 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.0-alpha.9/examples/nested.rs000064400000000000000000000036321046102023000155140ustar 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 = 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.0-alpha.9/examples/rmp-serde.rs000064400000000000000000000017251046102023000161310ustar 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 = 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.0-alpha.9/src/cursor.rs000064400000000000000000000344551046102023000145270ustar 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 i.e. `&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 `[put_current]` method. pub unsafe fn put_current_reserved_with_flags( &mut self, flags: PutFlags, key: &[u8], data_size: usize, mut write_func: F, ) -> Result where F: FnMut(&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 i.e. `&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.0-alpha.9/src/database.rs000064400000000000000000002314621046102023000147530ustar 00000000000000use std::any::TypeId; use 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 = 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 have 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, /// 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 call manually `RoTxn::commit` to get metadata /// and the databases 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); let types = (TypeId::of::(), TypeId::of::(), TypeId::of::()); match self.env.raw_init_database::(rtxn.txn, self.name, types, 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 have 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, /// 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 types = (TypeId::of::(), TypeId::of::(), TypeId::of::()); let flags = self.flags | AllDatabaseFlags::CREATE; match self.env.raw_init_database::(wtxn.txn.txn, self.name, types, 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 = 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 { 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(stats.ms_entries as u64) } Err(e) => Err(e.into()), } } /// 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 = 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) } /// Return a 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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. /// /// ``` /// # 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 = 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 = 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. 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 = 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. /// /// ``` /// # 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 = 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, mut write_func: F, ) -> Result<()> where KC: BytesEncode<'a>, F: FnMut(&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. 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 = 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(()) } /// 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 = 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 = 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 = 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 = 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 = 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() } } heed-0.20.0-alpha.9/src/env.rs000064400000000000000000001164551046102023000140030ustar 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::path::{Path, PathBuf}; 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, sync}; 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 = env_builder.open(dir.path())?; /// /// // we will open the default unamed 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. pub 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, dbi_open_mutex: sync::Mutex::default(), 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: _, dbi_open_mutex: _, path } = self.0.as_ref(); f.debug_struct("Env").field("path", &path.display()).finish_non_exhaustive() } } struct EnvInner { env: *mut ffi::MDB_env, dbi_open_mutex: sync::Mutex>, 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(); } } } } /// An helping 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). 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 C::compare(a, b) { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, } } /// 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, } 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 = 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.raw_flags().map(EnvFlags::from_bits) } /// Return the raw flags the environment was opened with. pub fn raw_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. 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()?; let dbi = self.raw_open_dbi::(rtxn.txn, None, 0)?; // we don’t want anyone to open an environment while we’re computing the stats // thus we take a lock on the dbi let dbi_open = self.0.dbi_open_mutex.lock().unwrap(); // 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); // if the db wasn’t already opened if !dbi_open.contains_key(&dbi) { unsafe { ffi::mdb_dbi_close(self.env_mut_ptr(), dbi) } } } } 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 have 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, /// 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 call manually `RoTxn::commit` to get metadata /// and the databases 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 in this program run, types will be checked. /// /// ## Important Information /// /// LMDB have 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, /// 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>, types: (TypeId, TypeId, TypeId), flags: AllDatabaseFlags, ) -> Result { let mut lock = self.0.dbi_open_mutex.lock().unwrap(); match self.raw_open_dbi::(raw_txn, name, flags.bits()) { Ok(dbi) => { let old_types = lock.entry(dbi).or_insert(types); if *old_types == types { Ok(dbi) } else { Err(Error::InvalidDatabaseTyping) } } 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. /// /// ## 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) } /// 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 docs](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5), /// it is ok 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, 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) } } /// Contains information about the environment. #[derive(Debug, Clone, Copy)] pub struct EnvInfo { /// Address of 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, /// Maximum 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 dead-lock. 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 = 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 = EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path()) .unwrap(); let result = 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 = EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path().join("babar.mdb")) .unwrap(); let _env = 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 = 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 = envbuilder.open(dir.path().join("data.mdb")).unwrap(); } #[test] fn create_database_without_commit() { let dir = tempfile::tempdir().unwrap(); let env = 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 = 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 = 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 = 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 = 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 = 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 = 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(); } } } heed-0.20.0-alpha.9/src/iteration_method.rs000064400000000000000000000021011046102023000165270ustar 00000000000000//! The set of possible iteration method for the different iterators. use crate::cursor::MoveOperation; /// The trait used to define the way iterators behaves. pub trait IterationMethod { /// The internal operation to move the cursor throught entries. const MOVE_OPERATION: MoveOperation; } /// Moves to the next or previous key if there /// is no more values associated to the current key. #[derive(Debug, Clone, Copy)] pub enum MoveThroughDuplicateValues {} impl IterationMethod for MoveThroughDuplicateValues { const MOVE_OPERATION: MoveOperation = MoveOperation::Any; } /// Moves between keys and ignore 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 ignore other keys. #[derive(Debug, Clone, Copy)] pub enum MoveOnCurrentKeyDuplicates {} impl IterationMethod for MoveOnCurrentKeyDuplicates { const MOVE_OPERATION: MoveOperation = MoveOperation::Dup; } heed-0.20.0-alpha.9/src/iterator/iter.rs000064400000000000000000000677771046102023000160230ustar 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 = 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 = 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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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.0-alpha.9/src/iterator/mod.rs000064400000000000000000000571051046102023000156170ustar 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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.0-alpha.9/src/iterator/prefix.rs000064400000000000000000000737761046102023000163510ustar 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 Vec) -> 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 Vec) -> 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 Vec, ) -> 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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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.0-alpha.9/src/iterator/range.rs000064400000000000000000000774241046102023000161420ustar 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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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() } } /// 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 i.e. `&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: FnMut(&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 i.e. `&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.0-alpha.9/src/lib.rs000064400000000000000000000163511046102023000137530ustar 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" )] //! Crate `heed` is a high-level wrapper of [LMDB], high-level doesn't mean heavy (think about Rust). //! //! It provides you a way to store types in LMDB without any limit and with a minimal overhead as possible, //! relying on the [bytemuck] library to avoid copying bytes when that's unnecessary and the serde library //! when this is unavoidable. //! //! The Lightning Memory-Mapped Database (LMDB) directly maps files parts into main memory, combined //! with the bytemuck library allows us to safely zero-copy parse and serialize Rust types into LMDB. //! //! [LMDB]: https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database //! //! # Examples //! //! Open a database, that will support some typed key/data and ensures, 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 = 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)] 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 {bytemuck, byteorder, heed_types as types}; use self::cursor::{RoCursor, RwCursor}; pub use self::database::{Database, DatabaseOpenOptions}; pub use self::env::{ env_closing_event, CompactionOption, DefaultComparator, Env, EnvClosingEvent, EnvInfo, EnvOpenOptions, }; 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 std or be a rewrapped [`MdbError`] Io(io::Error), /// Lmdb error Mdb(MdbError), /// Encoding error Encoding(BoxedError), /// Decoding error Decoding(BoxedError), /// Incoherent types when opening a database InvalidDatabaseTyping, /// 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::InvalidDatabaseTyping => { f.write_str("database was previously opened with different types") } 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.0-alpha.9/src/mdb/lmdb_error.rs000064400000000000000000000131041046102023000160670ustar 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 { /// key/data pair already exists. /// /// May be also returned by append functions, data are passed /// doesn't respect the database ordering. KeyExist, /// key/data pair 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. BadValSize, /// The specified DBI was changed unexpectedly. BadDbi, /// Unexpected problem - transaction should abort. Problem, /// Other error. Other(c_int), } impl Error { /// Return 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.0-alpha.9/src/mdb/lmdb_ffi.rs000064400000000000000000000037751046102023000155170ustar 00000000000000use std::ptr; pub use ffi::{ mdb_cursor_close, mdb_cursor_del, mdb_cursor_get, mdb_cursor_open, mdb_cursor_put, mdb_dbi_close, 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_info, mdb_env_open, 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_envinfo, MDB_stat, MDB_txn, MDB_val, MDB_APPEND, MDB_APPENDDUP, MDB_CP_COMPACT, MDB_CREATE, MDB_CURRENT, MDB_DUPSORT, MDB_NODUPDATA, MDB_NOOVERWRITE, 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.0-alpha.9/src/mdb/lmdb_flags.rs000064400000000000000000000425051046102023000160410ustar 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 = 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 = 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 = 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 = 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 = 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 = 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.0-alpha.9/src/mdb/mod.rs000064400000000000000000000002061046102023000145160ustar 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.0-alpha.9/src/reserved_space.rs000064400000000000000000000025531046102023000161760ustar 00000000000000use 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 { size: usize, start_ptr: *mut u8, written: usize, } impl ReservedSpace { pub(crate) unsafe fn from_val(val: ffi::MDB_val) -> ReservedSpace { ReservedSpace { size: val.mv_size, start_ptr: val.mv_data as *mut u8, written: 0 } } /// The total number of bytes that this memory buffer is. pub fn size(&self) -> usize { self.size } /// The remaining number of bytes that this memory buffer has. pub fn remaining(&self) -> usize { self.size - self.written } } impl io::Write for ReservedSpace { fn write(&mut self, buf: &[u8]) -> io::Result { if self.remaining() >= buf.len() { let dest = unsafe { self.start_ptr.add(self.written) }; unsafe { buf.as_ptr().copy_to_nonoverlapping(dest, buf.len()) }; self.written += buf.len(); Ok(buf.len()) } else { Err(io::Error::from(io::ErrorKind::WriteZero)) } } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl fmt::Debug for ReservedSpace { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ReservedSpace").finish() } } heed-0.20.0-alpha.9/src/txn.rs000064400000000000000000000113131046102023000140070ustar 00000000000000use 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 crash in the Posix semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if you program already use Posix Semaphore then you will have less available for heed/lmdb! /// /// You may changing it by editing at **your own risk**: `/Library/LaunchDaemons/sysctl.plist` pub struct RoTxn<'e> { pub(crate) txn: *mut ffi::MDB_txn, env: &'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 }) } 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 `drop`ed. 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, /// it two exist, the new one may wait on a Mutex for [RwTxn::commit] or [RwTxn::abort] of /// 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 crash in the Posix semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if you program already use Posix Semaphore then you will have less available for heed/lmdb! /// /// You may changing it by editing 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 } }) } 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 } }) } 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 } }