diff --git a/Cargo.lock b/Cargo.lock index 3c3797bc9..8193e44c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.25.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -71,9 +71,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -101,22 +101,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -175,7 +175,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_derive", "syn", @@ -211,9 +211,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -234,9 +234,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" dependencies = [ "aws-lc-sys", "zeroize", @@ -244,23 +244,22 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libloading", ] [[package]] name = "backtrace" -version = "0.3.76" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -268,7 +267,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link", + "windows-targets 0.52.6", ] [[package]] @@ -288,22 +287,25 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.72.1" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", + "lazy_static", + "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", + "which", ] [[package]] @@ -438,11 +440,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.39" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ - "find-msvc-tools", "jobserver", "libc", "shlex", @@ -459,9 +460,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" @@ -473,7 +474,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -582,15 +583,15 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "console" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", - "windows-sys 0.61.1", + "windows-sys 0.60.2", ] [[package]] @@ -631,9 +632,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] @@ -913,12 +914,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.60.2", ] [[package]] @@ -1004,12 +1005,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "find-msvc-tools" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" - [[package]] name = "findshlibs" version = "0.10.2" @@ -1088,6 +1083,7 @@ dependencies = [ "cbindgen", "chrono", "coarsetime", + "derive-where", "env_logger", "firewood", "firewood-storage", @@ -1218,9 +1214,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1356,26 +1352,26 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" -version = "0.32.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1417,21 +1413,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - [[package]] name = "heck" version = "0.5.0" @@ -1456,6 +1446,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.3.1" @@ -1504,14 +1503,13 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ - "atomic-waker", "bytes", "futures-channel", - "futures-core", + "futures-util", "h2", "http", "http-body", @@ -1519,7 +1517,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1557,9 +1554,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64", "bytes", @@ -1573,7 +1570,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1581,9 +1578,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1691,9 +1688,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.1.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", @@ -1769,12 +1766,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -1867,6 +1864,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1917,9 +1923,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.3", "libc", @@ -1927,9 +1933,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1961,11 +1967,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.176" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -1974,7 +1986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.4", + "windows-targets 0.53.2", ] [[package]] @@ -1985,9 +1997,15 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -2017,20 +2035,20 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" dependencies = [ - "hashbrown 0.15.5", + "hashbrown", ] [[package]] name = "memchr" -version = "2.7.6" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -2075,7 +2093,7 @@ dependencies = [ "aho-corasick", "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.5", + "hashbrown", "indexmap", "metrics", "ordered-float", @@ -2176,9 +2194,9 @@ dependencies = [ [[package]] name = "object" -version = "0.37.3" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2357,9 +2375,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" @@ -2453,9 +2471,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] @@ -2529,9 +2547,9 @@ checksum = "d8868e7264af614b3634ff0abbe37b178e61000611b8a75221aea40221924aba" [[package]] name = "prettyplease" -version = "0.2.37" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", @@ -2552,11 +2570,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.23.6", + "toml_edit", ] [[package]] @@ -2583,9 +2601,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -2748,9 +2766,9 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.6.0" +version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ "bitflags 2.9.4", ] @@ -2777,18 +2795,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags 2.9.4", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2798,9 +2816,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2809,15 +2827,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64", "bytes", @@ -2888,9 +2906,15 @@ checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" @@ -2906,22 +2930,35 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustix" -version = "1.1.2" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.61.1", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "aws-lc-rs", "once_cell", @@ -2954,9 +2991,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", @@ -2966,9 +3003,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -2987,20 +3024,20 @@ dependencies = [ [[package]] name = "scc" -version = "2.4.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.59.0", ] [[package]] @@ -3017,9 +3054,9 @@ checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "security-framework" -version = "3.5.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.4", "core-foundation", @@ -3030,9 +3067,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3046,28 +3083,18 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.227" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.227" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.227" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -3076,15 +3103,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", "ryu", "serde", - "serde_core", ] [[package]] @@ -3098,11 +3124,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ - "serde_core", + "serde", ] [[package]] @@ -3177,9 +3203,9 @@ checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "small_ctor" @@ -3193,6 +3219,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.0" @@ -3244,9 +3280,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.16.3" +version = "12.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50" +checksum = "9c5199e46f23c77c611aa2a383b2f72721dfee4fb2bf85979eea1e0f26ba6e35" dependencies = [ "debugid", "memmap2", @@ -3256,9 +3292,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.16.3" +version = "12.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8" +checksum = "fa3c03956e32254f74e461a330b9522a2689686d80481708fb2014780d8d3959" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -3267,9 +3303,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3317,8 +3353,8 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix", - "windows-sys 0.61.1", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -3371,18 +3407,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3452,15 +3488,15 @@ dependencies = [ "parking_lot", "pin-project-lite", "slab", - "socket2", + "socket2 0.6.0", "windows-sys 0.59.0", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", @@ -3479,9 +3515,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -3499,19 +3535,19 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_edit", ] [[package]] name = "toml" -version = "0.9.7" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ "indexmap", - "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", "toml_parser", "toml_writer", "winnow", @@ -3528,11 +3564,11 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ - "serde_core", + "serde", ] [[package]] @@ -3549,23 +3585,11 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_edit" -version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" -dependencies = [ - "indexmap", - "toml_datetime 0.7.2", - "toml_parser", - "winnow", -] - [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" dependencies = [ "winnow", ] @@ -3578,9 +3602,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tonic" @@ -3716,9 +3740,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.111" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" dependencies = [ "glob", "serde", @@ -3726,7 +3750,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 0.9.7", + "toml 0.9.2", ] [[package]] @@ -3769,9 +3793,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" @@ -3799,14 +3823,13 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -3823,9 +3846,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3873,20 +3896,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "wit-bindgen", + "wit-bindgen-rt", ] [[package]] @@ -3900,22 +3914,21 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", - "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -3927,9 +3940,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -3940,9 +3953,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3950,9 +3963,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -3963,18 +3976,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3990,6 +4003,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4008,11 +4033,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.11" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.59.0", ] [[package]] @@ -4023,22 +4048,22 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.62.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", @@ -4047,15 +4072,21 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.0" @@ -4064,20 +4095,20 @@ checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -4104,16 +4135,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", -] - -[[package]] -name = "windows-sys" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" -dependencies = [ - "windows-link", + "windows-targets 0.53.2", ] [[package]] @@ -4134,11 +4156,10 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4247,18 +4268,21 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.4", +] [[package]] name = "writeable" @@ -4301,18 +4325,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -4359,9 +4383,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index af176c996..6e6db493a 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -31,6 +31,7 @@ chrono = "0.4.42" oxhttp = "0.3.1" # Optional dependencies env_logger = { workspace = true, optional = true } +derive-where = "1.6.0" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/firewood.go b/ffi/firewood.go index 66b7240d0..6e2233ec7 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -214,21 +214,35 @@ func (db *Database) Root() ([]byte, error) { return bytes, err } +func (db *Database) LatestRevision() (*Revision, error) { + root, err := db.Root() + if err != nil { + return nil, err + } + if bytes.Equal(root, EmptyRoot) { + return nil, errRevisionNotFound + } + return db.Revision(root) +} + // Revision returns a historical revision of the database. func (db *Database) Revision(root []byte) (*Revision, error) { if root == nil || len(root) != RootLength { return nil, errInvalidRootLength } - // Attempt to get any value from the root. - // This will verify that the root is valid and accessible. - // If the root is not valid, this will return an error. - _, err := db.GetFromRoot(root, []byte{}) + var pinner runtime.Pinner + defer pinner.Unpin() + + rev, err := getRevisionFromResult(C.fwd_get_revision( + db.handle, + newBorrowedBytes(root, &pinner), + ), db) if err != nil { return nil, err } - return &Revision{database: db, root: root}, nil + return rev, nil } // Close releases the memory associated with the Database. diff --git a/ffi/firewood.h b/ffi/firewood.h index cb13778d1..b85aa1eae 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -24,6 +24,11 @@ typedef struct ChangeProofContext ChangeProofContext; */ typedef struct DatabaseHandle DatabaseHandle; +/** + * An opaque wrapper around a [`BoxKeyValueIter`]. + */ +typedef struct IteratorHandle IteratorHandle; + /** * An opaque wrapper around a Proposal that also retains a reference to the * database handle it was created from. @@ -35,6 +40,8 @@ typedef struct ProposalHandle ProposalHandle; */ typedef struct RangeProofContext RangeProofContext; +typedef struct RevisionHandle RevisionHandle; + /** * A database hash key, used in FFI functions that require hashes. * This type requires no allocation and can be copied freely and @@ -645,6 +652,213 @@ typedef struct VerifyRangeProofArgs { uint32_t max_length; } VerifyRangeProofArgs; +/** + * Owned version of `KeyValuePair`, returned to ffi callers. + * + * C callers must free this using [`crate::fwd_free_owned_kv_pair`], + * not the C standard library's `free` function. + */ +typedef struct OwnedKeyValuePair { + OwnedBytes key; + OwnedBytes value; +} OwnedKeyValuePair; + +/** + * A Rust-owned vector of bytes that can be passed to C code. + * + * C callers must free this memory using the respective FFI function for the + * concrete type (but not using the `free` function from the C standard library). + */ +typedef struct OwnedSlice_OwnedKeyValuePair { + struct OwnedKeyValuePair *ptr; + size_t len; +} OwnedSlice_OwnedKeyValuePair; + +/** + * A type alias for a rust-owned byte slice. + */ +typedef struct OwnedSlice_OwnedKeyValuePair OwnedKeyValueBatch; + +/** + * A result type returned from FFI functions that get a revision + */ +typedef enum RevisionResult_Tag { + /** + * The caller provided a null pointer to a database handle. + */ + RevisionResult_NullHandlePointer, + /** + * The provided root was not found in the database. + */ + RevisionResult_RevisionNotFound, + /** + * Getting the revision was successful and the revision handle and root + * hash are returned. + */ + RevisionResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. The + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + RevisionResult_Err, +} RevisionResult_Tag; + +typedef struct RevisionResult_Ok_Body { + /** + * An opaque pointer to the [`RevisionHandle`]. + * The value should be freed with [`fwd_free_revision`] + * + * [`fwd_free_revision`]: crate::fwd_free_revision + */ + struct RevisionHandle *handle; + /** + * The root hash of the revision. + */ + struct HashKey root_hash; +} RevisionResult_Ok_Body; + +typedef struct RevisionResult { + RevisionResult_Tag tag; + union { + struct { + struct HashKey revision_not_found; + }; + RevisionResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} RevisionResult; + +/** + * A result type returned from iterator FFI functions + */ +typedef enum KeyValueResult_Tag { + /** + * The caller provided a null pointer to an iterator handle. + */ + KeyValueResult_NullHandlePointer, + /** + * The iterator is exhausted + */ + KeyValueResult_None, + /** + * The next item is returned. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with the key and the value of this pair. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. The + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Err, +} KeyValueResult_Tag; + +typedef struct KeyValueResult { + KeyValueResult_Tag tag; + union { + struct { + struct OwnedKeyValuePair some; + }; + struct { + OwnedBytes err; + }; + }; +} KeyValueResult; + +/** + * A result type returned from iterator FFI functions + */ +typedef enum KeyValueBatchResult_Tag { + /** + * The caller provided a null pointer to an iterator handle. + */ + KeyValueBatchResult_NullHandlePointer, + /** + * The next batch of items on iterator are returned. + */ + KeyValueBatchResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueBatchResult_Err, +} KeyValueBatchResult_Tag; + +typedef struct KeyValueBatchResult { + KeyValueBatchResult_Tag tag; + union { + struct { + OwnedKeyValueBatch some; + }; + struct { + OwnedBytes err; + }; + }; +} KeyValueBatchResult; + +/** + * A result type returned from FFI functions that create an iterator + */ +typedef enum IteratorResult_Tag { + /** + * The caller provided a null pointer to a revision/proposal handle. + */ + IteratorResult_NullHandlePointer, + /** + * Building the iterator was successful and the iterator handle is returned + */ + IteratorResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + IteratorResult_Err, +} IteratorResult_Tag; + +typedef struct IteratorResult_Ok_Body { + /** + * An opaque pointer to the [`IteratorHandle`]. + * The value should be freed with [`fwd_free_iterator`] + * + * [`fwd_free_iterator`]: crate::fwd_free_iterator + */ + struct IteratorHandle *handle; +} IteratorResult_Ok_Body; + +typedef struct IteratorResult { + IteratorResult_Tag tag; + union { + IteratorResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} IteratorResult; + /** * The result type returned from the open or create database functions. */ @@ -1131,6 +1345,28 @@ struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, */ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); +/** + * Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. + * + * # Arguments + * + * * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a + * function from this library. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`VoidResult::Ok`] if the iterator was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `iterator` is not null and that it points to + * a valid [`IteratorHandle`] previously returned by a function from this library. + * + */ +struct VoidResult fwd_free_iterator(struct IteratorHandle *iterator); + /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. * @@ -1152,6 +1388,46 @@ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); */ struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); +/** + * Consumes the [`OwnedKeyValueBatch`] and frees the memory associated with it. + * + * # Arguments + * + * * `batch` - The [`OwnedKeyValueBatch`] struct to free, previously returned from any + * function from this library. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `batch` struct is valid and that the memory + * it points to is uniquely owned by this object. However, if `batch.ptr` is null, + * this function does nothing. + */ +struct VoidResult fwd_free_owned_key_value_batch(OwnedKeyValueBatch batch); + +/** + * Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. + * + * # Arguments + * + * * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any + * function from this library. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `kv` struct is valid. + */ +struct VoidResult fwd_free_owned_kv_pair(struct OwnedKeyValuePair kv); + /** * Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory. * @@ -1190,6 +1466,27 @@ struct VoidResult fwd_free_proposal(struct ProposalHandle *proposal); */ struct VoidResult fwd_free_range_proof(struct RangeProofContext *proof); +/** + * Consumes the [`RevisionHandle`] and frees the memory associated with it. + * + * # Arguments + * + * * `revision` - A pointer to a [`RevisionHandle`] previously returned by + * [`fwd_get_revision`]. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the provided revision handle is null. + * - [`VoidResult::Ok`] if the revision handle was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the revision handle is valid and is not used again after + * this function is called. + */ +struct VoidResult fwd_free_revision(struct RevisionHandle *revision); + /** * Gather latest metrics for this process. * @@ -1234,6 +1531,31 @@ struct ValueResult fwd_gather(void); */ struct ValueResult fwd_get_from_proposal(const struct ProposalHandle *handle, BorrowedBytes key); +/** + * Gets the value associated with the given key from the provided revision handle. + * + * # Arguments + * + * * `revision` - The revision handle returned by [`fwd_get_revision`]. + * * `key` - The key to look up as a [`BorrowedBytes`]. + * + * # Returns + * + * - [`ValueResult::NullHandlePointer`] if the provided revision handle is null. + * - [`ValueResult::None`] if the key was not found in the revision. + * - [`ValueResult::Some`] if the key was found with the associated value. + * - [`ValueResult::Err`] if an error occurred while retrieving the value. + * + * # Safety + * + * The caller must: + * * ensure that `revision` is a valid pointer to a [`RevisionHandle`]. + * * ensure that `key` is valid for [`BorrowedBytes`]. + * * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`] + * returned in the result. + */ +struct ValueResult fwd_get_from_revision(const struct RevisionHandle *revision, BorrowedBytes key); + /** * Gets a value assoicated with the given root hash and key. * @@ -1296,6 +1618,138 @@ struct ValueResult fwd_get_from_root(const struct DatabaseHandle *db, */ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); +/** + * Gets a handle to the revision identified by the provided root hash. + * + * # Arguments + * + * * `db` - The database handle returned by [`fwd_open_db`]. + * * `root` - The hash of the revision as a [`BorrowedBytes`]. + * + * # Returns + * + * - [`RevisionResult::NullHandlePointer`] if the provided database handle is null. + * - [`RevisionResult::Ok`] containing a [`RevisionHandle`] and root hash if the revision exists. + * - [`RevisionResult::Err`] if the revision cannot be fetched or the root hash is invalid. + * + * # Safety + * + * The caller must: + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. + * * ensure that `root` is valid for [`BorrowedBytes`]. + * * call [`fwd_free_revision`] to free the returned handle when it is no longer needed. + * + * [`BorrowedBytes`]: crate::value::BorrowedBytes + * [`RevisionHandle`]: crate::revision::RevisionHandle + */ +struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); + +/** + * Retrieves the next item from the iterator. + * + * # Arguments + * + * * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or + * [`fwd_iter_on_proposal`]. + * + * # Returns + * + * - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`KeyValueResult::None`] if the iterator is exhausted (no remaining values). Once returned, + * subsequent calls will continue returning [`KeyValueResult::None`]. You may still call this + * safely, but freeing the iterator with [`fwd_free_iterator`] is recommended. + * - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated + * key value pair. + * - [`KeyValueResult::Err`] if an I/O error occurred while retrieving the next item + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. + * * call [`fwd_free_owned_kv_pair`] on returned [`OwnedKeyValuePair`] + * to free the memory associated with the returned value. + * + */ +struct KeyValueResult fwd_iter_next(struct IteratorHandle *handle); + +/** + * Retrieves the next batch of items from the iterator. + * + * # Arguments + * + * * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or + * [`fwd_iter_on_proposal`]. + * + * # Returns + * + * - [`KeyValueBatchResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`KeyValueBatchResult::Some`] with up to `n` key/value pairs. If the iterator is + * exhausted, this may be fewer than `n`, including zero items. + * - [`KeyValueBatchResult::Err`] if an I/O error occurred while retrieving items + * + * Once an empty batch or items fewer than `n` is returned (iterator exhausted), subsequent calls + * will continue returning empty batches. You may still call this safely, but freeing the + * iterator with [`fwd_free_iterator`] is recommended. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. + * * call [`fwd_free_owned_key_value_batch`] on the returned batch to free any allocated memory. + * + */ +struct KeyValueBatchResult fwd_iter_next_n(struct IteratorHandle *handle, size_t n); + +/** + * Returns an iterator on the provided proposal optionally starting from a key + * + * # Arguments + * + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `key` is a valid for [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_proposal(const struct ProposalHandle *handle, BorrowedBytes key); + +/** + * Returns an iterator optionally starting from a key in the provided revision. + * + * # Arguments + * + * * `revision` - The revision handle returned by [`fwd_get_revision`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `revision` is a valid pointer to a [`RevisionHandle`] + * * ensure that `key` is a valid [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_revision(const struct RevisionHandle *revision, + BorrowedBytes key); + /** * Open a database with the given arguments. * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 61816bfb9..2f642b6e8 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -6,10 +6,12 @@ package ffi import ( "bytes" "encoding/hex" + "errors" "fmt" "os" "path/filepath" "runtime" + "slices" "strconv" "strings" "sync" @@ -126,12 +128,12 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func newTestDatabase(t *testing.T) *Database { +func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database { t.Helper() r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") - db, closeDB, err := newDatabase(dbFile) + db, closeDB, err := newDatabase(dbFile, configureFns...) r.NoError(err) t.Cleanup(func() { r.NoError(closeDB()) @@ -139,9 +141,12 @@ func newTestDatabase(t *testing.T) *Database { return db } -func newDatabase(dbFile string) (*Database, func() error, error) { +func newDatabase(dbFile string, configureFns ...func(*Config)) (*Database, func() error, error) { conf := DefaultConfig() conf.Truncate = true // in tests, we use filepath.Join, which creates an empty file + for _, fn := range configureFns { + fn(conf) + } f, err := New(dbFile, conf) if err != nil { @@ -244,9 +249,41 @@ func kvForTest(num int) ([][]byte, [][]byte) { keys[i] = keyForTest(i) vals[i] = valForTest(i) } + _ = sortKV(keys, vals) return keys, vals } +// sortKV sorts keys lexicographically and keeps vals paired. +func sortKV(keys, vals [][]byte) error { + if len(keys) != len(vals) { + return errors.New("keys/vals length mismatch") + } + n := len(keys) + if n <= 1 { + return nil + } + ord := make([]int, n) + for i := range ord { + ord[i] = i + } + slices.SortFunc(ord, func(i, j int) int { + return bytes.Compare(keys[i], keys[j]) + }) + perm := make([]int, n) + for dest, orig := range ord { + perm[orig] = dest + } + for i := 0; i < n; i++ { + for perm[i] != i { + j := perm[i] + keys[i], keys[j] = keys[j], keys[i] + vals[i], vals[j] = vals[j], vals[i] + perm[i], perm[j] = perm[j], j + } + } + return nil +} + // Tests that 100 key-value pairs can be inserted and retrieved. // This happens in three ways: // 1. By calling [Database.Propose] and then [Proposal.Commit]. @@ -783,6 +820,9 @@ func TestRevision(t *testing.T) { // Create a revision from this root. revision, err := db.Revision(root) r.NoError(err) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Check that all keys can be retrieved from the revision. for i := range keys { @@ -807,6 +847,10 @@ func TestRevision(t *testing.T) { // Create a "new" revision from the first old root. revision, err = db.Revision(root) r.NoError(err) + r.Equal(revision.Root(), root) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) @@ -815,6 +859,79 @@ func TestRevision(t *testing.T) { } } +// Tests that even if a proposal is committed, the corresponding revision will not go away +// as we're holding on to it +func TestRevisionOutlivesProposal(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(20) + _, err := db.Update(keys[:10], vals[:10]) + r.NoError(err) + + // Create a proposal with 10 key-value pairs. + nKeys, nVals := keys[10:], vals[10:] + proposal, err := db.Propose(nKeys, nVals) + r.NoError(err) + root, err := proposal.Root() + r.NoError(err) + + rev, err := db.Revision(root) + r.NoError(err) + + // we drop the proposal + r.NoError(proposal.Drop()) + + // revision should outlive the proposal, as we're still referencing its node store + for i, key := range nKeys { + val, err := rev.Get(key) + r.NoError(err) + r.Equal(val, nVals[i]) + } + + r.NoError(rev.Drop()) +} + +// Tests that holding a reference to revision will prevent from it being reaped +func TestRevisionOutlivesReaping(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t, func(config *Config) { + config.Revisions = 2 + }) + + keys, vals := kvForTest(40) + firstRoot, err := db.Update(keys[:10], vals[:10]) + r.NoError(err) + // let's get a revision at root + rev, err := db.Revision(firstRoot) + r.NoError(err) + + // commit two times, this would normally reap the first revision + secondRoot, err := db.Update(keys[10:20], vals[10:20]) + r.NoError(err) + _, err = db.Update(keys[20:30], vals[20:30]) + r.NoError(err) + + // revision should be still accessible, as we're hanging on to it, prevent reaping + nKeys, nVals := keys[:10], vals[:10] + for i, key := range nKeys { + val, err := rev.Get(key) + r.NoError(err) + r.Equal(val, nVals[i]) + } + r.NoError(rev.Drop()) + + // since we dropped the revision, if we commit, reaping will happen (cleaning first two revisions) + _, err = db.Update(keys[30:], vals[30:]) + r.NoError(err) + + _, err = db.Revision(firstRoot) + r.Error(err) + + _, err = db.Revision(secondRoot) + r.Error(err) +} + func TestInvalidRevision(t *testing.T) { r := require.New(t) db := newTestDatabase(t) @@ -850,6 +967,9 @@ func TestGetNilCases(t *testing.T) { r.NoError(err) revision, err := db.Revision(root) r.NoError(err) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Create edge case keys. specialKeys := [][]byte{ @@ -1050,3 +1170,258 @@ func TestGetFromRootParallel(t *testing.T) { r.NoError(err, "Parallel operation failed") } } + +type kvIter interface { + SetBatchSize(int) + Next() bool + Key() []byte + Value() []byte + Err() error + Drop() error +} +type borrowIter struct{ it *Iterator } + +func (b *borrowIter) SetBatchSize(batchSize int) { b.it.SetBatchSize(batchSize) } +func (b *borrowIter) Next() bool { return b.it.NextBorrowed() } +func (b *borrowIter) Key() []byte { return b.it.Key() } +func (b *borrowIter) Value() []byte { return b.it.Value() } +func (b *borrowIter) Err() error { return b.it.Err() } +func (b *borrowIter) Drop() error { return b.it.Drop() } + +func assertIteratorYields(r *require.Assertions, it kvIter, keys [][]byte, vals [][]byte) { + i := 0 + for ; it.Next(); i += 1 { + r.Equal(keys[i], it.Key()) + r.Equal(vals[i], it.Value()) + } + r.NoError(it.Err()) + r.Equal(len(keys), i) +} + +type iteratorConfigFn = func(it kvIter) kvIter + +var iterConfigs = map[string]iteratorConfigFn{ + "Owned": func(it kvIter) kvIter { return it }, + "Borrowed": func(it kvIter) kvIter { return &borrowIter{it: it.(*Iterator)} }, + "Single": func(it kvIter) kvIter { + it.SetBatchSize(1) + return it + }, + "Batched": func(it kvIter) kvIter { + it.SetBatchSize(100) + return it + }, +} + +func runIteratorTestForModes(t *testing.T, fn func(*testing.T, iteratorConfigFn), modes ...string) { + testName := strings.Join(modes, "/") + t.Run(testName, func(t *testing.T) { + r := require.New(t) + fn(t, func(it kvIter) kvIter { + for _, m := range modes { + config, ok := iterConfigs[m] + r.Truef(ok, "specified config mode %s does not exist", m) + it = config(it) + } + return it + }) + }) +} + +func runIteratorTestForAllModes(parentT *testing.T, fn func(*testing.T, iteratorConfigFn)) { + for _, dataMode := range []string{"Owned", "Borrowed"} { + for _, batchMode := range []string{"Single", "Batched"} { + runIteratorTestForModes(parentT, fn, batchMode, dataMode) + } + } +} + +// Tests that basic iterator functionality works +func TestIter(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(100) + _, err := db.Update(keys, vals) + r.NoError(err) + + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + + assertIteratorYields(r, cfn(it), keys, vals) + }) +} + +func TestIterOnRoot(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(240) + firstRoot, err := db.Update(keys[:80], vals[:80]) + r.NoError(err) + secondRoot, err := db.Update(keys[80:160], vals[80:160]) + r.NoError(err) + thirdRoot, err := db.Update(keys[160:], vals[160:]) + r.NoError(err) + + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + r1, err := db.Revision(firstRoot) + r.NoError(err) + h1, err := r1.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h1.Drop()) + r.NoError(r1.Drop()) + }) + + r2, err := db.Revision(secondRoot) + r.NoError(err) + h2, err := r2.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h2.Drop()) + r.NoError(r2.Drop()) + }) + + r3, err := db.Revision(thirdRoot) + r.NoError(err) + h3, err := r3.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h3.Drop()) + r.NoError(r3.Drop()) + }) + + assertIteratorYields(r, cfn(h1), keys[:80], vals[:80]) + assertIteratorYields(r, cfn(h2), keys[:160], vals[:160]) + assertIteratorYields(r, cfn(h3), keys, vals) + }) +} + +func TestIterOnProposal(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(240) + _, err := db.Update(keys, vals) + r.NoError(err) + + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + updatedValues := make([][]byte, len(vals)) + copy(updatedValues, vals) + + changedKeys := make([][]byte, 0) + changedVals := make([][]byte, 0) + for i := 0; i < len(vals); i += 4 { + changedKeys = append(changedKeys, keys[i]) + newVal := []byte{byte(i)} + changedVals = append(changedVals, newVal) + updatedValues[i] = newVal + } + p, err := db.Propose(changedKeys, changedVals) + r.NoError(err) + it, err := p.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + }) + + assertIteratorYields(r, cfn(it), keys, updatedValues) + }) +} + +// Tests that the iterator still works after proposal is committed +func TestIterAfterProposalCommit(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + p, err := db.Propose(keys, vals) + r.NoError(err) + + it, err := p.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + }) + + err = p.Commit() + r.NoError(err) + + // iterate after commit + // because iterator hangs on the nodestore reference of proposal + // the nodestore won't be dropped until we drop the iterator + assertIteratorYields(r, it, keys, vals) +} + +// Tests that the iterator on latest revision works properly after a proposal commit +func TestIterUpdate(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) + + // get an iterator on latest revision + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + + // update the database + keys2, vals2 := kvForTest(10) + _, err = db.Update(keys2, vals2) + r.NoError(err) + + // iterate after commit + // because iterator is fixed on the revision hash, it should return the initial values + assertIteratorYields(r, it, keys, vals) +} + +// Tests the iterator's behavior after exhaustion, should safely return empty item/batch, indicating done +func TestIterDone(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(18) + _, err := db.Update(keys, vals) + r.NoError(err) + + // get an iterator on latest revision + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + // consume the iterator + assertIteratorYields(r, it, keys, vals) + // calling next again should be safe and return false + r.False(it.Next()) + r.NoError(it.Err()) + + // get a new iterator + it2, err := rev.Iter(nil) + r.NoError(err) + // set batch size to 5 + it2.SetBatchSize(5) + // consume the iterator + assertIteratorYields(r, it2, keys, vals) + // calling next again should be safe and return false + r.False(it.Next()) + r.NoError(it.Err()) +} diff --git a/ffi/iterator.go b/ffi/iterator.go new file mode 100644 index 000000000..08a7cf779 --- /dev/null +++ b/ffi/iterator.go @@ -0,0 +1,177 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +// #include +// #include "firewood.h" +import "C" + +import ( + "errors" + "fmt" + "unsafe" +) + +type Iterator struct { + // handle is an opaque pointer to the iterator within Firewood. It should be + // passed to the C FFI functions that operate on iterators + // + // It is not safe to call these methods with a nil handle. + handle *C.IteratorHandle + + // batchSize is the number of items that are loaded at once + // to reduce ffi call overheads + batchSize int + + // loadedPairs is the latest loaded key value pairs retrieved + // from the iterator, not yet consumed by user + loadedPairs []*ownedKeyValue + + // current* fields correspond to the current cursor state + // nil/empty if not started or exhausted; refreshed on each Next(). + currentPair *ownedKeyValue + currentKey []byte + currentValue []byte + // FFI resource for current pair or batch to free on advance or drop + currentResource interface{ free() error } + + // err is the error from the iterator, if any + err error +} + +func (it *Iterator) freeCurrentAllocation() error { + if it.currentResource == nil { + return nil + } + e := it.currentResource.free() + it.currentResource = nil + return e +} + +func (it *Iterator) nextInternal() error { + if len(it.loadedPairs) > 0 { + it.currentPair, it.loadedPairs = it.loadedPairs[0], it.loadedPairs[1:] + return nil + } + + // current resources should **only** be freed, on the next call to the FFI + // this is to make sure we don't invalidate a batch in between iteration + if e := it.freeCurrentAllocation(); e != nil { + return e + } + if it.batchSize <= 1 { + kv, e := getKeyValueFromResult(C.fwd_iter_next(it.handle)) + if e != nil { + return e + } + it.currentPair = kv + it.currentResource = kv + } else { + batch, e := getKeyValueBatchFromResult(C.fwd_iter_next_n(it.handle, C.size_t(it.batchSize))) + if e != nil { + return e + } + pairs := batch.copy() + if len(pairs) > 0 { + it.currentPair, it.loadedPairs = pairs[0], pairs[1:] + } else { + it.currentPair = nil + } + it.currentResource = batch + } + + return nil +} + +// SetBatchSize sets the max number of pairs to be retrieved in one ffi call. +func (it *Iterator) SetBatchSize(batchSize int) { + it.batchSize = batchSize +} + +// Next proceeds to the next item on the iterator, and returns true +// if succeeded and there is a pair available. +// The new pair could be retrieved with Key and Value methods. +func (it *Iterator) Next() bool { + it.err = it.nextInternal() + if it.currentPair == nil || it.err != nil { + return false + } + k, v := it.currentPair.copy() + it.currentKey = k + it.currentValue = v + return true +} + +// NextBorrowed is like Next, but Key and Value **borrow** rust-owned buffers. +// +// Lifetime: the returned slices are valid **only until** the next call to +// Next, NextBorrowed, Close, or any operation that advances/invalidates the iterator. +// They alias FFI-owned memory that will be **freed or reused** on the next advance. +// +// Do **not** retain, store, or modify these slices. +// **Copy** or use Next if you need to keep them. +// Misuse can read freed memory and cause undefined behavior. +func (it *Iterator) NextBorrowed() bool { + it.err = it.nextInternal() + if it.currentPair == nil || it.err != nil { + return false + } + it.currentKey = it.currentPair.key.BorrowedBytes() + it.currentValue = it.currentPair.value.BorrowedBytes() + it.err = nil + return true +} + +// Key returns the key of the current pair +func (it *Iterator) Key() []byte { + if it.currentPair == nil || it.err != nil { + return nil + } + return it.currentKey +} + +// Value returns the value of the current pair +func (it *Iterator) Value() []byte { + if it.currentPair == nil || it.err != nil { + return nil + } + return it.currentValue +} + +// Err returns the error if Next failed +func (it *Iterator) Err() error { + return it.err +} + +// Drop drops the iterator and releases the resources +func (it *Iterator) Drop() error { + err := it.freeCurrentAllocation() + if it.handle != nil { + // Always free the iterator even if releasing the current KV/batch failed. + // The iterator holds a NodeStore ref that must be dropped. + return errors.Join( + err, + getErrorFromVoidResult(C.fwd_free_iterator(it.handle))) + } + return err +} + +// getIteratorFromIteratorResult converts a C.IteratorResult to an Iterator or error. +func getIteratorFromIteratorResult(result C.IteratorResult) (*Iterator, error) { + switch result.tag { + case C.IteratorResult_NullHandlePointer: + return nil, errDBClosed + case C.IteratorResult_Ok: + body := (*C.IteratorResult_Ok_Body)(unsafe.Pointer(&result.anon0)) + proposal := &Iterator{ + handle: body.handle, + } + return proposal, nil + case C.IteratorResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.IteratorResult tag: %d", result.tag) + } +} diff --git a/ffi/memory.go b/ffi/memory.go index 441964ca9..f7e9d91c6 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -309,6 +309,131 @@ func getValueFromValueResult(result C.ValueResult) ([]byte, error) { } } +type ownedKeyValueBatch struct { + owned C.OwnedKeyValueBatch +} + +func (b *ownedKeyValueBatch) copy() []*ownedKeyValue { + if b.owned.ptr == nil { + return nil + } + borrowed := b.borrow() + copied := make([]*ownedKeyValue, len(borrowed)) + for i, borrow := range borrowed { + copied[i] = newOwnedKeyValue(borrow) + } + return copied +} + +func (b *ownedKeyValueBatch) borrow() []C.OwnedKeyValuePair { + if b.owned.ptr == nil { + return nil + } + + return unsafe.Slice((*C.OwnedKeyValuePair)(unsafe.Pointer(b.owned.ptr)), b.owned.len) +} + +func (b *ownedKeyValueBatch) free() error { + if b == nil || b.owned.ptr == nil { + // we want ownedKeyValueBatch to be typed-nil safe + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_owned_key_value_batch(b.owned)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + + b.owned = C.OwnedKeyValueBatch{} + + return nil +} + +// newOwnedKeyValueBatch creates a ownedKeyValueBatch from a C.OwnedKeyValueBatch. +// +// The caller is responsible for calling Free() on the returned ownedKeyValue +// when it is no longer needed otherwise memory will leak. +func newOwnedKeyValueBatch(owned C.OwnedKeyValueBatch) *ownedKeyValueBatch { + return &ownedKeyValueBatch{ + owned: owned, + } +} + +type ownedKeyValue struct { + key *ownedBytes + value *ownedBytes +} + +func (kv *ownedKeyValue) copy() ([]byte, []byte) { + key := kv.key.CopiedBytes() + value := kv.value.CopiedBytes() + return key, value +} + +func (kv *ownedKeyValue) free() error { + if kv == nil { + // we want ownedKeyValue to be typed-nil safe + return nil + } + err := errors.Join(kv.key.Free(), kv.value.Free()) + if err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + return nil +} + +// newOwnedKeyValue creates a ownedKeyValue from a C.OwnedKeyValuePair. +// +// The caller is responsible for calling Free() on the returned ownedKeyValue +// when it is no longer needed otherwise memory will leak. +func newOwnedKeyValue(owned C.OwnedKeyValuePair) *ownedKeyValue { + return &ownedKeyValue{ + key: newOwnedBytes(owned.key), + value: newOwnedBytes(owned.value), + } +} + +// getKeyValueFromResult converts a C.KeyValueResult to a key value pair or error. +// +// It returns nil, nil if the result is None. +// It returns a *ownedKeyValue, nil if the result is Some. +// It returns an error if the result is an error. +func getKeyValueFromResult(result C.KeyValueResult) (*ownedKeyValue, error) { + switch result.tag { + case C.KeyValueResult_NullHandlePointer: + return nil, errDBClosed + case C.KeyValueResult_None: + return nil, nil + case C.KeyValueResult_Some: + ownedKvp := newOwnedKeyValue(*(*C.OwnedKeyValuePair)(unsafe.Pointer(&result.anon0))) + return ownedKvp, nil + case C.KeyValueResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.KeyValueResult tag: %d", result.tag) + } +} + +// getKeyValueBatchFromResult converts a C.KeyValueBatchResult to a key value batch or error. +// +// It returns nil, nil if the result is None. +// It returns a *ownedKeyValueBatch, nil if the result is Some. +// It returns an error if the result is an error. +func getKeyValueBatchFromResult(result C.KeyValueBatchResult) (*ownedKeyValueBatch, error) { + switch result.tag { + case C.KeyValueBatchResult_NullHandlePointer: + return nil, errDBClosed + case C.KeyValueBatchResult_Some: + ownedBatch := newOwnedKeyValueBatch(*(*C.OwnedKeyValueBatch)(unsafe.Pointer(&result.anon0))) + return ownedBatch, nil + case C.KeyValueBatchResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.KeyValueBatchResult tag: %d", result.tag) + } +} + // getDatabaseFromHandleResult converts a C.HandleResult to a Database or error. // // If the C.HandleResult is an error, it returns an error instead of a Database. diff --git a/ffi/proposal.go b/ffi/proposal.go index 285c14f5e..56ed0ad1a 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -58,6 +58,21 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { return getValueFromValueResult(C.fwd_get_from_proposal(p.handle, newBorrowedBytes(key, &pinner))) } +// Iter creates and iterator starting from the provided key on proposal. +// pass empty slice to start from beginning +func (p *Proposal) Iter(key []byte) (*Iterator, error) { + if p.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + itResult := C.fwd_iter_on_proposal(p.handle, newBorrowedBytes(key, &pinner)) + + return getIteratorFromIteratorResult(itResult) +} + // Propose creates a new proposal with the given keys and values. // The proposal is not committed until Commit is called. func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { diff --git a/ffi/revision.go b/ffi/revision.go index 6e5dd2fb0..0fc600aa4 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -13,19 +13,102 @@ import "C" import ( "errors" "fmt" + "runtime" + "unsafe" ) var ( errRevisionNotFound = errors.New("revision not found") errInvalidRootLength = fmt.Errorf("root hash must be %d bytes", RootLength) + errDroppedRevision = errors.New("revision already dropped") ) +// Revision is an immutable view over the database at a specific root hash. +// Instances are created via Database.Revision, provide read-only access to the revision, +// and must be released with Drop when no longer needed. type Revision struct { - database *Database + // The database this revision is associated with. Holding this ensures + // the DB outlives the revision for cleanup ordering. + db *Database + handle *C.RevisionHandle // The revision root root []byte } +// Get reads the value stored at the provided key within the revision. +// +// It returns errDroppedRevision if Drop has already been called and the underlying +// handle is no longer available. func (r *Revision) Get(key []byte) ([]byte, error) { - return r.database.GetFromRoot(r.root, key) + if r.handle == nil { + return nil, errDroppedRevision + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + return getValueFromValueResult(C.fwd_get_from_revision( + r.handle, + newBorrowedBytes(key, &pinner), + )) +} + +// Iter creates an iterator starting from the provided key on revision. +// pass empty slice to start from beginning +func (r *Revision) Iter(key []byte) (*Iterator, error) { + if r.handle == nil { + return nil, errDroppedRevision + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + itResult := C.fwd_iter_on_revision(r.handle, newBorrowedBytes(key, &pinner)) + + return getIteratorFromIteratorResult(itResult) +} + +// Drop releases the resources backed by the revision handle. +// +// It is safe to call Drop multiple times; subsequent calls after the first are no-ops. +func (r *Revision) Drop() error { + if r.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_revision(r.handle)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + + r.handle = nil // Prevent double free + + return nil +} + +func (r *Revision) Root() []byte { + return r.root +} + +// getRevisionFromResult converts a C.RevisionResult to a Revision or error. +func getRevisionFromResult(result C.RevisionResult, db *Database) (*Revision, error) { + switch result.tag { + case C.RevisionResult_NullHandlePointer: + return nil, errDBClosed + case C.RevisionResult_RevisionNotFound: + return nil, errRevisionNotFound + case C.RevisionResult_Ok: + body := (*C.RevisionResult_Ok_Body)(unsafe.Pointer(&result.anon0)) + hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) + rev := &Revision{ + db: db, + handle: body.handle, + root: hashKey[:], + } + return rev, nil + case C.RevisionResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.RevisionResult tag: %d", result.tag) + } } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index f03fa2621..b4061978b 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -9,6 +9,7 @@ use firewood::{ use crate::{BorrowedBytes, CView, CreateProposalResult, KeyValuePair, arc_cache::ArcCache}; +use crate::revision::{GetRevisionResult, RevisionHandle}; use metrics::counter; /// Arguments for creating or opening a database. These are passed to [`fwd_open_db`] @@ -176,6 +177,21 @@ impl DatabaseHandle { Ok(root_hash) } + /// Returns an owned handle to the revision corresponding to the provided root hash. + /// + /// # Errors + /// + /// Returns an error if could not get the view from underlying database for the specified + /// root hash, for example when the revision does not exist or an I/O error occurs while + /// accessing the database. + pub fn get_revision(&self, root: HashKey) -> Result { + let view = self.db.view(root.clone())?; + Ok(GetRevisionResult { + handle: RevisionHandle::new(view), + root_hash: root, + }) + } + pub(crate) fn get_root(&self, root: HashKey) -> Result { let mut cache_miss = false; let view = self.cached_view.get_or_try_insert_with(root, |key| { diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs new file mode 100644 index 000000000..2389cb234 --- /dev/null +++ b/ffi/src/iterator.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use derive_where::derive_where; +use firewood::merkle; +use firewood::v2::api::{self, BoxKeyValueIter}; +use std::iter::FusedIterator; + +type KeyValueItem = (merkle::Key, merkle::Value); + +/// An opaque wrapper around a [`BoxKeyValueIter`]. +#[derive(Default)] +#[derive_where(Debug)] +#[derive_where(skip_inner)] +pub struct IteratorHandle<'view>(Option>); + +impl<'view> From> for IteratorHandle<'view> { + fn from(value: BoxKeyValueIter<'view>) -> Self { + IteratorHandle(Some(value)) + } +} + +impl Iterator for IteratorHandle<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + let out = self.0.as_mut()?.next(); + if out.is_none() { + // iterator exhausted; drop it so the NodeStore can be released + self.0 = None; + } + out + } +} + +impl FusedIterator for IteratorHandle<'_> {} + +#[expect(clippy::missing_errors_doc)] +impl IteratorHandle<'_> { + pub fn iter_next_n(&mut self, n: usize) -> Result, api::Error> { + self.by_ref().take(n).collect() + } +} + +#[derive(Debug, Default)] +pub struct CreateIteratorResult<'db>(pub IteratorHandle<'db>); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index cb63d037b..4ceedecf4 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -24,18 +24,22 @@ mod arc_cache; mod handle; +mod iterator; mod logging; mod metrics_setup; mod proofs; mod proposal; +mod revision; mod value; use firewood::v2::api::DbView; pub use crate::handle::*; +pub use crate::iterator::*; pub use crate::logging::*; pub use crate::proofs::*; pub use crate::proposal::*; +pub use crate::revision::*; pub use crate::value::*; #[cfg(unix)] @@ -107,6 +111,230 @@ pub unsafe extern "C" fn fwd_get_latest( invoke_with_handle(db, move |db| db.get_latest(key)) } +/// Returns an iterator optionally starting from a key in the provided revision. +/// +/// # Arguments +/// +/// * `revision` - The revision handle returned by [`fwd_get_revision`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`] +/// * ensure that `key` is a valid [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_revision<'view>( + revision: Option<&'view RevisionHandle>, + key: BorrowedBytes, +) -> IteratorResult<'view> { + invoke_with_handle(revision, move |rev| rev.iter_from(Some(key.as_slice()))) +} + +/// Returns an iterator on the provided proposal optionally starting from a key +/// +/// # Arguments +/// +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `key` is a valid for [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( + handle: Option<&'p ProposalHandle<'_>>, + key: BorrowedBytes, +) -> IteratorResult<'p> { + invoke_with_handle(handle, move |p| p.iter_from(Some(key.as_slice()))) +} + +/// Retrieves the next item from the iterator. +/// +/// # Arguments +/// +/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or +/// [`fwd_iter_on_proposal`]. +/// +/// # Returns +/// +/// - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`KeyValueResult::None`] if the iterator is exhausted (no remaining values). Once returned, +/// subsequent calls will continue returning [`KeyValueResult::None`]. You may still call this +/// safely, but freeing the iterator with [`fwd_free_iterator`] is recommended. +/// - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated +/// key value pair. +/// - [`KeyValueResult::Err`] if an I/O error occurred while retrieving the next item +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. +/// * call [`fwd_free_owned_kv_pair`] on returned [`OwnedKeyValuePair`] +/// to free the memory associated with the returned value. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult { + invoke_with_handle(handle, Iterator::next) +} + +/// Retrieves the next batch of items from the iterator. +/// +/// # Arguments +/// +/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or +/// [`fwd_iter_on_proposal`]. +/// +/// # Returns +/// +/// - [`KeyValueBatchResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`KeyValueBatchResult::Some`] with up to `n` key/value pairs. If the iterator is +/// exhausted, this may be fewer than `n`, including zero items. +/// - [`KeyValueBatchResult::Err`] if an I/O error occurred while retrieving items +/// +/// Once an empty batch or items fewer than `n` is returned (iterator exhausted), subsequent calls +/// will continue returning empty batches. You may still call this safely, but freeing the +/// iterator with [`fwd_free_iterator`] is recommended. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. +/// * call [`fwd_free_owned_key_value_batch`] on the returned batch to free any allocated memory. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_next_n( + handle: Option<&mut IteratorHandle<'_>>, + n: usize, +) -> KeyValueBatchResult { + invoke_with_handle(handle, |it| it.iter_next_n(n)) +} + +/// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. +/// +/// # Arguments +/// +/// * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`VoidResult::Ok`] if the iterator was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `iterator` is not null and that it points to +/// a valid [`IteratorHandle`] previously returned by a function from this library. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_iterator( + iterator: Option>>, +) -> VoidResult { + invoke_with_handle(iterator, drop) +} + +/// Gets a handle to the revision identified by the provided root hash. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by [`fwd_open_db`]. +/// * `root` - The hash of the revision as a [`BorrowedBytes`]. +/// +/// # Returns +/// +/// - [`RevisionResult::NullHandlePointer`] if the provided database handle is null. +/// - [`RevisionResult::Ok`] containing a [`RevisionHandle`] and root hash if the revision exists. +/// - [`RevisionResult::Err`] if the revision cannot be fetched or the root hash is invalid. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. +/// * ensure that `root` is valid for [`BorrowedBytes`]. +/// * call [`fwd_free_revision`] to free the returned handle when it is no longer needed. +/// +/// [`BorrowedBytes`]: crate::value::BorrowedBytes +/// [`RevisionHandle`]: crate::revision::RevisionHandle +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_revision( + db: Option<&DatabaseHandle>, + root: BorrowedBytes, +) -> RevisionResult { + invoke_with_handle(db, move |db| db.get_revision(root.as_ref().try_into()?)) +} + +/// Gets the value associated with the given key from the provided revision handle. +/// +/// # Arguments +/// +/// * `revision` - The revision handle returned by [`fwd_get_revision`]. +/// * `key` - The key to look up as a [`BorrowedBytes`]. +/// +/// # Returns +/// +/// - [`ValueResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`ValueResult::None`] if the key was not found in the revision. +/// - [`ValueResult::Some`] if the key was found with the associated value. +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`]. +/// * ensure that `key` is valid for [`BorrowedBytes`]. +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`] +/// returned in the result. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_from_revision( + revision: Option<&RevisionHandle>, + key: BorrowedBytes, +) -> ValueResult { + invoke_with_handle(revision, move |db| db.val(key)) +} + +/// Consumes the [`RevisionHandle`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `revision` - A pointer to a [`RevisionHandle`] previously returned by +/// [`fwd_get_revision`]. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`VoidResult::Ok`] if the revision handle was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the revision handle is valid and is not used again after +/// this function is called. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_revision(revision: Option>) -> VoidResult { + invoke_with_handle(revision, drop) +} + /// Gets the value associated with the given key from the proposal provided. /// /// # Arguments @@ -495,3 +723,45 @@ pub unsafe extern "C" fn fwd_close_db(db: Option>) -> VoidRe pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { invoke(move || drop(bytes)) } + +/// Consumes the [`OwnedKeyValueBatch`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `batch` - The [`OwnedKeyValueBatch`] struct to free, previously returned from any +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `batch` struct is valid and that the memory +/// it points to is uniquely owned by this object. However, if `batch.ptr` is null, +/// this function does nothing. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_key_value_batch(batch: OwnedKeyValueBatch) -> VoidResult { + invoke(move || drop(batch)) +} + +/// Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `kv` struct is valid. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_kv_pair(kv: OwnedKeyValuePair) -> VoidResult { + invoke(move || drop(kv)) +} diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 2c7dcdad6..1281a9993 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,10 +1,11 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, DbView, HashKey, Proposal as _}; +use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; use crate::value::KeyValuePair; +use crate::iterator::CreateIteratorResult; use metrics::counter; /// An opaque wrapper around a Proposal that also retains a reference to the @@ -97,8 +98,17 @@ impl ProposalHandle<'_> { Ok(hash_key) } -} + /// Creates an iterator on the proposal starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult((Box::new(it) as BoxKeyValueIter<'_>).into()) + } +} #[derive(Debug)] pub struct CreateProposalResult<'db> { pub handle: ProposalHandle<'db>, diff --git a/ffi/src/revision.rs b/ffi/src/revision.rs new file mode 100644 index 000000000..eb73f15e1 --- /dev/null +++ b/ffi/src/revision.rs @@ -0,0 +1,74 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::CreateIteratorResult; +use firewood::v2::api; +use firewood::v2::api::{ArcDynDbView, BoxKeyValueIter, DbView, HashKey}; + +#[derive(Debug)] +pub struct RevisionHandle { + view: ArcDynDbView, +} + +impl RevisionHandle { + /// Creates a new revision handle for the provided database view. + pub(crate) fn new(view: ArcDynDbView) -> RevisionHandle { + RevisionHandle { view } + } + + /// Creates an iterator on the revision starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .view + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult(it.into()) + } +} + +impl DbView for RevisionHandle { + type Iter<'view> + = BoxKeyValueIter<'view> + where + Self: 'view; + + fn root_hash(&self) -> Result, api::Error> { + self.view.root_hash() + } + + fn val(&self, key: K) -> Result, api::Error> { + self.view.val(key.as_ref()) + } + + fn single_key_proof(&self, key: K) -> Result { + self.view.single_key_proof(key.as_ref()) + } + + fn range_proof( + &self, + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result { + self.view.range_proof( + first_key.as_ref().map(AsRef::as_ref), + last_key.as_ref().map(AsRef::as_ref), + limit, + ) + } + + fn iter_option( + &self, + first_key: Option, + ) -> Result, api::Error> { + self.view.iter_option(first_key.as_ref().map(AsRef::as_ref)) + } +} + +#[derive(Debug)] +pub struct GetRevisionResult { + pub handle: RevisionHandle, + pub root_hash: HashKey, +} diff --git a/ffi/src/value.rs b/ffi/src/value.rs index f2cb160e0..c80f2a610 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -11,12 +11,13 @@ mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::hash_key::HashKey; -pub use self::kvp::KeyValuePair; +pub use self::kvp::{KeyValuePair, OwnedKeyValueBatch, OwnedKeyValuePair}; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ - ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, ProposalResult, - RangeProofResult, ValueResult, VoidResult, + ChangeProofResult, HandleResult, HashResult, IteratorResult, KeyValueBatchResult, + KeyValueResult, NextKeyRangeResult, ProposalResult, RangeProofResult, RevisionResult, + ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index 98471147a..b693d5db4 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -3,9 +3,12 @@ use std::fmt; +use crate::value::BorrowedBytes; +use crate::{OwnedBytes, OwnedSlice}; use firewood::v2::api; -use crate::value::BorrowedBytes; +/// A type alias for a rust-owned byte slice. +pub type OwnedKeyValueBatch = OwnedSlice; /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] @@ -61,3 +64,23 @@ impl<'a> api::KeyValuePair for &KeyValuePair<'a> { (*self).into_batch() } } + +/// Owned version of `KeyValuePair`, returned to ffi callers. +/// +/// C callers must free this using [`crate::fwd_free_owned_kv_pair`], +/// not the C standard library's `free` function. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OwnedKeyValuePair { + pub key: OwnedBytes, + pub value: OwnedBytes, +} + +impl From<(Box<[u8]>, Box<[u8]>)> for OwnedKeyValuePair { + fn from(value: (Box<[u8]>, Box<[u8]>)) -> Self { + OwnedKeyValuePair { + key: value.0.into(), + value: value.1.into(), + } + } +} diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index edc52a180..20b2d206a 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -1,12 +1,14 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt; - +use firewood::merkle; use firewood::v2::api; +use std::fmt; +use crate::revision::{GetRevisionResult, RevisionHandle}; use crate::{ - ChangeProofContext, CreateProposalResult, HashKey, NextKeyRange, OwnedBytes, ProposalHandle, + ChangeProofContext, CreateIteratorResult, CreateProposalResult, HashKey, IteratorHandle, + NextKeyRange, OwnedBytes, OwnedKeyValueBatch, OwnedKeyValuePair, ProposalHandle, RangeProofContext, }; @@ -306,6 +308,163 @@ pub enum ProposalResult<'db> { Err(OwnedBytes), } +/// A result type returned from FFI functions that create an iterator +#[derive(Debug)] +#[repr(C)] +pub enum IteratorResult<'db> { + /// The caller provided a null pointer to a revision/proposal handle. + NullHandlePointer, + /// Building the iterator was successful and the iterator handle is returned + Ok { + /// An opaque pointer to the [`IteratorHandle`]. + /// The value should be freed with [`fwd_free_iterator`] + /// + /// [`fwd_free_iterator`]: crate::fwd_free_iterator + handle: Box>, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +/// A result type returned from iterator FFI functions +#[derive(Debug)] +#[repr(C)] +pub enum KeyValueResult { + /// The caller provided a null pointer to an iterator handle. + NullHandlePointer, + /// The iterator is exhausted + None, + /// The next item is returned. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with the key and the value of this pair. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Some(OwnedKeyValuePair), + /// An error occurred and the message is returned as an [`OwnedBytes`]. The + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From>> for KeyValueResult { + fn from(value: Option>) -> Self { + match value { + Some(value) => match value { + Ok(value) => KeyValueResult::Some(value.into()), + Err(err) => KeyValueResult::Err(err.to_string().into_bytes().into()), + }, + None => KeyValueResult::None, + } + } +} + +/// A result type returned from iterator FFI functions +#[derive(Debug)] +#[repr(C)] +pub enum KeyValueBatchResult { + /// The caller provided a null pointer to an iterator handle. + NullHandlePointer, + /// The next batch of items on iterator are returned. + Some(OwnedKeyValueBatch), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From, api::Error>> for KeyValueBatchResult { + fn from(value: Result, api::Error>) -> Self { + match value { + Ok(pairs) => { + let values: Vec<_> = pairs.into_iter().map(Into::into).collect(); + KeyValueBatchResult::Some(values.into()) + } + Err(err) => KeyValueBatchResult::Err(err.to_string().into_bytes().into()), + } + } +} + +impl<'db> From> for IteratorResult<'db> { + fn from(value: CreateIteratorResult<'db>) -> Self { + IteratorResult::Ok { + handle: Box::new(value.0), + } + } +} + +impl<'db, E: fmt::Display> From, E>> for IteratorResult<'db> { + fn from(value: Result, E>) -> Self { + match value { + Ok(res) => res.into(), + Err(err) => IteratorResult::Err(err.to_string().into_bytes().into()), + } + } +} + +/// A result type returned from FFI functions that get a revision +#[derive(Debug)] +#[repr(C)] +pub enum RevisionResult { + /// The caller provided a null pointer to a database handle. + NullHandlePointer, + /// The provided root was not found in the database. + RevisionNotFound(HashKey), + /// Getting the revision was successful and the revision handle and root + /// hash are returned. + Ok { + /// An opaque pointer to the [`RevisionHandle`]. + /// The value should be freed with [`fwd_free_revision`] + /// + /// [`fwd_free_revision`]: crate::fwd_free_revision + handle: Box, + /// The root hash of the revision. + root_hash: HashKey, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. The + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From for RevisionResult { + fn from(value: GetRevisionResult) -> Self { + RevisionResult::Ok { + handle: Box::new(value.handle), + root_hash: HashKey::from(value.root_hash), + } + } +} + +impl From> for RevisionResult { + fn from(value: Result) -> Self { + match value { + Ok(res) => res.into(), + Err(api::Error::RevisionNotFound { provided }) => RevisionResult::RevisionNotFound( + HashKey::from(provided.unwrap_or_else(api::HashKey::empty)), + ), + Err(err) => RevisionResult::Err(err.to_string().into_bytes().into()), + } + } +} + impl<'db, E: fmt::Display> From, E>> for ProposalResult<'db> { fn from(value: Result, E>) -> Self { match value { @@ -383,6 +542,10 @@ impl_null_handle_result!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, + RevisionResult, + KeyValueBatchResult, + KeyValueResult, ); impl_cresult!( @@ -394,6 +557,10 @@ impl_cresult!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, + RevisionResult, + KeyValueBatchResult, + KeyValueResult, ); enum Panic {