heed-0.22.0/.cargo_vcs_info.json0000644000000001420000000000100120170ustar { "git": { "sha1": "bc9cf18f6c3543fd492920c9c84bdebb4cdfb057" }, "path_in_vcs": "heed" }heed-0.22.0/Cargo.lock0000644000000526030000000000100100030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" dependencies = [ "serde", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "shlex", ] [[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.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doxygen-rs" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" dependencies = [ "phf", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi", "windows-targets", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heed" version = "0.22.0" dependencies = [ "bitflags", "byteorder", "heed-traits", "heed-types", "libc", "lmdb-master-sys", "memchr", "once_cell", "page_size", "serde", "synchronoise", "tempfile", "url", ] [[package]] name = "heed-traits" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" [[package]] name = "heed-types" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d" dependencies = [ "bincode", "byteorder", "heed-traits", "rmp-serde", "serde", "serde_json", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "linux-raw-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lmdb-master-sys" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864808e0b19fb6dd3b70ba94ee671b82fce17554cf80aeb0a155c65bb08027df" dependencies = [ "cc", "doxygen-rs", "libc", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[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.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "page_size" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", ] [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.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 = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rmp" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder", "num-traits", "paste", ] [[package]] name = "rmp-serde" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder", "rmp", "serde", ] [[package]] name = "rustix" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "indexmap", "itoa", "memchr", "ryu", "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 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 = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tempfile" version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom", "once_cell", "rustix", "windows-sys", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ "wit-bindgen-rt", ] [[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.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] heed-0.22.0/Cargo.toml0000644000000067060000000000100100310ustar # 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.22.0" authors = ["Kerollmops "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A fully typed LMDB (mdb.master) wrapper with minimum overhead" readme = "README.md" keywords = [ "lmdb", "database", "storage", "typed", ] categories = [ "database", "data-structures", ] license = "MIT" repository = "https://github.com/Kerollmops/heed" [features] arbitrary_precision = ["heed-types/arbitrary_precision"] default = [ "serde", "serde-bincode", "serde-json", ] longer-keys = ["lmdb-master-sys/longer-keys"] mdb_idl_logn_10 = ["lmdb-master-sys/mdb_idl_logn_10"] mdb_idl_logn_11 = ["lmdb-master-sys/mdb_idl_logn_11"] mdb_idl_logn_12 = ["lmdb-master-sys/mdb_idl_logn_12"] mdb_idl_logn_13 = ["lmdb-master-sys/mdb_idl_logn_13"] mdb_idl_logn_14 = ["lmdb-master-sys/mdb_idl_logn_14"] mdb_idl_logn_15 = ["lmdb-master-sys/mdb_idl_logn_15"] mdb_idl_logn_16 = ["lmdb-master-sys/mdb_idl_logn_16"] mdb_idl_logn_8 = ["lmdb-master-sys/mdb_idl_logn_8"] mdb_idl_logn_9 = ["lmdb-master-sys/mdb_idl_logn_9"] posix-sem = ["lmdb-master-sys/posix-sem"] preserve_order = ["heed-types/preserve_order"] raw_value = ["heed-types/raw_value"] serde = [ "bitflags/serde", "dep:serde", ] serde-bincode = ["heed-types/serde-bincode"] serde-json = ["heed-types/serde-json"] serde-rmp = ["heed-types/serde-rmp"] unbounded_depth = ["heed-types/unbounded_depth"] use-valgrind = ["lmdb-master-sys/use-valgrind"] [lib] name = "heed" path = "src/lib.rs" [[example]] name = "all-types" path = "examples/all-types.rs" [[example]] name = "clear-database" path = "examples/clear-database.rs" [[example]] name = "cursor-append" path = "examples/cursor-append.rs" [[example]] name = "custom-comparator" path = "examples/custom-comparator.rs" [[example]] name = "custom-dupsort-comparator" path = "examples/custom-dupsort-comparator.rs" [[example]] name = "multi-env" path = "examples/multi-env.rs" [[example]] name = "nested" path = "examples/nested.rs" [[example]] name = "prev-snapshot" path = "examples/prev-snapshot.rs" [[example]] name = "rmp-serde" path = "examples/rmp-serde.rs" required-features = ["serde-rmp"] [dependencies.bitflags] version = "2.9.0" features = ["serde"] [dependencies.byteorder] version = "1.5.0" default-features = false [dependencies.heed-traits] version = "0.20.0" [dependencies.heed-types] version = "0.21.0" default-features = false [dependencies.libc] version = "0.2.170" [dependencies.lmdb-master-sys] version = "0.2.5" [dependencies.once_cell] version = "1.20.3" [dependencies.page_size] version = "0.6.0" [dependencies.serde] version = "1.0.218" features = ["derive"] optional = true [dependencies.synchronoise] version = "1.0.1" [dev-dependencies.memchr] version = "2.7.4" [dev-dependencies.serde] version = "1.0.218" features = ["derive"] [dev-dependencies.tempfile] version = "3.18.0" [target."cfg(windows)".dependencies.url] version = "2.5.4" heed-0.22.0/Cargo.toml.orig000064400000000000000000000113721046102023000135050ustar 00000000000000[package] name = "heed" version = "0.22.0" authors = ["Kerollmops "] description = "A fully typed LMDB (mdb.master) 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.9.0", features = ["serde"] } byteorder = { version = "1.5.0", default-features = false } heed-traits = { version = "0.20.0", path = "../heed-traits" } heed-types = { version = "0.21.0", default-features = false, path = "../heed-types" } libc = "0.2.170" lmdb-master-sys = { version = "0.2.5", path = "../lmdb-master-sys" } once_cell = "1.20.3" page_size = "0.6.0" serde = { version = "1.0.218", features = ["derive"], optional = true } synchronoise = "1.0.1" [dev-dependencies] memchr = "2.7.4" serde = { version = "1.0.218", features = ["derive"] } tempfile = "3.18.0" [target.'cfg(windows)'.dependencies] url = "2.5.4" [features] # The `serde` feature makes some types serializable, # like the `EnvOpenOptions` struct. default = ["serde", "serde-bincode", "serde-json"] serde = ["bitflags/serde", "dep:serde"] # Enable the serde en/decoders for bincode, serde_json, or rmp_serde serde-bincode = ["heed-types/serde-bincode"] serde-json = ["heed-types/serde-json"] serde-rmp = ["heed-types/serde-rmp"] # serde_json features preserve_order = ["heed-types/preserve_order"] arbitrary_precision = ["heed-types/arbitrary_precision"] raw_value = ["heed-types/raw_value"] unbounded_depth = ["heed-types/unbounded_depth"] # Whether to tell LMDB to use POSIX semaphores during compilation # (instead of the default, which are System V semaphores). # POSIX semaphores are required for Apple's App Sandbox on iOS & macOS, # and are possibly faster and more appropriate for single-process use. # There are tradeoffs for both POSIX and SysV semaphores; which you # should look into before enabling this feature. Also, see here: # posix-sem = ["lmdb-master-sys/posix-sem"] # These features configure the MDB_IDL_LOGN macro, which determines # the size of the free and dirty page lists (and thus the amount of memory # allocated when opening an LMDB environment in read-write mode). # # Each feature defines MDB_IDL_LOGN as the value in the name of the feature. # That means these features are mutually exclusive, and you must not specify # more than one at the same time (or the crate will fail to compile). # # For more information on the motivation for these features (and their effect), # see https://github.com/mozilla/lmdb/pull/2. mdb_idl_logn_8 = ["lmdb-master-sys/mdb_idl_logn_8"] mdb_idl_logn_9 = ["lmdb-master-sys/mdb_idl_logn_9"] mdb_idl_logn_10 = ["lmdb-master-sys/mdb_idl_logn_10"] mdb_idl_logn_11 = ["lmdb-master-sys/mdb_idl_logn_11"] mdb_idl_logn_12 = ["lmdb-master-sys/mdb_idl_logn_12"] mdb_idl_logn_13 = ["lmdb-master-sys/mdb_idl_logn_13"] mdb_idl_logn_14 = ["lmdb-master-sys/mdb_idl_logn_14"] mdb_idl_logn_15 = ["lmdb-master-sys/mdb_idl_logn_15"] mdb_idl_logn_16 = ["lmdb-master-sys/mdb_idl_logn_16"] # Setting this enables you to use keys longer than 511 bytes. The exact limit # is computed by LMDB at compile time. You can find the exact value by calling # Env::max_key_size(). This value varies by architecture. # # Example max key sizes: # - Apple M1 (ARM64): 8126 bytes # - Apple Intel (AMD64): 1982 bytes # - Linux Intel (AMD64): 1982 bytes # # Setting this also enables you to use values larger than 511 bytes when using # a Database with the DatabaseFlags::DUP_SORT flag. # # This builds LMDB with the -DMDB_MAXKEYSIZE=0 option. # # Note: If you are moving database files between architectures then your longest # stored key must fit within the smallest limit of all architectures used. For # example, if you are moving databases between Apple M1 and Apple Intel # computers then you need to keep your keys within the smaller 1982 byte limit. longer-keys = ["lmdb-master-sys/longer-keys"] # Enable a better Valgrind support. This builds LMDB with the -DUSE_VALGRIND=1 option. # # You have to install the RPM valgrind-devel which contains memcheck.h. # # More information can be found at: # use-valgrind = ["lmdb-master-sys/use-valgrind"] [[example]] name = "all-types" [[example]] name = "clear-database" [[example]] name = "cursor-append" [[example]] name = "custom-comparator" [[example]] name = "custom-dupsort-comparator" [[example]] name = "multi-env" [[example]] name = "nested" [[example]] name = "prev-snapshot" [[example]] name = "rmp-serde" required-features = ["serde-rmp"] heed-0.22.0/README.md000064400000000000000000000061341046102023000120750ustar 00000000000000

heed & heed3

[![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) ![Discord](https://img.shields.io/discord/1006923006964154428?style=flat&logo=discord&logoColor=ffffff&label=&labelColor=6A7EC2&color=7389D8&link=https%3A%2F%2Fdiscord.com%2Fchannels%2F1006923006964154428%2F1347203493106024528) Rust-centric [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) abstractions with minimal overhead. These libraries enable the storage of various Rust types within LMDB, extending support to include Serde-compatible types. It supports not only the LMDB `mdb.master` branch but also the `mdb.master3` branch, which features encryption-at-rest. ## Simple Example Usage Here is an example on how to store and read entries into LMDB in a safe and ACID way. For usage examples, see [examples/](examples/). To see more advanced usage techniques go check our [Cookbook](https://docs.rs/heed/latest/heed/cookbook/index.html). ```rust use std::fs; use std::path::Path; use heed::{EnvOpenOptions, Database}; use heed::types::*; fn main() -> Result<(), Box> { let env = unsafe { EnvOpenOptions::new().open("my-first-db")? }; // We open the default unnamed database let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, None)?; // We open a write transaction db.put(&mut wtxn, "seven", &7)?; db.put(&mut wtxn, "zero", &0)?; db.put(&mut wtxn, "five", &5)?; db.put(&mut wtxn, "three", &3)?; wtxn.commit()?; // We open a read transaction to check if those values are now available let mut rtxn = env.read_txn()?; let ret = db.get(&rtxn, "zero")?; assert_eq!(ret, Some(0)); let ret = db.get(&rtxn, "five")?; assert_eq!(ret, Some(5)); Ok(()) } ``` ## Working with two Crates: heed and heed3 The heed and heed3 crates manage a shared codebase. Within the heed3 folder, you can find the Cargo.toml specific to the heed3 crate. To facilitate work on heed3, utilize the `convert-to-heed3.sh` script. This script conveniently moves the `heed3/Cargo.toml` file to the `heed/` folder, updates the `heed::` references to `heed3::`, and generates a commit for easy rollback if needed. ## Building from Source You can use this command to clone the repository: ```bash git clone --recursive https://github.com/meilisearch/heed.git cd heed cargo build ``` However, if you already cloned it and forgot to initialize the submodules, execute the following command: ```bash git submodule update --init ``` heed-0.22.0/build.rs000064400000000000000000000006131046102023000122570ustar 00000000000000use std::env; fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo::rustc-check-cfg=cfg(master3)"); let pkgname = env::var("CARGO_PKG_NAME").expect("Cargo didn't set the CARGO_PKG_NAME env var!"); match pkgname.as_str() { "heed" => (), "heed3" => println!("cargo:rustc-cfg=master3"), _ => panic!("unexpected package name!"), } } heed-0.22.0/examples/all-types.rs000064400000000000000000000057241046102023000147200ustar 00000000000000use std::error::Error; use heed::byteorder::BE; use heed::types::*; use heed::{Database, EnvOpenOptions}; use serde::{Deserialize, Serialize}; fn main() -> Result<(), Box> { let path = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // here the key will be an str and the data will be a slice of u8 let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("kiki"))?; db.put(&mut wtxn, "hello", &[2, 3][..])?; let ret: Option<&[u8]> = db.get(&wtxn, "hello")?; println!("{:?}", ret); wtxn.commit()?; // serde types are also supported!!! #[derive(Debug, Serialize, Deserialize)] struct Hello<'a> { string: &'a str, } let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-bincode"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-bincode:\t{:?}", ret); wtxn.commit()?; let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-json"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-json:\t{:?}", ret); wtxn.commit()?; // you can ignore the data let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?; db.put(&mut wtxn, "hello", &())?; let ret: Option<()> = db.get(&wtxn, "hello")?; println!("{:?}", ret); let ret: Option<()> = db.get(&wtxn, "non-existant")?; println!("{:?}", ret); wtxn.commit()?; // database opening and types are tested in a safe way // // we try to open a database twice with the same types let mut wtxn = env.write_txn()?; let _db: Database = env.create_database(&mut wtxn, Some("ignored-data"))?; // you can iterate over keys in order type BEI64 = I64; let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; db.put(&mut wtxn, &0, &())?; db.put(&mut wtxn, &68, &())?; db.put(&mut wtxn, &35, &())?; db.put(&mut wtxn, &42, &())?; let rets: Result, _> = db.iter(&wtxn)?.collect(); println!("{:?}", rets); // or iterate over ranges too!!! let range = 35..=42; let rets: Result, _> = db.range(&wtxn, &range)?.collect(); println!("{:?}", rets); // delete a range of key let range = 35..=42; let deleted: usize = db.delete_range(&mut wtxn, &range)?; let rets: Result, _> = db.iter(&wtxn)?.collect(); println!("deleted: {:?}, {:?}", deleted, rets); wtxn.commit()?; Ok(()) } heed-0.22.0/examples/clear-database.rs000064400000000000000000000024611046102023000156310ustar 00000000000000use std::error::Error; 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 = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, Some("first"))?; // We fill the db database with entries. db.put(&mut wtxn, "I am here", "to test things")?; db.put(&mut wtxn, "I am here too", "for the same purpose")?; wtxn.commit()?; let mut wtxn = env.write_txn()?; db.clear(&mut wtxn)?; db.put(&mut wtxn, "And I come back", "to test things")?; let mut iter = db.iter(&wtxn)?; assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things"))); assert_eq!(iter.next().transpose()?, None); drop(iter); wtxn.commit()?; let rtxn = env.read_txn()?; let mut iter = db.iter(&rtxn)?; assert_eq!(iter.next().transpose()?, Some(("And I come back", "to test things"))); assert_eq!(iter.next().transpose()?, None); Ok(()) } heed-0.22.0/examples/cursor-append.rs000064400000000000000000000024251046102023000155630ustar 00000000000000use std::error::Error; 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 = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let first: Database = env.create_database(&mut wtxn, Some("first"))?; let second: Database = env.create_database(&mut wtxn, Some("second"))?; // We fill the first database with entries. first.put(&mut wtxn, "I am here", "to test things")?; first.put(&mut wtxn, "I am here too", "for the same purpose")?; // We try to append ordered entries in the second database. let mut iter = second.iter_mut(&mut wtxn)?; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "aaaa", "lol")? }; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "abcd", "lol")? }; unsafe { iter.put_current_with_options::(PutFlags::APPEND, "bcde", "lol")? }; drop(iter); wtxn.commit()?; Ok(()) } heed-0.22.0/examples/custom-comparator.rs000064400000000000000000000034061046102023000164600ustar 00000000000000use std::cmp::Ordering; use std::error::Error; use std::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 = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let db = env .database_options() .types::() .key_comparator::() .create(&mut wtxn)?; wtxn.commit()?; let mut wtxn = env.write_txn()?; // We fill our database with entries. db.put(&mut wtxn, "-100000", &())?; db.put(&mut wtxn, "-10000", &())?; db.put(&mut wtxn, "-1000", &())?; db.put(&mut wtxn, "-100", &())?; db.put(&mut wtxn, "100", &())?; // We check that the key are in the right order ("-100" < "-1000" < "-10000"...) let mut iter = db.iter(&wtxn)?; assert_eq!(iter.next().transpose()?, Some(("-100000", ()))); assert_eq!(iter.next().transpose()?, Some(("-10000", ()))); assert_eq!(iter.next().transpose()?, Some(("-1000", ()))); assert_eq!(iter.next().transpose()?, Some(("-100", ()))); assert_eq!(iter.next().transpose()?, Some(("100", ()))); drop(iter); Ok(()) } heed-0.22.0/examples/custom-dupsort-comparator.rs000064400000000000000000000033341046102023000201560ustar 00000000000000use std::cmp::Ordering; use std::error::Error; use std::fs; use std::path::Path; use byteorder::BigEndian; use heed::{DatabaseFlags, EnvOpenOptions}; use heed_traits::Comparator; use heed_types::{Str, U128}; enum DescendingIntCmp {} impl Comparator for DescendingIntCmp { fn compare(a: &[u8], b: &[u8]) -> Ordering { a.cmp(b).reverse() } } fn main() -> Result<(), Box> { let env_path = Path::new("target").join("custom-dupsort-cmp.mdb"); let _ = fs::remove_dir_all(&env_path); fs::create_dir_all(&env_path)?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(env_path)? }; let mut wtxn = env.write_txn()?; let db = env .database_options() .types::>() .flags(DatabaseFlags::DUP_SORT) .dup_sort_comparator::() .create(&mut wtxn)?; wtxn.commit()?; let mut wtxn = env.write_txn()?; // We fill our database with entries. db.put(&mut wtxn, "1", &1)?; db.put(&mut wtxn, "1", &2)?; db.put(&mut wtxn, "1", &3)?; db.put(&mut wtxn, "2", &4)?; db.put(&mut wtxn, "1", &5)?; db.put(&mut wtxn, "0", &0)?; // We check that the keys are in lexicographic and values in descending order. let mut iter = db.iter(&wtxn)?; assert_eq!(iter.next().transpose()?, Some(("0", 0))); assert_eq!(iter.next().transpose()?, Some(("1", 5))); assert_eq!(iter.next().transpose()?, Some(("1", 3))); assert_eq!(iter.next().transpose()?, Some(("1", 2))); assert_eq!(iter.next().transpose()?, Some(("1", 1))); assert_eq!(iter.next().transpose()?, Some(("2", 4))); drop(iter); Ok(()) } heed-0.22.0/examples/multi-env.rs000064400000000000000000000023171046102023000147210ustar 00000000000000use std::error::Error; use byteorder::BE; use heed::types::*; use heed::{Database, EnvOpenOptions}; type BEU32 = U32; fn main() -> Result<(), Box> { let env1_path = tempfile::tempdir()?; let env2_path = tempfile::tempdir()?; let env1 = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(env1_path)? }; let env2 = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(env2_path)? }; let mut wtxn1 = env1.write_txn()?; let mut wtxn2 = env2.write_txn()?; let db1: Database = env1.create_database(&mut wtxn1, Some("hello"))?; let db2: Database = env2.create_database(&mut wtxn2, Some("hello"))?; // clear db db1.clear(&mut wtxn1)?; wtxn1.commit()?; // clear db db2.clear(&mut wtxn2)?; wtxn2.commit()?; // ----- let mut wtxn1 = env1.write_txn()?; db1.put(&mut wtxn1, "what", &[4, 5][..])?; db1.get(&wtxn1, "what")?; wtxn1.commit()?; let rtxn2 = env2.read_txn()?; let ret = db2.last(&rtxn2)?; assert_eq!(ret, None); Ok(()) } heed-0.22.0/examples/nested.rs000064400000000000000000000035521046102023000142650ustar 00000000000000use std::error::Error; use heed::types::*; use heed::{Database, EnvOpenOptions}; fn main() -> Result<(), Box> { let path = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // here the key will be an str and the data will be a slice of u8 let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, None)?; // clear db db.clear(&mut wtxn)?; wtxn.commit()?; // ----- let mut wtxn = env.write_txn()?; let mut nwtxn = env.nested_write_txn(&mut wtxn)?; db.put(&mut nwtxn, "what", &[4, 5][..])?; let ret = db.get(&nwtxn, "what")?; println!("nested(1) \"what\": {:?}", ret); println!("nested(1) abort"); nwtxn.abort(); let ret = db.get(&wtxn, "what")?; println!("parent \"what\": {:?}", ret); // ------ println!(); // also try with multiple levels of nesting let mut nwtxn = env.nested_write_txn(&mut wtxn)?; let mut nnwtxn = env.nested_write_txn(&mut nwtxn)?; db.put(&mut nnwtxn, "humm...", &[6, 7][..])?; let ret = db.get(&nnwtxn, "humm...")?; println!("nested(2) \"humm...\": {:?}", ret); println!("nested(2) commit"); nnwtxn.commit()?; nwtxn.commit()?; let ret = db.get(&wtxn, "humm...")?; println!("parent \"humm...\": {:?}", ret); db.put(&mut wtxn, "hello", &[2, 3][..])?; let ret = db.get(&wtxn, "hello")?; println!("parent \"hello\": {:?}", ret); println!("parent commit"); wtxn.commit()?; // ------ println!(); let rtxn = env.read_txn()?; let ret = db.get(&rtxn, "hello")?; println!("parent (reader) \"hello\": {:?}", ret); let ret = db.get(&rtxn, "humm...")?; println!("parent (reader) \"humm...\": {:?}", ret); Ok(()) } heed-0.22.0/examples/prev-snapshot.rs000064400000000000000000000050311046102023000156060ustar 00000000000000use std::error::Error; use heed::types::*; use heed::{Database, EnvFlags, EnvOpenOptions}; // In this test we are checking that we can move to a previous environement snapshot. fn main() -> Result<(), Box> { let env_path = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(&env_path)? }; let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, None)?; // 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()?; env.prepare_for_closing().wait(); // We can get the env state from before the last commit // and therefore see an empty env. let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .flags(EnvFlags::PREV_SNAPSHOT) .open(&env_path)? }; let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, None)?; assert!(db.is_empty(&wtxn)?); wtxn.abort(); env.prepare_for_closing().wait(); // However, if we don't commit we can still get // back the latest version of the env. let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .open(&env_path)? }; let mut wtxn = env.write_txn()?; let db: Database = env.create_database(&mut wtxn, None)?; assert_eq!(db.get(&wtxn, "I am here")?, Some("to test things")); assert_eq!(db.get(&wtxn, "I am here too")?, Some("for the same purpose")); // And write new stuff in the env. db.put(&mut wtxn, "I will fade away", "I am so sad")?; wtxn.commit()?; env.prepare_for_closing().wait(); // Once again we can get back the previous version // of the env and see some entries disappear. let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3) .flags(EnvFlags::PREV_SNAPSHOT) .open(&env_path)? }; let rtxn = env.read_txn()?; let db: Database = env.open_database(&rtxn, None)?.unwrap(); assert_eq!(db.get(&rtxn, "I am here")?, Some("to test things")); assert_eq!(db.get(&rtxn, "I am here too")?, Some("for the same purpose")); assert_eq!(db.get(&rtxn, "I will fade away")?, None); Ok(()) } heed-0.22.0/examples/rmp-serde.rs000064400000000000000000000016451046102023000147020ustar 00000000000000use std::error::Error; use heed::types::{SerdeRmp, Str}; use heed::{Database, EnvOpenOptions}; use serde::{Deserialize, Serialize}; fn main() -> Result<(), Box> { let path = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(path)? }; // you can specify that a database will support some typed key/data // serde types are also supported!!! #[derive(Debug, Serialize, Deserialize)] struct Hello<'a> { string: &'a str, } let mut wtxn = env.write_txn()?; let db: Database> = env.create_database(&mut wtxn, Some("serde-rmp"))?; let hello = Hello { string: "hi" }; db.put(&mut wtxn, "hello", &hello)?; let ret: Option = db.get(&wtxn, "hello")?; println!("serde-rmp:\t{:?}", ret); wtxn.commit()?; Ok(()) } heed-0.22.0/src/cookbook.rs000064400000000000000000000506661046102023000135720ustar 00000000000000//! A cookbook of examples on how to use heed. Here is the list of the different topics you can learn about: //! //! - [Decode Values on Demand](#decode-values-on-demand) //! - [Listing and Opening the Named Databases](#listing-and-opening-the-named-databases) //! - [Create Custom and Prefix Codecs](#create-custom-and-prefix-codecs) //! - [Change the Environment Size Dynamically](#change-the-environment-size-dynamically) //! - [Advanced Multithreaded Access of Entries](#advanced-multithreaded-access-of-entries) //! - [Use Custom Key Comparator](#use-custom-key-comparator) //! - [Use Custom Dupsort Comparator](#use-custom-dupsort-comparator) //! //! # Decode Values on Demand //! //! Sometimes, you need to iterate on the content of a database and //! conditionnaly decode the value depending on the key. You can use the //! [`Database::lazily_decode_data`] method to indicate this to heed. //! //! ``` //! use std::collections::HashMap; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! pub type StringMap = HashMap; //! //! fn main() -> Result<(), Box> { //! let path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(1024 * 1024 * 100) // 100 MiB //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database> = env.create_database(&mut wtxn, None)?; //! //! fill_with_data(&mut wtxn, db)?; //! //! // We make sure that iterating over this database will //! // not deserialize the values. We just want to decode //! // the value corresponding to 43th key. //! for (i, result) in db.lazily_decode_data().iter(&wtxn)?.enumerate() { //! let (_key, lazy_value) = result?; //! if i == 43 { //! // This is where the magic happens. We receive a Lazy type //! // that wraps a slice of bytes. We can decode on purpose. //! let value = lazy_value.decode()?; //! assert_eq!(value.get("secret"), Some(&String::from("434343"))); //! break; //! } //! } //! //! Ok(()) //! } //! //! fn fill_with_data( //! wtxn: &mut heed::RwTxn, //! db: Database>, //! ) -> heed::Result<()> { //! // This represents a very big value that we only want to decode when necessary. //! let mut big_string_map = HashMap::new(); //! big_string_map.insert("key1".into(), "I am a very long string".into()); //! big_string_map.insert("key2".into(), "I am a also very long string".into()); //! //! for i in 0..100 { //! let key = format!("{i:5}"); //! big_string_map.insert("secret".into(), format!("{i}{i}{i}")); //! db.put(wtxn, &key, &big_string_map)?; //! } //! Ok(()) //! } //! ``` //! //! # Listing and Opening the Named Databases //! //! Sometimes it is useful to list the databases available in an environment. //! LMDB automatically stores their names in the unnamed database, a database that doesn't //! need to be created in which you can write. //! //! Once you create new databases, after defining the [`EnvOpenOptions::max_dbs`] //! parameter, the names of those databases are automatically stored in the unnamed one. //! //! ``` //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! fn main() -> Result<(), Box> { //! let env_path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3) // Number of opened databases //! .open(env_path)? //! }; //! //! let rtxn = env.read_txn()?; //! // The database names are mixed with the user entries therefore we prefer //! // ignoring the values and try to open the databases one by one using the keys. //! let unnamed: Database = //! env.open_database(&rtxn, None)?.expect("the unnamed database always exists"); //! //! // The unnamed (or main) database contains the other //! // database names associated to empty values. //! for result in unnamed.iter(&rtxn)? { //! let (name, ()) = result?; //! //! if let Ok(Some(_db)) = env.open_database::(&rtxn, Some(name)) { //! // We succeeded into opening a new database that //! // contains strings associated to raw bytes. //! } //! } //! //! // When opening databases in a read-only transaction //! // you must commit your read transaction to make your //! // freshly opened databases globally available. //! rtxn.commit()?; //! //! // If you abort (or drop) your read-only transaction //! // the database handle will be invalid outside //! // the transaction scope. //! //! Ok(()) //! } //! ``` //! //! # Create Custom and Prefix Codecs //! //! With heed you can store any kind of data and serialize it the way you want. //! To do so you'll need to create a codec by using the [`BytesEncode`] and [`BytesDecode`] traits. //! //! Now imagine that your data is lexicographically well ordered. You can now leverage //! the use of prefix codecs. Those are classic codecs but are only used to encode key prefixes. //! //! In this example we will store logs associated to a timestamp. By encoding the timestamp //! in big endian we can create a prefix codec that restricts a subset of the data. It is recommended //! to create codecs to encode prefixes when possible instead of using a slice of bytes. //! //! ``` //! use std::borrow::Cow; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{BoxedError, BytesDecode, BytesEncode, Database, EnvOpenOptions}; //! //! #[derive(Debug, PartialEq, Eq)] //! pub enum Level { //! Debug, //! Warn, //! Error, //! } //! //! #[derive(Debug, PartialEq, Eq)] //! pub struct LogKey { //! timestamp: u32, //! level: Level, //! } //! //! pub struct LogKeyCodec; //! //! impl<'a> BytesEncode<'a> for LogKeyCodec { //! type EItem = LogKey; //! //! /// Encodes the u32 timestamp in big endian followed by the log level with a single byte. //! fn bytes_encode(log: &Self::EItem) -> Result, BoxedError> { //! let (timestamp_bytes, level_byte) = match log { //! LogKey { timestamp, level: Level::Debug } => (timestamp.to_be_bytes(), 0), //! LogKey { timestamp, level: Level::Warn } => (timestamp.to_be_bytes(), 1), //! LogKey { timestamp, level: Level::Error } => (timestamp.to_be_bytes(), 2), //! }; //! //! let mut output = Vec::new(); //! output.extend_from_slice(×tamp_bytes); //! output.push(level_byte); //! Ok(Cow::Owned(output)) //! } //! } //! //! impl<'a> BytesDecode<'a> for LogKeyCodec { //! type DItem = LogKey; //! //! fn bytes_decode(bytes: &'a [u8]) -> Result { //! use std::mem::size_of; //! //! let timestamp = match bytes.get(..size_of::()) { //! Some(bytes) => bytes.try_into().map(u32::from_be_bytes).unwrap(), //! None => return Err("invalid log key: cannot extract timestamp".into()), //! }; //! //! let level = match bytes.get(size_of::()) { //! Some(&0) => Level::Debug, //! Some(&1) => Level::Warn, //! Some(&2) => Level::Error, //! Some(_) => return Err("invalid log key: invalid log level".into()), //! None => return Err("invalid log key: cannot extract log level".into()), //! }; //! //! Ok(LogKey { timestamp, level }) //! } //! } //! //! /// Encodes the high part of a timestamp. As it is located //! /// at the start of the key it can be used to only return //! /// the logs that appeared during a, rather long, period. //! pub struct LogAtHalfTimestampCodec; //! //! impl<'a> BytesEncode<'a> for LogAtHalfTimestampCodec { //! type EItem = u32; //! //! /// This method encodes only the prefix of the keys in this particular case, the timestamp. //! fn bytes_encode(half_timestamp: &Self::EItem) -> Result, BoxedError> { //! Ok(Cow::Owned(half_timestamp.to_be_bytes()[..2].to_vec())) //! } //! } //! //! impl<'a> BytesDecode<'a> for LogAtHalfTimestampCodec { //! type DItem = LogKey; //! //! fn bytes_decode(bytes: &'a [u8]) -> Result { //! LogKeyCodec::bytes_decode(bytes) //! } //! } //! //! fn main() -> Result<(), Box> { //! let path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3000) //! .open(path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1608326232, level: Level::Debug }, //! "this is a very old log", //! )?; //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1708326232, level: Level::Debug }, //! "fibonacci was executed in 21ms", //! )?; //! db.put(&mut wtxn, &LogKey { timestamp: 1708326242, level: Level::Error }, "fibonacci crashed")?; //! db.put( //! &mut wtxn, //! &LogKey { timestamp: 1708326272, level: Level::Warn }, //! "fibonacci is running since 12s", //! )?; //! //! // We change the way we want to read our database by changing the key codec. //! // In this example we can prefix search only for the logs between a period of time //! // (the two high bytes of the u32 timestamp). //! let iter = db.remap_key_type::().prefix_iter(&wtxn, &1708326232)?; //! //! // As we filtered the log for a specific //! // period of time we must not see the very old log. //! for result in iter { //! let (LogKey { timestamp: _, level: _ }, content) = result?; //! assert_ne!(content, "this is a very old log"); //! } //! //! Ok(()) //! } //! ``` //! //! # Change the Environment Size Dynamically //! //! You must specify the maximum size of an LMDB environment when you open it. //! Environment do not dynamically increase there size for performance reasons and also to //! have more control on it. //! //! Here is a simple example on the way to go to dynamically increase the size //! of an environment when you detect that it is going out of space. //! //! ``` //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions}; //! //! fn main() -> Result<(), Box> { //! let path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(16384) // one page //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! // Ho! Crap! We don't have enough space in this environment... //! assert!(matches!( //! fill_with_data(&mut wtxn, db), //! Err(heed::Error::Mdb(heed::MdbError::MapFull)) //! )); //! //! drop(wtxn); //! //! // We need to increase the page size and we can only do that //! // when no transaction are running so closing the env is easier. //! env.prepare_for_closing().wait(); //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 16384) // 10 pages //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! // We now have enough space in the env to store all of our entries. //! assert!(matches!(fill_with_data(&mut wtxn, db), Ok(()))); //! //! Ok(()) //! } //! //! fn fill_with_data(wtxn: &mut heed::RwTxn, db: Database) -> heed::Result<()> { //! for i in 0..1000 { //! let key = i.to_string(); //! db.put(wtxn, &key, "I am a very long string")?; //! } //! Ok(()) //! } //! ``` //! //! # Advanced Multithreaded Access of Entries //! //! LMDB disallows sharing cursors among threads. It is only possible to send //! them between threads when the environment has been opened with //! [`EnvOpenOptions::read_txn_without_tls`] method. //! //! Please note that this should not be utilized with an encrypted heed3 database. These //! types of databases employ an internal cycling buffer for decrypting entries, which //! may result in reading keys that invalidate previous ones. In essence, the use of //! the `EncryptedDatabase` signature prevents this scenario. //! //! This limits some usecases that require a parallel access to the content of the databases //! to process stuff faster. This is the case of arroy, a multithreads fast approximate //! neighbors search library. I wrote [an article explaining how //! to read entries in parallel][arroy article]. //! //! It is forbidden to write in an environement while reading in it. However, it is possible //! to keep pointers to the values of the entries returned by LMDB. Those pointers are valid //! until the end of the transaction. //! //! Here is a small example on how to declare a datastructure to be used in parallel across thread, //! safely. The unsafe part declare that the datastructure can be shared between thread despite //! the write transaction not being `Send` nor `Sync`. //! //! [arroy article]: https://blog.kerollmops.com/multithreading-and-memory-mapping-refining-ann-performance-with-arroy //! //! ``` //! use std::collections::HashMap; //! use std::error::Error; //! use std::fs; //! use std::path::Path; //! //! use heed::types::*; //! use heed::{Database, EnvOpenOptions, RoTxn}; //! //! fn main() -> Result<(), Box> { //! let path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(1024 * 1024 * 100) // 100 MiB //! .open(&path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db: Database = env.create_database(&mut wtxn, None)?; //! //! fill_with_data(&mut wtxn, db)?; //! //! let immutable_map = ImmutableMap::from_db(&wtxn, db)?; //! //! // We can share the immutable map over multiple threads because it is Sync. //! // It is safe because we keep the write transaction lifetime in this type. //! std::thread::scope(|s| { //! s.spawn(|| { //! let value = immutable_map.get("10"); //! assert_eq!(value, Some("I am a very long string")); //! }); //! s.spawn(|| { //! let value = immutable_map.get("20"); //! assert_eq!(value, Some("I am a very long string")); //! }); //! }); //! //! // You can see that we always have it on the main thread. //! // We didn't sent it over threads. //! let value = immutable_map.get("50"); //! assert_eq!(value, Some("I am a very long string")); //! //! Ok(()) //! } //! //! fn fill_with_data(wtxn: &mut heed::RwTxn, db: Database) -> heed::Result<()> { //! for i in 0..100 { //! let key = i.to_string(); //! db.put(wtxn, &key, "I am a very long string")?; //! } //! Ok(()) //! } //! //! struct ImmutableMap<'a> { //! map: HashMap<&'a str, &'a str>, //! } //! //! impl<'t> ImmutableMap<'t> { //! fn from_db(rtxn: &'t RoTxn, db: Database) -> heed::Result { //! let mut map = HashMap::new(); //! for result in db.iter(rtxn)? { //! let (k, v) = result?; //! map.insert(k, v); //! } //! Ok(ImmutableMap { map }) //! } //! //! fn get(&self, key: &str) -> Option<&'t str> { //! self.map.get(key).copied() //! } //! } //! //! unsafe impl Sync for ImmutableMap<'_> {} //! ``` //! //! # Use Custom Key Comparator //! //! LMDB keys are sorted in lexicographic order by default. To change this behavior //! you can implement a custom [`Comparator`] and provide it when creating the database. //! //! Under the hood this translates into a [`mdb_set_compare`] call. //! //! ``` //! use std::cmp::Ordering; //! use std::error::Error; //! use std::str; //! //! use heed::EnvOpenOptions; //! use heed_traits::Comparator; //! use heed_types::{Str, Unit}; //! //! enum StringAsIntCmp {} //! //! // This function takes two strings which represent integers, //! // 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 path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3) //! .open(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(()) //! } //! ``` //! //! # Use Custom Dupsort Comparator //! //! When using DUPSORT LMDB sorts values of the same key in lexicographic order by default. //! To change this behavior you can implement a custom [`Comparator`] and provide it when //! creating the database. //! //! Under the hood this translates into a [`mdb_set_dupsort`] call. //! //! ``` //! use std::cmp::Ordering; //! use std::error::Error; //! //! use byteorder::BigEndian; //! use heed::{DatabaseFlags, EnvOpenOptions}; //! use heed_traits::Comparator; //! use heed_types::{Str, U128}; //! //! enum DescendingIntCmp {} //! //! impl Comparator for DescendingIntCmp { //! fn compare(a: &[u8], b: &[u8]) -> Ordering { //! a.cmp(&b).reverse() //! } //! } //! //! fn main() -> Result<(), Box> { //! let path = tempfile::tempdir()?; //! //! let env = unsafe { //! EnvOpenOptions::new() //! .map_size(10 * 1024 * 1024) // 10MB //! .max_dbs(3) //! .open(path)? //! }; //! //! let mut wtxn = env.write_txn()?; //! let db = env //! .database_options() //! .types::>() //! .flags(DatabaseFlags::DUP_SORT) //! .dup_sort_comparator::() //! .create(&mut wtxn)?; //! wtxn.commit()?; //! //! let mut wtxn = env.write_txn()?; //! //! // We fill our database with entries. //! db.put(&mut wtxn, "1", &1)?; //! db.put(&mut wtxn, "1", &2)?; //! db.put(&mut wtxn, "1", &3)?; //! db.put(&mut wtxn, "2", &4)?; //! db.put(&mut wtxn, "1", &5)?; //! db.put(&mut wtxn, "0", &0)?; //! //! // We check that the keys are in lexicographic and values in descending order. //! let mut iter = db.iter(&wtxn)?; //! assert_eq!(iter.next().transpose()?, Some(("0", 0))); //! assert_eq!(iter.next().transpose()?, Some(("1", 5))); //! assert_eq!(iter.next().transpose()?, Some(("1", 3))); //! assert_eq!(iter.next().transpose()?, Some(("1", 2))); //! assert_eq!(iter.next().transpose()?, Some(("1", 1))); //! assert_eq!(iter.next().transpose()?, Some(("2", 4))); //! drop(iter); //! //! Ok(()) //! } //! ``` //! // To let cargo generate doc links #![allow(unused_imports)] use crate::mdb::ffi::{mdb_set_compare, mdb_set_dupsort}; use crate::{BytesDecode, BytesEncode, Comparator, Database, EnvOpenOptions}; heed-0.22.0/src/cursor.rs000064400000000000000000000345451046102023000132770ustar 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(); let mut txn = txn.txn_ptr(); unsafe { mdb_result(ffi::mdb_cursor_open(txn.as_mut(), dbi, &mut cursor))? } Ok(RoCursor { cursor, _marker: marker::PhantomData }) } pub fn current(&mut self) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), ffi::cursor_op::MDB_GET_CURRENT, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_first(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_FIRST, MoveOperation::Dup => { unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, ptr::null_mut(), &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_FIRST_DUP, ))? }; ffi::cursor_op::MDB_GET_CURRENT } MoveOperation::NoDup => ffi::cursor_op::MDB_FIRST, }; // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_last(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_LAST, MoveOperation::Dup => { unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, ptr::null_mut(), &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_LAST_DUP, ))? }; ffi::cursor_op::MDB_GET_CURRENT } MoveOperation::NoDup => ffi::cursor_op::MDB_LAST, }; // Move the cursor on the first database key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_key(&mut self, key: &[u8]) -> Result { let mut key_val = unsafe { crate::into_val(key) }; // Move the cursor to the specified key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, &mut key_val, &mut ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }, ffi::cursor_op::MDB_SET, )) }; match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } pub fn move_on_key_greater_than_or_equal_to( &mut self, key: &[u8], ) -> Result> { let mut key_val = unsafe { crate::into_val(key) }; let mut data_val = mem::MaybeUninit::uninit(); // Move the cursor to the specified key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, &mut key_val, data_val.as_mut_ptr(), ffi::cursor_op::MDB_SET_RANGE, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_prev(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_PREV, MoveOperation::Dup => ffi::cursor_op::MDB_PREV_DUP, MoveOperation::NoDup => ffi::cursor_op::MDB_PREV_NODUP, }; // Move the cursor to the previous non-dup key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } pub fn move_on_next(&mut self, op: MoveOperation) -> Result> { let mut key_val = mem::MaybeUninit::uninit(); let mut data_val = mem::MaybeUninit::uninit(); let flag = match op { MoveOperation::Any => ffi::cursor_op::MDB_NEXT, MoveOperation::Dup => ffi::cursor_op::MDB_NEXT_DUP, MoveOperation::NoDup => ffi::cursor_op::MDB_NEXT_NODUP, }; // Move the cursor to the next non-dup key let result = unsafe { mdb_result(ffi::mdb_cursor_get( self.cursor, key_val.as_mut_ptr(), data_val.as_mut_ptr(), flag, )) }; match result { Ok(()) => { let key = unsafe { crate::from_val(key_val.assume_init()) }; let data = unsafe { crate::from_val(data_val.assume_init()) }; Ok(Some((key, data))) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } } impl Drop for RoCursor<'_> { fn drop(&mut self) { unsafe { ffi::mdb_cursor_close(self.cursor) } } } pub struct RwCursor<'txn> { cursor: RoCursor<'txn>, } impl<'txn> RwCursor<'txn> { pub(crate) fn new(txn: &'txn RwTxn, dbi: ffi::MDB_dbi) -> Result> { Ok(RwCursor { cursor: RoCursor::new(txn, dbi)? }) } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { // Delete the current entry let result = mdb_result(ffi::mdb_cursor_del(self.cursor.cursor, 0)); match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current(&mut self, key: &[u8], data: &[u8]) -> Result { let mut key_val = crate::into_val(key); let mut data_val = crate::into_val(data); // Modify the pointed data let result = mdb_result(ffi::mdb_cursor_put( self.cursor.cursor, &mut key_val, &mut data_val, ffi::MDB_CURRENT, )); match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`Self::put_current`] method. pub unsafe fn put_current_reserved_with_flags( &mut self, flags: PutFlags, key: &[u8], data_size: usize, write_func: F, ) -> Result where F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let mut key_val = crate::into_val(key); let mut reserved = ffi::reserve_size_val(data_size); let flags = ffi::MDB_RESERVE | flags.bits(); let result = mdb_result(ffi::mdb_cursor_put(self.cursor.cursor, &mut key_val, &mut reserved, flags)); let found = match result { Ok(()) => true, Err(e) if e.not_found() => false, Err(e) => return Err(e.into()), }; let mut reserved = ReservedSpace::from_val(reserved); write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(found) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } /// Append the given key/value pair to the end of the database. /// /// If a key is inserted that is less than any previous key a `KeyExist` error /// is returned and the key is not inserted into the database. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_flags( &mut self, flags: PutFlags, key: &[u8], data: &[u8], ) -> Result<()> { let mut key_val = crate::into_val(key); let mut data_val = crate::into_val(data); // Modify the pointed data let result = mdb_result(ffi::mdb_cursor_put( self.cursor.cursor, &mut key_val, &mut data_val, flags.bits(), )); result.map_err(Into::into) } } impl<'txn> Deref for RwCursor<'txn> { type Target = RoCursor<'txn>; fn deref(&self) -> &Self::Target { &self.cursor } } impl DerefMut for RwCursor<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cursor } } /// The way the `Iterator::next/prev` method behaves towards DUP data. #[derive(Debug, Clone, Copy)] pub enum MoveOperation { /// Move on the next/prev entry, wether it's the same key or not. Any, /// Move on the next/prev data of the current key. Dup, /// Move on the next/prev entry which is the next/prev key. /// Skip the multiple values of the current key. NoDup, } heed-0.22.0/src/databases/database.rs000064400000000000000000002774051046102023000154610ustar 00000000000000use std::borrow::Cow; use std::ops::{Bound, RangeBounds}; use std::{any, fmt, marker, mem, ptr}; use heed_traits::{Comparator, LexicographicComparator}; use types::{DecodeIgnore, LazyDecode}; use crate::cursor::MoveOperation; use crate::envs::DefaultComparator; use crate::iteration_method::MoveOnCurrentKeyDuplicates; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::mdb::lmdb_flags::{AllDatabaseFlags, DatabaseFlags}; use crate::*; /// Options and flags which can be used to configure how a [`Database`] is opened. /// /// # Examples /// /// Opening a file to read: /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// // Imagine you have an optional name /// let conditional_name = Some("big-endian-iter"); /// /// let mut wtxn = env.write_txn()?; /// let mut options = env.database_options().types::(); /// if let Some(name) = conditional_name { /// options.name(name); /// } /// let db = options.create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` #[derive(Debug)] pub struct DatabaseOpenOptions<'e, 'n, T, KC, DC, C = DefaultComparator, CDUP = DefaultComparator> { env: &'e Env, types: marker::PhantomData<(KC, DC, C, CDUP)>, name: Option<&'n str>, flags: AllDatabaseFlags, } impl<'e, T> DatabaseOpenOptions<'e, 'static, T, 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, T, KC, DC, C, CDUP> DatabaseOpenOptions<'e, 'n, T, KC, DC, C, CDUP> { /// 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, T, 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, T, KC, DC, NC, CDUP> { DatabaseOpenOptions { env: self.env, types: Default::default(), name: self.name, flags: self.flags, } } /// Change the customized dup sort compare function of the database. /// /// By default no customized compare function will be set when opening a database. pub fn dup_sort_comparator(self) -> DatabaseOpenOptions<'e, 'n, T, KC, DC, C, NCDUP> { DatabaseOpenOptions { env: self.env, types: Default::default(), name: self.name, flags: self.flags, } } /// Change the name of the database. /// /// By default the database is unnamed and there only is a single unnamed database. pub fn name(&mut self, name: &'n str) -> &mut Self { self.name = Some(name); self } /// Specify the set of flags used to open the database. pub fn flags(&mut self, flags: DatabaseFlags) -> &mut Self { self.flags = AllDatabaseFlags::from_bits(flags.bits()).unwrap(); self } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open(&self, rtxn: &RoTxn) -> Result>> where KC: 'static, DC: 'static, C: Comparator + 'static, CDUP: Comparator + 'static, { assert_eq_env_txn!(self.env, rtxn); match self.env.raw_init_database::(rtxn.txn_ptr(), self.name, self.flags) { Ok(dbi) => Ok(Some(Database::new(self.env.env_mut_ptr().as_ptr() as _, dbi))), Err(Error::Mdb(e)) if e.not_found() => Ok(None), Err(e) => Err(e), } } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create(&self, wtxn: &mut RwTxn) -> Result> where KC: 'static, DC: 'static, C: Comparator + 'static, CDUP: Comparator + 'static, { assert_eq_env_txn!(self.env, wtxn); let flags = self.flags | AllDatabaseFlags::CREATE; match self.env.raw_init_database::(wtxn.txn_ptr(), self.name, flags) { Ok(dbi) => Ok(Database::new(self.env.env_mut_ptr().as_ptr() as _, dbi)), Err(e) => Err(e), } } } impl Clone for DatabaseOpenOptions<'_, '_, T, KC, DC, C, CDUP> { fn clone(&self) -> Self { *self } } impl Copy for DatabaseOpenOptions<'_, '_, T, KC, DC, C, CDUP> {} /// A typed database that accepts only the types it was created with. /// /// # Example: Iterate over databases entries /// /// In this example we store numbers in big endian this way those are ordered. /// Thanks to their bytes representation, heed is able to iterate over them /// from the lowest to the highest. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over database entries in order /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (35, ()), /// (42, ()), /// (68, ()), /// ]; /// /// assert_eq!(rets, expected); /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// # Example: Iterate over and delete ranges of entries /// /// Discern also support ranges and ranges deletions. /// Same configuration as above, numbers are ordered, therefore it is safe to specify /// a range and be able to iterate over and/or delete it. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over ranges too!!! /// let range = 35..=42; /// let rets: Result<_, _> = db.range(&wtxn, &range)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (35, ()), /// (42, ()), /// ]; /// /// assert_eq!(rets, expected); /// /// // even delete a range of keys /// let range = 35..=42; /// let deleted: usize = db.delete_range(&mut wtxn, &range)?; /// /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (68, ()), /// ]; /// /// assert_eq!(deleted, 2); /// assert_eq!(rets, expected); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub struct Database { pub(crate) env_ident: usize, pub(crate) dbi: ffi::MDB_dbi, marker: marker::PhantomData<(KC, DC, C, CDUP)>, } impl Database { pub(crate) fn new(env_ident: usize, dbi: ffi::MDB_dbi) -> Database { Database { env_ident, dbi, marker: std::marker::PhantomData } } /// Retrieves the value associated with a key. /// /// If the key does not exist, then `None` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32= U32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("get-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// /// let ret = db.get(&wtxn, "i-am-forty-two")?; /// assert_eq!(ret, Some(42)); /// /// let ret = db.get(&wtxn, "i-am-twenty-one")?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get<'a, 'txn>(&self, txn: &'txn RoTxn, key: &'a KC::EItem) -> Result> where KC: BytesEncode<'a>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = mem::MaybeUninit::uninit(); let result = unsafe { mdb_result(ffi::mdb_get( txn.txn_ptr().as_mut(), self.dbi, &mut key_val, data_val.as_mut_ptr(), )) }; match result { Ok(()) => { let data = unsafe { crate::from_val(data_val.assume_init()) }; let data = DC::bytes_decode(data).map_err(Error::Decoding)?; Ok(Some(data)) } Err(e) if e.not_found() => Ok(None), Err(e) => Err(e.into()), } } /// Returns an iterator over all of the values of a single key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_duplicates<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result>> where KC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; if cursor.move_on_key(&key_bytes)? { Ok(Some(RoIter::new(cursor))) } else { Ok(None) } } /// Retrieves the key/value pair lower than the given one in this database. /// /// If the database if empty or there is no key lower than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than(&wtxn, &43)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_lower_than(&wtxn, &27)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; cursor.move_on_key_greater_than_or_equal_to(&key_bytes)?; match cursor.move_on_prev(MoveOperation::NoDup) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the key/value pair lower than or equal to the given one in this database. /// /// If the database if empty or there is no key lower than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &43)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &26)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than_or_equal_to<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let result = match cursor.move_on_key_greater_than_or_equal_to(&key_bytes) { Ok(Some((key, data))) if key == &key_bytes[..] => Ok(Some((key, data))), Ok(_) => cursor.move_on_prev(MoveOperation::NoDup), Err(e) => Err(e), }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the key/value pair greater than the given one in this database. /// /// If the database if empty or there is no key greater than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than(&wtxn, &42)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_greater_than(&wtxn, &43)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let entry = match cursor.move_on_key_greater_than_or_equal_to(&key_bytes)? { Some((key, data)) if key > &key_bytes[..] => Some((key, data)), Some((_key, _data)) => cursor.move_on_next(MoveOperation::NoDup)?, None => None, }; match entry { Some((key, data)) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, None => Ok(None), } } /// Retrieves the key/value pair greater than or equal to the given one in this database. /// /// If the database if empty or there is no key greater than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &42)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &44)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than_or_equal_to<'a, 'txn>( &self, txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; match cursor.move_on_key_greater_than_or_equal_to(&key_bytes) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the first key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("first-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.first(&wtxn)?; /// assert_eq!(ret, Some((27, "i-am-twenty-seven"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn first<'txn>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; match cursor.move_on_first(MoveOperation::Any) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Retrieves the last key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("last-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.last(&wtxn)?; /// assert_eq!(ret, Some((42, "i-am-forty-two"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn last<'txn>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { assert_eq_env_db_txn!(self, txn); let mut cursor = RoCursor::new(txn, self.dbi)?; match cursor.move_on_last(MoveOperation::Any) { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Ok(Some((key, data))), (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)), }, Ok(None) => Ok(None), Err(e) => Err(e), } } /// Returns the number of elements in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 4); /// /// db.delete(&mut wtxn, &27)?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 3); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn len(&self, txn: &RoTxn) -> Result { self.stat(txn).map(|stat| stat.entries as u64) } /// Returns `true` if and only if this database is empty. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, false); /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, true); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn is_empty(&self, txn: &RoTxn) -> Result { self.len(txn).map(|l| l == 0) } /// Returns some statistics for this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let stat = db.stat(&wtxn)?; /// assert_eq!(stat.depth, 1); /// assert_eq!(stat.branch_pages, 0); /// assert_eq!(stat.leaf_pages, 1); /// assert_eq!(stat.overflow_pages, 0); /// assert_eq!(stat.entries, 4); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn stat(&self, txn: &RoTxn) -> Result { assert_eq_env_db_txn!(self, txn); let mut db_stat = mem::MaybeUninit::uninit(); let result = unsafe { mdb_result(ffi::mdb_stat(txn.txn_ptr().as_mut(), self.dbi, db_stat.as_mut_ptr())) }; match result { Ok(()) => { let stats = unsafe { db_stat.assume_init() }; Ok(DatabaseStat { page_size: stats.ms_psize, depth: stats.ms_depth, branch_pages: stats.ms_branch_pages, leaf_pages: stats.ms_leaf_pages, overflow_pages: stats.ms_overflow_pages, entries: stats.ms_entries, }) } Err(e) => Err(e.into()), } } /// Return an ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoIter::new(cursor)) } /// Return a mutable ordered iterator of all key-value pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, Some("i-am-the-new-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwIter::new(cursor)) } /// Return a reverse ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoRevIter::new(cursor)) } /// Return a mutable reverse ordered iterator of all key-value\ /// pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.put_current(&13, "i-am-the-new-thirteen")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, Some("i-am-the-new-thirteen")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwRevIter::new(cursor)) } /// Return an ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut iter = db.range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// It can be complex to work with ranges of slices and using /// the `..` or `..=` range syntax is not the best way to deal /// with that. We highly recommend using the [`Bound`](std::ops::Bound) enum for that. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// use std::ops::Bound; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(30) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, None)?; /// /// // make sure to create slices and not ref array /// // by using the [..] syntax. /// let start = &[0, 0, 0][..]; /// let end = &[9, 0, 0][..]; /// /// // equivalent to start..end /// let range = (Bound::Included(start), Bound::Excluded(end)); /// /// let iter = db.range(&mut wtxn, &range)?; /// /// 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 ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-new-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RwCursor::new(txn, self.dbi).map(|cursor| RwRange::new(cursor, start_bound, end_bound)) } /// Return a reverse ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=43; /// let mut iter = db.rev_range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range<'a, 'txn, R>( &self, txn: &'txn RoTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RoCursor::new(txn, self.dbi).map(|cursor| RoRevRange::new(cursor, start_bound, end_bound)) } /// Return a mutable reverse ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.rev_range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.put_current(&27, "i-am-the-new-twenty-seven")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-the-new-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let start_bound = match range.start_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; let end_bound = match range.end_bound() { Bound::Included(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Included(bytes.into_owned()) } Bound::Excluded(bound) => { let bytes = KC::bytes_encode(bound).map_err(Error::Encoding)?; Bound::Excluded(bytes.into_owned()) } Bound::Unbounded => Bound::Unbounded, }; RwCursor::new(txn, self.dbi).map(|cursor| RwRevRange::new(cursor, start_bound, end_bound)) } /// Return a lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter<'a, 'txn>( &self, txn: &'txn RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RoCursor::new(txn, self.dbi).map(|cursor| RoPrefix::new(cursor, prefix_bytes)) } /// Return a mutable lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.put_current("i-am-twenty-seven", &27000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, Some(27000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter_mut<'a, 'txn>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RwCursor::new(txn, self.dbi).map(|cursor| RwPrefix::new(cursor, prefix_bytes)) } /// Return a reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter<'a, 'txn>( &self, txn: &'txn RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RoCursor::new(txn, self.dbi).map(|cursor| RoRevPrefix::new(cursor, prefix_bytes)) } /// Return a mutable reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.put_current("i-am-twenty-eight", &28000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, Some(28000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter_mut<'a, 'txn>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { assert_eq_env_db_txn!(self, txn); let prefix_bytes = KC::bytes_encode(prefix).map_err(Error::Encoding)?; let prefix_bytes = prefix_bytes.into_owned(); RwCursor::new(txn, self.dbi).map(|cursor| RwRevPrefix::new(cursor, prefix_bytes)) } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with no specific flag. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, Some("i-am-twenty-seven")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = 0; unsafe { mdb_result(ffi::mdb_put( txn.txn_ptr().as_mut(), self.dbi, &mut key_val, &mut data_val, flags, ))? } Ok(()) } /// Insert a key-value pair where the value can directly be written to disk, replacing any /// previous value. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let value = "I am a long long long value"; /// db.put_reserved(&mut wtxn, &42, value.len(), |reserved| { /// reserved.write_all(value.as_bytes()) /// })?; /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(value)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_reserved<'a, F>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result<()> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut reserved = ffi::reserve_size_val(data_size); let flags = ffi::MDB_RESERVE; unsafe { mdb_result(ffi::mdb_put( txn.txn_ptr().as_mut(), self.dbi, &mut key_val, &mut reserved, flags, ))? } let mut reserved = unsafe { ReservedSpace::from_val(reserved) }; write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(()) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with the specified flags. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags, DatabaseFlags, Error, MdbError}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .name("dup-i32") /// .flags(DatabaseFlags::DUP_SORT) /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &42, "i-am-so-cool")?; /// db.put(&mut wtxn, &42, "i-am-the-king")?; /// db.put(&mut wtxn, &42, "i-am-fun")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND, &54, "i-am-older-than-you")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP, &54, "ok-but-i-am-better-than-you")?; /// // You can compose flags by OR'ing them /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP | PutFlags::NO_OVERWRITE, &55, "welcome")?; /// /// // The NO_DUP_DATA flag will return KeyExist if we try to insert the exact same key/value pair. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_DUP_DATA, &54, "ok-but-i-am-better-than-you"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// // The NO_OVERWRITE flag will return KeyExist if we try to insert something with an already existing key. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_OVERWRITE, &54, "there-can-be-only-one-data"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-fun"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-so-cool"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-king"))); /// assert_eq!(iter.next().transpose()?, Some((54, "i-am-older-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((54, "ok-but-i-am-better-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((55, "welcome"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_with_flags<'a>( &self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = flags.bits(); unsafe { mdb_result(ffi::mdb_put( txn.txn_ptr().as_mut(), self.dbi, &mut key_val, &mut data_val, flags, ))? } Ok(()) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) flag. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put(&mut wtxn, &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put(&mut wtxn, &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put<'a, 'txn>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { self.get_or_put_with_flags(txn, PutFlags::empty(), key, data) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) which is always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_with_flags<'a, 'txn>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let flags = (flags | PutFlags::NO_OVERWRITE).bits(); let result = unsafe { mdb_result(ffi::mdb_put( txn.txn_ptr().as_mut(), self.dbi, &mut key_val, &mut data_val, flags, )) }; match result { // the value was successfully inserted Ok(()) => Ok(None), // the key already exists: the previous value is stored in the data parameter Err(MdbError::KeyExist) => { let bytes = unsafe { crate::from_val(data_val) }; let data = DC::bytes_decode(bytes).map_err(Error::Decoding)?; Ok(Some(data)) } Err(error) => Err(error.into()), } } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and /// [`MDB_RESERVE`](ffi::MDB_RESERVE) flags. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { self.get_or_put_reserved_with_flags(txn, PutFlags::empty(), key, data_size, write_func) } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and [`MDB_RESERVE`](ffi::MDB_RESERVE) /// which are always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved_with_flags<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut reserved = ffi::reserve_size_val(data_size); let flags = (flags | PutFlags::NO_OVERWRITE).bits() | ffi::MDB_RESERVE; let result = unsafe { mdb_result(ffi::mdb_put( txn.txn.txn_ptr().as_mut(), self.dbi, &mut key_val, &mut reserved, flags, )) }; match result { // value was inserted: fill the reserved space Ok(()) => { let mut reserved = unsafe { ReservedSpace::from_val(reserved) }; write_func(&mut reserved)?; if reserved.remaining() == 0 { Ok(None) } else { Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) } } // the key already exists: the previous value is stored in the data parameter Err(MdbError::KeyExist) => { let bytes = unsafe { crate::from_val(reserved) }; let data = DC::bytes_decode(bytes).map_err(Error::Decoding)?; Ok(Some(data)) } Err(error) => Err(error.into()), } } /// Deletes an entry or every duplicate data items of a key /// if the database supports duplicate data items. /// /// If the entry does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.delete(&mut wtxn, &27)?; /// assert_eq!(ret, true); /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, None); /// /// let ret = db.delete(&mut wtxn, &467)?; /// assert_eq!(ret, false); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem) -> Result where KC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let result = unsafe { mdb_result(ffi::mdb_del( txn.txn.txn_ptr().as_mut(), self.dbi, &mut key_val, ptr::null_mut(), )) }; match result { Ok(()) => Ok(true), Err(e) if e.not_found() => Ok(false), Err(e) => Err(e.into()), } } /// Deletes a single key-value pair in this database. /// /// If the database doesn't support duplicate data items the data is ignored. /// If the key does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_one_duplicate<'a>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { assert_eq_env_db_txn!(self, txn); let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; let mut key_val = unsafe { crate::into_val(&key_bytes) }; let mut data_val = unsafe { crate::into_val(&data_bytes) }; let result = unsafe { mdb_result(ffi::mdb_del( txn.txn.txn_ptr().as_mut(), 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 comparator `C`. /// /// [`clear`]: crate::Database::clear /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let ret = db.delete_range(&mut wtxn, &range)?; /// assert_eq!(ret, 2); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_range<'a, 'txn, R>(&self, txn: &'txn mut RwTxn, range: &'a R) -> Result where KC: BytesEncode<'a> + BytesDecode<'txn>, C: Comparator, R: RangeBounds, { assert_eq_env_db_txn!(self, txn); let mut count = 0; let mut iter = self.remap_data_type::().range_mut(txn, range)?; while iter.next().is_some() { // safety: We do not keep any reference from the database while using `del_current`. // The user can't keep any reference inside of the database as we ask for a // mutable reference to the `txn`. unsafe { iter.del_current()? }; count += 1; } Ok(count) } /// Deletes all key/value pairs in this database. /// /// Prefer using this method instead of a call to [`delete_range`] with a full range ([`..`]). /// /// [`delete_range`]: crate::Database::delete_range /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert!(ret); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn clear(&self, txn: &mut RwTxn) -> Result<()> { assert_eq_env_db_txn!(self, txn); unsafe { mdb_result(ffi::mdb_drop(txn.txn.txn_ptr().as_mut(), self.dbi, 0)).map_err(Into::into) } } /// Change the codec types of this database, specifying the codecs. /// /// # Safety /// /// It is up to you to ensure that the data read and written using the polymorphic /// handle correspond to the the typed, uniform one. If an invalid write is made, /// it can corrupt the database from the eyes of heed. /// /// # Example /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// // We remap the types for ease of use. /// let db = db.remap_types::(); /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn remap_types(&self) -> Database { Database::new(self.env_ident, self.dbi) } /// Change the key codec type of this database, specifying the new codec. pub fn remap_key_type(&self) -> Database { self.remap_types::() } /// Change the data codec type of this database, specifying the new codec. pub fn remap_data_type(&self) -> Database { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(&self) -> Database, C> { self.remap_types::>() } } impl Clone for Database { fn clone(&self) -> Database { *self } } impl Copy for Database {} impl fmt::Debug for Database { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Database") .field("key_codec", &any::type_name::()) .field("data_codec", &any::type_name::()) .field("key_comparator", &any::type_name::()) .field("dup_sort_comparator", &any::type_name::()) .finish() } } #[cfg(test)] mod tests { use byteorder::*; use heed_types::*; use super::*; use crate::IntegerComparator; #[test] fn put_overwrite() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env.create_database::(&mut txn, None)?; assert_eq!(db.get(&txn, b"hello").unwrap(), None); db.put(&mut txn, b"hello", b"hi").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"hi"[..])); db.put(&mut txn, b"hello", b"bye").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"bye"[..])); Ok(()) } #[test] #[cfg(feature = "longer-keys")] fn longer_keys() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = envs.write_txn()?; let db = envs.create_database::(&mut txn, None)?; // Try storing a key larger than 511 bytes (the default if MDB_MAXKEYSIZE is not set) let long_key = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut pharetra sit amet aliquam. Sit amet nisl purus in mollis nunc. Eget egestas purus viverra accumsan in nisl nisi scelerisque. Duis ultricies lacus sed turpis tincidunt. Sem nulla pharetra diam sit. Leo vel orci porta non pulvinar. Erat pellentesque adipiscing commodo elit at imperdiet dui. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Diam donec adipiscing tristique risus nec feugiat. In fermentum et sollicitudin ac orci. Ut sem nulla pharetra diam sit amet. Aliquam purus sit amet luctus venenatis lectus. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan. Urna duis convallis convallis tellus id interdum velit laoreet id. Ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Tincidunt arcu non sodales neque. Habitant morbi tristique senectus et netus et malesuada fames."; assert_eq!(db.get(&txn, long_key).unwrap(), None); db.put(&mut txn, long_key, b"hi").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"hi"[..])); db.put(&mut txn, long_key, b"bye").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"bye"[..])); Ok(()) } #[test] fn integer_keys() -> Result<()> { type NEU32 = U32; let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env .database_options() .types::() .key_comparator::() .create(&mut txn)?; let range = 1000..2000; for i in range.clone() { db.put(&mut txn, &i, &i)?; } let mut i = 0; for (val, expected) in db.range(&txn, &(0..10_000))?.zip(range.clone()) { assert_eq!(val?.0, expected); i += 1; } assert_eq!(i, range.end - range.start); Ok(()) } } heed-0.22.0/src/databases/encrypted_database.rs000064400000000000000000002334101046102023000175220ustar 00000000000000use std::ops::RangeBounds; use std::{any, fmt}; use heed_traits::{Comparator, LexicographicComparator}; use types::LazyDecode; use crate::envs::DefaultComparator; use crate::iteration_method::MoveOnCurrentKeyDuplicates; #[allow(unused)] // for cargo auto doc links use crate::mdb::ffi; use crate::mdb::lmdb_flags::DatabaseFlags; use crate::*; /// Options and flags which can be used to configure how a [`Database`] is opened. /// /// # Examples /// /// Opening a file to read: /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// // Imagine you have an optional name /// let conditional_name = Some("big-endian-iter"); /// /// let mut wtxn = env.write_txn()?; /// let mut options = env.database_options().types::(); /// if let Some(name) = conditional_name { /// options.name(name); /// } /// let db = options.create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` #[derive(Debug)] pub struct EncryptedDatabaseOpenOptions< 'e, 'n, T, KC, DC, C = DefaultComparator, CDUP = DefaultComparator, > { inner: DatabaseOpenOptions<'e, 'n, T, KC, DC, C, CDUP>, } impl<'e, T> EncryptedDatabaseOpenOptions<'e, 'static, T, Unspecified, Unspecified> { /// Create an options struct to open/create a database with specific flags. pub fn new(env: &'e EncryptedEnv) -> Self { EncryptedDatabaseOpenOptions { inner: DatabaseOpenOptions::new(&env.inner) } } } impl<'e, 'n, T, KC, DC, C, CDUP> EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, C, CDUP> { /// 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) -> EncryptedDatabaseOpenOptions<'e, 'n, T, NKC, NDC> { EncryptedDatabaseOpenOptions { inner: self.inner.types() } } /// 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) -> EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, NC, CDUP> { EncryptedDatabaseOpenOptions { inner: self.inner.key_comparator() } } /// Change the customized dup sort compare function of the database. /// /// By default no customized compare function will be set when opening a database. pub fn dup_sort_comparator( self, ) -> EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, C, NCDUP> { EncryptedDatabaseOpenOptions { inner: self.inner.dup_sort_comparator() } } /// 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.inner.name(name); self } /// Specify the set of flags used to open the database. pub fn flags(&mut self, flags: DatabaseFlags) -> &mut Self { self.inner.flags(flags); self } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open(&self, rtxn: &RoTxn) -> Result>> where KC: 'static, DC: 'static, C: Comparator + 'static, CDUP: Comparator + 'static, { self.inner.open(rtxn).map(|opt| opt.map(EncryptedDatabase::new)) } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create(&self, wtxn: &mut RwTxn) -> Result> where KC: 'static, DC: 'static, C: Comparator + 'static, CDUP: Comparator + 'static, { self.inner.create(wtxn).map(EncryptedDatabase::new) } } impl Clone for EncryptedDatabaseOpenOptions<'_, '_, T, KC, DC, C, CDUP> { fn clone(&self) -> Self { *self } } impl Copy for EncryptedDatabaseOpenOptions<'_, '_, T, KC, DC, C, CDUP> {} /// A typed database that accepts only the types it was created with. /// /// # Example: Iterate over databases entries /// /// In this example we store numbers in big endian this way those are ordered. /// Thanks to their bytes representation, heed is able to iterate over them /// from the lowest to the highest. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over database entries in order /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (35, ()), /// (42, ()), /// (68, ()), /// ]; /// /// assert_eq!(rets, expected); /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// # Example: Iterate over and delete ranges of entries /// /// Discern also support ranges and ranges deletions. /// Same configuration as above, numbers are ordered, therefore it is safe to specify /// a range and be able to iterate over and/or delete it. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("big-endian-iter"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &0, &())?; /// db.put(&mut wtxn, &68, &())?; /// db.put(&mut wtxn, &35, &())?; /// db.put(&mut wtxn, &42, &())?; /// /// // you can iterate over ranges too!!! /// let range = 35..=42; /// let rets: Result<_, _> = db.range(&wtxn, &range)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (35, ()), /// (42, ()), /// ]; /// /// assert_eq!(rets, expected); /// /// // even delete a range of keys /// let range = 35..=42; /// let deleted: usize = db.delete_range(&mut wtxn, &range)?; /// /// let rets: Result<_, _> = db.iter(&wtxn)?.collect(); /// let rets: Vec<(i64, _)> = rets?; /// /// let expected = vec![ /// (0, ()), /// (68, ()), /// ]; /// /// assert_eq!(deleted, 2); /// assert_eq!(rets, expected); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub struct EncryptedDatabase { inner: Database, } impl EncryptedDatabase { pub(crate) fn new(inner: Database) -> EncryptedDatabase { EncryptedDatabase { inner } } /// Retrieves the value associated with a key. /// /// If the key does not exist, then `None` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32= U32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("get-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// /// let ret = db.get(&wtxn, "i-am-forty-two")?; /// assert_eq!(ret, Some(42)); /// /// let ret = db.get(&wtxn, "i-am-twenty-one")?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesDecode<'txn>, { self.inner.get(txn, key) } /// Returns an iterator over all of the values of a single key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_duplicates<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result>> where KC: BytesEncode<'a>, { self.inner.get_duplicates(txn, key) } /// Retrieves the key/value pair lower than the given one in this database. /// /// If the database if empty or there is no key lower than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than(&wtxn, &43)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_lower_than(&wtxn, &27)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.get_lower_than(txn, key) } /// Retrieves the key/value pair lower than or equal to the given one in this database. /// /// If the database if empty or there is no key lower than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &4404)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &43)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_lower_than_or_equal_to(&wtxn, &26)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_lower_than_or_equal_to<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.get_lower_than_or_equal_to(txn, key) } /// Retrieves the key/value pair greater than the given one in this database. /// /// If the database if empty or there is no key greater than the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than(&wtxn, &42)?; /// assert_eq!(ret, Some((43, ()))); /// /// let ret = db.get_greater_than(&wtxn, &43)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.get_greater_than(txn, key) } /// Retrieves the key/value pair greater than or equal to the given one in this database. /// /// If the database if empty or there is no key greater than or equal to the given one, /// then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEU32 = U32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("get-lt-u32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &27, &())?; /// db.put(&mut wtxn, &42, &())?; /// db.put(&mut wtxn, &43, &())?; /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &0)?; /// assert_eq!(ret, Some((27, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &42)?; /// assert_eq!(ret, Some((42, ()))); /// /// let ret = db.get_greater_than_or_equal_to(&wtxn, &44)?; /// assert_eq!(ret, None); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_greater_than_or_equal_to<'a, 'txn>( &self, txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a> + BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.get_greater_than_or_equal_to(txn, key) } /// Retrieves the first key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("first-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.first(&wtxn)?; /// assert_eq!(ret, Some((27, "i-am-twenty-seven"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn first<'txn>(&self, txn: &'txn mut RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.first(txn) } /// Retrieves the last key/value pair of this database. /// /// If the database if empty, then `None` is returned. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("last-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// /// let ret = db.last(&wtxn)?; /// assert_eq!(ret, Some((42, "i-am-forty-two"))); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn last<'txn>(&self, txn: &'txn mut RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, { self.inner.last(txn) } /// Returns the number of elements in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 4); /// /// db.delete(&mut wtxn, &27)?; /// /// let ret = db.len(&wtxn)?; /// assert_eq!(ret, 3); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn len(&self, txn: &RoTxn) -> Result { self.inner.len(txn) } /// Returns `true` if and only if this database is empty. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, false); /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert_eq!(ret, true); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn is_empty(&self, txn: &RoTxn) -> Result { self.inner.is_empty(txn) } /// Returns some statistics for this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let stat = db.stat(&wtxn)?; /// assert_eq!(stat.depth, 1); /// assert_eq!(stat.branch_pages, 0); /// assert_eq!(stat.leaf_pages, 1); /// assert_eq!(stat.overflow_pages, 0); /// assert_eq!(stat.entries, 4); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn stat(&self, txn: &RoTxn) -> Result { self.inner.stat(txn) } /// Return a lexicographically ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter<'txn>(&self, txn: &'txn mut RoTxn) -> Result> { self.inner.iter(txn) } /// Return a mutable lexicographically ordered iterator of all key-value pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, Some("i-am-the-new-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { self.inner.iter_mut(txn) } /// Return a reversed lexicographically ordered iterator of all key-value pairs in this database. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter<'txn>(&self, txn: &'txn mut RoTxn) -> Result> { self.inner.rev_iter(txn) } /// Return a mutable reversed lexicographically ordered iterator of all key-value\ /// pairs in this database. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// /// let mut iter = db.rev_iter_mut(&mut wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// let ret = unsafe { iter.put_current(&13, "i-am-the-new-thirteen")? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, &42)?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, &13)?; /// assert_eq!(ret, Some("i-am-the-new-thirteen")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { self.inner.rev_iter_mut(txn) } /// Return an ordered iterator of a range of key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut iter = db.range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// It can be complex to work with ranges of slices and using /// the `..` or `..=` range syntax is not the best way to deal /// with that. We highly recommend using the [`Bound`](std::ops::Bound) enum for that. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// use std::ops::Bound; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(30) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, None)?; /// /// // make sure to create slices and not ref array /// // by using the [..] syntax. /// let start = &[0, 0, 0][..]; /// let end = &[9, 0, 0][..]; /// /// // equivalent to start..end /// let range = (Bound::Included(start), Bound::Excluded(end)); /// /// let iter = db.range(&mut wtxn, &range)?; /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn range<'a, 'txn, R>( &self, txn: &'txn mut RoTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { self.inner.range(txn, range) } /// Return a mutable ordered iterator of a range of /// key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.put_current(&42, "i-am-the-new-forty-two")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-new-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { self.inner.range_mut(txn, range) } /// Return a reverse ordered iterator of a range of key-value /// pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=43; /// let mut iter = db.rev_range(&wtxn, &range)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range<'a, 'txn, R>( &self, txn: &'txn mut RoTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { self.inner.rev_range(txn, range) } /// Return a mutable reverse ordered iterator of a range of /// key-value pairs in this database. /// /// Comparisons are made by using the comparator `C`. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let mut range = db.rev_range_mut(&mut wtxn, &range)?; /// assert_eq!(range.next().transpose()?, Some((42, "i-am-forty-two"))); /// let ret = unsafe { range.del_current()? }; /// assert!(ret); /// assert_eq!(range.next().transpose()?, Some((27, "i-am-twenty-seven"))); /// let ret = unsafe { range.put_current(&27, "i-am-the-new-twenty-seven")? }; /// assert!(ret); /// /// assert_eq!(range.next().transpose()?, None); /// drop(range); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((27, "i-am-the-new-twenty-seven"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_range_mut<'a, 'txn, R>( &self, txn: &'txn mut RwTxn, range: &'a R, ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, { self.inner.rev_range_mut(txn, range) } /// Return a lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter<'a, 'txn>( &self, txn: &'txn mut RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { self.inner.prefix_iter(txn, prefix) } /// Return a mutable lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.put_current("i-am-twenty-seven", &27000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, Some(27000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn prefix_iter_mut<'a, 'txn, T>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { self.inner.prefix_iter_mut(txn, prefix) } /// Return a reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// You can make this iterator `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter<'a, 'txn>( &self, txn: &'txn mut RoTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { self.inner.rev_prefix_iter(txn, prefix) } /// Return a mutable reversed lexicographically ordered iterator of all key-value pairs /// in this database that starts with the given prefix. /// /// Comparisons are made by using the bytes representation of the key. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, "i-am-twenty-eight", &28)?; /// db.put(&mut wtxn, "i-am-twenty-seven", &27)?; /// db.put(&mut wtxn, "i-am-twenty-nine", &29)?; /// db.put(&mut wtxn, "i-am-forty-one", &41)?; /// db.put(&mut wtxn, "i-am-forty-two", &42)?; /// /// let mut iter = db.rev_prefix_iter_mut(&mut wtxn, "i-am-twenty")?; /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-seven", 27))); /// let ret = unsafe { iter.del_current()? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-nine", 29))); /// assert_eq!(iter.next().transpose()?, Some(("i-am-twenty-eight", 28))); /// let ret = unsafe { iter.put_current("i-am-twenty-eight", &28000)? }; /// assert!(ret); /// /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// /// let ret = db.get(&wtxn, "i-am-twenty-seven")?; /// assert_eq!(ret, None); /// /// let ret = db.get(&wtxn, "i-am-twenty-eight")?; /// assert_eq!(ret, Some(28000)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn rev_prefix_iter_mut<'a, 'txn, T>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, { self.inner.rev_prefix_iter_mut(txn, prefix) } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with no specific flag. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, Some("i-am-twenty-seven")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { self.inner.put(txn, key, data) } /// Insert a key-value pair where the value can directly be written to disk, replacing any /// previous value. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let value = "I am a long long long value"; /// db.put_reserved(&mut wtxn, &42, value.len(), |reserved| { /// reserved.write_all(value.as_bytes()) /// })?; /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(value)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_reserved<'a, F>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result<()> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { self.inner.put_reserved(txn, key, data_size, write_func) } /// Insert a key-value pair in this database, replacing any previous value. The entry is /// written with the specified flags. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags, DatabaseFlags, Error, MdbError}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .name("dup-i32") /// .flags(DatabaseFlags::DUP_SORT) /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &42, "i-am-so-cool")?; /// db.put(&mut wtxn, &42, "i-am-the-king")?; /// db.put(&mut wtxn, &42, "i-am-fun")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND, &54, "i-am-older-than-you")?; /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP, &54, "ok-but-i-am-better-than-you")?; /// // You can compose flags by OR'ing them /// db.put_with_flags(&mut wtxn, PutFlags::APPEND_DUP | PutFlags::NO_OVERWRITE, &55, "welcome")?; /// /// // The NO_DUP_DATA flag will return KeyExist if we try to insert the exact same key/value pair. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_DUP_DATA, &54, "ok-but-i-am-better-than-you"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// // The NO_OVERWRITE flag will return KeyExist if we try to insert something with an already existing key. /// let ret = db.put_with_flags(&mut wtxn, PutFlags::NO_OVERWRITE, &54, "there-can-be-only-one-data"); /// assert!(matches!(ret, Err(Error::Mdb(MdbError::KeyExist)))); /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-forty-two"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-fun"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-so-cool"))); /// assert_eq!(iter.next().transpose()?, Some((42, "i-am-the-king"))); /// assert_eq!(iter.next().transpose()?, Some((54, "i-am-older-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((54, "ok-but-i-am-better-than-you"))); /// assert_eq!(iter.next().transpose()?, Some((55, "welcome"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn put_with_flags<'a>( &self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { self.inner.put_with_flags(txn, flags, key, data) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) flag. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put(&mut wtxn, &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put(&mut wtxn, &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put<'a, 'txn, T>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { self.inner.get_or_put(txn, key, data) } /// Attempt to insert a key-value pair in this database, or if a value already exists for the /// key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) which is always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "i-am-forty-two")?, None); /// assert_eq!(db.get_or_put_with_flags(&mut wtxn, PutFlags::empty(), &42, "the meaning of life")?, Some("i-am-forty-two")); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some("i-am-forty-two")); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_with_flags<'a, 'txn, T>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result> where KC: BytesEncode<'a>, DC: BytesEncode<'a> + BytesDecode<'a>, { self.inner.get_or_put_with_flags(txn, flags, key, data) } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is always written with the [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and /// [`MDB_RESERVE`](ffi::MDB_RESERVE) flags. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved(&mut wtxn, &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { self.inner.get_or_put_reserved(txn, key, data_size, write_func) } /// Attempt to insert a key-value pair in this database, where the value can be directly /// written to disk, or if a value already exists for the key, returns the previous value. /// /// The entry is written with the specified flags, in addition to /// [`NO_OVERWRITE`](PutFlags::NO_OVERWRITE) and [`MDB_RESERVE`](ffi::MDB_RESERVE) /// which are always used. /// /// ``` /// # use heed::EnvOpenOptions; /// use std::io::Write; /// use heed::{Database, PutFlags}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("number-string"))?; /// /// # db.clear(&mut wtxn)?; /// let long = "I am a long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, long.len(), |reserved| { /// reserved.write_all(long.as_bytes()) /// })?, /// None /// ); /// /// let longer = "I am an even longer long long long value"; /// assert_eq!( /// db.get_or_put_reserved_with_flags(&mut wtxn, PutFlags::empty(), &42, longer.len(), |reserved| { /// unreachable!() /// })?, /// Some(long) /// ); /// /// let ret = db.get(&mut wtxn, &42)?; /// assert_eq!(ret, Some(long)); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn get_or_put_reserved_with_flags<'a, 'txn, F>( &'txn self, txn: &mut RwTxn, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result> where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, DC: BytesDecode<'a>, { self.inner.get_or_put_reserved_with_flags(txn, flags, key, data_size, write_func) } /// Deletes an entry or every duplicate data items of a key /// if the database supports duplicate data items. /// /// If the entry does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let ret = db.delete(&mut wtxn, &27)?; /// assert_eq!(ret, true); /// /// let ret = db.get(&mut wtxn, &27)?; /// assert_eq!(ret, None); /// /// let ret = db.delete(&mut wtxn, &467)?; /// assert_eq!(ret, false); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete<'a>(&self, txn: &mut RwTxn, key: &'a KC::EItem) -> Result where KC: BytesEncode<'a>, { self.inner.delete(txn, key) } /// Deletes a single key-value pair in this database. /// /// If the database doesn't support duplicate data items the data is ignored. /// If the key does not exist, then `false` is returned. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_one_duplicate<'a>( &self, txn: &mut RwTxn, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { self.inner.delete_one_duplicate(txn, key, data) } /// 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 comparator `C`. /// /// [`clear`]: crate::Database::clear /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// let range = 27..=42; /// let ret = db.delete_range(&mut wtxn, &range)?; /// assert_eq!(ret, 2); /// /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((13, "i-am-thirteen"))); /// assert_eq!(iter.next().transpose()?, Some((521, "i-am-five-hundred-and-twenty-one"))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn delete_range<'a, 'txn, R>(&self, txn: &'txn mut RwTxn, range: &'a R) -> Result where KC: BytesEncode<'a> + BytesDecode<'txn>, C: Comparator, R: RangeBounds, { self.inner.delete_range(txn, range) } /// Deletes all key/value pairs in this database. /// /// Prefer using this method instead of a call to [`delete_range`] with a full range ([`..`]). /// /// [`delete_range`]: crate::Database::delete_range /// [`..`]: std::ops::RangeFull /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// db.clear(&mut wtxn)?; /// /// let ret = db.is_empty(&wtxn)?; /// assert!(ret); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn clear(&self, txn: &mut RwTxn) -> Result<()> { self.inner.clear(txn) } /// Change the codec types of this database, specifying the codecs. /// /// # Safety /// /// It is up to you to ensure that the data read and written using the polymorphic /// handle correspond to the the typed, uniform one. If an invalid write is made, /// it can corrupt the database from the eyes of heed. /// /// # Example /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::EnvOpenOptions; /// use heed::Database; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("iter-i32"))?; /// /// # db.clear(&mut wtxn)?; /// // We remap the types for ease of use. /// let db = db.remap_types::(); /// db.put(&mut wtxn, &42, "i-am-forty-two")?; /// db.put(&mut wtxn, &27, "i-am-twenty-seven")?; /// db.put(&mut wtxn, &13, "i-am-thirteen")?; /// db.put(&mut wtxn, &521, "i-am-five-hundred-and-twenty-one")?; /// /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn remap_types(&self) -> EncryptedDatabase { EncryptedDatabase::new(self.inner.remap_types::()) } /// Change the key codec type of this database, specifying the new codec. pub fn remap_key_type(&self) -> EncryptedDatabase { self.remap_types::() } /// Change the data codec type of this database, specifying the new codec. pub fn remap_data_type(&self) -> EncryptedDatabase { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(&self) -> EncryptedDatabase, C> { self.remap_types::>() } } impl Clone for EncryptedDatabase { fn clone(&self) -> EncryptedDatabase { *self } } impl Copy for EncryptedDatabase {} impl fmt::Debug for EncryptedDatabase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EncryptedDatabase") .field("key_codec", &any::type_name::()) .field("data_codec", &any::type_name::()) .field("key_comparator", &any::type_name::()) .field("dup_sort_comparator", &any::type_name::()) .finish() } } #[cfg(test)] mod tests { use heed_types::*; use super::*; #[test] fn put_overwrite() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env.create_database::(&mut txn, None)?; assert_eq!(db.get(&txn, b"hello").unwrap(), None); db.put(&mut txn, b"hello", b"hi").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"hi"[..])); db.put(&mut txn, b"hello", b"bye").unwrap(); assert_eq!(db.get(&txn, b"hello").unwrap(), Some(&b"bye"[..])); Ok(()) } #[test] #[cfg(feature = "longer-keys")] fn longer_keys() -> Result<()> { let dir = tempfile::tempdir()?; let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; let mut txn = env.write_txn()?; let db = env.create_database::(&mut txn, None)?; // Try storing a key larger than 511 bytes (the default if MDB_MAXKEYSIZE is not set) let long_key = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut pharetra sit amet aliquam. Sit amet nisl purus in mollis nunc. Eget egestas purus viverra accumsan in nisl nisi scelerisque. Duis ultricies lacus sed turpis tincidunt. Sem nulla pharetra diam sit. Leo vel orci porta non pulvinar. Erat pellentesque adipiscing commodo elit at imperdiet dui. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Diam donec adipiscing tristique risus nec feugiat. In fermentum et sollicitudin ac orci. Ut sem nulla pharetra diam sit amet. Aliquam purus sit amet luctus venenatis lectus. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan. Urna duis convallis convallis tellus id interdum velit laoreet id. Ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Tincidunt arcu non sodales neque. Habitant morbi tristique senectus et netus et malesuada fames."; assert_eq!(db.get(&txn, long_key).unwrap(), None); db.put(&mut txn, long_key, b"hi").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"hi"[..])); db.put(&mut txn, long_key, b"bye").unwrap(); assert_eq!(db.get(&txn, long_key).unwrap(), Some(&b"bye"[..])); Ok(()) } } heed-0.22.0/src/databases/mod.rs000064400000000000000000000013301046102023000144520ustar 00000000000000pub use database::{Database, DatabaseOpenOptions}; #[cfg(master3)] pub use encrypted_database::{EncryptedDatabase, EncryptedDatabaseOpenOptions}; mod database; #[cfg(master3)] mod encrypted_database; /// Statistics for a database in the environment. #[derive(Debug, Clone, Copy)] pub struct DatabaseStat { /// Size of a database page. /// This is currently the same for all databases. pub page_size: u32, /// Depth (height) of the B-tree. pub depth: u32, /// Number of internal (non-leaf) pages pub branch_pages: usize, /// Number of leaf pages. pub leaf_pages: usize, /// Number of overflow pages. pub overflow_pages: usize, /// Number of data items. pub entries: usize, } heed-0.22.0/src/envs/encrypted_env.rs000064400000000000000000000411421046102023000155710ustar 00000000000000use std::fmt; use std::fs::File; use std::panic::catch_unwind; use std::path::Path; use aead::generic_array::typenum::Unsigned; use aead::{AeadMutInPlace, Key, KeyInit, Nonce, Tag}; use super::{Env, EnvClosingEvent, EnvInfo, FlagSetMode}; use crate::databases::{EncryptedDatabase, EncryptedDatabaseOpenOptions}; use crate::mdb::ffi::{self}; use crate::{CompactionOption, EnvFlags, Result, RoTxn, RwTxn, Unspecified, WithTls}; #[allow(unused)] // fro cargo auto doc links use crate::{Database, EnvOpenOptions}; /// An environment handle constructed by using [`EnvOpenOptions::open_encrypted`]. #[derive(Clone)] pub struct EncryptedEnv { pub(crate) inner: Env, } impl EncryptedEnv { /// The size of the data file on disk. /// /// # Example /// /// ``` /// use heed::EnvOpenOptions; /// /// # fn main() -> Result<(), Box> { /// let dir = tempfile::tempdir()?; /// let size_in_bytes = 1024 * 1024; /// let env = unsafe { EnvOpenOptions::new().map_size(size_in_bytes).open(dir.path())? }; /// /// let actual_size = env.real_disk_size()? as usize; /// assert!(actual_size < size_in_bytes); /// # Ok(()) } /// ``` pub fn real_disk_size(&self) -> Result { self.inner.real_disk_size() } /// 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.inner.flags() } /// Enable or disable the environment's currently active [`EnvFlags`]. /// /// ``` /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode}; /// use heed::types::*; /// /// # fn main() -> Result<(), Box> { /// let mut env_builder = EnvOpenOptions::new(); /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// // Env was opened without flags. /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// /// // Enable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Enable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::NO_SYNC.bits()); /// /// // Disable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Disable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// # Ok(()) } /// ``` /// /// # Safety /// /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`. /// /// LMDB also requires that only 1 thread calls this function at any given moment. /// Neither `heed` or LMDB check for this condition, so the caller must ensure it explicitly. pub unsafe fn set_flags(&self, flags: EnvFlags, mode: FlagSetMode) -> Result<()> { self.inner.set_flags(flags, mode) } /// Return the raw flags the environment is currently set with. pub fn get_flags(&self) -> Result { self.inner.get_flags() } /// Returns some basic informations about this environment. pub fn info(&self) -> EnvInfo { self.inner.info() } /// Returns the size used by all the databases in the environment without the free pages. /// /// It is crucial to configure [`EnvOpenOptions::max_dbs`] with a sufficiently large value /// before invoking this function. All databases within the environment will be opened /// and remain so. pub fn non_free_pages_size(&self) -> Result { self.inner.non_free_pages_size() } /// Options and flags which can be used to configure how a [`Database`] is opened. pub fn database_options(&self) -> EncryptedDatabaseOpenOptions { EncryptedDatabaseOpenOptions::new(self) } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open_database( &self, rtxn: &RoTxn, name: Option<&str>, ) -> Result>> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.open(rtxn) } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened during this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create_database( &self, wtxn: &mut RwTxn, name: Option<&str>, ) -> Result> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.create(wtxn) } /// 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 { self.inner.write_txn() } /// 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> { self.inner.nested_write_txn(parent) } /// Create a transaction with read-only access for use with the environment. /// /// You can make this transaction `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// See [`Self::static_read_txn`] if you want the txn to own the environment. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn read_txn(&self) -> Result> { self.inner.read_txn() } /// Create a transaction with read-only access for use with the environment. /// Contrary to [`Self::read_txn`], this version **owns** the environment, which /// means you won't be able to close the environment while this transaction is alive. /// /// You can make this transaction `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn static_read_txn(self) -> Result> { self.inner.static_read_txn() } /// 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. /// /// Note that the file must be seek to the beginning after the copy is complete. /// /// ``` /// use std::fs; /// use std::io::{Read, Seek, SeekFrom}; /// use std::path::Path; /// use heed3::{EnvOpenOptions, Database, EnvFlags, FlagSetMode, CompactionOption}; /// use heed3::types::*; /// use memchr::memmem::find_iter; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, None)?; /// /// db.put(&mut wtxn, &"hello0", &"world0")?; /// db.put(&mut wtxn, &"hello1", &"world1")?; /// db.put(&mut wtxn, &"hello2", &"world2")?; /// db.put(&mut wtxn, &"hello3", &"world3")?; /// /// wtxn.commit()?; /// /// let mut tmp_file = tempfile::tempfile()?; /// env.copy_to_file(&mut tmp_file, CompactionOption::Enabled)?; /// let offset = tmp_file.seek(SeekFrom::Current(0))?; /// assert_ne!(offset, 0); /// /// let offset = tmp_file.seek(SeekFrom::Start(0))?; /// assert_eq!(offset, 0); /// /// let mut content = Vec::new(); /// tmp_file.read_to_end(&mut content)?; /// assert!(content.len() > 8 * 6); // more than 8 times hellox + worldx /// # Ok(()) } /// ``` pub fn copy_to_file(&self, file: &mut File, option: CompactionOption) -> Result<()> { self.inner.copy_to_file(file, option) } /// 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<()> { self.inner.copy_to_fd(fd, option) } /// Flush the data buffers to disk. pub fn force_sync(&self) -> Result<()> { self.inner.force_sync() } /// Returns the canonicalized path where this env lives. pub fn path(&self) -> &Path { self.inner.path() } /// Returns the maximum number of threads/reader slots for the environment. pub fn max_readers(&self) -> u32 { self.inner.max_readers() } /// Get the maximum size of keys and MDB_DUPSORT data we can write. /// /// Depends on the compile-time constant MDB_MAXKEYSIZE. Default 511 pub fn max_key_size(&self) -> usize { self.inner.max_key_size() } /// 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 { self.inner.prepare_for_closing() } /// 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 { self.inner.clear_stale_readers() } /// Resize the memory map to a new size. /// /// # Safety /// /// According to the [LMDB documentation](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5), /// it is okay to call `mdb_env_set_mapsize` for an open environment as long as no transactions are active, /// but the library does not check for this condition, so the caller must ensure it explicitly. pub unsafe fn resize(&self, new_size: usize) -> Result<()> { self.inner.resize(new_size) } } unsafe impl Send for EncryptedEnv {} unsafe impl Sync for EncryptedEnv {} impl fmt::Debug for EncryptedEnv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EncryptedEnv") .field("path", &self.inner.path().display()) .finish_non_exhaustive() } } fn encrypt( key: &[u8], nonce: &[u8], aad: &[u8], plaintext: &[u8], chipertext_out: &mut [u8], auth_out: &mut [u8], ) -> aead::Result<()> { chipertext_out.copy_from_slice(plaintext); let key: &Key = key.into(); let nonce: &Nonce = if nonce.len() >= A::NonceSize::USIZE { nonce[..A::NonceSize::USIZE].into() } else { return Err(aead::Error); }; let mut aead = A::new(key); let tag = aead.encrypt_in_place_detached(nonce, aad, chipertext_out)?; auth_out.copy_from_slice(&tag); Ok(()) } fn decrypt( key: &[u8], nonce: &[u8], aad: &[u8], chipher_text: &[u8], output: &mut [u8], auth_in: &[u8], ) -> aead::Result<()> { output.copy_from_slice(chipher_text); let key: &Key = key.into(); let nonce: &Nonce = if nonce.len() >= A::NonceSize::USIZE { nonce[..A::NonceSize::USIZE].into() } else { return Err(aead::Error); }; let tag: &Tag = auth_in.into(); let mut aead = A::new(key); aead.decrypt_in_place_detached(nonce, aad, output, tag) } /// The wrapper function that is called by LMDB that directly calls /// the Rust idiomatic function internally. pub(crate) unsafe extern "C" fn encrypt_func_wrapper( src: *const ffi::MDB_val, dst: *mut ffi::MDB_val, key_ptr: *const ffi::MDB_val, encdec: i32, ) -> i32 { let result = catch_unwind(|| { let input = std::slice::from_raw_parts((*src).mv_data as *const u8, (*src).mv_size); let output = std::slice::from_raw_parts_mut((*dst).mv_data as *mut u8, (*dst).mv_size); let key = std::slice::from_raw_parts((*key_ptr).mv_data as *const u8, (*key_ptr).mv_size); let iv = std::slice::from_raw_parts( (*key_ptr.offset(1)).mv_data as *const u8, (*key_ptr.offset(1)).mv_size, ); let auth = std::slice::from_raw_parts_mut( (*key_ptr.offset(2)).mv_data as *mut u8, (*key_ptr.offset(2)).mv_size, ); let aad = []; let nonce = iv; let result = if encdec == 1 { encrypt::(key, nonce, &aad, input, output, auth) } else { decrypt::(key, nonce, &aad, input, output, auth) }; result.is_err() as i32 }); result.unwrap_or(1) } heed-0.22.0/src/envs/env.rs000064400000000000000000001056671046102023000135310ustar 00000000000000use std::any::TypeId; use std::ffi::CString; use std::fs::{self, File}; use std::io::Seek; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::ptr::{self, NonNull}; use std::sync::Arc; use std::{fmt, io, mem}; use heed_traits::Comparator; use synchronoise::SignalEvent; use super::{ custom_key_cmp_wrapper, get_file_fd, metadata_from_fd, DefaultComparator, EnvClosingEvent, EnvInfo, FlagSetMode, IntegerComparator, OPENED_ENV, }; use crate::cursor::{MoveOperation, RoCursor}; use crate::mdb::ffi::{self, MDB_env}; use crate::mdb::lmdb_error::mdb_result; use crate::mdb::lmdb_flags::AllDatabaseFlags; #[allow(unused)] // for cargo auto doc links use crate::EnvOpenOptions; use crate::{ CompactionOption, Database, DatabaseOpenOptions, EnvFlags, Error, Result, RoTxn, RwTxn, Unspecified, WithTls, }; /// An environment handle constructed by using [`EnvOpenOptions::open`]. #[repr(transparent)] pub struct Env { pub(crate) inner: Arc, _tls_marker: PhantomData, } impl Env { pub(crate) fn new( env_ptr: NonNull, path: PathBuf, signal_event: Arc, ) -> Self { Env { inner: Arc::new(EnvInner { env_ptr, path, signal_event }), _tls_marker: PhantomData } } pub(crate) fn env_mut_ptr(&self) -> NonNull { self.inner.env_mut_ptr() } /// The size of the data file on disk. /// /// # Example /// /// ``` /// use heed::EnvOpenOptions; /// /// # fn main() -> Result<(), Box> { /// let dir = tempfile::tempdir()?; /// let size_in_bytes = 1024 * 1024; /// let env = unsafe { EnvOpenOptions::new().map_size(size_in_bytes).open(dir.path())? }; /// /// let actual_size = env.real_disk_size()? as usize; /// assert!(actual_size < size_in_bytes); /// # Ok(()) } /// ``` pub fn real_disk_size(&self) -> Result { let mut fd = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_get_fd(self.env_mut_ptr().as_mut(), fd.as_mut_ptr()))? }; let fd = unsafe { fd.assume_init() }; let metadata = unsafe { metadata_from_fd(fd)? }; Ok(metadata.len()) } /// Return the raw flags the environment was opened with. /// /// Returns `None` if the environment flags are different from the [`EnvFlags`] set. pub fn flags(&self) -> Result> { self.get_flags().map(EnvFlags::from_bits) } /// Enable or disable the environment's currently active [`EnvFlags`]. /// /// ``` /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode}; /// use heed::types::*; /// /// # fn main() -> Result<(), Box> { /// let mut env_builder = EnvOpenOptions::new(); /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// // Env was opened without flags. /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// /// // Enable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Enable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::NO_SYNC.bits()); /// /// // Disable a flag after opening. /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Disable).unwrap(); } /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits()); /// # Ok(()) } /// ``` /// /// # Safety /// /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`. /// /// LMDB also requires that only 1 thread calls this function at any given moment. /// Neither `heed` or LMDB check for this condition, so the caller must ensure it explicitly. pub unsafe fn set_flags(&self, flags: EnvFlags, mode: FlagSetMode) -> Result<()> { // safety: caller must ensure no other thread is calling this function. // mdb_result(unsafe { ffi::mdb_env_set_flags( self.env_mut_ptr().as_mut(), flags.bits(), mode.as_mdb_env_set_flags_input(), ) }) .map_err(Into::into) } /// Return the raw flags the environment is currently set with. pub fn get_flags(&self) -> Result { let mut flags = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_get_flags(self.env_mut_ptr().as_mut(), 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.inner.env_ptr.as_ptr(), raw_info.as_mut_ptr()) }; let raw_info = unsafe { raw_info.assume_init() }; EnvInfo { map_addr: raw_info.me_mapaddr, map_size: raw_info.me_mapsize, last_page_number: raw_info.me_last_pgno, last_txn_id: raw_info.me_last_txnid, maximum_number_of_readers: raw_info.me_maxreaders, number_of_readers: raw_info.me_numreaders, } } /// Returns the size used by all the databases in the environment without the free pages. /// /// It is crucial to configure [`EnvOpenOptions::max_dbs`] with a sufficiently large value /// before invoking this function. All databases within the environment will be opened /// and remain so. pub fn non_free_pages_size(&self) -> Result { let compute_size = |stat: ffi::MDB_stat| { (stat.ms_leaf_pages + stat.ms_branch_pages + stat.ms_overflow_pages) as u64 * stat.ms_psize as u64 }; let mut size = 0; let mut stat = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_env_stat(self.env_mut_ptr().as_mut(), stat.as_mut_ptr()))? }; let stat = unsafe { stat.assume_init() }; size += compute_size(stat); let rtxn = self.read_txn()?; // Open the main database let dbi = self.raw_open_dbi(rtxn.txn_ptr(), None, 0)?; // We're going to iterate on the unnamed database let mut cursor = RoCursor::new(&rtxn, dbi)?; while let Some((key, _value)) = cursor.move_on_next(MoveOperation::NoDup)? { if key.contains(&0) { continue; } let key = String::from_utf8(key.to_vec()).unwrap(); // Calling `ffi::db_stat` on a database instance does not involve key comparison // in LMDB, so it's safe to specify a noop key compare function for it. if let Ok(dbi) = self.raw_open_dbi(rtxn.txn_ptr(), Some(&key), 0) { let mut stat = mem::MaybeUninit::uninit(); unsafe { mdb_result(ffi::mdb_stat(rtxn.txn_ptr().as_mut(), dbi, stat.as_mut_ptr()))? }; let stat = unsafe { stat.assume_init() }; size += compute_size(stat); } } Ok(size) } /// Options and flags which can be used to configure how a [`Database`] is opened. pub fn database_options(&self) -> DatabaseOpenOptions { DatabaseOpenOptions::new(self) } /// Opens a typed database that already exists in this environment. /// /// If the database was previously opened in this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. /// /// ## LMDB read-only access of existing database /// /// In the case of accessing a database in a read-only manner from another process /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata /// and the database handles opened and shared with the global [`Env`] handle. /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. pub fn open_database( &self, rtxn: &RoTxn, name: Option<&str>, ) -> Result>> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.open(rtxn) } /// Creates a typed database that can already exist in this environment. /// /// If the database was previously opened during this program run, types will be checked. /// /// ## Important Information /// /// LMDB has an important restriction on the unnamed database when named ones are opened. /// The names of the named databases are stored as keys in the unnamed one and are immutable, /// and these keys can only be read and not written. pub fn create_database( &self, wtxn: &mut RwTxn, name: Option<&str>, ) -> Result> where KC: 'static, DC: 'static, { let mut options = self.database_options().types::(); if let Some(name) = name { options.name(name); } options.create(wtxn) } pub(crate) fn raw_init_database( &self, mut raw_txn: NonNull, name: Option<&str>, mut flags: AllDatabaseFlags, ) -> Result { if TypeId::of::() == TypeId::of::() { flags.insert(AllDatabaseFlags::INTEGER_KEY); } if TypeId::of::() == TypeId::of::() { flags.insert(AllDatabaseFlags::INTEGER_DUP); } let dbi = self.raw_open_dbi(raw_txn, name, flags.bits())?; let cmp_type_id = TypeId::of::(); if cmp_type_id != TypeId::of::() && cmp_type_id != TypeId::of::() { unsafe { mdb_result(ffi::mdb_set_compare( raw_txn.as_mut(), dbi, Some(custom_key_cmp_wrapper::), ))? }; } let cmp_dup_type_id = TypeId::of::(); if cmp_dup_type_id != TypeId::of::() && cmp_dup_type_id != TypeId::of::() { unsafe { mdb_result(ffi::mdb_set_dupsort( raw_txn.as_mut(), dbi, Some(custom_key_cmp_wrapper::), ))? }; } Ok(dbi) } fn raw_open_dbi( &self, mut raw_txn: NonNull, 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.as_mut(), name_ptr, flags, &mut dbi))? }; Ok(dbi) } /// Create a transaction with read and write access for use with the environment. /// /// ## LMDB Limitations /// /// Only one [`RwTxn`] may exist simultaneously in the current environment. /// If another write transaction is initiated, while another write transaction exists /// the thread initiating the new one will wait on a mutex upon completion of the previous /// transaction. pub fn write_txn(&self) -> Result { RwTxn::new(self) } /// Create a nested transaction with read and write access for use with the environment. /// /// The new transaction will be a nested transaction, with the transaction indicated by parent /// as its parent. Transactions may be nested to any level. /// /// A parent transaction and its cursors may not issue any other operations than _commit_ and /// _abort_ while it has active child transactions. pub fn nested_write_txn<'p>(&'p self, parent: &'p mut RwTxn) -> Result> { RwTxn::nested(self, parent) } /// Create a transaction with read-only access for use with the environment. /// /// You can make this transaction `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// See [`Self::static_read_txn`] if you want the txn to own the environment. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn read_txn(&self) -> Result> { RoTxn::new(self) } /// Create a transaction with read-only access for use with the environment. /// Contrary to [`Self::read_txn`], this version **owns** the environment, which /// means you won't be able to close the environment while this transaction is alive. /// /// You can make this transaction `Send`able between threads by opening /// the environment with the [`EnvOpenOptions::read_txn_without_tls`] /// method. /// /// ## LMDB Limitations /// /// It's possible to have multiple read transactions in the same environment /// while there is a write transaction ongoing. /// /// But read transactions prevent reuse of pages freed by newer write transactions, /// thus the database can grow quickly. Write transactions prevent other write transactions, /// since writes are serialized. /// /// So avoid long-lived read transactions. /// /// ## Errors /// /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full pub fn static_read_txn(self) -> Result> { RoTxn::static_read_txn(self) } /// Copy an LMDB environment to the specified path, with options. /// /// This function may be used to make a backup of an existing environment. /// No lockfile is created, since it gets recreated at need. /// /// Note that the file must be seek to the beginning after the copy is complete. /// /// ``` /// use std::fs; /// use std::io::{Read, Seek, SeekFrom}; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode, CompactionOption}; /// use heed::types::*; /// use memchr::memmem::find_iter; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, None)?; /// /// db.put(&mut wtxn, &"hello0", &"world0")?; /// db.put(&mut wtxn, &"hello1", &"world1")?; /// db.put(&mut wtxn, &"hello2", &"world2")?; /// db.put(&mut wtxn, &"hello3", &"world3")?; /// /// wtxn.commit()?; /// /// let mut tmp_file = tempfile::tempfile()?; /// env.copy_to_file(&mut tmp_file, CompactionOption::Enabled)?; /// let offset = tmp_file.seek(SeekFrom::Current(0))?; /// assert_ne!(offset, 0); /// /// let offset = tmp_file.seek(SeekFrom::Start(0))?; /// assert_eq!(offset, 0); /// /// let mut content = Vec::new(); /// tmp_file.read_to_end(&mut content)?; /// assert_eq!(find_iter(&content, b"hello").count(), 4); /// assert_eq!(find_iter(&content, b"world").count(), 4); /// # Ok(()) } /// ``` pub fn copy_to_file(&self, file: &mut File, option: CompactionOption) -> Result<()> { let fd = get_file_fd(file); unsafe { self.copy_to_fd(fd, option) } } /// Copy an LMDB environment to a file created at the given 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. /// /// Note that the file is automatically seeked to the beginning after the copy /// is complete and deleted in case of error. /// /// ``` /// use std::fs; /// use std::io::{Read, Seek, SeekFrom}; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode, CompactionOption}; /// use heed::types::*; /// use memchr::memmem::find_iter; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, None)?; /// /// db.put(&mut wtxn, &"hello0", &"world0")?; /// db.put(&mut wtxn, &"hello1", &"world1")?; /// db.put(&mut wtxn, &"hello2", &"world2")?; /// db.put(&mut wtxn, &"hello3", &"world3")?; /// /// wtxn.commit()?; /// /// let tmp_dir = tempfile::tempdir()?; /// let path = tmp_dir.path().join("data.mdb"); /// let mut tmp_file = env.copy_to_path(path, CompactionOption::Enabled)?; /// let offset = tmp_file.seek(SeekFrom::Current(0))?; /// assert_eq!(offset, 0); /// /// let mut content = Vec::new(); /// tmp_file.read_to_end(&mut content)?; /// assert_eq!(find_iter(&content, b"hello").count(), 4); /// assert_eq!(find_iter(&content, b"world").count(), 4); /// # Ok(()) } /// ``` pub fn copy_to_path>(&self, path: P, option: CompactionOption) -> Result { let path = path.as_ref(); let mut file = File::options().write(true).create(true).truncate(true).read(true).open(path)?; match self.copy_to_file(&mut file, option) { Ok(_) => { file.rewind()?; Ok(file) } Err(err) => { fs::remove_file(path)?; Err(err) } } } /// 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.inner.env_ptr.as_ptr(), fd, flags))?; Ok(()) } /// Flush the data buffers to disk. pub fn force_sync(&self) -> Result<()> { unsafe { mdb_result(ffi::mdb_env_sync(self.inner.env_ptr.as_ptr(), 1))? } Ok(()) } /// Returns the canonicalized path where this env lives. pub fn path(&self) -> &Path { &self.inner.path } /// Returns the maximum number of threads/reader slots for the environment. pub fn max_readers(&self) -> u32 { let env_ptr = self.inner.env_ptr.as_ptr(); let mut max_readers = 0; // safety: The env and the max_readers pointer are valid unsafe { mdb_result(ffi::mdb_env_get_maxreaders(env_ptr, &mut max_readers)).unwrap() }; max_readers } /// Get the maximum size of keys and MDB_DUPSORT data we can write. /// /// Depends on the compile-time constant MDB_MAXKEYSIZE. Default 511 pub fn max_key_size(&self) -> usize { let maxsize: i32 = unsafe { ffi::mdb_env_get_maxkeysize(self.env_mut_ptr().as_mut()) }; maxsize as usize } /// 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 { EnvClosingEvent(self.inner.signal_event.clone()) } /// 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.inner.env_ptr.as_ptr(), &mut dead))? } // safety: The reader_check function asks for an i32, initialize it to zero // and never decrements it. It is safe to use either an u32 or u64 (usize). Ok(dead as usize) } /// Resize the memory map to a new size. /// /// # Safety /// /// According to the [LMDB documentation](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5), /// it is okay to call `mdb_env_set_mapsize` for an open environment as long as no transactions are active, /// but the library does not check for this condition, so the caller must ensure it explicitly. pub unsafe fn resize(&self, new_size: usize) -> Result<()> { if new_size % page_size::get() != 0 { let msg = format!( "map size ({}) must be a multiple of the system page size ({})", new_size, page_size::get() ); return Err(Error::Io(io::Error::new(io::ErrorKind::InvalidInput, msg))); } mdb_result(unsafe { ffi::mdb_env_set_mapsize(self.env_mut_ptr().as_mut(), new_size) }) .map_err(Into::into) } } impl Clone for Env { fn clone(&self) -> Self { Env { inner: self.inner.clone(), _tls_marker: PhantomData } } } unsafe impl Send for Env {} unsafe impl Sync for Env {} impl fmt::Debug for Env { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Env").field("path", &self.inner.path.display()).finish_non_exhaustive() } } pub(crate) struct EnvInner { env_ptr: NonNull, signal_event: Arc, pub(crate) path: PathBuf, } impl EnvInner { pub(crate) fn env_mut_ptr(&self) -> NonNull { self.env_ptr } } 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(); let removed = lock.remove(&self.path); debug_assert!(removed.is_some()); unsafe { ffi::mdb_env_close(self.env_ptr.as_mut()) }; self.signal_event.signal(); } } #[cfg(test)] mod tests { use std::io::ErrorKind; use std::time::Duration; use std::{fs, thread}; use crate::types::*; use crate::{env_closing_event, EnvOpenOptions, Error}; #[test] fn close_env() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(30) .open(dir.path()) .unwrap() }; // Force a thread to keep the env for 1 second. let env_cloned = env.clone(); thread::spawn(move || { let _env = env_cloned; thread::sleep(Duration::from_secs(1)); }); let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, "hello", "hello").unwrap(); db.put(&mut wtxn, "world", "world").unwrap(); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some(("hello", "hello"))); assert_eq!(iter.next().transpose().unwrap(), Some(("world", "world"))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.commit().unwrap(); let signal_event = env.prepare_for_closing(); eprintln!("waiting for the env to be closed"); signal_event.wait(); eprintln!("env closed successfully"); // Make sure we don't have a reference to the env assert!(env_closing_event(dir.path()).is_none()); } #[test] fn reopen_env_with_different_options_is_err() { let dir = tempfile::tempdir().unwrap(); let _env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path()) .unwrap() }; let result = unsafe { EnvOpenOptions::new() .map_size(12 * 1024 * 1024) // 12MB .open(dir.path()) }; assert!(matches!(result, Err(Error::EnvAlreadyOpened))); } #[test] fn open_env_with_named_path() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join("babar.mdb")).unwrap(); let _env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path().join("babar.mdb")) .unwrap() }; let error = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .open(dir.path().join("babar.mdb")) .unwrap_err() }; assert!(matches!(error, Error::EnvAlreadyOpened)); } #[test] #[cfg(not(windows))] fn open_database_with_writemap_flag() { let dir = tempfile::tempdir().unwrap(); let mut envbuilder = EnvOpenOptions::new(); envbuilder.map_size(10 * 1024 * 1024); // 10MB envbuilder.max_dbs(10); unsafe { envbuilder.flags(crate::EnvFlags::WRITE_MAP) }; let env = unsafe { envbuilder.open(dir.path()).unwrap() }; let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); } #[test] fn open_database_with_nosubdir() { let dir = tempfile::tempdir().unwrap(); let mut envbuilder = EnvOpenOptions::new(); unsafe { envbuilder.flags(crate::EnvFlags::NO_SUB_DIR) }; let _env = unsafe { envbuilder.open(dir.path().join("data.mdb")).unwrap() }; } #[test] fn create_database_without_commit() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.abort(); let rtxn = env.read_txn().unwrap(); let option = env.open_database::(&rtxn, Some("my-super-db")).unwrap(); assert!(option.is_none()); } #[test] fn open_already_existing_database() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; // we first create a database let mut wtxn = env.write_txn().unwrap(); let _db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); // Close the environement and reopen it, databases must not be loaded in memory. env.prepare_for_closing().wait(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(10) .open(dir.path()) .unwrap() }; let rtxn = env.read_txn().unwrap(); let option = env.open_database::(&rtxn, Some("my-super-db")).unwrap(); assert!(option.is_some()); } #[test] fn resize_database() { let dir = tempfile::tempdir().unwrap(); let page_size = page_size::get(); let env = unsafe { EnvOpenOptions::new().map_size(9 * page_size).max_dbs(1).open(dir.path()).unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, Some("my-super-db")).unwrap(); wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); for i in 0..64 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); for i in 64..128 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().expect_err("cannot commit a transaction that would reach the map size limit"); unsafe { env.resize(10 * page_size).unwrap(); } let mut wtxn = env.write_txn().unwrap(); for i in 64..128 { db.put(&mut wtxn, &i.to_string(), "world").unwrap(); } wtxn.commit().expect("transaction should commit after resizing the map size"); assert_eq!(10 * page_size, env.info().map_size); } /// Non-regression test for /// /// /// We should be able to open database Read-Only Env with /// no prior Read-Write Env opening. And query data. #[test] fn open_read_only_without_no_env_opened_before() { let expected_data0 = "Data Expected db0"; let dir = tempfile::tempdir().unwrap(); { // We really need this env to be dropped before the read-only access. let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let database0 = env.create_database::(&mut wtxn, Some("shared0")).unwrap(); wtxn.commit().unwrap(); let mut wtxn = env.write_txn().unwrap(); database0.put(&mut wtxn, "shared0", expected_data0).unwrap(); wtxn.commit().unwrap(); // We also really need that no other env reside in memory in other thread doing tests. env.prepare_for_closing().wait(); } { // Open now we do a read-only opening let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let database0 = { let rtxn = env.read_txn().unwrap(); let database0 = env.open_database::(&rtxn, Some("shared0")).unwrap().unwrap(); // This commit is mandatory if not committed you might get // Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" }) rtxn.commit().unwrap(); database0 }; { // If we didn't committed the opening it might fail with EINVAL. let rtxn = env.read_txn().unwrap(); let value = database0.get(&rtxn, "shared0").unwrap().unwrap(); assert_eq!(value, expected_data0); } env.prepare_for_closing().wait(); } // To avoid reintroducing the bug let's try to open again but without the commit { // Open now we do a read-only opening let env = unsafe { EnvOpenOptions::new() .map_size(16 * 1024 * 1024 * 1024) // 10MB .max_dbs(32) .open(dir.path()) .unwrap() }; let database0 = { let rtxn = env.read_txn().unwrap(); let database0 = env.open_database::(&rtxn, Some("shared0")).unwrap().unwrap(); // No commit it's important, dropping explicitly drop(rtxn); database0 }; { // We didn't committed the opening we will get EINVAL. let rtxn = env.read_txn().unwrap(); // The dbg!() is intentional in case of a change in rust-std or in lmdb related // to the windows error. let err = dbg!(database0.get(&rtxn, "shared0")); // The error kind is still ErrorKind Uncategorized on windows. // Behind it's a ERROR_BAD_COMMAND code 22 like EINVAL. if cfg!(windows) { assert!(err.is_err()); } else { assert!( matches!(err, Err(Error::Io(ref e)) if e.kind() == ErrorKind::InvalidInput) ); } } env.prepare_for_closing().wait(); } } #[test] fn max_key_size() { let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new().open(dir.path().join(dir.path())).unwrap() }; let maxkeysize = env.max_key_size(); eprintln!("maxkeysize: {}", maxkeysize); if cfg!(feature = "longer-keys") { // Should be larger than the default of 511 assert!(maxkeysize > 511); } else { // Should be the default of 511 assert_eq!(maxkeysize, 511); } } } heed-0.22.0/src/envs/env_open_options.rs000064400000000000000000000502571046102023000163170ustar 00000000000000use std::ffi::CString; #[cfg(windows)] use std::ffi::OsStr; use std::io::ErrorKind::NotFound; use std::marker::PhantomData; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::ptr::NonNull; use std::sync::Arc; use std::{io, ptr}; #[cfg(master3)] use aead::{generic_array::typenum::Unsigned, AeadCore, AeadMutInPlace, Key, KeyInit}; use synchronoise::SignalEvent; #[cfg(master3)] use super::encrypted_env::{encrypt_func_wrapper, EncryptedEnv}; use super::env::Env; use super::{canonicalize_path, OPENED_ENV}; #[cfg(windows)] use crate::envs::OsStrExtLmdb as _; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::txn::{TlsUsage, WithTls, WithoutTls}; use crate::{EnvFlags, Error, Result}; /// Options and flags which can be used to configure how an environment is opened. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EnvOpenOptions { map_size: Option, max_readers: Option, max_dbs: Option, flags: EnvFlags, _tls_marker: PhantomData, } 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(), _tls_marker: PhantomData, } } } impl EnvOpenOptions { /// Make the read transactions `!Send` by specifying they will /// use Thread Local Storage (TLS). It is often faster to open /// TLS-backed transactions. /// /// A thread can only use one transaction at a time, plus any /// child (nested) transactions. Each transaction belongs to one /// thread. A `BadRslot` error will be thrown when multiple read /// transactions exists on the same thread. /// /// # Example /// /// This example shows that the `RoTxn<'_, WithTls>` cannot be sent between threads. /// /// ```compile_fail /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags}; /// use heed::types::*; /// /// /// Checks, at compile time, that a type can be sent accross threads. /// fn is_sendable(_x: S) {} /// /// # fn main() -> Result<(), Box> { /// let mut env_builder = EnvOpenOptions::new().read_txn_with_tls(); /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// let rtxn = env.read_txn()?; /// is_sendable(rtxn); /// # Ok(()) } /// ``` pub fn read_txn_with_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } } /// Make the read transactions `Send` by specifying they will /// not use Thread Local Storage (TLS). /// /// A thread can use any number of read transactions at a time on /// the same thread. Read transactions can be moved in between /// threads (`Send`). /// /// ## From LMDB's documentation /// /// Don't use Thread-Local Storage. Tie reader locktable slots to /// #MDB_txn objects instead of to threads. I.e. #mdb_txn_reset() keeps /// the slot reserved for the #MDB_txn object. A thread may use parallel /// read-only transactions. A read-only transaction may span threads if /// the user synchronizes its use. Applications that multiplex many /// user threads over individual OS threads need this option. Such an /// application must also serialize the write transactions in an OS /// thread, since LMDB's write locking is unaware of the user threads. /// /// # Example /// /// This example shows that the `RoTxn<'_, WithoutTls>` can be sent between threads. /// /// ``` /// use std::fs; /// use std::path::Path; /// use heed::{EnvOpenOptions, Database, EnvFlags}; /// use heed::types::*; /// /// /// Checks, at compile time, that a type can be sent accross threads. /// fn is_sendable(_x: S) {} /// /// # fn main() -> Result<(), Box> { /// let mut env_builder = EnvOpenOptions::new().read_txn_without_tls(); /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { env_builder.open(dir.path())? }; /// /// let rtxn = env.read_txn()?; /// is_sendable(rtxn); /// # Ok(()) } /// ``` pub fn read_txn_without_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } } /// Set the size of the memory map to use for this environment. /// /// It must be a multiple of the OS page size. 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> { /// let mut env_builder = EnvOpenOptions::new(); /// unsafe { env_builder.flags(EnvFlags::NO_META_SYNC); } /// let dir = tempfile::tempdir().unwrap(); /// let env = unsafe { 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. /// /// # Safety /// LMDB is backed by a memory map [^1] which comes with some safety precautions. /// /// Memory map constructors are marked `unsafe` because of the potential /// for Undefined Behavior (UB) using the map if the underlying file is /// subsequently modified, in or out of process. /// /// LMDB itself has a locking system that solves this problem, /// but it will not save you from making mistakes yourself. /// /// These are some things to take note of: /// /// - Avoid long-lived transactions, they will cause the database to grow quickly [^2] /// - Avoid aborting your process with an active transaction [^3] /// - Do not use LMDB on remote filesystems, even between processes on the same host [^4] /// - You must manage concurrent accesses yourself if using [`EnvFlags::NO_LOCK`] [^5] /// - Anything that causes LMDB's lock file to be broken will cause synchronization issues and may introduce UB [^6] /// /// `heed` itself upholds some safety invariants, including but not limited to: /// - Calling [`EnvOpenOptions::open`] twice in the same process, at the same time is OK [^7] /// /// For more details, it is highly recommended to read LMDB's official documentation. [^8] /// /// [^1]: /// [^2]: /// [^3]: /// [^4]: /// [^5]: /// [^6]: /// [^7]: /// [^8]: pub unsafe fn open>(&self, path: P) -> Result> { self.raw_open_with_encryption( path.as_ref(), #[cfg(master3)] None, ) } /// Open an encrypted-at-rest environment that will be located at the specified path. /// /// # Safety /// LMDB is backed by a memory map [^1] which comes with some safety precautions. /// /// Memory map constructors are marked `unsafe` because of the potential /// for Undefined Behavior (UB) using the map if the underlying file is /// subsequently modified, in or out of process. /// /// LMDB itself has a locking system that solves this problem, /// but it will not save you from making mistakes yourself. /// /// These are some things to take note of: /// /// - Avoid long-lived transactions, they will cause the database to grow quickly [^2] /// - Avoid aborting your process with an active transaction [^3] /// - Do not use LMDB on remote filesystems, even between processes on the same host [^4] /// - You must manage concurrent accesses yourself if using [`EnvFlags::NO_LOCK`] [^5] /// - Anything that causes LMDB's lock file to be broken will cause synchronization issues and may introduce UB [^6] /// /// `heed` itself upholds some safety invariants, including but not limited to: /// - Calling [`EnvOpenOptions::open`] twice in the same process, at the same time is OK [^7] /// /// For more details, it is highly recommended to read LMDB's official documentation. [^8] /// /// # Basic Example /// /// Creates and open a database. The [`Env`] is encrypted-at-rest using the `E` algorithm with the /// given `key`. You can find more compatible algorithms on /// [the RustCrypto/AEADs page](https://github.com/RustCrypto/AEADs#crates). /// /// Note that you cannot use **any** type of encryption algorithm as LMDB exposes a nonce of 16 bytes. /// Heed makes sure to truncate it if necessary. /// /// As an example, XChaCha20 requires a 20 bytes long nonce. However, XChaCha20 is used to protect /// against nonce misuse in systems that use randomly generated nonces i.e., to protect against /// weak RNGs. There is no need to use this kind of algorithm in LMDB since LMDB nonces aren't /// random and are guaranteed to be unique. /// /// ``` /// use std::fs; /// use std::path::Path; /// use argon2::Argon2; /// use chacha20poly1305::{ChaCha20Poly1305, Key}; /// use heed3::types::*; /// use heed3::{EnvOpenOptions, Database}; /// /// # fn main() -> Result<(), Box> { /// let env_path = tempfile::tempdir()?; /// let password = "This is the password that will be hashed by the argon2 algorithm"; /// let salt = "The salt added to the password hashes to add more security when stored"; /// /// fs::create_dir_all(&env_path)?; /// /// let mut key = Key::default(); /// Argon2::default().hash_password_into(password.as_bytes(), salt.as_bytes(), &mut key)?; /// /// // We open the environment /// let mut options = EnvOpenOptions::new(); /// let env = unsafe { /// options /// .map_size(10 * 1024 * 1024) // 10MB /// .max_dbs(3) /// .open_encrypted::(key, &env_path)? /// }; /// /// let key1 = "first-key"; /// let val1 = "this is a secret info"; /// let key2 = "second-key"; /// let val2 = "this is another secret info"; /// /// // We create database and write secret values in it /// let mut wtxn = env.write_txn()?; /// let db = env.create_database::(&mut wtxn, Some("first"))?; /// db.put(&mut wtxn, key1, val1)?; /// db.put(&mut wtxn, key2, val2)?; /// wtxn.commit()?; /// # Ok(()) } /// ``` /// /// # Example Showing limitations /// /// At the end of this example file you can see that we can not longer use the `val1` /// variable as we performed a read in the database just after fetching it and keeping /// a reference to it. /// /// That's the main limitation of LMDB with the encryption-at-rest feature: entries cannot /// be kept for too long as they are kept in a cycling buffer when decrypting them on the fly. /// /// ```compile_fail,E0499 /// use std::fs; /// use std::path::Path; /// use argon2::Argon2; /// use chacha20poly1305::{ChaCha20Poly1305, Key}; /// use heed3_encryption::types::*; /// use heed3_encryption::{EnvOpenOptions, Database}; /// /// # fn main() -> Result<(), Box> { /// let env_path = tempfile::tempdir()?; /// let password = "This is the password that will be hashed by the argon2 algorithm"; /// let salt = "The salt added to the password hashes to add more security when stored"; /// /// fs::create_dir_all(&env_path)?; /// /// let mut key = Key::default(); /// Argon2::default().hash_password_into(password.as_bytes(), salt.as_bytes(), &mut key)?; /// /// // We open the environment /// let mut options = EnvOpenOptions::::new_encrypted_with(key); /// let env = unsafe { /// options /// .map_size(10 * 1024 * 1024) // 10MB /// .max_dbs(3) /// .open(&env_path)? /// }; /// /// let key1 = "first-key"; /// let key2 = "second-key"; /// /// // We create the database /// let mut wtxn = env.write_txn()?; /// let db: Database = env.create_database(&mut wtxn, Some("first"))?; /// wtxn.commit()?; /// /// // Declare the read transaction as mutable because LMDB, when using encryption, /// // does not allow keeping keys between reads due to the use of an internal cache. /// let mut rtxn = env.read_txn()?; /// let val1 = db.get(&mut rtxn, key1)?; /// let val2 = db.get(&mut rtxn, key2)?; /// /// // This example won't compile because val1 cannot be used /// // after we performed another read in the database (val2). /// let _force_keep = val1; /// # Ok(()) } /// ``` /// /// [^1]: /// [^2]: /// [^3]: /// [^4]: /// [^5]: /// [^6]: /// [^7]: /// [^8]: #[cfg(master3)] pub unsafe fn open_encrypted(&self, key: Key, path: P) -> Result> where E: AeadMutInPlace + KeyInit, P: AsRef, { self.raw_open_with_encryption( path.as_ref(), Some((Some(encrypt_func_wrapper::), &key, ::TagSize::U32)), ) .map(|inner| EncryptedEnv { inner }) } fn raw_open_with_encryption( &self, path: &Path, #[cfg(master3)] enc: Option<(ffi::MDB_enc_func, &[u8], u32)>, ) -> Result> { let mut lock = OPENED_ENV.write().unwrap(); let path = match canonicalize_path(path) { Err(err) => { if err.kind() == NotFound && self.flags.contains(EnvFlags::NO_SUB_DIR) { 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, }; if lock.contains_key(&path) { Err(Error::EnvAlreadyOpened) } else { 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))?; #[cfg(master3)] if let Some((encrypt_func, key, tag_size)) = enc { mdb_result(ffi::mdb_env_set_encrypt( env, encrypt_func, &crate::into_val(key), tag_size, ))?; } 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 `::ENABLED` is true, we must tell // LMDB to avoid using the thread local storage, this way we // allow users to move RoTxn between threads safely. #[allow(deprecated)] // ok because NO_TLS is inside of the crate let flags = if T::ENABLED { self.flags } else { self.flags | EnvFlags::NO_TLS }; let result = ffi::mdb_env_open(env, path_str.as_ptr(), flags.bits(), 0o600); match mdb_result(result) { Ok(()) => { let env_ptr = NonNull::new(env).unwrap(); let signal_event = Arc::new(SignalEvent::manual(false)); let inserted = lock.insert(path.clone(), signal_event.clone()); debug_assert!(inserted.is_none()); Ok(Env::new(env_ptr, path, signal_event)) } Err(e) => { ffi::mdb_env_close(env); Err(e.into()) } } } } } } impl Default for EnvOpenOptions { fn default() -> Self { Self::new() } } impl Clone for EnvOpenOptions { fn clone(&self) -> Self { let Self { map_size, max_readers, max_dbs, flags, _tls_marker } = *self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker } } } heed-0.22.0/src/envs/mod.rs000064400000000000000000000221201046102023000134760ustar 00000000000000use std::cmp::Ordering; use std::collections::HashMap; use std::ffi::c_void; use std::fs::{File, Metadata}; #[cfg(unix)] use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd}; use std::panic::catch_unwind; use std::path::{Path, PathBuf}; use std::process::abort; use std::sync::{Arc, LazyLock, RwLock}; use std::time::Duration; #[cfg(windows)] use std::{ ffi::OsStr, os::windows::io::{AsRawHandle as _, BorrowedHandle, RawHandle}, }; use std::{fmt, io}; use heed_traits::{Comparator, LexicographicComparator}; use synchronoise::event::SignalEvent; use crate::mdb::ffi; #[allow(unused)] // for cargo auto doc links use crate::{Database, DatabaseFlags}; #[cfg(master3)] mod encrypted_env; mod env; mod env_open_options; #[cfg(master3)] pub use encrypted_env::EncryptedEnv; pub use env::Env; pub(crate) use env::EnvInner; pub use env_open_options::EnvOpenOptions; /// Records the current list of opened environments for tracking purposes. The canonical /// path of an environment is removed when either an `Env` or `EncryptedEnv` is closed. static OPENED_ENV: LazyLock>>> = LazyLock::new(RwLock::default); /// 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(|signal_event| EnvClosingEvent(signal_event.clone())) } /// Contains information about the environment. #[derive(Debug, Clone, Copy)] pub struct EnvInfo { /// Address of the map, if fixed. pub map_addr: *mut c_void, /// Size of the data memory map. pub map_size: usize, /// ID of the last used page. pub last_page_number: usize, /// ID of the last committed transaction. pub last_txn_id: usize, /// Maximum number of reader slots in the environment. pub maximum_number_of_readers: u32, /// Number of reader slots used in the environment. pub number_of_readers: u32, } /// A structure that can be used to wait for the closing event. /// Multiple threads can wait on this event. #[derive(Clone)] pub struct EnvClosingEvent(Arc); impl EnvClosingEvent { /// Blocks this thread until the environment is effectively closed. /// /// # Safety /// /// Make sure that you don't have any copy of the environment in the thread /// that is waiting for a close event. If you do, you will have a deadlock. pub fn wait(&self) { self.0.wait() } /// Blocks this thread until either the environment has been closed /// or until the timeout elapses. Returns `true` if the environment /// has been effectively closed. pub fn wait_timeout(&self, timeout: Duration) -> bool { self.0.wait_timeout(timeout) } } impl fmt::Debug for EnvClosingEvent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnvClosingEvent").finish() } } // 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() } /// A helper function that transforms the LMDB types into Rust types (`MDB_val` into slices) /// and vice versa, the Rust types into C types (`Ordering` into an integer). /// /// # Safety /// /// `a` and `b` should both properly aligned, valid for reads and should point to a valid /// [`MDB_val`][ffi::MDB_val]. An [`MDB_val`][ffi::MDB_val] (consists of a pointer and size) is /// valid when its pointer (`mv_data`) is valid for reads of `mv_size` bytes and is not null. unsafe extern "C" fn custom_key_cmp_wrapper( a: *const ffi::MDB_val, b: *const ffi::MDB_val, ) -> i32 { let a = unsafe { ffi::from_val(*a) }; let b = unsafe { ffi::from_val(*b) }; match catch_unwind(|| C::compare(a, b)) { Ok(Ordering::Less) => -1, Ok(Ordering::Equal) => 0, Ok(Ordering::Greater) => 1, Err(_) => abort(), } } /// A representation of LMDB's default comparator behavior. /// /// This enum is used to indicate the absence of a custom comparator for an LMDB /// database instance. When a [`Database`] is created or opened with /// [`DefaultComparator`], it signifies that the comparator should not be explicitly /// set via [`ffi::mdb_set_compare`]. Consequently, the database /// instance utilizes LMDB's built-in default comparator, which inherently performs /// lexicographic comparison of keys. /// /// This comparator's lexicographic implementation is employed in scenarios involving /// prefix iterators. Specifically, methods other than [`Comparator::compare`] are utilized /// to determine the lexicographic successors and predecessors of byte sequences, which /// is essential for these iterators' operation. /// /// When a custom comparator is provided, the wrapper is responsible for setting /// it with the [`ffi::mdb_set_compare`] function, which overrides the default comparison /// behavior of LMDB with the user-defined logic. #[derive(Debug)] 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 } } /// A representation of LMDB's `MDB_INTEGERKEY` and `MDB_INTEGERDUP` comparator behavior. /// /// This enum is used to indicate a table should be sorted by the keys numeric /// value in native byte order. When a [`Database`] is created or opened with /// [`IntegerComparator`], it signifies that the comparator should not be explicitly /// set via [`ffi::mdb_set_compare`], instead the flag [`DatabaseFlags::INTEGER_KEY`] /// or [`DatabaseFlags::INTEGER_DUP`] is set on the table. /// /// This can only be used on certain types: either `u32` or `usize`. /// The keys must all be of the same size. #[derive(Debug)] pub enum IntegerComparator {} impl Comparator for IntegerComparator { fn compare(a: &[u8], b: &[u8]) -> Ordering { #[cfg(target_endian = "big")] return a.cmp(b); #[cfg(target_endian = "little")] { let len = a.len(); for i in (0..len).rev() { match a[i].cmp(&b[i]) { Ordering::Equal => continue, other => return other, } } Ordering::Equal } } } /// Whether to perform compaction while copying an environment. #[derive(Debug, Copy, Clone)] pub enum CompactionOption { /// Omit free pages and sequentially renumber all pages in output. /// /// This option consumes more CPU and runs more slowly than the default. /// Currently it fails if the environment has suffered a page leak. Enabled, /// Copy everything without taking any special action about free pages. Disabled, } /// Whether to enable or disable flags in [`Env::set_flags`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum FlagSetMode { /// Enable the flags. Enable, /// Disable the flags. Disable, } impl FlagSetMode { /// Convert the enum into the `i32` required by LMDB. /// "A non-zero value sets the flags, zero clears them." /// fn as_mdb_env_set_flags_input(self) -> i32 { match self { Self::Enable => 1, Self::Disable => 0, } } } heed-0.22.0/src/iteration_method.rs000064400000000000000000000021051046102023000153030ustar 00000000000000//! The set of possible iteration methods for the different iterators. use crate::cursor::MoveOperation; /// The trait used to define the way iterators behave. pub trait IterationMethod { /// The internal operation to move the cursor through entries. const MOVE_OPERATION: MoveOperation; } /// Moves to the next or previous key if there /// are no more values associated with the current key. #[derive(Debug, Clone, Copy)] pub enum MoveThroughDuplicateValues {} impl IterationMethod for MoveThroughDuplicateValues { const MOVE_OPERATION: MoveOperation = MoveOperation::Any; } /// Moves between keys and ignores the duplicate values of keys. #[derive(Debug, Clone, Copy)] pub enum MoveBetweenKeys {} impl IterationMethod for MoveBetweenKeys { const MOVE_OPERATION: MoveOperation = MoveOperation::NoDup; } /// Moves only on the duplicate values of a given key and ignores other keys. #[derive(Debug, Clone, Copy)] pub enum MoveOnCurrentKeyDuplicates {} impl IterationMethod for MoveOnCurrentKeyDuplicates { const MOVE_OPERATION: MoveOperation = MoveOperation::Dup; } heed-0.22.0/src/iterator/iter.rs000064400000000000000000000700711046102023000145500ustar 00000000000000use std::borrow::Cow; use std::marker; use types::LazyDecode; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; /// A read-only iterator structure. pub struct RoIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RoCursor<'txn>) -> RoIter<'txn, KC, DC, IM> { RoIter { cursor, move_on_first: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?.move_between_keys(); /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn move_between_keys(self) -> RoIter<'txn, KC, DC, MoveBetweenKeys> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?.move_through_duplicate_values(); /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// /// drop(iter); /// wtxn.commit()?; /// # Ok(()) } /// ``` pub fn move_through_duplicate_values(self) -> RoIter<'txn, KC, DC, MoveThroughDuplicateValues> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoIter<'txn, KC2, DC2, IM> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_first(IM::MOVE_OPERATION) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { self.cursor.move_on_last(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_last(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoIter").finish() } } /// A read-write iterator structure. pub struct RwIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RwCursor<'txn>) -> RwIter<'txn, KC, DC, IM> { RwIter { cursor, move_on_first: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwIter::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwIter<'txn, KC, DC, MoveBetweenKeys> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values(self) -> RwIter<'txn, KC, DC, MoveThroughDuplicateValues> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwIter<'txn, KC2, DC2, IM> { RwIter { cursor: self.cursor, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_first(IM::MOVE_OPERATION) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { self.cursor.move_on_last(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_last(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwIter").finish() } } /// A reverse read-only iterator structure. pub struct RoRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RoCursor<'txn>) -> RoRevIter<'txn, KC, DC, IM> { RoRevIter { cursor, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRevIter<'txn, KC, DC, MoveBetweenKeys> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRevIter<'txn, KC, DC, MoveThroughDuplicateValues> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRevIter<'txn, KC2, DC2, IM> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRevIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRevIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RoRevIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; self.cursor.move_on_last(IM::MOVE_OPERATION) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_first(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_first(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRevIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRevIter").finish() } } /// A reverse read-write iterator structure. pub struct RwRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } impl<'txn, KC, DC, IM> RwRevIter<'txn, KC, DC, IM> { pub(crate) fn new(cursor: RwCursor<'txn>) -> RwRevIter<'txn, KC, DC, IM> { RwRevIter { cursor, move_on_last: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevIter::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevIter<'txn, KC, DC, MoveBetweenKeys> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRevIter<'txn, KC, DC, MoveThroughDuplicateValues> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRevIter<'txn, KC2, DC2, IM> { RwRevIter { cursor: self.cursor, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRevIter<'txn, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRevIter<'txn, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevIter<'txn, KC, LazyDecode, IM> { self.remap_types::>() } } impl<'txn, KC, DC, IM> Iterator for RwRevIter<'txn, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; self.cursor.move_on_last(IM::MOVE_OPERATION) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_first(IM::MOVE_OPERATION) } else { match (self.cursor.current(), self.cursor.move_on_first(IM::MOVE_OPERATION)) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), }, Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRevIter<'_, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevIter").finish() } } heed-0.22.0/src/iterator/mod.rs000064400000000000000000000641451046102023000143710ustar 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}; /// This is just set of tests to check that the Cursors /// are not Send. We need to use doc test as it is the /// only way to check for expected compilation failures. /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoIter; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoRevIter; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoRange; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoRevRange; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoPrefix; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RoRevPrefix; /// fn is_send() {} /// is_send::>(); /// ``` /// /// Starting the next section with the Read-write Iterators. /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwIter; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwRevIter; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwRange; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwRevRange; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwPrefix; /// fn is_send() {} /// is_send::>(); /// ``` /// /// ```rust,compile_fail /// use heed::types::*; /// use heed::RwRevPrefix; /// fn is_send() {} /// is_send::>(); /// ``` #[doc(hidden)] #[allow(unused)] fn test_txns_are_not_send() {} #[cfg(test)] mod tests { use std::ops; #[test] fn prefix_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], "hello").unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 254, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 104, 101, 108, 108, 111], "hello").unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 119, 111, 114, 108, 100], "world").unwrap(); db.put(&mut wtxn, &[255, 255, 1, 0, 119, 111, 114, 108, 100], "world").unwrap(); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); // Lets check that we can prefix_iter on that sequence with the key "255". let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0u8, 0, 0, 255, 104, 101, 108, 108, 111][..], "hello")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], "world")) ); // Lets check that we can prefix_iter on that sequence with the key "255". let mut iter = db.prefix_iter(&wtxn, &[255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 254, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 104, 101, 108, 108, 111][..], "hello")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], "world")) ); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } #[test] fn iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), None); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.iter(&wtxn).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn range_iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), None); assert_eq!(iter.last().transpose().unwrap(), None); let range = 2..=4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((4, ()))); let range = 2..4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((3, ()))); let range = 2..4; let mut iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let range = 2..2; let iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.last().transpose().unwrap(), None); #[allow(clippy::reversed_empty_ranges)] let range = 2..=1; let iter = db.range(&wtxn, &range).unwrap(); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.range(&wtxn, &(..)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn range_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 1], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 2], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db .range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); // Lets check that we can range_iter on that sequence with the key "255". let mut iter = db .range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 2][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } #[test] fn prefix_iter_last() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_prefix_iter_last() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 1]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_prefix_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 254, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 104, 101, 108, 108, 111], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 0, 255, 119, 111, 114, 108, 100], &()).unwrap(); db.put(&mut wtxn, &[255, 255, 1, 0, 119, 111, 114, 108, 100], &()).unwrap(); // Lets check that we can get last entry on that sequence ending with the key "255". let iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.last().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); // Lets check that we can prefix_iter on that sequence ending with the key "255". let mut iter = db.rev_prefix_iter(&wtxn, &[0, 0, 0, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[0, 0, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); let mut iter = db.rev_prefix_iter(&wtxn, &[255, 255]).unwrap(); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 1, 0, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 119, 111, 114, 108, 100][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 255, 104, 101, 108, 108, 111][..], ())) ); assert_eq!( iter.next().transpose().unwrap(), Some((&[255, 255, 0, 254, 119, 111, 114, 108, 100][..], ())) ); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_range_iter_last() { use crate::byteorder::BigEndian; use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); type BEI32 = I32; // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &1, &()).unwrap(); db.put(&mut wtxn, &2, &()).unwrap(); db.put(&mut wtxn, &3, &()).unwrap(); db.put(&mut wtxn, &4, &()).unwrap(); // Lets check that we properly get the last entry. let iter = db.rev_range(&wtxn, &(1..=3)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(0..4)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(0..=5)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((3, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((2, ()))); assert_eq!(iter.next().transpose().unwrap(), Some((1, ()))); assert_eq!(iter.last().transpose().unwrap(), None); let iter = db.rev_range(&wtxn, &(0..=5)).unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((1, ()))); let mut iter = db.rev_range(&wtxn, &(4..=4)).unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((4, ()))); assert_eq!(iter.last().transpose().unwrap(), None); wtxn.abort(); } #[test] fn rev_range_iter_last_with_byte_255() { use crate::types::*; use crate::EnvOpenOptions; let dir = tempfile::tempdir().unwrap(); let env = unsafe { EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs(3000) .open(dir.path()) .unwrap() }; let mut wtxn = env.write_txn().unwrap(); let db = env.create_database::(&mut wtxn, None).unwrap(); wtxn.commit().unwrap(); // Create an ordered list of keys... let mut wtxn = env.write_txn().unwrap(); db.put(&mut wtxn, &[0, 0, 0], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 1], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 0, 2], &()).unwrap(); db.put(&mut wtxn, &[0, 0, 1, 0], &()).unwrap(); // Lets check that we properly get the last entry. let iter = db .rev_range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.last().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); // Lets check that we can range_iter on that sequence with the key "255". let mut iter = db .rev_range( &wtxn, &(ops::Bound::Excluded(&[0, 0, 0][..]), ops::Bound::Included(&[0, 0, 1, 0][..])), ) .unwrap(); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 1, 0][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 2][..], ()))); assert_eq!(iter.next().transpose().unwrap(), Some((&[0, 0, 0, 1][..], ()))); assert_eq!(iter.next().transpose().unwrap(), None); drop(iter); wtxn.abort(); } } heed-0.22.0/src/iterator/prefix.rs000064400000000000000000000740041046102023000151020ustar 00000000000000use std::borrow::Cow; use std::marker; use heed_traits::LexicographicComparator; use types::LazyDecode; use crate::cursor::MoveOperation; use crate::envs::DefaultComparator; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; /// Advances `bytes` to the immediate lexicographic successor of equal length, as /// defined by the `C` comparator. If no successor exists (i.e. `bytes` is the maximal /// value), it remains unchanged and the function returns `false`. Otherwise, updates /// `bytes` and returns `true`. fn advance_prefix(bytes: &mut [u8]) -> bool { let mut idx = bytes.len(); while idx > 0 && bytes[idx - 1] == C::max_elem() { idx -= 1; } if idx == 0 { return false; } bytes[idx - 1] = C::successor(bytes[idx - 1]).expect("Cannot advance byte; this is a bug."); for i in (idx + 1)..=bytes.len() { bytes[i - 1] = C::min_elem(); } true } /// Retreats `bytes` to the immediate lexicographic predecessor of equal length, as /// defined by the `C` comparator. If no predecessor exists (i.e. `bytes` is the minimum /// value), it remains unchanged and the function returns `false`. Otherwise, updates /// `bytes` and returns `true`. fn retreat_prefix(bytes: &mut [u8]) -> bool { let mut idx = bytes.len(); while idx > 0 && bytes[idx - 1] == C::min_elem() { idx -= 1; } if idx == 0 { return false; } bytes[idx - 1] = C::predecessor(bytes[idx - 1]).expect("Cannot retreat byte; this is a bug."); for i in (idx + 1)..=bytes.len() { bytes[i - 1] = C::max_elem(); } true } fn move_on_prefix_end<'txn, C: LexicographicComparator>( cursor: &mut RoCursor<'txn>, prefix: &mut [u8], ) -> Result> { if advance_prefix::(prefix) { let result = cursor .move_on_key_greater_than_or_equal_to(prefix) .and_then(|_| cursor.move_on_prev(MoveOperation::NoDup)); retreat_prefix::(prefix); result } else { // `prefix` is the maximum among all bytes sequence of the same length. cursor.move_on_last(MoveOperation::NoDup) } } /// A read-only prefix iterator structure. pub struct RoPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, prefix: Vec, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoPrefix<'txn, KC, DC, C, IM> { RoPrefix { cursor, prefix, move_on_first: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoPrefix<'txn, KC2, DC2, C, IM> { RoPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoPrefix").finish() } } /// A read-write prefix iterator structure. pub struct RwPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, prefix: Vec, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RwCursor<'txn>, prefix: Vec) -> RwPrefix<'txn, KC, DC, C, IM> { RwPrefix { cursor, prefix, move_on_first: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwPrefix::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwPrefix<'txn, KC2, DC2, C, IM> { RwPrefix { cursor: self.cursor, prefix: self.prefix, move_on_first: self.move_on_first, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_first { self.move_on_first = false; self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { self.cursor.move_on_next(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_first { move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwPrefix").finish() } } /// A reverse read-only prefix iterator structure. pub struct RoRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, prefix: Vec, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoRevPrefix<'txn, KC, DC, C, IM> { RoRevPrefix { cursor, prefix, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RoRevPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RoRevPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RoRevPrefix<'txn, KC2, DC2, C, IM> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RoRevPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RoRevPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoRevPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { let current = self.cursor.current(); let start = self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RoRevPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoRevPrefix").finish() } } /// A reverse read-write prefix iterator structure. pub struct RwRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, prefix: Vec, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwRevPrefix<'txn, KC, DC, C, IM> { pub(crate) fn new(cursor: RwCursor<'txn>, prefix: Vec) -> RwRevPrefix<'txn, KC, DC, C, IM> { RwRevPrefix { cursor, prefix, move_on_last: true, _phantom: marker::PhantomData } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevPrefix::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevPrefix<'txn, KC, DC, C, MoveBetweenKeys> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Move through key/values entries and output duplicate values. /// /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, ) -> RwRevPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the codec types of this iterator, specifying the codecs. pub fn remap_types(self) -> RwRevPrefix<'txn, KC2, DC2, C, IM> { RwRevPrefix { cursor: self.cursor, prefix: self.prefix, move_on_last: self.move_on_last, _phantom: marker::PhantomData, } } /// Change the key codec type of this iterator, specifying the new codec. pub fn remap_key_type(self) -> RwRevPrefix<'txn, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. pub fn remap_data_type(self) -> RwRevPrefix<'txn, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevPrefix<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwRevPrefix<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, C: LexicographicComparator, IM: IterationMethod, { type Item = Result<(KC::DItem, DC::DItem)>; fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } fn last(mut self) -> Option { let result = if self.move_on_last { self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix) } else { let current = self.cursor.current(); let start = self.cursor.move_on_key_greater_than_or_equal_to(&self.prefix); match (current, start) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) } (Ok(_), Ok(_)) => Ok(None), (Err(e), _) | (_, Err(e)) => Err(e), } }; match result { Ok(Some((key, data))) => { if key.starts_with(&self.prefix) { match (KC::bytes_decode(key), DC::bytes_decode(data)) { (Ok(key), Ok(data)) => Some(Ok((key, data))), (Err(e), _) | (_, Err(e)) => Some(Err(Error::Decoding(e))), } } else { None } } Ok(None) => None, Err(e) => Some(Err(e)), } } } impl fmt::Debug for RwRevPrefix<'_, KC, DC, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevPrefix").finish() } } heed-0.22.0/src/iterator/range.rs000064400000000000000000001007441046102023000147020ustar 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, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_start: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { pub(crate) fn new( cursor: RoCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RoRange<'txn, KC, DC, C, 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, C, 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, C, 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, C, 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, C, 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, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRange<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoRange<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, C: Comparator, { 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) => C::compare(key, end).is_le(), Bound::Excluded(end) => C::compare(key, end).is_lt(), 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 C::compare(ckey, key).is_ne() => { 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) => C::compare(key, start).is_ge(), Bound::Excluded(start) => C::compare(key, start).is_gt(), 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, C, 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, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_start: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwRange<'txn, KC, DC, C, IM> { pub(crate) fn new( cursor: RwCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RwRange<'txn, KC, DC, C, IM> { RwRange { cursor, move_on_start: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRange::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRange<'txn, KC, DC, C, 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, C, 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, C, 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, C, 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, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRange<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwRange<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, C: Comparator, { 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) => C::compare(key, end).is_le(), Bound::Excluded(ref end) => C::compare(key, end).is_lt(), 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 C::compare(ckey, key).is_ne() => { 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) => C::compare(key, start).is_ge(), Bound::Excluded(start) => C::compare(key, start).is_gt(), 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, C, 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, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RoCursor<'txn>, move_on_end: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { pub(crate) fn new( cursor: RoCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RoRevRange<'txn, KC, DC, C, 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, C, 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, C, 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, C, 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, C, 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, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RoRevRange<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RoRevRange<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, C: Comparator, { 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) => C::compare(key, start).is_ge(), Bound::Excluded(start) => C::compare(key, start).is_gt(), 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 C::compare(ckey, key).is_ne() => { 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) => C::compare(key, end).is_le(), Bound::Excluded(end) => C::compare(key, end).is_lt(), 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, C, 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, C = DefaultComparator, IM = MoveThroughDuplicateValues> { cursor: RwCursor<'txn>, move_on_end: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } impl<'txn, KC, DC, C, IM> RwRevRange<'txn, KC, DC, C, IM> { pub(crate) fn new( cursor: RwCursor<'txn>, start_bound: Bound>, end_bound: Bound>, ) -> RwRevRange<'txn, KC, DC, C, IM> { RwRevRange { cursor, move_on_end: true, start_bound, end_bound, _phantom: marker::PhantomData, } } /// Delete the entry the cursor is currently pointing to. /// /// Returns `true` if the entry was successfully deleted. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database /// while modifying it. /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn del_current(&mut self) -> Result { self.cursor.del_current() } /// Write a new value to the current entry. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current<'a>( &mut self, key: &'a KC::EItem, data: &'a DC::EItem, ) -> Result where KC: BytesEncode<'a>, DC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = DC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current(&key_bytes, &data_bytes) } /// Write a new value to the current entry. The entry is written with the specified flags. /// /// The given key **must** be equal to the one this cursor is pointing otherwise the database /// can be put into an inconsistent state. /// /// Returns `true` if the entry was successfully written. /// /// > This is intended to be used when the new data is the same size as the old. /// > Otherwise it will simply perform a delete of the old record followed by an insert. /// /// # Safety /// /// Please read the safety notes of the [`RwRevRange::put_current`] method. pub unsafe fn put_current_reserved_with_flags<'a, F>( &mut self, flags: PutFlags, key: &'a KC::EItem, data_size: usize, write_func: F, ) -> Result where KC: BytesEncode<'a>, F: FnOnce(&mut ReservedSpace) -> io::Result<()>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; self.cursor.put_current_reserved_with_flags(flags, &key_bytes, data_size, write_func) } /// Insert a key-value pair in this database. The entry is written with the specified flags and data codec. /// /// For more info, see [`RwIter::put_current_with_options`]. /// /// # Safety /// /// It is _[undefined behavior]_ to keep a reference of a value from this database while /// modifying it, so you can't use the key/value that comes from the cursor to feed /// this function. /// /// In other words: Transform the key and value that you borrow from this database into an owned /// version of them (e.g. `&str` into `String`). /// /// > [Values returned from the database are valid only until a subsequent update operation, /// > or the end of the transaction.](http://www.lmdb.tech/doc/group__mdb.html#structMDB__val) /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub unsafe fn put_current_with_options<'a, NDC>( &mut self, flags: PutFlags, key: &'a KC::EItem, data: &'a NDC::EItem, ) -> Result<()> where KC: BytesEncode<'a>, NDC: BytesEncode<'a>, { let key_bytes: Cow<[u8]> = KC::bytes_encode(key).map_err(Error::Encoding)?; let data_bytes: Cow<[u8]> = NDC::bytes_encode(data).map_err(Error::Encoding)?; self.cursor.put_current_with_flags(flags, &key_bytes, &data_bytes) } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. pub fn move_between_keys(self) -> RwRevRange<'txn, KC, DC, C, 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, C, 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, C, 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, C, 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, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. pub fn lazily_decode_data(self) -> RwRevRange<'txn, KC, LazyDecode, C, IM> { self.remap_types::>() } } impl<'txn, KC, DC, C, IM> Iterator for RwRevRange<'txn, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, IM: IterationMethod, C: Comparator, { 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) => C::compare(key, start).is_ge(), Bound::Excluded(start) => C::compare(key, start).is_gt(), 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 C::compare(ckey, key).is_ne() => { 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) => C::compare(key, end).is_le(), Bound::Excluded(end) => C::compare(key, end).is_lt(), 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, C, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RwRevRange").finish() } } heed-0.22.0/src/lib.rs000064400000000000000000000175701046102023000125270ustar 00000000000000#![doc( html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon.ico?raw=true" )] #![doc( html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true" )] //! `heed` and `heed3` are high-level wrappers of [LMDB]. //! //! - `heed` is a wrapper around LMDB on the `mdb.master` branch, //! - `heed3` derives from the `heed` wrapper but on the `mdb.master3` branch. //! //! The `heed3` crate will be stable once the LMDB version on the `mdb.master3` branch //! will be officially released. It features encryption-at-rest and checksumming features //! that the `heed` crate doesn't. //! //! The [cookbook] will give you a variety of complete Rust programs to use with `heed`. //! //! ---- //! //! This crate simply facilitates the use of LMDB by providing a mechanism to store and //! retrieve Rust types. It abstracts away some of the complexities of the raw LMDB usage //! while retaining its performance characteristics. The functionality is achieved with the help //! of the serde library for data serialization concerns. //! //! LMDB stands for Lightning Memory-Mapped Database, which utilizes memory-mapped files //! for efficient data storage and retrieval by mapping file content directly into the virtual //! address space. `heed` derives its efficiency from the underlying LMDB without imposing //! additional runtime costs. //! //! [LMDB]: https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database //! //! # Examples //! //! Open a database that will support some typed key/data and ensure, at compile time, //! that you'll write those types and not others. //! //! ``` //! use std::fs; //! use std::path::Path; //! use heed::{EnvOpenOptions, Database}; //! use heed::types::*; //! //! # fn main() -> Result<(), Box> { //! let dir = tempfile::tempdir()?; //! let env = unsafe { EnvOpenOptions::new().open(dir.path())? }; //! //! // we will open the default unnamed database //! let mut wtxn = env.write_txn()?; //! let db: Database> = env.create_database(&mut wtxn, None)?; //! //! // opening a write transaction //! db.put(&mut wtxn, "seven", &7)?; //! db.put(&mut wtxn, "zero", &0)?; //! db.put(&mut wtxn, "five", &5)?; //! db.put(&mut wtxn, "three", &3)?; //! wtxn.commit()?; //! //! // opening a read transaction //! // to check if those values are now available //! let mut rtxn = env.read_txn()?; //! //! let ret = db.get(&rtxn, "zero")?; //! assert_eq!(ret, Some(0)); //! //! let ret = db.get(&rtxn, "five")?; //! assert_eq!(ret, Some(5)); //! # Ok(()) } //! ``` #![warn(missing_docs)] pub mod cookbook; mod cursor; mod databases; mod envs; pub mod iteration_method; mod iterator; mod mdb; mod reserved_space; mod txn; use std::ffi::CStr; use std::{error, fmt, io, mem, result}; use heed_traits as traits; pub use {byteorder, heed_types as types}; use self::cursor::{RoCursor, RwCursor}; pub use self::databases::{Database, DatabaseOpenOptions, DatabaseStat}; #[cfg(master3)] pub use self::databases::{EncryptedDatabase, EncryptedDatabaseOpenOptions}; #[cfg(master3)] pub use self::envs::EncryptedEnv; pub use self::envs::{ env_closing_event, CompactionOption, DefaultComparator, Env, EnvClosingEvent, EnvInfo, EnvOpenOptions, FlagSetMode, IntegerComparator, }; 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::{AnyTls, RoTxn, RwTxn, TlsUsage, WithTls, WithoutTls}; /// 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_master = LmdbVersion { /// string: "LMDB 0.9.70: (December 19, 2015)", /// major: 0, /// minor: 9, /// patch: 70, /// }; /// /// let expected_master3 = LmdbVersion { /// string: "LMDB 0.9.90: (May 1, 2017)", /// major: 0, /// minor: 9, /// patch: 90, /// }; /// /// let actual = lmdb_version(); /// assert!(actual == expected_master || actual == expected_master3); /// ``` pub fn lmdb_version() -> LmdbVersion { let mut major = mem::MaybeUninit::uninit(); let mut minor = mem::MaybeUninit::uninit(); let mut patch = mem::MaybeUninit::uninit(); unsafe { let string_ptr = mdb::ffi::mdb_version(major.as_mut_ptr(), minor.as_mut_ptr(), patch.as_mut_ptr()); LmdbVersion { string: CStr::from_ptr(string_ptr).to_str().unwrap(), major: major.assume_init(), minor: minor.assume_init(), patch: patch.assume_init(), } } } /// An error that encapsulates all possible errors in this crate. #[derive(Debug)] pub enum Error { /// I/O error: can come from the standard library or be a rewrapped [`MdbError`]. Io(io::Error), /// LMDB error. Mdb(MdbError), /// Encoding error. Encoding(BoxedError), /// Decoding error. Decoding(BoxedError), /// The environment is already open in this program; /// close it to be able to open it again with different options. EnvAlreadyOpened, } 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::EnvAlreadyOpened => f.write_str( "environment already open in this program; \ close it to be able to open it again 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 == unsafe { $txn.env_mut_ptr().as_mut() as *mut _ 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.22.0/src/mdb/lmdb_error.rs000064400000000000000000000155101046102023000146420ustar 00000000000000use std::error::Error as StdError; use std::ffi::CStr; use std::os::raw::c_char; use std::{fmt, str}; use libc::c_int; #[cfg(master3)] use lmdb_master3_sys as ffi; #[cfg(not(master3))] use lmdb_master_sys as ffi; /// An LMDB error kind. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Error { /// A key/data pair already exists. /// /// May be also returned by append functions. Data passed /// doesn't respect the database ordering. KeyExist, /// A key/data pair was not found (EOF). NotFound, /// Requested page not found - this usually indicates corruption. PageNotFound, /// Located page was wrong type. Corrupted, /// Update of meta page failed or environment had fatal error. Panic, /// Environment version mismatch. VersionMismatch, /// File is not a valid LMDB file. Invalid, /// Environment mapsize reached. MapFull, /// Environment maxdbs reached. DbsFull, /// Environment maxreaders reached. ReadersFull, /// Too many TLS keys in use - Windows only. TlsFull, /// Txn has too many dirty pages. TxnFull, /// Cursor stack too deep - internal error. CursorFull, /// Page has not enough space - internal error. PageFull, /// Database contents grew beyond environment mapsize. MapResized, /// Operation and DB incompatible, or DB type changed. This can mean: /// - The operation expects an MDB_DUPSORT / MDB_DUPFIXED database. /// - Opening a named DB when the unnamed DB has MDB_DUPSORT / MDB_INTEGERKEY. /// - Accessing a data record as a database, or vice versa. /// - The database was dropped and recreated with different flags. Incompatible, /// Invalid reuse of reader locktable slot. BadRslot, /// Transaction cannot recover - it must be aborted. BadTxn, /// Unsupported size of key/DB name/data, or wrong DUP_FIXED size. /// /// Common causes of this error: /// - You tried to store a zero-length key /// - You tried to store a key longer than the max allowed key (511 bytes by default) /// - You are using [DUP_SORT](crate::DatabaseFlags::DUP_SORT) and trying to store a /// value longer than the max allowed key size (511 bytes by default) /// /// In the last two cases you can enable the `longer-keys` feature to increase the max allowed key size. BadValSize, /// The specified DBI was changed unexpectedly. BadDbi, /// Unexpected problem - transaction should abort. Problem, /// Page checksum incorrect. #[cfg(master3)] BadChecksum, /// Encryption/decryption failed. #[cfg(master3)] CryptoFail, /// Environment encryption mismatch. #[cfg(master3)] EnvEncryption, /// Other error. Other(c_int), } impl Error { /// Returns `true` if the given error is [`Error::NotFound`]. pub fn not_found(&self) -> bool { *self == Error::NotFound } /// Converts a raw error code to an `Error`. pub fn from_err_code(err_code: c_int) -> Error { match err_code { ffi::MDB_KEYEXIST => Error::KeyExist, ffi::MDB_NOTFOUND => Error::NotFound, ffi::MDB_PAGE_NOTFOUND => Error::PageNotFound, ffi::MDB_CORRUPTED => Error::Corrupted, ffi::MDB_PANIC => Error::Panic, ffi::MDB_VERSION_MISMATCH => Error::VersionMismatch, ffi::MDB_INVALID => Error::Invalid, ffi::MDB_MAP_FULL => Error::MapFull, ffi::MDB_DBS_FULL => Error::DbsFull, ffi::MDB_READERS_FULL => Error::ReadersFull, ffi::MDB_TLS_FULL => Error::TlsFull, ffi::MDB_TXN_FULL => Error::TxnFull, ffi::MDB_CURSOR_FULL => Error::CursorFull, ffi::MDB_PAGE_FULL => Error::PageFull, ffi::MDB_MAP_RESIZED => Error::MapResized, ffi::MDB_INCOMPATIBLE => Error::Incompatible, ffi::MDB_BAD_RSLOT => Error::BadRslot, ffi::MDB_BAD_TXN => Error::BadTxn, ffi::MDB_BAD_VALSIZE => Error::BadValSize, ffi::MDB_BAD_DBI => Error::BadDbi, ffi::MDB_PROBLEM => Error::Problem, #[cfg(master3)] ffi::MDB_BAD_CHECKSUM => Error::BadChecksum, #[cfg(master3)] ffi::MDB_CRYPTO_FAIL => Error::CryptoFail, #[cfg(master3)] ffi::MDB_ENV_ENCRYPTION => Error::EnvEncryption, 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, #[cfg(master3)] Error::BadChecksum => ffi::MDB_BAD_CHECKSUM, #[cfg(master3)] Error::CryptoFail => ffi::MDB_CRYPTO_FAIL, #[cfg(master3)] Error::EnvEncryption => ffi::MDB_ENV_ENCRYPTION, 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.22.0/src/mdb/lmdb_ffi.rs000064400000000000000000000041611046102023000142550ustar 00000000000000use std::ptr; pub use ffi::{ mdb_cursor_close, mdb_cursor_del, mdb_cursor_get, mdb_cursor_open, mdb_cursor_put, mdb_dbi_open, mdb_del, mdb_drop, mdb_env_close, mdb_env_copyfd2, mdb_env_create, mdb_env_get_fd, mdb_env_get_flags, mdb_env_get_maxkeysize, mdb_env_get_maxreaders, mdb_env_info, mdb_env_open, mdb_env_set_flags, mdb_env_set_mapsize, mdb_env_set_maxdbs, mdb_env_set_maxreaders, mdb_env_stat, mdb_env_sync, mdb_filehandle_t, mdb_get, mdb_put, mdb_reader_check, mdb_set_compare, mdb_set_dupsort, mdb_stat, mdb_txn_abort, mdb_txn_begin, mdb_txn_commit, mdb_txn_id, mdb_version, MDB_cursor, MDB_dbi, MDB_env, MDB_stat, MDB_txn, MDB_val, MDB_CP_COMPACT, MDB_CURRENT, MDB_RDONLY, MDB_RESERVE, }; #[cfg(master3)] pub use ffi::{mdb_env_set_encrypt, MDB_enc_func}; #[cfg(master3)] use lmdb_master3_sys as ffi; #[cfg(not(master3))] 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.22.0/src/mdb/lmdb_flags.rs000064400000000000000000000454041046102023000146120ustar 00000000000000use bitflags::bitflags; #[cfg(master3)] use lmdb_master3_sys as ffi; #[cfg(not(master3))] use lmdb_master_sys as ffi; #[allow(unused)] // for cargo auto doc links use crate::{Database, IntegerComparator}; 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 FIXED_MAP = ffi::MDB_FIXEDMAP; /// No environment directory. const NO_SUB_DIR = ffi::MDB_NOSUBDIR; /// Don't fsync after commit. const NO_SYNC = ffi::MDB_NOSYNC; /// Open the previous transaction. const PREV_SNAPSHOT = ffi::MDB_PREVSNAPSHOT; /// 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. // Note to self: When removing this flag from here, we must introduce an // internal-only AllEnvFlags akin to the AllDatabaseFlags bitflags. #[deprecated(since="0.22.0", note="please use `EnvOpenOptions::read_txn_with_tls` or `EnvOpenOptions::read_txn_without_tls` instead")] 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. /// /// It is recommended to set the comparator to [`IntegerComparator`](crate::IntegerComparator), /// rather than setting this flag manually. const INTEGER_KEY = ffi::MDB_INTEGERKEY; /// With [`DatabaseFlags::DUP_SORT`], sorted dup items have fixed size. const DUP_FIXED = ffi::MDB_DUPFIXED; /// With [`DatabaseFlags::DUP_SORT`], dups are [`DatabaseFlags::INTEGER_KEY`]-style integers. const INTEGER_DUP = ffi::MDB_INTEGERDUP; /// With [`DatabaseFlags::DUP_SORT`], use reverse string dups. const REVERSE_DUP = ffi::MDB_REVERSEDUP; /// Create DB if not already existing. const CREATE = ffi::MDB_CREATE; } } bitflags! { /// LMDB database flags (see for more details). // It is a subset of the whole list of possible flags LMDB exposes but // we only want users to be able to specify these with the DUP flags. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct DatabaseFlags: u32 { /// Use reverse string keys. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::REVERSE_KEY) /// .name("reverse-key") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &"bonjour", &())?; /// db.put(&mut wtxn, &"hello", &())?; /// db.put(&mut wtxn, &"hola", &())?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some(("hola", ()))); /// 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(("hola", ()))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const REVERSE_KEY = ffi::MDB_REVERSEKEY; /// Use sorted duplicates. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const DUP_SORT = ffi::MDB_DUPSORT; /// Numeric keys in native byte order: either `u32` or `usize`. /// The keys must all be of the same size. /// /// It is recommended to use the [`IntegerComparator`] when /// opening the [`Database`] instead. This comparator provides /// better support for ranges and does not allow for prefix /// iteration, as it is not applicable in this context. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::INTEGER_KEY) /// .name("integer-key") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.iter(&wtxn)?; /// assert_eq!(iter.next().transpose()?, Some((0, 120))); /// assert_eq!(iter.next().transpose()?, Some((35, 120))); /// assert_eq!(iter.next().transpose()?, Some((42, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((92, 32))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` #[deprecated(since="0.21.0", note="prefer using `IntegerComparator` with the `DatabaseOpenOptions::key_comparator` method instead")] const INTEGER_KEY = ffi::MDB_INTEGERKEY; /// With [`DatabaseFlags::DUP_SORT`], sorted dup items have fixed size. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::DUP_FIXED) /// .name("dup-sort-fixed") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` const DUP_FIXED = ffi::MDB_DUPFIXED; /// With [`DatabaseFlags::DUP_SORT`], dups are [`DatabaseFlags::INTEGER_KEY`]-style integers. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI32 = I32; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::INTEGER_DUP) /// .name("dup-sort-integer-dup") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &120)?; /// db.put(&mut wtxn, &68, &121)?; /// db.put(&mut wtxn, &68, &122)?; /// db.put(&mut wtxn, &68, &123)?; /// db.put(&mut wtxn, &92, &32)?; /// db.put(&mut wtxn, &35, &120)?; /// db.put(&mut wtxn, &0, &120)?; /// db.put(&mut wtxn, &42, &120)?; /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// assert_eq!(iter.next().transpose()?, Some((68, 121))); /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.last().transpose()?, Some((68, 123))); /// /// assert!(db.delete_one_duplicate(&mut wtxn, &68, &121)?, "The entry must exist"); /// /// let mut iter = db.get_duplicates(&wtxn, &68)?.expect("the key exists"); /// assert_eq!(iter.next().transpose()?, Some((68, 120))); /// // No more (68, 121) returned here! /// assert_eq!(iter.next().transpose()?, Some((68, 122))); /// assert_eq!(iter.next().transpose()?, Some((68, 123))); /// assert_eq!(iter.next().transpose()?, None); /// drop(iter); /// /// wtxn.commit()?; /// # Ok(()) } /// ``` #[deprecated(since="0.22.0", note="prefer using `IntegerComparator` with the `DatabaseOpenOptions::dup_sort_comparator` method instead")] const INTEGER_DUP = ffi::MDB_INTEGERDUP; /// With [`DatabaseFlags::DUP_SORT`], use reverse string dups. /// /// ``` /// # use std::fs; /// # use std::path::Path; /// # use heed::{DatabaseFlags, EnvOpenOptions}; /// use heed::types::*; /// use heed::byteorder::BigEndian; /// /// # fn main() -> Result<(), Box> { /// # let dir = tempfile::tempdir()?; /// # let env = unsafe { EnvOpenOptions::new() /// # .map_size(10 * 1024 * 1024) // 10MB /// # .max_dbs(3000) /// # .open(dir.path())? /// # }; /// type BEI64 = I64; /// /// let mut wtxn = env.write_txn()?; /// let db = env.database_options() /// .types::() /// .flags(DatabaseFlags::DUP_SORT | DatabaseFlags::REVERSE_DUP) /// .name("dup-sort") /// .create(&mut wtxn)?; /// /// # db.clear(&mut wtxn)?; /// db.put(&mut wtxn, &68, &"bonjour")?; /// db.put(&mut wtxn, &68, &"hola")?; /// 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, "hola"))); /// 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.22.0/src/mdb/mod.rs000064400000000000000000000002061046102023000132660ustar 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.22.0/src/reserved_space.rs000064400000000000000000000135241046102023000147460ustar 00000000000000use std::mem::MaybeUninit; use std::{fmt, io}; use crate::mdb::ffi; /// A structure that is used to improve the write speed in LMDB. /// /// You must write the exact amount of bytes, no less, no more. pub struct ReservedSpace<'a> { bytes: &'a mut [MaybeUninit], /// Number of bytes which have been written: all bytes in `0..written`. written: usize, /// Index of the byte which should be written next. /// /// # Safety /// /// To ensure there are no unwritten gaps in the buffer this should be kept in the range /// `0..=written` at all times. write_head: usize, } impl ReservedSpace<'_> { pub(crate) unsafe fn from_val<'a>(val: ffi::MDB_val) -> ReservedSpace<'a> { let len = val.mv_size; let ptr = val.mv_data; ReservedSpace { bytes: std::slice::from_raw_parts_mut(ptr.cast(), len), written: 0, write_head: 0, } } /// The total number of bytes that this memory buffer has. #[inline] pub fn size(&self) -> usize { self.bytes.len() } /// The remaining number of bytes that this memory buffer has. #[inline] pub fn remaining(&self) -> usize { self.bytes.len() - self.write_head } /// Get a slice of all the bytes that have previously been written. /// /// This can be used to write information which cannot be known until the very end of /// serialization. For example, this method can be used to serialize a value, then compute a /// checksum over the bytes, and then write that checksum to a header at the start of the /// reserved space. #[inline] pub fn written_mut(&mut self) -> &mut [u8] { let ptr = self.bytes.as_mut_ptr(); let len = self.written; unsafe { std::slice::from_raw_parts_mut(ptr.cast(), len) } } /// Fills the remaining reserved space with zeroes. /// /// This can be used together with [`written_mut`](Self::written_mut) to get a mutable view of /// the entire reserved space. /// /// ### Note /// /// After calling this function, the entire space is considered to be filled and any /// further attempt to [`write`](std::io::Write::write) anything else will fail. #[inline] pub fn fill_zeroes(&mut self) { self.bytes[self.write_head..].fill(MaybeUninit::new(0)); self.written = self.bytes.len(); self.write_head = self.bytes.len(); } /// Get a slice of bytes corresponding to the *entire* reserved space. /// /// It is safe to write to any byte within the slice. However, for a write past the end of the /// prevously written bytes to take effect, [`assume_written`](Self::assume_written) has to be /// called to mark those bytes as initialized. /// /// # Safety /// /// As the memory comes from within the database itself, the bytes may not yet be /// initialized. Thus, it is up to the caller to ensure that only initialized memory is read /// (ensured by the [`MaybeUninit`] API). #[inline] pub fn as_uninit_mut(&mut self) -> &mut [MaybeUninit] { self.bytes } /// Marks the bytes in the range `0..len` as being initialized by advancing the internal write /// pointer. /// /// # Safety /// /// The caller guarantees that all bytes in the range have been initialized. #[inline] pub unsafe fn assume_written(&mut self, len: usize) { debug_assert!(len <= self.bytes.len()); self.written = len; self.write_head = len; } } impl io::Write for ReservedSpace<'_> { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.write_all(buf)?; Ok(buf.len()) } #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { let remaining = unsafe { self.bytes.get_unchecked_mut(self.write_head..) }; if buf.len() > remaining.len() { return Err(io::Error::from(io::ErrorKind::WriteZero)); } unsafe { // SAFETY: we can always cast `T` -> `MaybeUninit` as it's a transparent wrapper let buf_uninit = std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len()); remaining.as_mut_ptr().copy_from_nonoverlapping(buf_uninit.as_ptr(), buf.len()); } self.write_head += buf.len(); self.written = usize::max(self.written, self.write_head); Ok(()) } #[inline(always)] fn flush(&mut self) -> io::Result<()> { Ok(()) } } /// ## Note /// /// May only seek within the previously written space. /// Attempts to do otherwise will result in an error. impl io::Seek for ReservedSpace<'_> { #[inline] fn seek(&mut self, pos: io::SeekFrom) -> io::Result { let (base, offset) = match pos { io::SeekFrom::Start(start) => (start, 0), io::SeekFrom::End(offset) => (self.written as u64, offset), io::SeekFrom::Current(offset) => (self.write_head as u64, offset), }; let Some(new_pos) = base.checked_add_signed(offset) else { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "cannot seek before start of reserved space", )); }; if new_pos > self.written as u64 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "cannot seek past end of reserved space", )); } self.write_head = new_pos as usize; Ok(new_pos) } #[inline] fn rewind(&mut self) -> io::Result<()> { self.write_head = 0; Ok(()) } #[inline] fn stream_position(&mut self) -> io::Result { Ok(self.write_head as u64) } } impl fmt::Debug for ReservedSpace<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ReservedSpace").finish() } } heed-0.22.0/src/txn.rs000064400000000000000000000271071046102023000125670ustar 00000000000000use std::borrow::Cow; use std::marker::PhantomData; use std::ops::Deref; use std::ptr::{self, NonNull}; use std::sync::Arc; use crate::envs::{Env, EnvInner}; use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::Result; /// A read-only transaction. /// /// ## LMDB Limitations /// /// It's a must to keep read transactions short-lived. /// /// Active Read transactions prevent the reuse of pages freed /// by newer write transactions, thus the database can grow quickly. /// /// ## OSX/Darwin Limitation /// /// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can /// be active at the same time for a process. Threads are in the same process space. /// /// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB! /// /// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist` /// /// ## This struct is covariant /// /// ```rust /// #[allow(dead_code)] /// trait CovariantMarker<'a>: 'static { /// type R: 'a; /// /// fn is_covariant(&'a self) -> &'a Self::R; /// } /// /// impl<'a, T> CovariantMarker<'a> for heed::RoTxn<'static, T> { /// type R = heed::RoTxn<'a, T>; /// /// fn is_covariant(&'a self) -> &'a heed::RoTxn<'a, T> { /// self /// } /// } /// ``` #[repr(transparent)] pub struct RoTxn<'e, T = AnyTls> { inner: RoTxnInner<'e>, _tls_marker: PhantomData<&'e T>, } struct RoTxnInner<'e> { /// Makes the struct covariant and !Sync pub(crate) txn: Option>, env: Cow<'e, Arc>, } impl<'e, T> RoTxn<'e, T> { 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().as_mut(), ptr::null_mut(), ffi::MDB_RDONLY, &mut txn, ))? }; Ok(RoTxn { inner: RoTxnInner { txn: NonNull::new(txn), env: Cow::Borrowed(&env.inner) }, _tls_marker: PhantomData, }) } pub(crate) fn static_read_txn(env: Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { mdb_result(ffi::mdb_txn_begin( env.env_mut_ptr().as_mut(), ptr::null_mut(), ffi::MDB_RDONLY, &mut txn, ))? }; Ok(RoTxn { inner: RoTxnInner { txn: NonNull::new(txn), env: Cow::Owned(env.inner) }, _tls_marker: PhantomData, }) } pub(crate) fn txn_ptr(&self) -> NonNull { self.inner.txn.unwrap() } pub(crate) fn env_mut_ptr(&self) -> NonNull { self.inner.env.env_mut_ptr() } /// Return the transaction's ID. /// /// This returns the identifier associated with this transaction. For a /// [`RoTxn`], this corresponds to the snapshot being read; /// concurrent readers will frequently have the same transaction ID. pub fn id(&self) -> usize { unsafe { ffi::mdb_txn_id(self.inner.txn.unwrap().as_ptr()) } } /// Commit a read transaction. /// /// Synchronizing some [`Env`] metadata with the global handle. /// /// ## LMDB /// /// It's mandatory in a multi-process setup to call [`RoTxn::commit`] upon read-only database opening. /// After the transaction opening, the database is dropped. The next transaction might return /// `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` known as `EINVAL`. pub fn commit(mut self) -> Result<()> { // Asserts that the transaction hasn't been already // committed/aborter and ensure we cannot use it twice. let mut txn = self.inner.txn.take().unwrap(); let result = unsafe { mdb_result(ffi::mdb_txn_commit(txn.as_mut())) }; result.map_err(Into::into) } } impl<'a> Deref for RoTxn<'a, WithTls> { type Target = RoTxn<'a, AnyTls>; fn deref(&self) -> &Self::Target { // SAFETY: OK because repr(transparent) means RoTxn always has the same layout // as RoTxnInner. unsafe { std::mem::transmute(self) } } } #[cfg(master3)] impl std::ops::DerefMut for RoTxn<'_, WithTls> { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: OK because repr(transparent) means RoTxn always has the same layout // as RoTxnInner. unsafe { std::mem::transmute(self) } } } impl<'a> Deref for RoTxn<'a, WithoutTls> { type Target = RoTxn<'a, AnyTls>; fn deref(&self) -> &Self::Target { // SAFETY: OK because repr(transparent) means RoTxn always has the same layout // as RoTxnInner. unsafe { std::mem::transmute(self) } } } #[cfg(master3)] impl std::ops::DerefMut for RoTxn<'_, WithoutTls> { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: OK because repr(transparent) means RoTxn always has the same layout // as RoTxnInner. unsafe { std::mem::transmute(self) } } } impl Drop for RoTxn<'_, T> { fn drop(&mut self) { if let Some(mut txn) = self.inner.txn.take() { // Asserts that the transaction hasn't been already // committed/aborter and ensure we cannot use it twice. unsafe { ffi::mdb_txn_abort(txn.as_mut()) } } } } /// Parameter defining that read transactions are opened with /// Thread Local Storage (TLS) and cannot be sent between threads /// `!Send`. It is often faster to open TLS-backed transactions. /// /// When used to open transactions: A thread can only use one transaction /// at a time, plus any child (nested) transactions. Each transaction belongs /// to one thread. A `BadRslot` error will be thrown when multiple read /// transactions exists on the same thread. #[derive(Debug, PartialEq, Eq)] pub enum WithTls {} /// Parameter defining that read transactions are opened without /// Thread Local Storage (TLS) and are therefore `Send`. /// /// When used to open transactions: A thread can use any number /// of read transactions at a time on the same thread. Read transactions /// can be moved in between threads (`Send`). #[derive(Debug, PartialEq, Eq)] pub enum WithoutTls {} /// Parameter defining that read transactions might have been opened with or /// without Thread Local Storage (TLS). /// /// `RwTxn`s and any `RoTxn` dereference to `&RoTxn`. pub enum AnyTls {} /// Specificies if Thread Local Storage (TLS) must be used when /// opening transactions. It is often faster to open TLS-backed /// transactions but makes them `!Send`. /// /// The `#MDB_NOTLS` flag is set on `Env` opening, `RoTxn`s and /// iterators implements the `Send` trait. This allows the user to /// move `RoTxn`s and iterators between threads as read transactions /// will no more use thread local storage and will tie reader /// locktable slots to transaction objects instead of to threads. pub trait TlsUsage { /// True if TLS must be used, false otherwise. const ENABLED: bool; } impl TlsUsage for WithTls { const ENABLED: bool = true; } impl TlsUsage for WithoutTls { const ENABLED: bool = false; } impl TlsUsage for AnyTls { // Users cannot open environments with AnyTls; therefore, this will never be read. // We prefer to put the most restrictive value. const ENABLED: bool = false; } /// Is sendable only if `MDB_NOTLS` has been used to open this transaction. unsafe impl Send for RoTxn<'_, WithoutTls> {} /// A read-write transaction. /// /// ## LMDB Limitations /// /// Only one [`RwTxn`] may exist in the same environment at the same time. /// If two exist, the new one may wait on a mutex for [`RwTxn::commit`] or [`RwTxn::abort`] to /// be called for the first one. /// /// ## OSX/Darwin Limitation /// /// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can /// be active at the same time for a process. Threads are in the same process space. /// /// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked. /// /// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB! /// /// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist` /// /// ## This struct is covariant /// /// ```rust /// #[allow(dead_code)] /// trait CovariantMarker<'a>: 'static { /// type T: 'a; /// /// fn is_covariant(&'a self) -> &'a Self::T; /// } /// /// impl<'a> CovariantMarker<'a> for heed::RwTxn<'static> { /// type T = heed::RwTxn<'a>; /// /// fn is_covariant(&'a self) -> &'a heed::RwTxn<'a> { /// self /// } /// } /// ``` pub struct RwTxn<'p> { pub(crate) txn: RoTxn<'p, WithoutTls>, } 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().as_mut(), ptr::null_mut(), 0, &mut txn, ))? }; Ok(RwTxn { txn: RoTxn { inner: RoTxnInner { txn: NonNull::new(txn), env: Cow::Borrowed(&env.inner) }, _tls_marker: PhantomData, }, }) } 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 = unsafe { parent.txn.inner.txn.unwrap().as_mut() }; unsafe { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr().as_mut(), parent_ptr, 0, &mut txn))? }; Ok(RwTxn { txn: RoTxn { inner: RoTxnInner { txn: NonNull::new(txn), env: Cow::Borrowed(&env.inner) }, _tls_marker: PhantomData, }, }) } pub(crate) fn env_mut_ptr(&self) -> NonNull { self.txn.inner.env.env_mut_ptr() } /// Commit all the operations of a transaction into the database. /// The transaction is reset. pub fn commit(mut self) -> Result<()> { // Asserts that the transaction hasn't been already // committed/aborter and ensure we cannot use it two times. let mut txn = self.txn.inner.txn.take().unwrap(); let result = unsafe { mdb_result(ffi::mdb_txn_commit(txn.as_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) { // Asserts that the transaction hasn't been already // committed/aborter and ensure we cannot use it twice. let mut txn = self.txn.inner.txn.take().unwrap(); unsafe { ffi::mdb_txn_abort(txn.as_mut()) } } } impl<'p> Deref for RwTxn<'p> { type Target = RoTxn<'p, WithoutTls>; fn deref(&self) -> &Self::Target { &self.txn } } // TODO can't we just always implement it? #[cfg(master3)] impl std::ops::DerefMut for RwTxn<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.txn } } #[cfg(test)] mod tests { #[test] fn ro_txns_are_send() { use crate::{RoTxn, WithoutTls}; fn is_send() {} is_send::>(); } #[test] fn rw_txns_are_send() { use crate::RwTxn; fn is_send() {} is_send::(); } }