diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9fc197f54e5498b4982b16af19dda5420c45c524..63f8f9ba4809fade3cbfff1bfad07f6a65cae29f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,7 @@ workflow: - server.ts .env: &env - image: registry.duniter.org/docker/duniter-ci:v0.0.4 + image: registry.duniter.org/docker/duniter-ci:v0.2.0 tags: - redshift before_script: diff --git a/Cargo.lock b/Cargo.lock index 11e4923e074358799b14a01a8f966ff63a12cb7f..335797456d9a61fea4f92e07c37f298968ed9c84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.13.0" @@ -17,9 +27,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" dependencies = [ "memchr", ] @@ -39,6 +49,12 @@ version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + [[package]] name = "arrayref" version = "0.3.6" @@ -51,6 +67,123 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +[[package]] +name = "async-attributes" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21279cfaa4f47df10b1816007e738ca3747ef2ee53ffc51cdbf57a8bb266fee3" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-global-executor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fefeb39da249f4c33af940b779a56723ce45809ef5c54dad84bb538d4ffb6d9e" +dependencies = [ + "async-executor", + "async-io", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a027f4b662e59d7070791e33baafd36affe67388965701b50039db5ebb240d2" +dependencies = [ + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fa76751505e8df1c7a77762f60486f60c71bbd9b8557f4da6ad47d083732ed" +dependencies = [ + "async-attributes", + "async-global-executor", + "async-io", + "async-mutex", + "blocking", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab27c1aa62945039e44edaeee1dc23c74cc0c303dd5fe0fb462a184f1c3a518" + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "atty" version = "0.2.14" @@ -64,15 +197,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1" dependencies = [ "addr2line", "cfg-if", @@ -102,9 +235,9 @@ checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43" [[package]] name = "bincode" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder", "serde", @@ -148,17 +281,43 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "blocking" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2640778f8053e72c11f621b0a5175a0560a269282aa98ed85107773ab8e2a556" +dependencies = [ + "async-channel", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", + "waker-fn", +] + [[package]] name = "bs58" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" +[[package]] +name = "bstr" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byte-tools" @@ -172,11 +331,26 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" -version = "1.0.52" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" [[package]] name = "cfg-if" @@ -221,6 +395,53 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "cmake" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" +dependencies = [ + "cc", +] + +[[package]] +name = "comfy-table" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f97a418f1dee79b100875499e272ea81882c1b931a9c8432e165b595b461752" +dependencies = [ + "crossterm", + "strum", + "strum_macros", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -236,6 +457,52 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -263,24 +530,39 @@ dependencies = [ ] [[package]] -name = "crossbeam-queue" -version = "0.2.1" +name = "crossbeam-utils" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ + "autocfg", "cfg-if", - "crossbeam-utils", + "lazy_static", ] [[package]] -name = "crossbeam-utils" -version = "0.7.2" +name = "crossterm" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7" dependencies = [ - "autocfg", - "cfg-if", + "bitflags", + "crossterm_winapi", "lazy_static", + "libc", + "mio", + "parking_lot 0.10.2", + "signal-hook", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c" +dependencies = [ + "winapi", ] [[package]] @@ -295,6 +577,28 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40" +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "ctrlc" version = "3.1.6" @@ -316,6 +620,12 @@ dependencies = [ "snafu", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -352,16 +662,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] -name = "dubp-common" -version = "0.2.0" +name = "downcast" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b41d722bf752e5f87d07685aba6df25f69881f9aafa7060fae459e2948d8080" -dependencies = [ - "dup-crypto 0.18.0", - "serde", - "serde_json", - "thiserror", -] +checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" [[package]] name = "dubp-common" @@ -369,7 +673,7 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00588975a1ce2511ba0072563dc69e79534c28846ad68701699ae112d01975ff" dependencies = [ - "dup-crypto 0.25.2", + "dup-crypto", "serde", "serde_json", "thiserror", @@ -409,7 +713,7 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45d87c99e3e231625ba35c0f0ad4f8c6186f91ae5f0504fa752b47e46762dbc0" dependencies = [ - "dubp-common 0.25.2", + "dubp-common", "serde", "smallvec", "thiserror", @@ -420,12 +724,47 @@ name = "dubp-wot" version = "0.11.0" dependencies = [ "bincode", - "dubp-common 0.2.0", + "dubp-common", "log", "rayon", "serde", ] +[[package]] +name = "duniter-dbex" +version = "0.1.0" +dependencies = [ + "arrayvec", + "comfy-table", + "dirs", + "dubp-common", + "duniter-dbs", + "rayon", + "serde", + "serde_json", + "structopt", + "unwrap", +] + +[[package]] +name = "duniter-dbs" +version = "0.1.0" +dependencies = [ + "arrayvec", + "chrono", + "dubp-common", + "kv_typed", + "log", + "mockall", + "once_cell", + "serde", + "serde_json", + "smallvec", + "tempdir", + "thiserror", + "unwrap", +] + [[package]] name = "duniter-launcher" version = "1.8.1" @@ -447,7 +786,7 @@ version = "1.8.1" dependencies = [ "bincode", "bs58", - "dubp-common 0.25.2", + "dubp-common", "dubp-documents", "dubp-documents-parser", "dubp-wallet", @@ -461,22 +800,6 @@ dependencies = [ "unwrap", ] -[[package]] -name = "dup-crypto" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7315898eda74bf7c9e825f9afae1c9f16debb401fdb7d658fb851fb00a6260c" -dependencies = [ - "base64 0.11.0", - "bs58", - "byteorder", - "ring", - "serde", - "thiserror", - "unwrap", - "zeroize", -] - [[package]] name = "dup-crypto" version = "0.25.2" @@ -496,9 +819,9 @@ dependencies = [ [[package]] name = "either" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "envmnt" @@ -520,17 +843,32 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] + [[package]] name = "flate2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94" +checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee" dependencies = [ "cfg-if", "crc32fast", @@ -551,12 +889,88 @@ dependencies = [ "thiserror", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fsio" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-lite" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b2ec5fce115ef4d4cc77f46025232fba6a2f84381ff42337794147050aae971" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.12.3" @@ -599,6 +1013,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "half" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" + [[package]] name = "hashbrown" version = "0.9.1" @@ -616,9 +1049,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" dependencies = [ "libc", ] @@ -633,6 +1066,24 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.6" @@ -641,9 +1092,9 @@ checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "js-sys" -version = "0.3.37" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" dependencies = [ "wasm-bindgen", ] @@ -660,17 +1111,97 @@ dependencies = [ "unwrap", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "kv_typed" +version = "0.1.0" +dependencies = [ + "async-channel", + "async-std", + "criterion", + "crossbeam-channel", + "kv_typed_code_gen", + "leveldb_minimal", + "maybe-async", + "mockall", + "parking_lot 0.11.0", + "rayon", + "regex", + "serde_json", + "sled", + "smallvec", + "thiserror", + "unwrap", +] + +[[package]] +name = "kv_typed_code_gen" +version = "0.1.0" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leveldb-sys" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c44b9b785ca705d58190ebd432a4e7edb900eadf236ff966d7d1307e482e87" +dependencies = [ + "cmake", + "libc", + "num_cpus", +] + +[[package]] +name = "leveldb_minimal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b4cb22d7d3cce486fc6e2ef7cd25d38e118f525f79c4a946ac48d89c5d16b1" +dependencies = [ + "leveldb-sys", + "libc", +] + [[package]] name = "libc" -version = "0.2.69" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lock_api" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" +dependencies = [ + "scopeguard", +] [[package]] name = "log" @@ -693,6 +1224,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "maybe-async" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51772c1b1c6c129eeeaf8ffe7581906aa14c8cca116b40d41ce1285f5a4fdfa0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -707,20 +1249,81 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" dependencies = [ "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53a6ea5f38c0a48ca42159868c6d8e1bd56c0451238856cc08d58563643bdc3" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi", +] + +[[package]] +name = "mockall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ecfc6340c5b98a9a270b56e5f43353d87ebb18d9458a9301344bc79317c563" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b873f753808fe0c3827ce76edb3ace27804966dfde3043adfac1c24d0a2559df" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nb-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi", ] [[package]] @@ -790,11 +1393,26 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "ntapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" +dependencies = [ + "winapi", ] [[package]] @@ -883,12 +1501,74 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +[[package]] +name = "oorandom" +version = "11.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" + [[package]] name = "opaque-debug" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.7.2", +] + +[[package]] +name = "parking_lot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" +dependencies = [ + "instant", + "lock_api 0.4.1", + "parking_lot_core 0.8.0", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi 0.0.3", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if", + "cloudabi 0.1.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "pest" version = "2.1.3" @@ -932,6 +1612,72 @@ dependencies = [ "sha-1", ] +[[package]] +name = "pin-project-lite" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plotters" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" +dependencies = [ + "js-sys", + "num-traits", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "polling" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7215a098a80ab8ebd6349db593dc5faf741781bad0c4b7c5701fea6af548d52c" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-sys", + "winapi", +] + +[[package]] +name = "predicates" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" + +[[package]] +name = "predicates-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -958,28 +1704,57 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rayon" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +checksum = "dcf6960dc9a5b4ee8d3e4c5787b4a112a8818e0290a42ff664ad60692fdf2032" dependencies = [ + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -987,17 +1762,26 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" +checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" dependencies = [ + "crossbeam-channel", "crossbeam-deque", - "crossbeam-queue", "crossbeam-utils", "lazy_static", "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -1017,9 +1801,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.7" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr", @@ -1027,11 +1811,29 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] [[package]] name = "ring" @@ -1066,6 +1868,15 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "rusty-hook" version = "0.11.2" @@ -1084,6 +1895,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1107,18 +1927,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" dependencies = [ "proc-macro2", "quote", @@ -1127,9 +1957,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" dependencies = [ "itoa", "ryu", @@ -1148,6 +1978,49 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "signal-hook" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +dependencies = [ + "libc", + "mio", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" +dependencies = [ + "arc-swap", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "sled" +version = "0.34.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f72c064e63fbca3138ad07f3588c58093f1684f3a99f60dcfa6d46b87e60fde7" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.0", +] + [[package]] name = "smallvec" version = "1.4.2" @@ -1178,6 +2051,18 @@ dependencies = [ "syn", ] +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -1214,11 +2099,29 @@ dependencies = [ "syn", ] +[[package]] +name = "strum" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89a286a7e3b5720b9a477b23253bc50debac207c8d21505f8e70b36792f11b5" + +[[package]] +name = "strum_macros" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e61bb0be289045cb80bfce000512e32d09f8337e54c186725da381377ad1f8d5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" -version = "1.0.39" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" +checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" dependencies = [ "proc-macro2", "quote", @@ -1227,9 +2130,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", "quote", @@ -1237,6 +2140,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -1286,6 +2199,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinytemplate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "toml" version = "0.5.6" @@ -1295,6 +2218,12 @@ dependencies = [ "serde", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "typenum" version = "1.12.0" @@ -1321,9 +2250,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "untrusted" @@ -1337,6 +2266,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" + [[package]] name = "vec_map" version = "0.8.2" @@ -1355,6 +2290,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1369,9 +2321,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.60" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1379,9 +2331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.60" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" dependencies = [ "bumpalo", "lazy_static", @@ -1392,11 +2344,23 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.60" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1404,9 +2368,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.60" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ "proc-macro2", "quote", @@ -1417,25 +2381,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.60" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" [[package]] name = "web-sys" -version = "0.3.37" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "wepoll-sys" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc2cba3fe88be1a8fcb55c727fa4cd5b0cf2d7438722792e22f26f04bc1e0" +dependencies = [ + "cc", +] + [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", @@ -1447,6 +2420,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1463,18 +2445,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e146e0790cb5cf9344057486f718130c3496e027..90a051b9c4c06f1dd6f307707e18fe04c1974128 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,12 @@ rusty-hook = "0.11.2" [workspace] members = [ "neon/native", + "rust-bins/duniter-dbex", "rust-bins/xtask", - "rust-libs/dubp-wot" + "rust-libs/dubp-wot", + "rust-libs/duniter-dbs", + "rust-libs/tools/kv_typed", + "rust-libs/tools/kv_typed_code_gen" ] [patch.crates-io] @@ -38,3 +42,4 @@ members = [ #dubp-documents = { path = "../dubp-rs-libs/documents" } #dubp-documents-parser = { path = "../dubp-rs-libs/documents-parser" } #dubp-wallet = { path = "../dubp-rs-libs/wallet" } +#leveldb_minimal = { path = "../../../../rust/leveldb_minimal" } diff --git a/rust-bins/duniter-dbex/Cargo.toml b/rust-bins/duniter-dbex/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2cb639e6e9a09eb1132b03bd608625cc96d8be98 --- /dev/null +++ b/rust-bins/duniter-dbex/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "duniter-dbex" +version = "0.1.0" +authors = ["elois <elois@duniter.org>"] +description = "Duniter blockchain DB" +repository = "https://git.duniter.org/nodes/typescript/duniter/rust-bins/duniter-dbs-explorer" +readme = "README.md" +keywords = ["duniter", "database"] +license = "AGPL-3.0" +edition = "2018" + +[[bin]] +bench = false +path = "src/main.rs" +name = "dex" + +[build-dependencies] +structopt = "0.3.16" + +[dependencies] +arrayvec = "0.5.1" +comfy-table = "1.0.0" +dirs = "3.0.1" +dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] } +duniter-dbs = { path = "../../rust-libs/duniter-dbs", default-features = false, features = ["explorer", "leveldb_backend", "sync"] } +rayon = "1.3.1" +serde_json = "1.0.53" +structopt = "0.3.16" + +[dev-dependencies] +serde = { version = "1.0.105", features = ["derive"] } +unwrap = "1.2.1" diff --git a/rust-bins/duniter-dbex/README.md b/rust-bins/duniter-dbex/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f49c48fd89510f6efa24c7b495396cac545d7d21 --- /dev/null +++ b/rust-bins/duniter-dbex/README.md @@ -0,0 +1,25 @@ +# Duniter databases explorer (dex) + +## Compile + + git clone https://git.duniter.org/nodes/typescript/duniter.git + cd duniter + cargo build --release -p duniter-dbex + +The binary executable is then here: `target/release/dex` + +## Use + +See `dex --help` + +## Autocompletion + +Bash autocompletion script is available here : `target/release/dex.bash` + +**Several others Shell are supported : Zsh, Fish, Powershell and Elvish!** + +To generate the autocompletion script for your shell, recompile with env var `COMPLETION_SHELL`. + +For exemple for fish : `COMPLETION_SHELL=fish cargo build --release -p duniter-dbex` + +The autocompletion script can be found in : `target/release/` diff --git a/rust-bins/duniter-dbex/build.rs b/rust-bins/duniter-dbex/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd655380a11772f3baafcd6d14fcde679dcf07f3 --- /dev/null +++ b/rust-bins/duniter-dbex/build.rs @@ -0,0 +1,47 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +extern crate structopt; + +include!("src/cli.rs"); + +use std::env; +use structopt::clap::Shell; + +fn main() { + // Define out dir + let current_dir = match env::current_dir() { + Err(_e) => return, + Ok(current_dir) => current_dir, + }; + let out_dir = current_dir.as_path().join(format!( + "../../target/{}", + env::var("PROFILE").unwrap_or_else(|_| "debug".to_owned()) + )); + + // Define shell + let shell = if let Some(shell_str) = option_env!("COMPLETION_SHELL") { + Shell::from_str(shell_str).expect("Unknown shell") + } else { + Shell::Bash + }; + + let mut app = Opt::clap(); + app.gen_completions( + "dex", // We need to specify the bin name manually + shell, // Then say which shell to build completions for + out_dir, + ); // Then say where write the completions to +} diff --git a/rust-bins/duniter-dbex/src/cli.rs b/rust-bins/duniter-dbex/src/cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..e5da817361b9acf60b25c2fe70165c2feee7d277 --- /dev/null +++ b/rust-bins/duniter-dbex/src/cli.rs @@ -0,0 +1,125 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::{num::NonZeroUsize, path::PathBuf, str::FromStr}; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "duniter-dbex", about = "Duniter databases explorer.")] +pub struct Opt { + /// Duniter profile name + #[structopt(short, long)] + pub profile: Option<String>, + + /// Duniter home directory + #[structopt(short, long, parse(from_os_str))] + pub home: Option<PathBuf>, + + /// database + #[structopt(default_value = "bc_v1", possible_values = &["bc_v1", "bc_v2", "mp_v1"])] + pub database: Database, + + #[structopt(subcommand)] + pub cmd: SubCommand, +} + +#[derive(Debug)] +pub enum Database { + BcV1, +} + +impl FromStr for Database { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "bc_v1" => Ok(Self::BcV1), + "bc_v2" | "mp_v1" => unimplemented!(), + _ => unreachable!(), + } + } +} + +#[derive(Debug, StructOpt)] +pub enum SubCommand { + /// Count collection entries + Count { collection: String }, + /// Get one value + Get { collection: String, key: String }, + /// Search values by criteria + Find { + collection: String, + #[structopt(short, long)] + /// Key min + start: Option<String>, + #[structopt(short, long)] + /// Key max + end: Option<String>, + /// Filter keys by a regular expression + #[structopt(short = "k", long)] + key_regex: Option<String>, + /// Show keys only + #[structopt(long)] + keys_only: bool, + /// Filter values by a regular expression + #[structopt(short = "v", long)] + value_regex: Option<String>, + /// Maximum number of entries to be found (Slower because force sequential search) + #[structopt(short, long)] + limit: Option<usize>, + /// Browse the collection upside down + #[structopt(short, long)] + reverse: bool, + /// Step by + #[structopt(long, default_value = "1")] + step: NonZeroUsize, + /// Output format + #[structopt(short, long, default_value = "table-json", possible_values = &["csv", "json", "table-json", "table-properties"])] + output: OutputFormat, + /// Pretty json (Only for output format json or table-json) + #[structopt(long)] + pretty: bool, + /// Show only the specified properties + #[structopt(short, long)] + properties: Vec<String>, + /// Export found data to a file + #[structopt(short, long, parse(from_os_str))] + file: Option<PathBuf>, + }, + /// Show database schema + Schema, +} + +#[derive(Clone, Copy, Debug)] +pub enum OutputFormat { + Table, + TableJson, + Json, + Csv, +} + +impl FromStr for OutputFormat { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "csv" => Ok(Self::Csv), + "json" => Ok(Self::Json), + "table-properties" => Ok(Self::Table), + "table-json" => Ok(Self::TableJson), + _ => unreachable!(), + } + } +} diff --git a/rust-bins/duniter-dbex/src/main.rs b/rust-bins/duniter-dbex/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..5d6b646645ed63f67953bf2f663e503804e0a44b --- /dev/null +++ b/rust-bins/duniter-dbex/src/main.rs @@ -0,0 +1,305 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces +)] + +mod cli; +mod print_found_data; +mod stringify_json_value; + +use self::cli::{Database, Opt, OutputFormat, SubCommand}; +use self::stringify_json_value::stringify_json_value; +use comfy_table::Table; +use duniter_dbs::kv_typed::prelude::*; +use duniter_dbs::prelude::*; +use duniter_dbs::regex::Regex; +use duniter_dbs::serde_json::{Map, Value}; +use duniter_dbs::smallvec::{smallvec, SmallVec}; +use duniter_dbs::BcV1Db; +use duniter_dbs::BcV1DbWritable; +use rayon::prelude::*; +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::{stdin, Write}, + iter::FromIterator, + time::Instant, +}; +use structopt::StructOpt; + +const DATA_DIR: &str = "data"; +const TOO_MANY_ENTRIES_ALERT: usize = 5_000; + +fn main() -> Result<(), String> { + let opt = Opt::from_args(); + + let home = if let Some(home) = opt.home { + home + } else { + dirs::config_dir() + .ok_or_else(|| { + "Fail to auto find duniter's home directory, please specify it explicitly." + .to_owned() + })? + .as_path() + .join("duniter") + }; + + let profile_name = if let Some(profile_name) = opt.profile { + profile_name + } else { + "duniter_default".to_owned() + }; + + let profile_path = home.as_path().join(&profile_name); + let data_path = profile_path.as_path().join(DATA_DIR); + + if !data_path.exists() { + return Err(format!( + "Path '{}' don't exist !", + data_path.to_str().expect("non-UTF-8 strings not supported") + )); + } + + let open_db_start_time = Instant::now(); + match opt.database { + Database::BcV1 => apply_subcommand( + BcV1Db::<LevelDb>::open(LevelDbConf { + db_path: data_path.as_path().join("leveldb"), + ..Default::default() + }) + .map_err(|e| format!("{}", e))?, + opt.cmd, + open_db_start_time, + ), + } +} + +fn apply_subcommand<DB: DbExplorable>( + db: DB, + cmd: SubCommand, + open_db_start_time: Instant, +) -> Result<(), String> { + let duration = open_db_start_time.elapsed(); + println!( + "Database opened in {}.{:06} seconds.", + duration.as_secs(), + duration.subsec_micros() + ); + let start_time = Instant::now(); + + match cmd { + SubCommand::Count { collection } => { + if let ExplorerActionResponse::Count(count) = db + .explore(&collection, ExplorerAction::Count, stringify_json_value) + .map_err(|e| format!("{}", e))? + .map_err(|e| e.0)? + { + let duration = start_time.elapsed(); + println!( + "Count operation performed in {}.{:06} seconds.", + duration.as_secs(), + duration.subsec_micros() + ); + println!("\nThis collection contains {} entries.", count); + } + } + SubCommand::Get { collection, key } => { + if let ExplorerActionResponse::Get(value_opt) = db + .explore( + &collection, + ExplorerAction::Get { key: &key }, + stringify_json_value, + ) + .map_err(|e| format!("{}", e))? + .map_err(|e| e.0)? + { + if let Some(value) = value_opt { + println!("\n{}", value) + } else { + println!("\nThis collection not contains this key.") + } + } + } + SubCommand::Find { + collection, + start, + end, + key_regex, + keys_only, + value_regex, + limit, + reverse, + properties, + output: output_format, + pretty: pretty_json, + file: output_file, + step, + } => { + let value_regex_opt = opt_string_to_res_opt_regex(value_regex)?; + let captures_headers = if let Some(ref value_regex) = value_regex_opt { + value_regex + .capture_names() + .skip(1) + .enumerate() + .map(|(i, name_opt)| { + if let Some(name) = name_opt { + name.to_owned() + } else { + format!("CAP{}", i + 1) + } + }) + .collect() + } else { + vec![] + }; + if let ExplorerActionResponse::Find(entries) = db + .explore( + &collection, + ExplorerAction::Find { + key_min: start, + key_max: end, + key_regex: opt_string_to_res_opt_regex(key_regex)?, + value_regex: value_regex_opt, + limit, + reverse, + step, + }, + stringify_json_value, + ) + .map_err(|e| format!("{}", e))? + .map_err(|e| e.0)? + { + let duration = start_time.elapsed(); + println!( + "Search performed in {}.{:06} seconds.\n\n{} entries found.", + duration.as_secs(), + duration.subsec_micros(), + entries.len() + ); + + if !too_many_entries(entries.len(), output_file.is_none()) + .map_err(|e| format!("{}", e))? + { + return Ok(()); + } + + let start_print = Instant::now(); + if let Some(output_file) = output_file { + let mut file = + File::create(output_file.as_path()).map_err(|e| format!("{}", e))?; + + //let mut file_buffer = BufWriter::new(file); + print_found_data::print_found_data( + &mut file, + output_format, + pretty_json, + false, + print_found_data::DataToShow { + entries, + keys_only, + only_properties: properties, + }, + captures_headers, + ) + .map_err(|e| format!("{}", e))?; + //file_buffer.flush().map_err(|e| format!("{}", e))?; + + let export_duration = start_print.elapsed(); + println!( + "Search results were written to file: '{}' in {}.{:06} seconds.", + output_file + .to_str() + .expect("output-file contains invalid utf8 characters"), + export_duration.as_secs(), + export_duration.subsec_micros(), + ); + } else { + print_found_data::print_found_data( + &mut std::io::stdout(), + output_format, + pretty_json, + true, + print_found_data::DataToShow { + entries, + keys_only, + only_properties: properties, + }, + captures_headers, + ) + .map_err(|e| format!("{}", e))?; + let print_duration = start_print.elapsed(); + println!( + "Search results were displayed in {}.{:06} seconds.", + print_duration.as_secs(), + print_duration.subsec_micros(), + ); + }; + } + } + SubCommand::Schema => { + show_db_schema(db.list_collections()); + } + }; + + Ok(()) +} + +fn too_many_entries(entries_len: usize, output_in_term: bool) -> std::io::Result<bool> { + if entries_len > TOO_MANY_ENTRIES_ALERT { + println!( + "{} all {} entries ? (Be careful, may crash your system!) [y/N]", + if output_in_term { "Display" } else { "Export" }, + entries_len + ); + let mut buffer = String::new(); + stdin().read_line(&mut buffer)?; + Ok(buffer == "y\n") + } else { + Ok(true) + } +} + +fn show_db_schema(collections_names: Vec<(&'static str, &'static str, &'static str)>) { + let mut table = Table::new(); + table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); + table.set_header(&["Collection name", "Key type", "Value type"]); + for (collection_name, key_full_type_name, value_full_type_name) in collections_names { + let key_type_name_opt = key_full_type_name.split(':').last(); + let value_type_name_opt = value_full_type_name.split(':').last(); + table.add_row(&[ + collection_name, + key_type_name_opt.unwrap_or("?"), + value_type_name_opt.unwrap_or("?"), + ]); + } + println!("{}", table); +} + +#[inline] +fn opt_string_to_res_opt_regex(str_regex_opt: Option<String>) -> Result<Option<Regex>, String> { + if let Some(str_regex) = str_regex_opt { + Ok(Some(Regex::new(&str_regex).map_err(|e| format!("{}", e))?)) + } else { + Ok(None) + } +} diff --git a/rust-bins/duniter-dbex/src/print_found_data.rs b/rust-bins/duniter-dbex/src/print_found_data.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c600ef0b2ab22b7831a79a1ec0f83d4558ebaa0 --- /dev/null +++ b/rust-bins/duniter-dbex/src/print_found_data.rs @@ -0,0 +1,383 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +const KEY_COLUMN_NAME: &str = "Key"; +const VALUE_COLUMN_NAME: &str = "Value"; + +pub struct DataToShow { + pub entries: Vec<EntryFound>, + pub keys_only: bool, + pub only_properties: Vec<String>, +} + +pub fn print_found_data<W: Write>( + output: &mut W, + output_format: OutputFormat, + pretty_json: bool, + dynamic_content_arrangement: bool, + data_to_show: DataToShow, + captures_names: Vec<String>, +) -> std::io::Result<()> { + let DataToShow { + entries, + keys_only, + only_properties, + } = data_to_show; + if entries.is_empty() { + return Ok(()); + } + + let only_properties_set = if !only_properties.is_empty() { + HashSet::from_iter(only_properties.into_iter()) + } else { + HashSet::with_capacity(0) + }; + + match output_format { + OutputFormat::Json => { + let json_array = if keys_only { + entries + .into_par_iter() + .map(|entry| key_to_json(entry.key)) + .collect() + } else { + entries + .into_par_iter() + .map(|entry| entry_to_json(&only_properties_set, &captures_names, entry)) + .collect() + }; + if pretty_json { + writeln!(output, "{:#}", Value::Array(json_array)) + } else { + writeln!(output, "{}", Value::Array(json_array)) + } + } + OutputFormat::Table => { + // If value is not an object or an array of objects, force raw output format + let mut entries_iter = entries.iter(); + let first_object_opt = if keys_only { + None + } else { + loop { + if let Some(EntryFound { value, .. }) = entries_iter.next() { + if let Value::Array(ref json_array) = value { + if json_array.is_empty() { + continue; + } else { + break json_array[0].as_object(); + } + } else { + break value.as_object(); + } + } else { + // All values are empty array, force raw output format + break None; + } + } + }; + + let properties_names = if let Some(first_object) = first_object_opt { + if only_properties_set.is_empty() { + first_object.keys().cloned().collect::<HashSet<String>>() + } else { + first_object + .keys() + .filter(|property_name| { + only_properties_set.contains(property_name.as_str()) + }) + .cloned() + .collect::<HashSet<String>>() + } + } else { + return print_found_data( + output, + OutputFormat::TableJson, + pretty_json, + dynamic_content_arrangement, + print_found_data::DataToShow { + entries, + keys_only, + only_properties: vec![], + }, + captures_names, + ); + }; + + // Create table + let mut table = Table::new(); + if dynamic_content_arrangement { + table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); + } + + // Map data by property + let entries_map: Vec<HashMap<String, String>> = entries + .into_par_iter() + .map(|entry| entry_to_rows(&only_properties_set, &captures_names, entry)) + .flatten() + .collect(); + + // Define table headers + let mut headers = Vec::with_capacity(1 + properties_names.len() + captures_names.len()); + headers.push(KEY_COLUMN_NAME.to_owned()); + if !keys_only { + for property_name in properties_names { + headers.push(property_name); + } + headers.sort_by(|a, b| { + if a == KEY_COLUMN_NAME { + std::cmp::Ordering::Less + } else if b == KEY_COLUMN_NAME { + std::cmp::Ordering::Greater + } else { + a.cmp(b) + } + }); + headers.extend(captures_names); + } + table.set_header(&headers); + + // Fill table + for properties_values in entries_map { + let mut row = SmallVec::<[&str; 8]>::new(); + for column_name in &headers { + if let Some(property_value) = properties_values.get(column_name) { + row.push(property_value.as_str()); + } else { + row.push("") + } + } + table.add_row(row); + } + + // Print table + writeln!(output, "{}", table) + } + OutputFormat::TableJson => { + let mut table = Table::new(); + if dynamic_content_arrangement { + table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); + } + let mut headers = Vec::with_capacity(2 + captures_names.len()); + headers.push(KEY_COLUMN_NAME); + if !keys_only { + headers.push(VALUE_COLUMN_NAME); + } + headers.extend(captures_names.iter().map(String::as_str)); + table.set_header(headers); + for EntryFound { + key, + value, + captures: captures_opt, + } in entries + { + let mut row = Vec::with_capacity(2 + captures_names.len()); + row.push(key); + if keys_only { + table.add_row(row); + } else { + if pretty_json { + row.push(format!("{:#}", value)); + } else { + row.push(value.to_string()); + } + let rows = caps_to_rows(row, &captures_names[..], captures_opt); + for row in rows { + table.add_row(row); + } + } + } + writeln!(output, "{}", table) + } + _ => todo!(), + } +} + +#[inline(always)] +fn key_to_json(key: String) -> Value { + Value::String(key) +} + +fn entry_to_json( + only_properties_set: &HashSet<String>, + captures_names: &[String], + entry: EntryFound, +) -> Value { + let EntryFound { + key, + mut value, + captures: captures_opt, + } = entry; + if !only_properties_set.is_empty() { + match value { + Value::Object(ref mut json_map) => { + let mut properties_to_rm = SmallVec::<[String; 64]>::new(); + for property_name in json_map.keys() { + if !only_properties_set.contains(property_name) { + properties_to_rm.push(property_name.clone()); + } + } + for property_name in properties_to_rm { + json_map.remove(&property_name); + } + } + Value::Array(ref mut json_array) => { + for sub_value in json_array { + if let Value::Object(ref mut json_map) = sub_value { + let mut properties_to_rm = SmallVec::<[String; 64]>::new(); + for property_name in json_map.keys() { + if !only_properties_set.contains(property_name) { + properties_to_rm.push(property_name.clone()); + } + } + for property_name in properties_to_rm { + json_map.remove(&property_name); + } + } + } + } + _ => (), + } + } + let mut json_map = Map::with_capacity(2); + json_map.insert("key".to_owned(), Value::String(key)); + json_map.insert("value".to_owned(), value); + if !captures_names.is_empty() { + let mut captures_objects = Vec::new(); + if let Some(ValueCaptures(captures)) = captures_opt { + for capture in captures { + let mut capture_object = Map::with_capacity(captures_names.len()); + for (i, capture_group_value_opt) in capture.into_iter().enumerate() { + if let Some(capture_group_value) = capture_group_value_opt { + capture_object.insert( + captures_names[i].to_owned(), + Value::String(capture_group_value), + ); + } + } + captures_objects.push(Value::Object(capture_object)); + } + } + json_map.insert("captures".to_owned(), Value::Array(captures_objects)); + } + Value::Object(json_map) +} + +fn entry_to_rows( + only_properties_set: &HashSet<String>, + captures_names: &[String], + entry: EntryFound, +) -> Vec<HashMap<String, String>> { + let EntryFound { + key, + value, + captures: captures_opt, + } = entry; + match value { + Value::Object(value_json_map) => { + let row_map = map_entry_by_properties(&only_properties_set, key, value_json_map); + caps_to_rows_maps(row_map, captures_names, captures_opt) + } + Value::Array(json_array) => json_array + .into_iter() + .map(|sub_value| { + if let Value::Object(sub_value_json_map) = sub_value { + map_entry_by_properties(&only_properties_set, key.clone(), sub_value_json_map) + } else { + unreachable!() + } + }) + .collect(), + _ => unreachable!(), + } +} + +fn map_entry_by_properties( + only_properties_set: &HashSet<String>, + k: String, + value_json_map: Map<String, Value>, +) -> HashMap<String, String> { + let mut row_map = HashMap::with_capacity(1 + value_json_map.len()); + row_map.insert(KEY_COLUMN_NAME.to_owned(), k); + for (property_name, property_value) in value_json_map { + if only_properties_set.is_empty() || only_properties_set.contains(&property_name) { + if let Value::String(property_value_string) = property_value { + row_map.insert(property_name, property_value_string); + } else { + row_map.insert(property_name, property_value.to_string()); + } + } + } + row_map +} + +fn caps_to_rows( + mut first_row_begin: Vec<String>, + captures_names: &[String], + captures_opt: Option<ValueCaptures>, +) -> SmallVec<[Vec<String>; 2]> { + if !captures_names.is_empty() { + if let Some(ValueCaptures(captures)) = captures_opt { + let first_row_begin_len = first_row_begin.len(); + let mut rows = SmallVec::with_capacity(captures.len()); + let mut current_row = first_row_begin; + for capture in captures { + for capture_group_value_opt in capture.into_iter() { + if let Some(capture_group_value) = capture_group_value_opt { + current_row.push(capture_group_value); + } else { + current_row.push(String::new()); + } + } + rows.push(current_row); + current_row = (0..first_row_begin_len).map(|_| String::new()).collect(); + } + rows + } else { + first_row_begin.extend((0..captures_names.len()).map(|_| String::new())); + smallvec![first_row_begin] + } + } else { + smallvec![first_row_begin] + } +} + +fn caps_to_rows_maps( + first_row_map_begin: HashMap<String, String>, + captures_names: &[String], + captures_opt: Option<ValueCaptures>, +) -> Vec<HashMap<String, String>> { + if !captures_names.is_empty() { + if let Some(ValueCaptures(captures)) = captures_opt { + let mut rows = Vec::with_capacity(captures.len()); + let mut current_row_map = first_row_map_begin; + for capture in captures { + for (i, capture_group_value_opt) in capture.into_iter().enumerate() { + if let Some(capture_group_value) = capture_group_value_opt { + current_row_map.insert(captures_names[i].to_owned(), capture_group_value); + } + } + rows.push(current_row_map); + current_row_map = HashMap::with_capacity(captures_names.len()); + } + rows + } else { + vec![first_row_map_begin] + } + } else { + vec![first_row_map_begin] + } +} diff --git a/rust-bins/duniter-dbex/src/stringify_json_value.rs b/rust-bins/duniter-dbex/src/stringify_json_value.rs new file mode 100644 index 0000000000000000000000000000000000000000..7070c166c5baa8cd8b7b0fda2be5f66f321ad853 --- /dev/null +++ b/rust-bins/duniter-dbex/src/stringify_json_value.rs @@ -0,0 +1,219 @@ +use arrayvec::ArrayVec; +use dubp_common::crypto::bases::b58::ToBase58 as _; +use dubp_common::crypto::hashs::Hash; +use dubp_common::crypto::keys::ed25519::{PublicKey, Signature}; +use dubp_common::crypto::keys::Signature as _; +use std::convert::TryFrom; + +pub fn stringify_json_value(mut json_value: serde_json::Value) -> serde_json::Value { + match json_value { + serde_json::Value::Object(ref mut json_obj) => stringify_json_object(json_obj), + serde_json::Value::Array(ref mut json_array) => { + for json_array_cell in json_array { + if let serde_json::Value::Object(json_obj) = json_array_cell { + stringify_json_object(json_obj) + } + } + } + _ => (), + } + + json_value +} + +fn stringify_json_object(json_object: &mut serde_json::Map<String, serde_json::Value>) { + let mut stringified_values: Vec<(String, serde_json::Value)> = Vec::new(); + for (k, v) in json_object.iter_mut() { + match k.as_str() { + "pub" | "pubkey" | "issuer" => { + if let serde_json::Value::Object(json_pubkey) = v { + let json_pubkey_data = json_pubkey.get("datas").expect("corrupted db"); + if let serde_json::Value::Array(json_array) = json_pubkey_data { + let pubkey_string = + PublicKey::try_from(&json_array_to_32_bytes(json_array)[..]) + .expect("corrupted db") + .to_base58(); + stringified_values + .push((k.to_owned(), serde_json::Value::String(pubkey_string))); + } else { + panic!("corrupted db"); + } + } + } + "hash" | "inner_hash" | "previous_hash" => { + if let serde_json::Value::Array(json_array) = v { + let hash_string = Hash(json_array_to_32_bytes(json_array)).to_hex(); + stringified_values.push((k.to_owned(), serde_json::Value::String(hash_string))); + } + } + "sig" | "signature" => { + if let serde_json::Value::Array(json_array) = v { + let sig_string = Signature(json_array_to_64_bytes(json_array)).to_base64(); + stringified_values.push((k.to_owned(), serde_json::Value::String(sig_string))); + } + } + _ => { + if let serde_json::Value::Object(ref mut json_sub_object) = v { + stringify_json_object(json_sub_object) + } + } + } + } + for (k, v) in stringified_values { + json_object.insert(k, v); + } +} + +#[inline] +fn json_array_to_32_bytes(json_array: &[serde_json::Value]) -> [u8; 32] { + let bytes = json_array + .iter() + .map(|jv| { + if let serde_json::Value::Number(jn) = jv { + jn.as_u64().unwrap_or_default() as u8 + } else { + panic!("corrupted db") + } + }) + .collect::<ArrayVec<[u8; 32]>>(); + bytes.into_inner().expect("corrupted db") +} + +#[inline] +fn json_array_to_64_bytes(json_array: &[serde_json::Value]) -> [u8; 64] { + let bytes = json_array + .iter() + .map(|jv| { + if let serde_json::Value::Number(jn) = jv { + jn.as_u64().unwrap_or_default() as u8 + } else { + panic!("corrupted db") + } + }) + .collect::<ArrayVec<[u8; 64]>>(); + bytes.into_inner().expect("corrupted db") +} + +#[cfg(test)] +mod tests { + use super::*; + use dubp_common::crypto::keys::PublicKey as _; + use serde_json::Number; + use serde_json::Value; + use unwrap::unwrap; + + #[derive(serde::Serialize)] + struct JsonObjectTest { + pubkey: PublicKey, + hash: Hash, + other: usize, + inner: JsonSubObjectTest, + } + + #[derive(serde::Serialize)] + struct JsonSubObjectTest { + issuer: PublicKey, + } + + #[test] + fn test_stringify_json_object() { + let mut json_value = unwrap!(serde_json::to_value(JsonObjectTest { + pubkey: unwrap!(PublicKey::from_base58( + "A2C6cVJnkkT2n4ivMPiLH2njQHeHSZcVf1cSTwZYScQ6" + )), + hash: unwrap!(Hash::from_hex( + "51DF2FCAB8809596253CD98594D0DBCEECAAF3A88A43C6EDD285B6B24FB9D50D" + )), + other: 3, + inner: JsonSubObjectTest { + issuer: unwrap!(PublicKey::from_base58( + "4agK3ycEQNahuRGoFJDXA2aQGt4iV2YSMPKcoMeR6ZfA" + )) + } + })); + + if let serde_json::Value::Object(ref mut json_obj) = json_value { + stringify_json_object(json_obj); + + assert_eq!( + json_obj.get("pubkey"), + Some(Value::String( + "A2C6cVJnkkT2n4ivMPiLH2njQHeHSZcVf1cSTwZYScQ6".to_owned() + )) + .as_ref() + ); + assert_eq!( + json_obj.get("hash"), + Some(Value::String( + "51DF2FCAB8809596253CD98594D0DBCEECAAF3A88A43C6EDD285B6B24FB9D50D".to_owned() + )) + .as_ref() + ); + assert_eq!( + json_obj.get("other"), + Some(Value::Number(Number::from(3))).as_ref() + ); + + let json_sub_obj = unwrap!(json_obj.get("inner")); + if let serde_json::Value::Object(json_sub_obj) = json_sub_obj { + assert_eq!( + json_sub_obj.get("issuer"), + Some(Value::String( + "4agK3ycEQNahuRGoFJDXA2aQGt4iV2YSMPKcoMeR6ZfA".to_owned() + )) + .as_ref() + ); + } else { + panic!("json_sub_obj must be an abject"); + } + } else { + panic!("json_value must be an abject"); + } + } + + #[test] + fn test_json_array_to_32_bytes() { + let json_array = vec![ + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(0)), + Value::Number(Number::from(1)), + ]; + + assert_eq!( + [ + 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, + 1, 2, 0, 1 + ], + json_array_to_32_bytes(json_array.as_ref()), + ) + } +} diff --git a/rust-libs/dubp-wot/Cargo.toml b/rust-libs/dubp-wot/Cargo.toml index 6f619bea6e445be9673b064499cdceb64fc4d5ba..5120cd47a0728dc3f85130c7f88779bd8e56eb64 100644 --- a/rust-libs/dubp-wot/Cargo.toml +++ b/rust-libs/dubp-wot/Cargo.toml @@ -19,6 +19,6 @@ serde = { version = "1.0.105", features = ["derive"] } [dev-dependencies] bincode = "1.2.0" -dubp-common = { version = "0.2.0", features = ["rand", "scrypt"] } +dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] } [features] diff --git a/rust-libs/dubp-wot/src/data/mod.rs b/rust-libs/dubp-wot/src/data/mod.rs index 3aa86a7deeafd2cdec72607c9949508b8fac6c27..eddc11e19b5083148615cd53848fd212cd894f6d 100644 --- a/rust-libs/dubp-wot/src/data/mod.rs +++ b/rust-libs/dubp-wot/src/data/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/data/rusty.rs b/rust-libs/dubp-wot/src/data/rusty.rs index 553bdfb023190814118ab65478e4d6f52c30819d..398027350a5fc2d25ea1da83ba0c58e252d2e7fc 100644 --- a/rust-libs/dubp-wot/src/data/rusty.rs +++ b/rust-libs/dubp-wot/src/data/rusty.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/operations/centrality.rs b/rust-libs/dubp-wot/src/operations/centrality.rs index 029d8927a4d00bf0bee1ca8c610f1d8bc231b9e5..933e43621c24863dbfb6b410d336dff7a32d9c59 100644 --- a/rust-libs/dubp-wot/src/operations/centrality.rs +++ b/rust-libs/dubp-wot/src/operations/centrality.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/operations/density.rs b/rust-libs/dubp-wot/src/operations/density.rs index 029d176c7095a11143a2d6969aec9d5a8ae03420..76ae9a0f2ca00e540c9ad55c64d2b1dafaf2e8cf 100644 --- a/rust-libs/dubp-wot/src/operations/density.rs +++ b/rust-libs/dubp-wot/src/operations/density.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/operations/distance.rs b/rust-libs/dubp-wot/src/operations/distance.rs index 3badf16cbf9f87ab4c039ec7b2ceb76123dff279..f67dffbe18bd8c5b13fd04dc83bf26987f51b9c9 100644 --- a/rust-libs/dubp-wot/src/operations/distance.rs +++ b/rust-libs/dubp-wot/src/operations/distance.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/operations/mod.rs b/rust-libs/dubp-wot/src/operations/mod.rs index 376ad1c281812d82094c2a66810bedaf351051ee..e3fb53ca1cbe492ef0f6b2aba76f6a3d2f8ad133 100644 --- a/rust-libs/dubp-wot/src/operations/mod.rs +++ b/rust-libs/dubp-wot/src/operations/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/dubp-wot/src/operations/path.rs b/rust-libs/dubp-wot/src/operations/path.rs index 5ca9b70283c86827ea6da7b1699ec2fa8fb8db85..5aef5149c1ebe34d2bf9cbebe63a752522c08858 100644 --- a/rust-libs/dubp-wot/src/operations/path.rs +++ b/rust-libs/dubp-wot/src/operations/path.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2020 Éloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as diff --git a/rust-libs/duniter-dbs/Cargo.toml b/rust-libs/duniter-dbs/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5d2d1c4e36bf487c03544f130886ae984ec2b24a --- /dev/null +++ b/rust-libs/duniter-dbs/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "duniter-dbs" +version = "0.1.0" +authors = ["elois <elois@duniter.org>"] +description = "Duniter blockchain DB" +repository = "https://git.duniter.org/nodes/typescript/duniter" +keywords = ["dubp", "duniter", "blockchain", "database"] +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +arrayvec = "0.5.1" +chrono = { version = "0.4.15", optional = true } +dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] } +kv_typed = { path = "../tools/kv_typed", default-features = false } +log = "0.4.8" +mockall = { version = "0.8.0", optional = true } +serde = { version = "1.0.105", features = ["derive"] } +serde_json = "1.0.53" +smallvec = { version = "1.4.0", features = ["serde"] } +thiserror = "1.0.20" + +[dev-dependencies] +once_cell = "1.4.0" +tempdir = "0.3.7" +unwrap = "1.2.1" + +[features] +default = ["leveldb_backend", "memory_backend", "sled_backend", "subscription", "sync"] + +explorer = ["chrono", "kv_typed/explorer"] +leveldb_backend = ["kv_typed/leveldb_backend"] +memory_backend = ["kv_typed/memory_backend"] +mock = ["kv_typed/mock", "mockall"] +sled_backend = ["kv_typed/sled_backend"] +subscription = ["kv_typed/subscription"] +sync = ["kv_typed/sync"] diff --git a/rust-libs/duniter-dbs/src/bc_v1.rs b/rust-libs/duniter-dbs/src/bc_v1.rs new file mode 100644 index 0000000000000000000000000000000000000000..870bea9add36fcb3a3d7b3b3b0510968e0a33c19 --- /dev/null +++ b/rust-libs/duniter-dbs/src/bc_v1.rs @@ -0,0 +1,158 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +db_schema!( + BcV1, + [ + ["level_blockchain", main_blocks, BlockNumberKeyV1, BlockDbV1,], + [ + "level_blockchain/idty", + mb_idty, + PubKeyKeyV1, + BlockNumberArrayV1, + ], + [ + "level_blockchain/certs", + mb_certs, + PubKeyKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/joiners", + mb_joiners, + PubKeyKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/actives", + mb_actives, + PubKeyKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/leavers", + mb_leavers, + PubKeyKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/excluded", + mb_excluded, + PubKeyKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/revoked", + mb_revoked, + PubKeyAndSigV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/dividends", + mb_dividends, + AllKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/transactions", + mb_transactions, + AllKeyV1, + BlockNumberArrayV1 + ], + [ + "level_blockchain/forks", + fork_blocks, + BlockstampKeyV1, + BlockDbV1 + ], + ["level_bindex", bindex, BlockNumberKeyV1, BlockHeadDbV1], + ["level_iindex", iindex, PubKeyKeyV1, IIndexDbV1], + [ + "level_iindex/hash", + iindex_hash, + HashKeyV1, + PublicKeySingletonDbV1 + ], + ["level_iindex/kick", iindex_kick, PubKeyKeyV1, KickDbV1], + [ + "level_iindex/writtenOn", + iindex_written_on, + BlockNumberKeyV1, + PublicKeyArrayDbV1 + ], + ["level_iindex/uid", uids, UidKeyV1, PublicKeySingletonDbV1], + ["level_mindex", mindex, PubKeyKeyV1, MIndexDbV1], + [ + "level_mindex/expiresOn", + mindex_expires_on, + TimestampKeyV1, + PublicKeyArrayDbV1 + ], + [ + "level_mindex/revokesOn", + mindex_revokes_on, + TimestampKeyV1, + PublicKeyArrayDbV1 + ], + [ + "level_mindex/writtenOn", + mindex_written_on, + BlockNumberKeyV1, + PublicKeyArrayDbV1 + ], + ["level_cindex", cindex, PubKeyKeyV1, CIndexDbV1], + [ + "level_cindex/expiresOn", + cindex_expires_on, + BlockNumberKeyV1, + PublicKeyArrayDbV1 + ], + [ + "level_cindex/writtenOn", + cindex_written_on, + BlockNumberKeyV1, + PublicKeyArrayDbV1 + ], + ["level_wallet", Wallet, WalletConditionsV1, WalletDbV1], + ["level_dividend", uds, PubKeyKeyV1, UdEntryDbV1], + [ + "level_dividend/level_dividend_trim_index", + uds_trim, + BlockNumberKeyV1, + PublicKeyArrayDbV1 + ], + ["level_sindex", sindex, SourceKeyV1, SIndexDBV1], + [ + "level_sindex/written_on", + sindex_written_on, + BlockNumberKeyV1, + SourceKeyArrayDbV1 + ], + [ + "level_sindex/consumed_on", + sindex_consumed_on, + BlockNumberKeyV1, + SourceKeyArrayDbV1 + ], + [ + "level_sindex/conditions", + sindex_conditions, + WalletConditionsV1, + SourceKeyArrayDbV1 + ], + ] +); diff --git a/rust-libs/duniter-dbs/src/errors.rs b/rust-libs/duniter-dbs/src/errors.rs new file mode 100644 index 0000000000000000000000000000000000000000..d945b9235813f2beb06708ae9b103e7f0bdda637 --- /dev/null +++ b/rust-libs/duniter-dbs/src/errors.rs @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +pub type Result<T> = std::result::Result<T, ErrorDb>; + +#[derive(Debug, Error)] +pub enum ErrorDb { + #[error("batch already consumed")] + BatchConsumed, + #[error("Collection '{collection_typename}' not exist on db '{db_version}'")] + NonexistentCollection { + collection_typename: &'static str, + db_version: &'static str, + }, + #[cfg(feature = "explorer")] + #[error("Collection '{collection_name}' not exist on db '{db_version}'")] + NonexistentCollectionName { + collection_name: String, + db_version: &'static str, + }, + #[error("DbError: {0}")] + DbError(String), + #[error("DeserError: {0}")] + DeserError(String), + #[error("SerError: {0}")] + Ser(String), + #[error("Fail to create DB folder: {0}")] + FailToCreateDbFolder(std::io::Error), +} + +impl From<KvError> for ErrorDb { + fn from(e: KvError) -> Self { + ErrorDb::DbError(format!("{}", e)) + } +} diff --git a/rust-libs/duniter-dbs/src/keys.rs b/rust-libs/duniter-dbs/src/keys.rs new file mode 100644 index 0000000000000000000000000000000000000000..d05a345d404619552de75b41844f14667445875c --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +pub mod all; +pub mod block_number; +pub mod blockstamp; +pub mod hash; +pub mod pubkey; +pub mod pubkey_and_sig; +pub mod source_key; +pub mod timestamp; +pub mod uid; +pub mod wallet_conditions; diff --git a/rust-libs/duniter-dbs/src/keys/all.rs b/rust-libs/duniter-dbs/src/keys/all.rs new file mode 100644 index 0000000000000000000000000000000000000000..067e44616932bc7d3ef733fc10dd8601d38113a0 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/all.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct AllKeyV1; + +impl KeyAsBytes for AllKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(b"ALL") + } +} + +impl kv_typed::prelude::FromBytes for AllKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + if bytes == b"ALL" { + Ok(Self) + } else { + Err(StringErr(format!( + "Invalid key: expected '{:?}', found '{:?}'", + b"ALL", bytes + ))) + } + } +} + +impl ToDumpString for AllKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for AllKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + <Self as kv_typed::prelude::FromBytes>::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/block_number.rs b/rust-libs/duniter-dbs/src/keys/block_number.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2cd7755bf11987c261bbc08f0757319d76d41ad --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/block_number.rs @@ -0,0 +1,97 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct BlockNumberKeyV1(pub BlockNumber); + +impl KeyAsBytes for BlockNumberKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + if self.0 == BlockNumber(u32::MAX) { + f(b"0000000NaN") + } else { + f(format!("{:010}", (self.0).0).as_bytes()) + } + } +} + +impl FromBytes for BlockNumberKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let key_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + if key_str == "0000000NaN" { + Ok(BlockNumberKeyV1(BlockNumber(u32::MAX))) + } else { + Ok(BlockNumberKeyV1(BlockNumber( + key_str + .parse() + .map_err(|e| StringErr(format!("{}: {}", e, key_str)))?, + ))) + } + } +} + +impl ToDumpString for BlockNumberKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for BlockNumberKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + Ok(format!("{}", (self.0).0)) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct BlockNumberKeyV2(pub BlockNumber); + +impl KeyAsBytes for BlockNumberKeyV2 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + if cfg!(target_endian = "big") { + println!("TMP: big"); + f(unsafe { std::mem::transmute::<u32, [u8; 4]>((self.0).0) }.as_ref()) + } else { + println!("TMP: little"); + f(unsafe { std::mem::transmute::<u32, [u8; 4]>((self.0).0.to_be()) }.as_ref()) + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_block_number_str_10_ser() { + BlockNumberKeyV1(BlockNumber(35)) + .as_bytes(|bytes| assert_eq!(bytes, &[48, 48, 48, 48, 48, 48, 48, 48, 51, 53])) + } + + #[test] + fn block_number_key_v2() { + let k = BlockNumberKeyV2(BlockNumber(3)); + k.as_bytes(|bytes| { + assert_eq!(bytes, &[0, 0, 0, 3]); + }); + } +} diff --git a/rust-libs/duniter-dbs/src/keys/blockstamp.rs b/rust-libs/duniter-dbs/src/keys/blockstamp.rs new file mode 100644 index 0000000000000000000000000000000000000000..36cf85b1ccb65c075489b82244b5ba498d45e646 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/blockstamp.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct BlockstampKeyV1(Blockstamp); + +impl KeyAsBytes for BlockstampKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(format!("{:010}-{}", self.0.number.0, self.0.hash).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for BlockstampKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let blockstamp_strs: ArrayVec<[&str; 2]> = std::str::from_utf8(bytes) + .map_err(|e| StringErr(format!("{}", e)))? + .split('-') + .collect(); + let block_number = blockstamp_strs[0] + .parse() + .map_err(|e| StringErr(format!("{}", e)))?; + let block_hash = + Hash::from_hex(blockstamp_strs[1]).map_err(|e| StringErr(format!("{}", e)))?; + Ok(BlockstampKeyV1(Blockstamp { + number: BlockNumber(block_number), + hash: BlockHash(block_hash), + })) + } +} + +impl ToDumpString for BlockstampKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for BlockstampKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + Ok(format!("{}", self.0)) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/hash.rs b/rust-libs/duniter-dbs/src/keys/hash.rs new file mode 100644 index 0000000000000000000000000000000000000000..a3a11f8b0c6a47f1e069960aca8ac90fd5eca7dd --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/hash.rs @@ -0,0 +1,52 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct HashKeyV1(pub Hash); + +impl KeyAsBytes for HashKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(self.0.to_hex().as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for HashKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let hash_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + Ok(HashKeyV1( + Hash::from_hex(&hash_str).map_err(|e| StringErr(format!("{}", e)))?, + )) + } +} + +impl ToDumpString for HashKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for HashKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/pubkey.rs b/rust-libs/duniter-dbs/src/keys/pubkey.rs new file mode 100644 index 0000000000000000000000000000000000000000..9476cca71a0baa350f0d3b892ca7825d77c0d07d --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/pubkey.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct PubKeyKeyV1(pub PublicKey); + +impl PubKeyKeyV1 { + const ALL: &'static str = "ALL"; + const ALL_WITH_LEADING_1: &'static str = "11111111111111111111111111111ALL"; + + pub fn all() -> Self { + Self(PublicKey::from_base58(Self::ALL).expect("invalid PubKeyKeyV1::all()")) + } +} + +impl KeyAsBytes for PubKeyKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + let b58_string = self.0.to_base58(); + if b58_string == Self::ALL_WITH_LEADING_1 { + f(Self::ALL.as_bytes()) + } else { + f(self.0.to_base58().as_bytes()) + } + } +} + +impl kv_typed::prelude::FromBytes for PubKeyKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let pubkey_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + Ok(PubKeyKeyV1(PublicKey::from_base58(&pubkey_str).map_err( + |e| StringErr(format!("{}: {}", e, pubkey_str)), + )?)) + } +} + +impl ToDumpString for PubKeyKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[test] + fn pubkey_all() { + let all = PubKeyKeyV1::all(); + assert_eq!( + all.as_bytes(|bytes| bytes.to_vec()), + PubKeyKeyV1::ALL.as_bytes() + ) + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for PubKeyKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs new file mode 100644 index 0000000000000000000000000000000000000000..65934750d8e4aee2e073d9cc2aa6a876788fd104 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs @@ -0,0 +1,69 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct PubKeyAndSigV1(PublicKey, Signature); + +impl PubKeyAndSigV1 { + pub fn all() -> Self { + Self(PublicKey::default(), Signature([0u8; 64])) + } +} + +impl KeyAsBytes for PubKeyAndSigV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + if self == &Self::all() { + f(b"ALL") + } else { + f(format!("{}:{}", self.0.to_base58(), self.1.to_base64()).as_bytes()) + } + } +} + +impl kv_typed::prelude::FromBytes for PubKeyAndSigV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let raw_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + if raw_str == "ALL" { + Ok(PubKeyAndSigV1::all()) + } else { + let array_str: ArrayVec<[&str; 2]> = raw_str.split(':').collect(); + let pubkey = + PublicKey::from_base58(array_str[0]).map_err(|e| StringErr(format!("{}", e)))?; + let sig = + Signature::from_base64(array_str[1]).map_err(|e| StringErr(format!("{}", e)))?; + Ok(PubKeyAndSigV1(pubkey, sig)) + } + } +} + +impl ToDumpString for PubKeyAndSigV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for PubKeyAndSigV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/source_key.rs b/rust-libs/duniter-dbs/src/keys/source_key.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c1e781c2863f047c5f1000d28db60729b3c0d33 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/source_key.rs @@ -0,0 +1,91 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] +pub struct SourceKeyV1 { + pub tx_hash: Hash, + pub pos: u32, + pub consumed: Option<bool>, +} + +impl ToString for SourceKeyV1 { + fn to_string(&self) -> String { + format!( + "{}-{:010}{}", + self.tx_hash, + self.pos, + match self.consumed { + Some(true) => "-1", + Some(false) => "-0", + None => "", + } + ) + } +} + +impl KeyAsBytes for SourceKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(self.to_string().as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for SourceKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let strs: ArrayVec<[&str; 3]> = std::str::from_utf8(bytes) + .map_err(|e| StringErr(format!("{}", e)))? + .split('-') + .collect(); + let tx_hash = Hash::from_hex(strs[0]).map_err(|e| StringErr(format!("{}", e)))?; + let pos = strs[1].parse().map_err(|e| StringErr(format!("{}", e)))?; + let consumed = if strs.len() <= 2 { + None + } else { + match strs[2] { + "1" => Some(true), + "0" => Some(false), + _ => { + return Err(StringErr( + "invalid format: field consumed must be encoded with '0' or '1'".to_owned(), + )) + } + } + }; + Ok(SourceKeyV1 { + tx_hash, + pos, + consumed, + }) + } +} + +impl ToDumpString for SourceKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for SourceKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/timestamp.rs b/rust-libs/duniter-dbs/src/keys/timestamp.rs new file mode 100644 index 0000000000000000000000000000000000000000..691e0478468b88bc64f881d093976568fc9be109 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/timestamp.rs @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct TimestampKeyV1(pub u64); + +impl KeyAsBytes for TimestampKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(format!("{}", self.0).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for TimestampKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let key_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + Ok(TimestampKeyV1( + key_str + .parse() + .map_err(|e| StringErr(format!("{}: {}", e, key_str)))?, + )) + } +} + +impl ToDumpString for TimestampKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for TimestampKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + NaiveDateTime::parse_from_str(source, "%Y-%m-%d %H:%M:%S") + .map(|dt| TimestampKeyV1(dt.timestamp() as u64)) + .map_err(|e| StringErr(format!("{}: {}", e, source))) + } + fn to_explorer_string(&self) -> KvResult<String> { + Ok(NaiveDateTime::from_timestamp(self.0 as i64, 0) + .format("%Y-%m-%d %H:%M:%S") + .to_string()) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/uid.rs b/rust-libs/duniter-dbs/src/keys/uid.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a481de4b16c722723570c117b29796ff5944ead --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/uid.rs @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +const USERNAME_MAX_LEN: usize = 100; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct UidKeyV1(pub ArrayString<[u8; USERNAME_MAX_LEN]>); + +impl KeyAsBytes for UidKeyV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(self.0.as_str().as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for UidKeyV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let uid_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + Ok(Self( + ArrayString::<[u8; USERNAME_MAX_LEN]>::from_str(uid_str) + .map_err(|e| StringErr(format!("{}", e)))?, + )) + } +} + +impl FromStr for UidKeyV1 { + type Err = arrayvec::CapacityError; + + fn from_str(source: &str) -> std::result::Result<Self, Self::Err> { + Ok(UidKeyV1(ArrayString::<[u8; USERNAME_MAX_LEN]>::from_str( + source, + )?)) + } +} + +impl ToDumpString for UidKeyV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for UidKeyV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs b/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs new file mode 100644 index 0000000000000000000000000000000000000000..9432eab0c8d9bc30539c75cb9a814dab4b0782c8 --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +const CONDITIONS_MAX_LEN: usize = 256; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct WalletConditionsV1(pub ArrayString<[u8; CONDITIONS_MAX_LEN]>); + +impl KeyAsBytes for WalletConditionsV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(self.0.as_str().as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for WalletConditionsV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let uid_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?; + Ok(Self( + ArrayString::<[u8; CONDITIONS_MAX_LEN]>::from_str(uid_str) + .map_err(|e| StringErr(format!("{}", e)))?, + )) + } +} + +impl FromStr for WalletConditionsV1 { + type Err = arrayvec::CapacityError; + + fn from_str(source: &str) -> std::result::Result<Self, Self::Err> { + Ok(WalletConditionsV1( + ArrayString::<[u8; CONDITIONS_MAX_LEN]>::from_str(source)?, + )) + } +} + +impl ToDumpString for WalletConditionsV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for WalletConditionsV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_string(&self) -> KvResult<String> { + self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned())) + } +} diff --git a/rust-libs/duniter-dbs/src/lib.rs b/rust-libs/duniter-dbs/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a30b4ae2af2da1afe7284ed957e3b2e5d112bc9 --- /dev/null +++ b/rust-libs/duniter-dbs/src/lib.rs @@ -0,0 +1,95 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces +)] + +mod bc_v1; +mod errors; +mod keys; +mod values; + +// Re-export dependencies +pub use arrayvec; +pub use dubp_common; +#[cfg(feature = "explorer")] +pub use kv_typed::regex; +pub use serde; +pub use serde_json; +pub use smallvec; + +// Re-export crates +pub use kv_typed; + +// Prelude +pub mod prelude { + pub use crate::errors::ErrorDb; + #[cfg(feature = "explorer")] + pub use kv_typed::explorer::{ + DbExplorable, EntryFound, ExplorerAction, ExplorerActionResponse, ValueCaptures, + }; +} + +// Export technical types +pub use crate::errors::Result; + +// Export profession types +pub use bc_v1::{BcV1Db, BcV1DbReadable, BcV1DbRo, BcV1DbWritable, MainBlockEvent, UidEvent}; +pub use keys::all::AllKeyV1; +pub use keys::block_number::BlockNumberKeyV1; +pub use keys::blockstamp::BlockstampKeyV1; +pub use keys::hash::HashKeyV1; +pub use keys::pubkey::PubKeyKeyV1; +pub use keys::pubkey_and_sig::PubKeyAndSigV1; +pub use keys::source_key::SourceKeyV1; +pub use keys::timestamp::TimestampKeyV1; +pub use keys::uid::UidKeyV1; +pub use keys::wallet_conditions::WalletConditionsV1; +pub use values::block_db::{BlockDbEnum, BlockDbV1, TransactionInBlockDbV1}; +pub use values::block_head_db::BlockHeadDbV1; +pub use values::block_number_array_db::BlockNumberArrayV1; +pub use values::cindex_db::CIndexDbV1; +pub use values::iindex_db::IIndexDbV1; +pub use values::kick_db::KickDbV1; +pub use values::mindex_db::MIndexDbV1; +pub use values::pubkey_db::{PublicKeyArrayDbV1, PublicKeySingletonDbV1}; +pub use values::sindex_db::{SIndexDBV1, SourceKeyArrayDbV1}; +pub use values::ud_entry_db::{ConsumedUdDbV1, UdAmountDbV1, UdEntryDbV1}; +pub use values::wallet_db::WalletDbV1; + +// Crate imports +pub(crate) use arrayvec::{ArrayString, ArrayVec}; +#[cfg(feature = "explorer")] +use chrono::NaiveDateTime; +pub(crate) use dubp_common::crypto::bases::b58::ToBase58 as _; +pub(crate) use dubp_common::crypto::bases::BaseConversionError; +pub(crate) use dubp_common::crypto::hashs::Hash; +pub(crate) use dubp_common::crypto::keys::ed25519::{PublicKey, Signature}; +pub(crate) use dubp_common::crypto::keys::{PublicKey as _, Signature as _}; +pub(crate) use dubp_common::prelude::*; +pub(crate) use kv_typed::prelude::*; +pub(crate) use serde::{Deserialize, Serialize}; +pub(crate) use smallvec::SmallVec; +pub(crate) use std::{fmt::Debug, iter::Iterator, str::FromStr}; + +pub trait ToDumpString { + fn to_dump_string(&self) -> String; +} diff --git a/rust-libs/duniter-dbs/src/values.rs b/rust-libs/duniter-dbs/src/values.rs new file mode 100644 index 0000000000000000000000000000000000000000..a542fe0bccd32d08919bf6cc1f60d4e87838b9f9 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +pub mod block_db; +pub mod block_head_db; +pub mod block_number_array_db; +pub mod cindex_db; +pub mod iindex_db; +pub mod kick_db; +pub mod mindex_db; +pub mod pubkey_db; +pub mod sindex_db; +pub mod ud_entry_db; +pub mod wallet_db; diff --git a/rust-libs/duniter-dbs/src/values/block_db.rs b/rust-libs/duniter-dbs/src/values/block_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..eae51fae1efa17d42b85f981e2c364fa4ce8bb27 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/block_db.rs @@ -0,0 +1,117 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug)] +pub enum BlockDbEnum { + BlockDbV1(BlockDbV1), +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockDbV1 { + pub version: u64, + pub number: u64, + pub currency: String, + pub hash: String, + pub signature: String, + #[serde(rename = "inner_hash")] + pub inner_hash: String, + pub previous_hash: Option<String>, + pub issuer: String, + pub previous_issuer: Option<String>, + pub time: u64, + pub pow_min: u64, + #[serde(rename = "unitbase")] + pub unit_base: u64, + pub members_count: u64, + pub issuers_count: u64, + pub issuers_frame: u64, + pub issuers_frame_var: i64, + pub identities: Vec<String>, + pub joiners: Vec<String>, + pub actives: Vec<String>, + pub leavers: Vec<String>, + pub revoked: Vec<String>, + pub excluded: Vec<String>, + pub certifications: Vec<String>, + pub transactions: Vec<TransactionInBlockDbV1>, + pub median_time: u64, + pub nonce: u64, + pub fork: bool, + pub parameters: String, + pub monetary_mass: u64, + pub dividend: Option<u64>, + #[serde(rename = "UDTime")] + pub ud_time: Option<u64>, + #[serde(rename = "writtenOn")] + pub written_on: Option<u64>, + #[serde(rename = "written_on")] + pub written_on_str: String, + pub wrong: bool, +} + +impl ValueAsBytes for BlockDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for BlockDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for BlockDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for BlockDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionInBlockDbV1 { + version: u64, + currency: String, + #[serde(rename = "locktime")] + lock_time: u64, + hash: Option<String>, + blockstamp: String, + blockstamp_time: u64, + issuers: SmallVec<[String; 1]>, + inputs: SmallVec<[String; 4]>, + outputs: SmallVec<[String; 2]>, + unlocks: SmallVec<[String; 4]>, + signatures: SmallVec<[String; 1]>, + comment: String, +} diff --git a/rust-libs/duniter-dbs/src/values/block_head_db.rs b/rust-libs/duniter-dbs/src/values/block_head_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..99d101406c18a5defc58cfc4ce93c9528889671d --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/block_head_db.rs @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockHeadDbV1 { + pub version: u64, + pub currency: Option<String>, + #[serde(rename = "bsize")] + pub block_size: u64, + pub avg_block_size: u64, + pub ud_time: u64, + pub ud_reeval_time: u64, + pub mass_reeval: u64, + pub mass: u64, + pub hash: String, + pub previous_hash: Option<String>, + pub previous_issuer: Option<String>, + pub issuer: String, + pub time: u64, + pub median_time: u64, + pub number: u64, + pub pow_min: u64, + pub diff_number: u64, + pub issuers_count: u64, + pub issuers_frame: u64, + pub issuers_frame_var: i64, + pub issuer_diff: u64, + pub pow_zeros: u64, + pub pow_remainder: u64, + pub speed: f64, + pub unit_base: u64, + pub members_count: u64, + pub dividend: u64, + #[serde(rename = "new_dividend")] + pub new_dividend: Option<u64>, + pub issuer_is_member: bool, +} + +impl ValueAsBytes for BlockHeadDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for BlockHeadDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for BlockHeadDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for BlockHeadDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} diff --git a/rust-libs/duniter-dbs/src/values/block_number_array_db.rs b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd4a97937f92f055b886320079cf0ca16280506e --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct BlockNumberArrayV1(pub SmallVec<[BlockNumber; 1]>); + +impl ValueAsBytes for BlockNumberArrayV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json_string = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(format!("[{}]", json_string).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for BlockNumberArrayV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + //println!("json_str='{}'", &json_str); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for BlockNumberArrayV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for BlockNumberArrayV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} diff --git a/rust-libs/duniter-dbs/src/values/cindex_db.rs b/rust-libs/duniter-dbs/src/values/cindex_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..7afc8169fd37ea16a3b98b2d459aae8d95349b21 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/cindex_db.rs @@ -0,0 +1,99 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct CIndexDbV1 { + pub received: SmallVec<[String; 10]>, + pub issued: Vec<CIndexLineDbV1>, +} + +impl ValueAsBytes for CIndexDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json_string = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(format!("[{}]", json_string).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for CIndexDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + //println!("json_str='{}'", &json_str); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for CIndexDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for CIndexDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CIndexLineDbV1 { + pub op: String, + #[serde(rename = "writtenOn")] + pub written_on: Option<u64>, + #[serde(rename = "written_on")] + pub written_on_str: String, + pub issuer: String, + pub receiver: String, + #[serde(rename = "created_on")] + pub created_on: u64, + pub sig: Option<String>, + #[serde(rename = "chainable_on")] + pub chainable_on: Option<u64>, + #[serde(rename = "replayable_on")] + pub replayable_on: Option<u64>, + #[serde(rename = "expires_on")] + pub expires_on: Option<u64>, + #[serde(rename = "expired_on")] + pub expired_on: u64, + pub unchainables: Option<u64>, + pub age: Option<u64>, + pub stock: Option<u64>, + pub from_member: Option<bool>, + pub to_member: Option<bool>, + pub to_newcomer: Option<bool>, + pub to_leaver: Option<bool>, + pub is_replay: Option<bool>, + pub is_replayable: Option<bool>, + #[serde(rename = "sigOK")] + pub sig_ok: Option<bool>, + #[serde(rename = "created_on_ref")] + pub created_on_ref: Option<CreatedOnRef>, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CreatedOnRef { + pub median_time: u64, +} diff --git a/rust-libs/duniter-dbs/src/values/iindex_db.rs b/rust-libs/duniter-dbs/src/values/iindex_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..70a4ebc32d93225843fae3686882f1241a830f16 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/iindex_db.rs @@ -0,0 +1,82 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct IIndexDbV1(pub SmallVec<[IIndexLineDbV1; 1]>); + +impl ValueAsBytes for IIndexDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json_string = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(format!("[{}]", json_string).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for IIndexDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + //println!("json_str='{}'", &json_str); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for IIndexDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for IIndexDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IIndexLineDbV1 { + pub op: String, + #[serde(rename = "writtenOn")] + pub written_on: Option<u64>, + #[serde(rename = "written_on")] + pub written_on_str: String, + pub uid: Option<String>, + #[serde(rename = "pub")] + pub pubkey: String, + pub hash: Option<String>, + pub sig: Option<String>, + #[serde(rename = "created_on")] + pub created_on: Option<String>, + pub member: Option<bool>, + pub was_member: Option<bool>, + pub kick: Option<bool>, + #[serde(rename = "wotb_id")] + pub wotb_id: Option<usize>, + pub age: Option<u64>, + pub pub_unique: Option<bool>, + pub excluded_is_member: Option<bool>, + pub is_being_kicked: Option<bool>, + pub uid_unique: Option<bool>, + pub has_to_be_excluded: Option<bool>, +} diff --git a/rust-libs/duniter-dbs/src/values/kick_db.rs b/rust-libs/duniter-dbs/src/values/kick_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d7c93f50694ab76dd47ea0bb9c86f5bb085d41c --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/kick_db.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct KickDbV1 { + on: Option<u64>, // The next time that the identity must be kicked + done: SmallVec<[u64; 4]>, // The reversion history +} + +impl ValueAsBytes for KickDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for KickDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for KickDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for KickDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} diff --git a/rust-libs/duniter-dbs/src/values/mindex_db.rs b/rust-libs/duniter-dbs/src/values/mindex_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..f65f856c460f061b85cac2cf085856a05ef52ce7 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/mindex_db.rs @@ -0,0 +1,106 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct MIndexDbV1(pub SmallVec<[MIndexLineDbV1; 1]>); + +impl ValueAsBytes for MIndexDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json_string = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(format!("[{}]", json_string).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for MIndexDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + //println!("json_str='{}'", &json_str); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for MIndexDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for MIndexDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MIndexLineDbV1 { + pub op: String, + #[serde(rename = "writtenOn")] + pub written_on: Option<u64>, + #[serde(rename = "written_on")] + pub written_on_str: String, + #[serde(rename = "pub")] + pub pubkey: String, + pub created_on: Option<String>, + #[serde(rename = "type")] + pub r#type: Option<String>, + #[serde(rename = "expires_on")] + pub expires_on: Option<u64>, + #[serde(rename = "expired_on")] + pub expired_on: Option<u64>, + pub revocation: Option<String>, + #[serde(rename = "revokes_on")] + pub revokes_on: Option<u64>, + #[serde(rename = "chainable_on")] + pub chainable_on: Option<u64>, + #[serde(rename = "revoked_on")] + pub revoked_on: Option<String>, + pub leaving: Option<bool>, + pub age: Option<u64>, + pub is_being_revoked: Option<bool>, + pub unchainables: Option<u64>, + pub number_following: Option<bool>, + #[serde(rename = "distanceOK")] + pub distance_ok: Option<bool>, + pub on_revoked: Option<bool>, + pub joins_twice: Option<bool>, + pub enough_certs: Option<bool>, + pub leaver_is_member: Option<bool>, + pub active_is_member: Option<bool>, + pub revoked_is_member: Option<bool>, + pub already_revoked: Option<bool>, + #[serde(rename = "revocationSigOK")] + pub revocation_sig_ok: Option<bool>, + #[serde(rename = "created_on_ref")] + pub created_on_ref: Option<BlockstampTimed>, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockstampTimed { + pub median_time: u64, + pub number: u32, + pub hash: String, +} diff --git a/rust-libs/duniter-dbs/src/values/pubkey_db.rs b/rust-libs/duniter-dbs/src/values/pubkey_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..78e750ef65a72939f504e007b9920e4e948d301c --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/pubkey_db.rs @@ -0,0 +1,109 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PublicKeySingletonDbV1(pub PublicKey); + +impl ValueAsBytes for PublicKeySingletonDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + f(format!("[\"{}\"]", self.0).as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for PublicKeySingletonDbV1 { + type Err = BaseConversionError; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let mut pubkey_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + + pubkey_str = &pubkey_str[2..pubkey_str.len() - 2]; + Ok(Self(PublicKey::from_base58(pubkey_str)?)) + } +} + +impl ToDumpString for PublicKeySingletonDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for PublicKeySingletonDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Ok(Self( + PublicKey::from_base58(source).map_err(|e| StringErr(format!("{}", e)))?, + )) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + Ok(serde_json::Value::String(self.0.to_base58())) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct PublicKeyArrayDbV1(pub SmallVec<[PublicKey; 8]>); + +impl ValueAsBytes for PublicKeyArrayDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let vec_pub_str = self + .0 + .iter() + .map(|pubkey| pubkey.to_base58()) + .collect::<SmallVec<[String; 8]>>(); + let json = serde_json::to_string(&vec_pub_str) + .map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for PublicKeyArrayDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + let vec_pub_str: SmallVec<[String; 8]> = serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?; + Ok(Self( + vec_pub_str + .into_iter() + .map(|pub_str| { + PublicKey::from_base58(&pub_str).map_err(|e| StringErr(format!("{}", e))) + }) + .collect::<std::result::Result<SmallVec<[PublicKey; 8]>, Self::Err>>()?, + )) + } +} + +impl ToDumpString for PublicKeyArrayDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for PublicKeyArrayDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + Ok(serde_json::Value::Array( + self.0 + .iter() + .map(|pubkey| serde_json::Value::String(pubkey.to_base58())) + .collect(), + )) + } +} diff --git a/rust-libs/duniter-dbs/src/values/sindex_db.rs b/rust-libs/duniter-dbs/src/values/sindex_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..27f7ed0db5db166e528893c03103b460b6c8573a --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/sindex_db.rs @@ -0,0 +1,128 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SIndexDBV1 { + pub src_type: String, + pub tx: Option<String>, + pub identifier: String, + pub pos: u32, + #[serde(rename = "created_on")] + pub created_on: Option<String>, + #[serde(rename = "written_time")] + pub written_time: u64, + #[serde(rename = "locktime")] + pub lock_time: u64, + pub unlock: Option<String>, + pub amount: u32, + pub base: u32, + pub conditions: String, + pub consumed: bool, + pub tx_obj: TransactionInBlockDbV1, + pub age: u64, + #[serde(rename = "type")] + pub type_: Option<String>, + pub available: Option<bool>, + pub is_locked: Option<bool>, + pub is_time_locked: Option<bool>, +} + +impl ValueAsBytes for SIndexDBV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for SIndexDBV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for SIndexDBV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for SIndexDBV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct SourceKeyArrayDbV1(pub SmallVec<[SourceKeyV1; 8]>); + +impl ValueAsBytes for SourceKeyArrayDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let vec_pub_str = self + .0 + .iter() + .map(|source_key| source_key.to_string()) + .collect::<SmallVec<[String; 8]>>(); + let json = serde_json::to_string(&vec_pub_str) + .map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for SourceKeyArrayDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + let vec_source_key_str: SmallVec<[String; 8]> = serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?; + Ok(Self( + vec_source_key_str + .into_iter() + .map(|source_key_str| { + SourceKeyV1::from_bytes(source_key_str.as_bytes()) + .map_err(|e| StringErr(format!("{}", e))) + }) + .collect::<std::result::Result<SmallVec<[SourceKeyV1; 8]>, Self::Err>>()?, + )) + } +} + +impl ToDumpString for SourceKeyArrayDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for SourceKeyArrayDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} diff --git a/rust-libs/duniter-dbs/src/values/ud_entry_db.rs b/rust-libs/duniter-dbs/src/values/ud_entry_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..13bf96baa25da7adba3ca00f946cfe2b4ecd684e --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/ud_entry_db.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UdEntryDbV1 { + #[serde(rename = "pub")] + pub pubkey: String, + pub member: bool, + pub availables: Vec<u32>, + pub consumed: Vec<u32>, + #[serde(rename = "consumedUDs")] + pub consumed_uds: Vec<ConsumedUdDbV1>, + pub dividends: Vec<UdAmountDbV1>, +} + +impl ValueAsBytes for UdEntryDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for UdEntryDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for UdEntryDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for UdEntryDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsumedUdDbV1 { + pub dividend_number: u32, + pub tx_hash: String, + pub tx_created_on: String, + #[serde(rename = "txLocktime")] + pub tx_lock_time: u32, + pub dividend: UdAmountDbV1, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct UdAmountDbV1 { + pub amount: u32, + pub base: u32, +} diff --git a/rust-libs/duniter-dbs/src/values/wallet_db.rs b/rust-libs/duniter-dbs/src/values/wallet_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..12b7a3fa9caa3bad9cb6e55e577fbb1c1b8ded77 --- /dev/null +++ b/rust-libs/duniter-dbs/src/values/wallet_db.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct WalletDbV1 { + pub conditions: String, + pub balance: u64, +} + +impl ValueAsBytes for WalletDbV1 { + fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> { + let json = + serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?; + f(json.as_bytes()) + } +} + +impl kv_typed::prelude::FromBytes for WalletDbV1 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes"); + Ok(serde_json::from_str(&json_str) + .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?) + } +} + +impl ToDumpString for WalletDbV1 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableValue for WalletDbV1 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + Self::from_bytes(source.as_bytes()) + } + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e))) + } +} diff --git a/rust-libs/duniter-dbs/tests/test_explorer.rs b/rust-libs/duniter-dbs/tests/test_explorer.rs new file mode 100644 index 0000000000000000000000000000000000000000..96db3271e88cb795236f327ed34b4dad441d1872 --- /dev/null +++ b/rust-libs/duniter-dbs/tests/test_explorer.rs @@ -0,0 +1,254 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +#[cfg(feature = "explorer")] +mod explorer { + use dubp_common::crypto::keys::ed25519::PublicKey; + use dubp_common::crypto::keys::PublicKey as _; + //use dubp_common::prelude::*; + use duniter_dbs::kv_typed::prelude::*; + use duniter_dbs::kv_typed::regex; + use duniter_dbs::prelude::*; + use duniter_dbs::smallvec::smallvec; + use duniter_dbs::{BcV1Db, BcV1DbWritable, PublicKeySingletonDbV1, Result, UidKeyV1}; + use std::{num::NonZeroUsize, str::FromStr}; + use tempdir::TempDir; + use unwrap::unwrap; + + const COLLECTION_NAME: &str = "uids"; + + fn stringify_json_value_test(v: serde_json::Value) -> serde_json::Value { + v + } + + #[test] + fn explorer_test_leveldb() -> Result<()> { + let tmp_dir = unwrap!(TempDir::new("explorer_test_leveldb")); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?; + + explorer_test(&db) + } + + #[test] + fn explorer_test_sled() -> Result<()> { + let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?; + + explorer_test(&db) + } + + fn explorer_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> { + // Defines test data + let k1 = unwrap!(UidKeyV1::from_str("toto")); + let k2 = unwrap!(UidKeyV1::from_str("titi")); + let v1 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "ByE9TU6qhktHYYVAqeTcWcaULBx151siQLyL3TrKvY85" + ))); + let v2 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp" + ))); + + // Insert test data + db.uids_write().upsert(k1, v1)?; + db.uids_write().upsert(k2, v2)?; + + // Test action count + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Count, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::Count(2)), res); + + // Test action get + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Get { key: "unexist" }, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::Get(None)), res); + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Get { key: "toto" }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Get(Some( + serde_json::Value::String( + "ByE9TU6qhktHYYVAqeTcWcaULBx151siQLyL3TrKvY85".to_owned() + ) + ))), + res + ); + + // Test action put + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Put { + key: "titu", + value: "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr", + }, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::PutOk), res); + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Get { key: "titu" }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Get(Some( + serde_json::Value::String( + "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned() + ) + ))), + res + ); + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Count, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::Count(3)), res); + + // Test action find + let range_res = db.explore( + COLLECTION_NAME, + ExplorerAction::Find { + key_min: Some("ti00".to_owned()), + key_max: Some("tizz".to_owned()), + key_regex: None, + value_regex: None, + limit: Some(10), + reverse: false, + step: unsafe { NonZeroUsize::new_unchecked(1) }, + }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Find(vec![ + EntryFound { + key: "titi".to_owned(), + value: serde_json::Value::String( + "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp".to_owned() + ), + captures: None, + }, + EntryFound { + key: "titu".to_owned(), + value: serde_json::Value::String( + "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned() + ), + captures: None, + }, + ])), + range_res + ); + + // Test action find with limit + let range_res = db.explore( + COLLECTION_NAME, + ExplorerAction::Find { + key_min: Some("ti00".to_owned()), + key_max: Some("tizz".to_owned()), + key_regex: None, + value_regex: None, + limit: Some(1), + reverse: false, + step: unsafe { NonZeroUsize::new_unchecked(1) }, + }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Find(vec![EntryFound { + key: "titi".to_owned(), + value: serde_json::Value::String( + "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp".to_owned() + ), + captures: None, + }])), + range_res + ); + + // Test action find with limit and reverse + let range_res = db.explore( + COLLECTION_NAME, + ExplorerAction::Find { + key_min: Some("ti00".to_owned()), + key_max: Some("tizz".to_owned()), + key_regex: None, + value_regex: None, + limit: Some(1), + reverse: true, + step: unsafe { NonZeroUsize::new_unchecked(1) }, + }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Find(vec![EntryFound { + key: "titu".to_owned(), + value: serde_json::Value::String( + "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned() + ), + captures: None, + }])), + range_res + ); + + // Test action find with regex capture + let range_res = db.explore( + COLLECTION_NAME, + ExplorerAction::Find { + key_min: Some("ti00".to_owned()), + key_max: Some("tizz".to_owned()), + key_regex: None, + value_regex: Some(regex::Regex::new("(E[Cv])[A-Z]").expect("wrong regex")), + limit: Some(10), + reverse: false, + step: unsafe { NonZeroUsize::new_unchecked(1) }, + }, + stringify_json_value_test, + )?; + assert_eq!( + Ok(ExplorerActionResponse::Find(vec![EntryFound { + key: "titu".to_owned(), + value: serde_json::Value::String( + "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned() + ), + captures: Some(ValueCaptures(smallvec![ + smallvec![Some("EC".to_owned())], + smallvec![Some("Ev".to_owned())] + ])), + }])), + range_res + ); + + // Test action delete + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Delete { key: "toto" }, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::DeleteOk), res); + let res = db.explore( + COLLECTION_NAME, + ExplorerAction::Get { key: "toto" }, + stringify_json_value_test, + )?; + assert_eq!(Ok(ExplorerActionResponse::Get(None)), res); + + Ok(()) + } +} diff --git a/rust-libs/duniter-dbs/tests/test_read_write.rs b/rust-libs/duniter-dbs/tests/test_read_write.rs new file mode 100644 index 0000000000000000000000000000000000000000..480c4b6b89dd41b74b113e2b0b423a4984a68f5f --- /dev/null +++ b/rust-libs/duniter-dbs/tests/test_read_write.rs @@ -0,0 +1,350 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use dubp_common::crypto::keys::ed25519::PublicKey; +use dubp_common::crypto::keys::PublicKey as _; +use dubp_common::prelude::*; +use duniter_dbs::kv_typed::prelude::*; +use duniter_dbs::{ + BcV1Db, BcV1DbReadable, BcV1DbWritable, BlockDbV1, BlockNumberKeyV1, MainBlockEvent, + PublicKeySingletonDbV1, Result, UidKeyV1, +}; +use kv_typed::channel::TryRecvError; +use std::str::FromStr; +use tempdir::TempDir; +use unwrap::unwrap; + +#[test] +fn write_read_delete_b0_leveldb() -> Result<()> { + let tmp_dir = unwrap!(TempDir::new("write_read_delete_b0_leveldb")); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?; + + write_read_delete_b0_test(&db) +} + +#[test] +fn write_read_delete_b0_sled() -> Result<()> { + let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?; + + write_read_delete_b0_test(&db) +} + +#[test] +fn iter_test_leveldb() -> Result<()> { + let tmp_dir = unwrap!(TempDir::new("batch_test_leveldb")); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?; + + write_some_entries_and_iter(&db) +} + +#[test] +fn iter_test_mem() -> Result<()> { + let db = BcV1Db::<Mem>::open(MemConf::default())?; + + write_some_entries_and_iter(&db) +} + +#[test] +fn iter_test_sled() -> Result<()> { + let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?; + + write_some_entries_and_iter(&db) +} + +#[test] +fn batch_test_leveldb() -> Result<()> { + let tmp_dir = unwrap!(TempDir::new("batch_test_leveldb")); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?; + + batch_test(&db) +} + +#[test] +fn batch_test_mem() -> Result<()> { + let db = BcV1Db::<Mem>::open(MemConf::default())?; + + batch_test(&db) +} + +#[test] +fn batch_test_sled() -> Result<()> { + let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?; + + batch_test(&db) +} + +fn write_read_delete_b0_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> { + let main_blocks_reader = db.main_blocks(); + + let (subscriber, events_recv) = kv_typed::channel::unbounded(); + + main_blocks_reader.subscribe(subscriber)?; + + // Empty db + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?, + None + ); + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?, + None + ); + assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None); + assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None); + if let Err(TryRecvError::Empty) = events_recv.try_recv() { + } else { + panic!("should not receive event"); + } + + // Insert b0 + let b0 = BlockDbV1::default(); + let main_blocks_writer = db.main_blocks_write(); + main_blocks_writer.upsert(BlockNumberKeyV1(BlockNumber(0)), b0.clone())?; + assert_eq!( + main_blocks_reader + .get(&BlockNumberKeyV1(BlockNumber(0)))? + .as_ref(), + Some(&b0) + ); + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?, + None + ); + let mut keys_iter = main_blocks_reader.iter(..).keys(); + assert_eq!( + keys_iter.next_res()?, + Some(BlockNumberKeyV1(BlockNumber(0))) + ); + assert_eq!(keys_iter.next_res()?, None); + let mut values_iter = main_blocks_reader.iter(..).values(); + assert_eq!(values_iter.next_res()?, Some(b0.clone())); + assert_eq!(values_iter.next_res()?, None); + if let Ok(events) = events_recv.try_recv() { + assert_eq!(events.len(), 1); + let event = &events[0]; + assert_eq!( + event, + &MainBlockEvent::Upsert { + key: BlockNumberKeyV1(BlockNumber(0)), + value: b0, + }, + ); + } else { + panic!("should receive event"); + } + + // Delete b0 + main_blocks_writer.remove(BlockNumberKeyV1(BlockNumber(0)))?; + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?, + None + ); + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?, + None + ); + assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None); + assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None); + if let Ok(events) = events_recv.try_recv() { + assert_eq!(events.len(), 1); + let event = &events[0]; + assert_eq!( + event, + &MainBlockEvent::Remove { + key: BlockNumberKeyV1(BlockNumber(0)), + }, + ); + } else { + panic!("should receive event"); + } + + Ok(()) +} + +fn write_some_entries_and_iter<B: Backend>(db: &BcV1Db<B>) -> Result<()> { + let k1 = unwrap!(UidKeyV1::from_str("titi")); + let p1 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "42jMJtb8chXrpHMAMcreVdyPJK7LtWjEeRqkPw4eSEVp" + ))); + let k2 = unwrap!(UidKeyV1::from_str("titu")); + let p2 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C" + ))); + let k3 = unwrap!(UidKeyV1::from_str("toto")); + let p3 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp" + ))); + let uids_writer = db.uids_write(); + uids_writer.upsert(k1, p1)?; + uids_writer.upsert(k2, p2)?; + uids_writer.upsert(k3, p3)?; + + let uids_reader = db.uids(); + { + let mut values_iter_step_2 = uids_reader.iter(..).values().step_by(2); + assert_eq!(Some(p1), values_iter_step_2.next_res()?); + assert_eq!(Some(p3), values_iter_step_2.next_res()?); + assert_eq!(None, values_iter_step_2.next_res()?); + + let mut entries_iter_step_2 = uids_reader.iter(..).step_by(2); + assert_eq!(Some((k1, p1)), entries_iter_step_2.next_res()?); + assert_eq!(Some((k3, p3)), entries_iter_step_2.next_res()?); + assert_eq!(None, entries_iter_step_2.next_res()?); + + let mut entries_iter = uids_reader.iter(k2..); + assert_eq!(Some((k2, p2)), entries_iter.next_res()?); + assert_eq!(Some((k3, p3)), entries_iter.next_res()?); + assert_eq!(None, entries_iter.next_res()?); + + let mut entries_iter = uids_reader.iter(..=k2); + assert_eq!(Some((k1, p1)), entries_iter.next_res()?); + assert_eq!(Some((k2, p2)), entries_iter.next_res()?); + assert_eq!(None, entries_iter.next_res()?); + + let mut entries_iter_rev = uids_reader.iter(k2..).reverse(); + assert_eq!(Some((k3, p3)), entries_iter_rev.next_res()?); + assert_eq!(Some((k2, p2)), entries_iter_rev.next_res()?); + assert_eq!(None, entries_iter_rev.next_res()?); + + let mut entries_iter_rev = uids_reader.iter(..=k2).reverse(); + assert_eq!(Some((k2, p2)), entries_iter_rev.next_res()?); + assert_eq!(Some((k1, p1)), entries_iter_rev.next_res()?); + assert_eq!(None, entries_iter.next_res()?); + + let mut keys_iter_rev = uids_reader.iter(..=k2).keys().reverse(); + assert_eq!(Some(k2), keys_iter_rev.next_res()?); + assert_eq!(Some(k1), keys_iter_rev.next_res()?); + assert_eq!(None, keys_iter_rev.next_res()?); + } + + uids_writer.remove(k3)?; + + let mut keys_iter_rev = uids_reader.iter(..).keys(); + assert_eq!(Some(k1), keys_iter_rev.next_res()?); + assert_eq!(Some(k2), keys_iter_rev.next_res()?); + assert_eq!(None, keys_iter_rev.next_res()?); + + Ok(()) +} + +fn batch_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> { + let main_blocks_reader = db.main_blocks(); + + let mut batch = db.new_batch(); + + let (subscriber, events_recv) = kv_typed::channel::unbounded(); + + main_blocks_reader.subscribe(subscriber)?; + + // Empty db + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?, + None + ); + assert_eq!( + main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?, + None + ); + assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None); + assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None); + if let Err(TryRecvError::Empty) = events_recv.try_recv() { + } else { + panic!("should not receive event"); + } + + // Insert b0 in batch + let b0 = BlockDbV1::default(); + batch + .main_blocks() + .upsert(BlockNumberKeyV1(BlockNumber(0)), b0.clone()); + + // bo should written in batch + assert_eq!( + batch.main_blocks().get(&BlockNumberKeyV1(BlockNumber(0))), + Some(&b0) + ); + + // bo should not written in db + assert_eq!( + db.main_blocks().get(&BlockNumberKeyV1(BlockNumber(0)))?, + None + ); + + if let Err(TryRecvError::Empty) = events_recv.try_recv() { + } else { + panic!("should not receive event"); + } + + // Insert b1 in batch + let b1 = BlockDbV1 { + number: 1, + ..Default::default() + }; + batch + .main_blocks() + .upsert(BlockNumberKeyV1(BlockNumber(1)), b1.clone()); + + // Write batch in db + db.write_batch(batch)?; + + // bo should written in db + assert_eq!( + db.main_blocks() + .get(&BlockNumberKeyV1(BlockNumber(0)))? + .as_ref(), + Some(&b0) + ); + let mut keys_iter = db.main_blocks().iter(..).keys(); + assert_eq!( + keys_iter.next_res()?, + Some(BlockNumberKeyV1(BlockNumber(0))) + ); + assert_eq!( + keys_iter.next_res()?, + Some(BlockNumberKeyV1(BlockNumber(1))) + ); + assert_eq!(keys_iter.next_res()?, None); + let mut values_iter = db.main_blocks().iter(..).values(); + assert_eq!(values_iter.next_res()?.as_ref(), Some(&b0)); + assert_eq!(values_iter.next_res()?.as_ref(), Some(&b1)); + assert_eq!(values_iter.next_res()?, None); + if let Ok(events) = events_recv.try_recv() { + assert_eq!(events.len(), 2); + assert!(assert_eq_pairs( + [&events[0], &events[1]], + [ + &MainBlockEvent::Upsert { + key: BlockNumberKeyV1(BlockNumber(0)), + value: b0, + }, + &MainBlockEvent::Upsert { + key: BlockNumberKeyV1(BlockNumber(1)), + value: b1, + } + ] + )); + } else { + panic!("should receive event"); + } + + Ok(()) +} + +fn assert_eq_pairs<T: PartialEq>(a: [T; 2], b: [T; 2]) -> bool { + (a[0] == b[0] && a[1] == b[1]) || (a[1] == b[0] && a[0] == b[1]) +} diff --git a/rust-libs/duniter-dbs/tests/test_tmp_real.rs b/rust-libs/duniter-dbs/tests/test_tmp_real.rs new file mode 100644 index 0000000000000000000000000000000000000000..00b2c81cd1e4b353deaadb97c440706e5411c69c --- /dev/null +++ b/rust-libs/duniter-dbs/tests/test_tmp_real.rs @@ -0,0 +1,736 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use dubp_common::crypto::bases::b58::ToBase58 as _; +use dubp_common::crypto::hashs::Hash; +use dubp_common::crypto::keys::PublicKey; +use dubp_common::prelude::*; +use duniter_dbs::kv_typed::prelude::*; +use duniter_dbs::*; +use duniter_dbs::{ + BcV1Db, BcV1DbReadable, BcV1DbWritable, BlockDbV1, BlockNumberKeyV1, PublicKeySingletonDbV1, + Result, UidKeyV1, +}; +use once_cell::sync::Lazy; +use std::{path::PathBuf, str::FromStr, sync::Mutex}; +use unwrap::unwrap; + +// Empty mutex used to ensure that only one test runs at a time +static MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(())); + +//const DB_PATH: &str = "/home/elois/.config/duniter/duniter_default/data"; +const DB_PATH: &str = "/home/elois/Documents/ml/leveldb-archives/g1-317499/leveldb"; + +#[test] +fn db_v1_main_blocks__() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + //let block52 = db.get::<MainBlocksColV1>(&MainBlockKeyV1(52))?; + //println!("{:#?}", block52); + + let current_block_number_opt = db + .main_blocks() + .iter(..) + .keys() + .reverse() + .next() + .transpose()?; + if let Some(current_block_number) = current_block_number_opt { + println!("current_block_number={:#?}", current_block_number); + let current_block = db.main_blocks().get(¤t_block_number)?; + println!("current_block={:#?}", current_block); + } + + /*// Collect all main blocks + let entries = db + .main_blocks() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, BlockDbV1)>>>()?; + println!("entries_len={}", entries.len());*/ + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_idty() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_idty().get(&PubKeyKeyV1::all())?); + assert!(all.0.len() > 2); + + // Collect all main blocks idty + let entries = db + .mb_idty() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("identities_count={}", entries.len() - 1); + for (k, v) in &entries { + if v.0.len() == 2 { + println!("{:?}", k.0.as_ref()); + } + } + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_certs() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all main blocks idty + let entries = db + .mb_certs() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("certifications_count={}", entries.len() - 1); + for (k, v) in &entries[..10] { + if v.0.len() > 1 { + println!("{}={:?}", k.0, v.0); + } + } + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_joiners() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_joiners().get(&PubKeyKeyV1::all())?); + assert!(all.0.len() > 100); + + // Collect all main blocks joiners + let entries = db + .mb_joiners() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("joiners_count={}", entries.len() - 1); + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_actives() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_actives().get(&PubKeyKeyV1::all())?); + assert!(all.0.len() > 100); + + // Collect all main blocks actives + let entries = db + .mb_actives() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("actives_count={}", entries.len() - 1); + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_leavers() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_leavers().get(&PubKeyKeyV1::all())?); + assert!(all.0.len() >= 3); + + // Collect all main blocks with leavers + let entries = db + .mb_leavers() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("leavers_count={}", entries.len() - 1); + for (k, v) in entries { + println!("{}={:?}", k.0, v.0); + } + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_excluded() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_excluded().get(&PubKeyKeyV1::all())?); + assert!(all.0.len() >= 50); + + // Collect all main blocks with excluded + let entries = db + .mb_excluded() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?; + println!("excluded_count={}", entries.len() - 1); + /*for (k, v) in entries { + println!("{}={:?}", k.0, v.0); + }*/ + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_revoked() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_revoked().get(&PubKeyAndSigV1::all())?); + assert!(all.0.len() >= 20); + + // Collect all main blocks with revoked + let entries = db + .mb_revoked() + .iter(..) + .collect::<KvResult<Vec<(PubKeyAndSigV1, BlockNumberArrayV1)>>>()?; + println!("revoked_count={}", entries.len() - 1); + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_dividend() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_dividends().get(&AllKeyV1)?); + assert!(all.0.len() >= 900); + println!("blocks with dividend={}", all.0.len()); + println!("last block with dividend={:?}", all.0.last()); + + // Collect all main blocks with dividends + let entries = db + .mb_dividends() + .iter(..) + .collect::<KvResult<Vec<(AllKeyV1, BlockNumberArrayV1)>>>()?; + println!("dividends_keys={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_main_blocks_transactions() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let all = unwrap!(db.mb_transactions().get(&AllKeyV1)?); + assert!(all.0.len() >= 900); + println!("blocks with tx={}", all.0.len()); + println!("last block with tx={:?}", all.0.last()); + + // Collect all main blocks with transactions + let entries = db + .mb_transactions() + .iter(..) + .collect::<KvResult<Vec<(AllKeyV1, BlockNumberArrayV1)>>>()?; + println!("transactions_keys={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_fork_blocks() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + /*let fork_blocks_keys = db + .keys_iter::<ForkBlocksColV1, _>(..) + .take(1) + .collect::<KvResult<Vec<BlockstampKeyV1>>>()?; + let one_fork_block = unwrap!(db.get::<ForkBlocksColV1>(&fork_blocks_keys[0])?); + + println!("{:#?}", one_fork_block);*/ + + // Collect all fork blocks + let entries = db + .fork_blocks() + .iter(..) + .collect::<KvResult<Vec<(BlockstampKeyV1, BlockDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_bindex() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all bindex entries + let entries = db + .bindex() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, BlockHeadDbV1)>>>()?; + println!("entries_len={}", entries.len()); + //println!("last_bindex={:?}", entries.last()); + //for (_k, v) in entries {} + + Ok(()) +} + +#[test] +fn db_v1_iindex__() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let iindex_keys = db + .iindex() + .iter(..) + .keys() + .take(1) + .collect::<KvResult<Vec<PubKeyKeyV1>>>()?; + let one_iindex_db = unwrap!(db.iindex().get(&iindex_keys[0])?); + assert_eq!(one_iindex_db.0[0].pubkey, iindex_keys[0].0.to_base58()); + + //println!("{:#?}", one_iindex_db); + + if let Some(ref hash) = one_iindex_db.0[0].hash { + let pubkey = unwrap!(db + .iindex_hash() + .get(&HashKeyV1(unwrap!(Hash::from_hex(hash))))?); + assert_eq!(pubkey.0, iindex_keys[0].0); + } + + // Count iindex entries + let count = db.iindex().count()?; + println!("iindex size={}", count); + + // Count members + let count_members = db + .iindex() + .iter(..) + .filter_map(KvResult::ok) + .filter(|(_k, v)| v.0[0].member.is_some() && unwrap!(v.0[0].member)) + .count(); + println!("count_members={}", count_members); + + // Collect all iindex entries + let entries = db + .iindex() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, IIndexDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_iindex_hash() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let iindex_entries = db + .iindex_hash() + .iter(..) + .take(3) + .collect::<KvResult<Vec<(HashKeyV1, PublicKeySingletonDbV1)>>>()?; + + println!( + "(hash, pub)=({:#?},{:#?})", + iindex_entries[0].0, iindex_entries[0].1 + ); + + // Collect all iindex/hash entries + let entries = db + .iindex_hash() + .iter(..) + .collect::<KvResult<Vec<(HashKeyV1, PublicKeySingletonDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_iindex_kick() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let entries = db + .iindex_kick() + .iter(..) + .take(3) + .collect::<KvResult<Vec<(PubKeyKeyV1, KickDbV1)>>>()?; + + println!("(pub, kick)=({:#?},{:#?})", entries[0].0, entries[0].1); + + // Collect all iindex/kick entries + let entries = db + .iindex_kick() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, KickDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_iindex_written_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all iindex/written_on entries + let entries = db + .iindex_written_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + println!("entries={:?}", entries); + + Ok(()) +} + +#[test] +fn db_v1_uid_col() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let uid_keys = db + .uids() + .iter(..) + .keys() + .take(1) + .collect::<KvResult<Vec<UidKeyV1>>>()?; + let one_pubkey_db = db.uids().get(&uid_keys[0])?; + + println!( + "(uid, pubkey) = ({}, {:#?})", + uid_keys[0].0.as_str(), + one_pubkey_db + ); + + let start_key = unwrap!(UidKeyV1::from_str("1b")); + let end_key = unwrap!(UidKeyV1::from_str("404_not_found")); + let uid_index = db + .uids() + .iter(start_key..end_key) + .collect::<KvResult<Vec<(UidKeyV1, PublicKeySingletonDbV1)>>>()?; + assert_eq!( + uid_index, + vec![( + unwrap!(UidKeyV1::from_str("1claude1")), + PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58( + "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp" + ))) + )], + ); + + // Collect all iindex/uid entries + let entries = db + .uids() + .iter(..) + .collect::<KvResult<Vec<(UidKeyV1, PublicKeySingletonDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_mindex__() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + let mindex_keys = db + .mindex() + .iter(..) + .keys() + .take(1) + .collect::<KvResult<Vec<PubKeyKeyV1>>>()?; + let one_mindex_db = unwrap!(db.mindex().get(&mindex_keys[0])?); + assert_eq!(one_mindex_db.0[0].pubkey, mindex_keys[0].0.to_base58()); + + //println!("{:#?}", one_mindex_db); + + // Count mindex entries + let count = db.mindex().count()?; + println!("mindex size={}", count); + + // Collect all mindex entries + let entries = db + .mindex() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, MIndexDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_mindex_expires_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all mindex/expires_on entries + let entries = db + .mindex_expires_on() + .iter(..) + .collect::<KvResult<Vec<(TimestampKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + /*for (k, v) in entries { + if k.0 == BlockNumber(u32::MAX) { + println!("{:?}", v.0) + } + }*/ + + Ok(()) +} + +#[test] +fn db_v1_mindex_revokes_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all mindex/revokes_on entries + let entries = db + .mindex_revokes_on() + .iter(..) + .collect::<KvResult<Vec<(TimestampKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_mindex_written_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all mindex/written_on entries + let entries = db + .mindex_written_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + for (k, v) in entries { + if k.0 == BlockNumber(u32::MAX) { + println!("{:?}", v.0) + } + } + + Ok(()) +} + +#[test] +fn db_v1_cindex__() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all bindex entries + let entries = db + .cindex() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, CIndexDbV1)>>>()?; + println!("entries_len={}", entries.len()); + //println!("last_bindex={:?}", entries.last()); + for (_k, v) in entries { + for cindex_line in v.issued { + if cindex_line.created_on_ref.is_some() { + println!("cindex_line={:?}", cindex_line) + } + } + } + + Ok(()) +} + +#[test] +fn db_v1_cindex_expires_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all cindex/expires_on entries + let entries = db + .cindex_expires_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_cindex_written_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all cindex/written_on entries + let entries = db + .cindex_written_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_wallet() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all wallet entries + let entries = db + .wallet() + .iter(..) + .collect::<KvResult<Vec<(WalletConditionsV1, WalletDbV1)>>>()?; + println!("entries_len={}", entries.len()); + let mut max_cond_len = 0; + for (k, _v) in entries { + if k.0.len() > max_cond_len { + max_cond_len = k.0.len(); + println!("k={}", k.0.as_str()); + } + } + println!("max_cond_len={}", max_cond_len); + + Ok(()) +} + +#[test] +fn db_v1_dividend() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all level_dividend entries + let entries = db + .uds() + .iter(..) + .collect::<KvResult<Vec<(PubKeyKeyV1, UdEntryDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + println!("entries[0]=({:?}, {:?})", entries[0].0, entries[0].1); + + Ok(()) +} + +#[test] +fn db_v1_dividend_written_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all level_dividend/level_dividend_trim_index entries + let entries = db + .uds_trim() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + + Ok(()) +} + +#[test] +fn db_v1_sindex() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all level_sindex entries + let entries = db + .sindex() + .iter(..) + .collect::<KvResult<Vec<(SourceKeyV1, SIndexDBV1)>>>()?; + println!("entries_len={}", entries.len()); + + println!("entries[0]=({:?}, {:?})", entries[0].0, entries[0].1); + + Ok(()) +} + +#[test] +fn db_v1_sindex_written_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all mindex/written_on entries + let entries = db + .sindex_written_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, SourceKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + for (k, v) in entries { + if k.0 == BlockNumber(u32::MAX) { + println!("{:?}", v.0) + } + } + + Ok(()) +} + +#[test] +fn db_v1_sindex_consumed_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + // Collect all mindex/written_on entries + let entries = db + .sindex_consumed_on() + .iter(..) + .collect::<KvResult<Vec<(BlockNumberKeyV1, SourceKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + for (k, v) in entries { + println!("{:?} => {:?}", k.0, v.0) + } + + Ok(()) +} + +#[test] +fn db_v1_sindex_conditions_on() -> Result<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + + let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?; + + // Collect all mindex/written_on entries + let entries = db + .sindex_conditions() + .iter(..) + .collect::<KvResult<Vec<(WalletConditionsV1, SourceKeyArrayDbV1)>>>()?; + println!("entries_len={}", entries.len()); + /*for (k, v) in entries { + println!("{:?} => {:?}", k.0, v.0) + }*/ + + Ok(()) +} diff --git a/rust-libs/tools/kv_typed/Cargo.toml b/rust-libs/tools/kv_typed/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e87cbd58b3ad21ffe8325999f0a566157f94b3ff --- /dev/null +++ b/rust-libs/tools/kv_typed/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "kv_typed" +version = "0.1.0" +authors = ["elois <c@elo.tf>"] +description = "Strongly typed key-value storage" +repository = "https://git.duniter.org/nodes/typescript/duniter" +keywords = ["database", "key", "sled"] +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +async-channel = { version = "1.4.2", optional = true } +crossbeam-channel = { version = "0.4.4", optional = true } +kv_typed_code_gen = { path = "../kv_typed_code_gen" } +leveldb_minimal = { version = "0.1.0", optional = true } +mockall = { version = "0.8.0", optional = true } +parking_lot = { version = "0.11.0", optional = true } +rayon = { version = "1.3.1", optional = true } +regex = { version = "1.3.9", optional = true } +serde_json = { version = "1.0.53", optional = true } +sled = { version = "0.34.4", optional = true } +smallvec = { version = "1.4.0", features = ["serde"] } +thiserror = "1.0.20" + +[[bench]] +name = "compare_backends" +harness = false +required-features = ["leveldb_backend", "memory_backend", "sled_backend"] + +[dev-dependencies] +async-std = { version = "1.6.3", features = ["attributes"] } +maybe-async = "0.2.0" +smallvec = { version = "1.4.0", features = ["serde"] } +unwrap = "1.2.1" + +# Benches dependencies +criterion = { version = "0.3.1" } + +[features] +#default = ["memory_backend"] + +async = ["async-channel"] +explorer = ["rayon", "regex", "serde_json"] +leveldb_backend = ["leveldb_minimal"] +memory_backend = ["parking_lot"] +sled_backend = ["sled"] +subscription = ["parking_lot"] +sync = ["crossbeam-channel"] + +mock = ["mockall"] + +default = ["memory_backend", "subscription", "sync"] +#default = ["memory_backend", "subscription", "sync", "explorer"] +#default = ["memory_backend", "subscription", "sync", "mock"] diff --git a/rust-libs/tools/kv_typed/benches/compare_backends.rs b/rust-libs/tools/kv_typed/benches/compare_backends.rs new file mode 100644 index 0000000000000000000000000000000000000000..b89d4be1a2256a7e63a16f2c5c097cd56dfb24f9 --- /dev/null +++ b/rust-libs/tools/kv_typed/benches/compare_backends.rs @@ -0,0 +1,101 @@ +use criterion::{criterion_group, criterion_main, AxisScale, Criterion, PlotConfiguration}; +use kv_typed::prelude::*; +use std::{fmt::Debug, path::PathBuf}; + +db_schema!(Test, [["c1", col_1, u32, String],]); +const LEVELDB_DIR_PATH: &str = "/dev/shm/kv_typed/benches/compare_backends/leveldb"; +static SMALL_VAL: &str = "abcdefghijklmnopqrst"; +static LARGE_VAL: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +fn read_n_entries<B: Backend>(db: &TestDb<B>, n: u32, val: String) { + let mut iter = db.col_1().iter(..).values(); + for _ in 0..n { + assert_eq!(iter.next_res().expect(""), Some(val.clone())); + //assert_eq!(db.col_1().get(&i).expect(""), Some(val.clone())); + } + assert_eq!(iter.next_res().expect(""), None); +} +fn write_n_entries<B: Backend>(db: &TestDb<B>, n: u32, val: String) { + for i in 0..n { + db.col_1_write().remove(i).expect("fail to write"); + db.col_1_write() + .upsert(i, val.clone()) + .expect("fail to write"); + } +} + +pub fn benchmark(c: &mut Criterion) { + // Read chart config + let read_chart_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + // Create DBs + std::fs::create_dir_all(LEVELDB_DIR_PATH).expect("fail to create leveldb dir"); + let leveldb_db = TestDb::<LevelDb>::open(LevelDbConf { + db_path: PathBuf::from(LEVELDB_DIR_PATH), + ..Default::default() + }) + .expect("fail to open db"); + let mem_db = TestDb::<Mem>::open(MemConf::default()).expect("fail to open db"); + let sled_db = + TestDb::<Sled>::open(SledConf::default().temporary(true)).expect("fail to open db"); + + // Test write small values + let mut group = c.benchmark_group("write small values"); + group.bench_function("leveldb", |b| { + b.iter(|| write_n_entries::<LevelDb>(&leveldb_db, 100, String::from(SMALL_VAL))) + }); + group.bench_function("mem", |b| { + b.iter(|| write_n_entries::<Mem>(&mem_db, 100, String::from(SMALL_VAL))) + }); + group.bench_function("sled", |b| { + b.iter(|| write_n_entries::<Sled>(&sled_db, 100, String::from(SMALL_VAL))) + }); + group.finish(); + + // Test read small values + let mut group = c.benchmark_group("read small values"); + group.plot_config(read_chart_config.clone()); + group.bench_function("leveldb", |b| { + b.iter(|| read_n_entries(&leveldb_db, 100, String::from(SMALL_VAL))) + }); + group.bench_function("mem", |b| { + b.iter(|| read_n_entries(&mem_db, 100, String::from(SMALL_VAL))) + }); + group.bench_function("sled", |b| { + b.iter(|| read_n_entries(&sled_db, 100, String::from(SMALL_VAL))) + }); + group.finish(); + + // Test write large values + let mut group = c.benchmark_group("write large values"); + group.bench_function("leveldb", |b| { + b.iter(|| write_n_entries::<LevelDb>(&leveldb_db, 100, String::from(LARGE_VAL))) + }); + group.bench_function("mem", |b| { + b.iter(|| write_n_entries::<Mem>(&mem_db, 100, String::from(LARGE_VAL))) + }); + group.bench_function("sled", |b| { + b.iter(|| write_n_entries::<Sled>(&sled_db, 100, String::from(LARGE_VAL))) + }); + group.finish(); + + // Test read large values + let mut group = c.benchmark_group("read large values"); + group.plot_config(read_chart_config); + group.bench_function("leveldb", |b| { + b.iter(|| read_n_entries(&leveldb_db, 100, String::from(LARGE_VAL))) + }); + group.bench_function("mem", |b| { + b.iter(|| read_n_entries(&mem_db, 100, String::from(LARGE_VAL))) + }); + group.bench_function("sled", |b| { + b.iter(|| read_n_entries(&sled_db, 100, String::from(LARGE_VAL))) + }); + group.finish(); + + // Close DBs + std::fs::remove_dir_all(LEVELDB_DIR_PATH).expect("fail to create leveldb dir"); +} + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/rust-libs/tools/kv_typed/src/as_bytes.rs b/rust-libs/tools/kv_typed/src/as_bytes.rs new file mode 100644 index 0000000000000000000000000000000000000000..33cb6012dc24aefb96564437d37eb1fcea7c80c2 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/as_bytes.rs @@ -0,0 +1,36 @@ +use crate::*; + +pub trait KeyAsBytes { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, f: F) -> T; +} +pub trait ValueAsBytes { + fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, f: F) -> Result<T, KvError>; +} + +impl KeyAsBytes for String { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(self.as_bytes()) + } +} +impl ValueAsBytes for String { + fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, mut f: F) -> Result<T, KvError> { + f(self.as_bytes()) + } +} + +macro_rules! impl_as_bytes_for_numbers { + ($($T:ty),*) => {$( + impl KeyAsBytes for $T { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + f(&self.to_be_bytes()[..]) + } + } + impl ValueAsBytes for $T { + fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, mut f: F) -> Result<T, KvError> { + f(&self.to_be_bytes()[..]) + } + } + )*}; +} + +impl_as_bytes_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); diff --git a/rust-libs/tools/kv_typed/src/backend.rs b/rust-libs/tools/kv_typed/src/backend.rs new file mode 100644 index 0000000000000000000000000000000000000000..6992e41f8bb8e547ee49f264d00e1ed38ac99fa7 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend.rs @@ -0,0 +1,74 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed Backend Trait + +#[cfg(feature = "leveldb_backend")] +pub mod leveldb; +#[cfg(feature = "memory_backend")] +pub mod memory; +#[cfg(feature = "mock")] +pub mod mock; +#[cfg(feature = "sled_backend")] +pub mod sled; + +use crate::*; + +pub trait TransactionalBackend<DbReader: From<Vec<Self::TxCol>>, DbWriter: From<Vec<Self::TxCol>>>: + Backend +{ + type Err: Error + Send + Sync + 'static; + type TxCol: BackendCol; + + fn read<A: Debug, D, F: Fn(&DbReader) -> TransactionResult<D, A, Self::Err>>( + &self, + f: F, + ) -> TransactionResult<D, A, Self::Err>; + fn write<A: Debug, F: Fn(&DbWriter) -> TransactionResult<(), A, Self::Err>>( + &self, + f: F, + ) -> TransactionResult<(), A, Self::Err>; +} + +pub trait Backend: 'static + Clone + Sized { + const NAME: &'static str; + type Col: BackendCol; + type Conf: Default; + + fn open(conf: &Self::Conf) -> KvResult<Self>; + fn open_col(&mut self, conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col>; +} + +pub trait BackendCol: 'static + Clone { + type Batch: BackendBatch; + type KeyBytes: AsRef<[u8]>; + type ValueBytes: AsRef<[u8]>; + type Iter: Iterator<Item = Result<(Self::KeyBytes, Self::ValueBytes), DynErr>> + + ReversableIterator; + + fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>>; + fn count(&self) -> KvResult<usize>; + fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter; + fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()>; + fn delete<K: Key>(&self, k: &K) -> KvResult<()>; + fn new_batch() -> Self::Batch; + fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()>; +} + +#[cfg_attr(feature = "mock", mockall::automock)] +pub trait BackendBatch: Default { + fn upsert(&mut self, k: &[u8], v: &[u8]); + fn remove(&mut self, k: &[u8]); +} diff --git a/rust-libs/tools/kv_typed/src/backend/leveldb.rs b/rust-libs/tools/kv_typed/src/backend/leveldb.rs new file mode 100644 index 0000000000000000000000000000000000000000..83892d4cfe9ed574bb3dab3b14753b47458eb3cb --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend/leveldb.rs @@ -0,0 +1,208 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! LevelDb backend for KV Typed + +use crate::*; +pub use leveldb_minimal::database::batch::{Batch as _, Writebatch as WriteBatch}; +use leveldb_minimal::database::cache::Cache as LevelDbCache; +pub use leveldb_minimal::database::error::Error as LevelDbError; +use leveldb_minimal::database::iterator::Iterator as LevelDbIterator; +pub use leveldb_minimal::database::Database as LevelDbDb; +use leveldb_minimal::iterator::{Iterable, LevelDBIterator as _}; +use leveldb_minimal::kv::KV as _; +pub use leveldb_minimal::options::{Options as LevelDbOptions, ReadOptions, WriteOptions}; +use leveldb_minimal::Compression; +use std::path::PathBuf; + +#[derive(Clone, Copy, Debug)] +pub struct LevelDb; + +impl Backend for LevelDb { + const NAME: &'static str = "leveldb"; + type Col = LevelDbCol; + type Conf = LevelDbConf; + + fn open(_conf: &Self::Conf) -> KvResult<Self> { + Ok(LevelDb) + } + fn open_col(&mut self, conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col> { + Ok(LevelDbCol(Arc::new(LevelDbDb::open( + &conf.db_path.join(col_name), + conf.clone().into(), + )?))) + } +} + +#[derive(Clone)] +pub struct LevelDbCol(Arc<LevelDbDb>); + +impl Debug for LevelDbCol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LevelDbCol") + .field("0", &"Arc<LevelDbDb>") + .finish() + } +} + +impl BackendBatch for WriteBatch { + fn upsert(&mut self, k: &[u8], v: &[u8]) { + self.put(k, v) + } + + fn remove(&mut self, k: &[u8]) { + self.delete(k) + } +} + +impl BackendCol for LevelDbCol { + type Batch = WriteBatch; + type KeyBytes = Vec<u8>; + type ValueBytes = Vec<u8>; + type Iter = LevelDbIter; + + #[inline(always)] + fn new_batch() -> Self::Batch { + WriteBatch::default() + } + #[inline(always)] + fn count(&self) -> KvResult<usize> { + Ok(self + .0 + .iter(ReadOptions { + verify_checksums: false, + fill_cache: false, + snapshot: None, + }) + .count()) + } + #[inline(always)] + fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> { + k.as_bytes(|k_bytes| { + self.0 + .get(ReadOptions::new(), k_bytes)? + .map(|bytes| { + V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e))) + }) + .transpose() + }) + } + #[inline(always)] + fn delete<K: Key>(&self, k: &K) -> KvResult<()> { + k.as_bytes(|k_bytes| self.0.delete(WriteOptions::new(), k_bytes))?; + Ok(()) + } + #[inline(always)] + fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> { + value.as_bytes(|value_bytes| { + k.as_bytes(|k_bytes| self.0.put(WriteOptions::new(), k_bytes, value_bytes))?; + Ok(()) + }) + } + #[inline(always)] + fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> { + self.0.write(WriteOptions::new(), &inner_batch)?; + Ok(()) + } + #[inline(always)] + fn iter<K: Key, V: Value>(&self, _range: RangeBytes) -> Self::Iter { + LevelDbIter(self.0.iter(ReadOptions::new())) + } +} + +pub struct LevelDbIter(LevelDbIterator); +impl Debug for LevelDbIter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LevelDbIter") + .field("0", &"LevelDbIterator<'db>") + .finish() + } +} + +impl Iterator for LevelDbIter { + type Item = Result<(Vec<u8>, Vec<u8>), DynErr>; + + #[inline(always)] + fn next(&mut self) -> Option<Self::Item> { + self.0.next().map(Ok) + } +} +impl ReversableIterator for LevelDbIter { + #[inline(always)] + fn reverse(self) -> Self { + Self(self.0.reverse()) + } +} + +#[derive(Clone, Debug)] +/// leveldb configuration +pub struct LevelDbConf { + pub create_if_missing: bool, + pub db_path: PathBuf, + pub error_if_exists: bool, + pub paranoid_checks: bool, + pub write_buffer_size: Option<usize>, + pub max_open_files: Option<i32>, + pub block_size: Option<usize>, + pub block_restart_interval: Option<i32>, + pub compression: bool, + pub cache: Option<usize>, +} + +impl LevelDbConf { + pub fn path(db_path: PathBuf) -> Self { + Self { + db_path, + ..Default::default() + } + } +} + +impl Default for LevelDbConf { + fn default() -> Self { + LevelDbConf { + create_if_missing: true, + db_path: PathBuf::default(), + error_if_exists: false, + paranoid_checks: false, + write_buffer_size: None, + max_open_files: None, + block_size: None, + block_restart_interval: None, + compression: true, + cache: None, + } + } +} + +impl Into<LevelDbOptions> for LevelDbConf { + fn into(self) -> LevelDbOptions { + LevelDbOptions { + create_if_missing: self.create_if_missing, + error_if_exists: self.error_if_exists, + paranoid_checks: self.paranoid_checks, + write_buffer_size: self.write_buffer_size, + max_open_files: self.max_open_files, + block_size: self.block_size, + block_restart_interval: self.block_restart_interval, + compression: if self.compression { + Compression::Snappy + } else { + Compression::No + }, + cache: self.cache.map(LevelDbCache::new), + } + } +} diff --git a/rust-libs/tools/kv_typed/src/backend/memory.rs b/rust-libs/tools/kv_typed/src/backend/memory.rs new file mode 100644 index 0000000000000000000000000000000000000000..68aa9275b501af4cbcb18c1e1dffdd1f372b4489 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend/memory.rs @@ -0,0 +1,200 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Memory backend for KV Typed, + +use crate::*; +use parking_lot::{RwLock, RwLockReadGuard}; +use std::collections::BTreeMap; + +#[derive(Clone, Copy, Debug)] +pub struct Mem; + +#[derive(Clone, Copy, Debug, Default)] +pub struct MemConf { + // Allows to prevent `MemConf` from being instantiated without using the `Default` trait. + // Thus the eventual addition of a field in the future will not be a breaking change. + phantom: PhantomData<()>, +} + +type KeyBytes = IVec; +type ValueBytes = IVec; +type Map = BTreeMap<KeyBytes, ValueBytes>; +type ArcSharedMap = Arc<RwLock<Map>>; + +impl Backend for Mem { + const NAME: &'static str = "mem"; + type Col = MemCol; + type Conf = MemConf; + + fn open(_conf: &Self::Conf) -> KvResult<Self> { + Ok(Mem) + } + fn open_col(&mut self, _conf: &Self::Conf, _col_name: &str) -> KvResult<Self::Col> { + Ok(MemCol(Arc::new(RwLock::new(BTreeMap::new())))) + } +} + +#[derive(Debug, Default)] +pub struct MemBatch { + upsert_ops: Vec<(KeyBytes, ValueBytes)>, + remove_ops: Vec<KeyBytes>, +} + +impl BackendBatch for MemBatch { + fn upsert(&mut self, k: &[u8], v: &[u8]) { + self.upsert_ops.push((k.into(), v.into())); + } + + fn remove(&mut self, k: &[u8]) { + self.remove_ops.push(k.into()); + } +} + +#[derive(Clone, Debug)] +pub struct MemCol(ArcSharedMap); + +impl BackendCol for MemCol { + type Batch = MemBatch; + type KeyBytes = KeyBytes; + type ValueBytes = ValueBytes; + type Iter = MemIter; + + #[inline(always)] + fn new_batch() -> Self::Batch { + MemBatch::default() + } + #[inline(always)] + fn count(&self) -> KvResult<usize> { + let reader = self.0.read(); + Ok(reader.len()) + } + #[inline(always)] + fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> { + k.as_bytes(|k_bytes| { + let reader = self.0.read(); + reader + .get(k_bytes) + .map(|bytes| { + V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e))) + }) + .transpose() + }) + } + #[inline(always)] + fn delete<K: Key>(&self, k: &K) -> KvResult<()> { + k.as_bytes(|k_bytes| { + let mut writer = self.0.write(); + writer.remove(k_bytes) + }); + Ok(()) + } + #[inline(always)] + fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> { + value.as_bytes(|value_bytes| { + k.as_bytes(|k_bytes| { + let mut writer = self.0.write(); + writer.insert(k_bytes.into(), value_bytes.into()); + }); + Ok(()) + }) + } + #[inline(always)] + fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> { + let mut writer = self.0.write(); + for (k, v) in inner_batch.upsert_ops { + writer.insert(k, v); + } + for k in inner_batch.remove_ops { + writer.remove(&k); + } + Ok(()) + } + #[inline(always)] + fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter { + let map_shared_arc = self.0.clone(); + let map_shared_ref = map_shared_arc.as_ref(); + + let reader = map_shared_ref.read(); + self.iter_inner(range, reader) + } +} + +impl MemCol { + fn iter_inner( + &self, + range: RangeBytes, + reader: RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>, + ) -> MemIter { + let reader = unsafe { + std::mem::transmute::< + RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>, + RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>, + >(reader) + }; + let reader_ref = unsafe { + std::mem::transmute::< + &RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>, + &'static RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>, + >(&reader) + }; + let iter = reader_ref.range(range); + + MemIter { + col: self.clone(), + reader: Some(reader), + iter, + reversed: false, + } + } +} + +pub struct MemIter { + #[allow(dead_code)] + // Needed for safety + col: MemCol, + #[allow(dead_code)] + // Needed for safety + reader: Option<RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>>, + iter: std::collections::btree_map::Range<'static, KeyBytes, ValueBytes>, + reversed: bool, +} + +impl Debug for MemIter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MemIter").field("0", &"???").finish() + } +} +impl Iterator for MemIter { + type Item = Result<(KeyBytes, ValueBytes), DynErr>; + + #[inline(always)] + fn next(&mut self) -> Option<Self::Item> { + if self.reversed { + self.iter.next_back() + } else { + self.iter.next() + } + .map(|(k, v)| Ok((k.to_owned(), v.to_owned()))) + } +} + +impl ReversableIterator for MemIter { + #[inline(always)] + fn reverse(mut self) -> Self { + self.reversed = !self.reversed; + self + } +} diff --git a/rust-libs/tools/kv_typed/src/backend/mock.rs b/rust-libs/tools/kv_typed/src/backend/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..5e8b99879d96f18fc8ed01159f4139c750ce7c02 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend/mock.rs @@ -0,0 +1,70 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed mock backend + +use super::MockBackendBatch; +use crate::*; + +#[cfg(feature = "mock")] +mockall::mock! { + pub BackendIter {} + trait Iterator { + type Item = Result<(Vec<u8>, Vec<u8>), DynErr>; + + fn next(&mut self) -> Option<<Self as Iterator>::Item>; + } + trait ReversableIterator { + fn reverse(self) -> Self; + } +} + +#[cfg(feature = "mock")] +mockall::mock! { + pub BackendCol {} + trait Clone { + fn clone(&self) -> Self; + } + trait BackendCol { + type Batch = MockBackendBatch; + type KeyBytes = Vec<u8>; + type ValueBytes = Vec<u8>; + type Iter = MockBackendIter; + + fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>>; + fn count(&self) -> KvResult<usize>; + fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> MockBackendIter; + fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()>; + fn delete<K: Key>(&self, k: &K) -> KvResult<()>; + fn new_batch() -> MockBackendBatch; + fn write_batch(&self, inner_batch: MockBackendBatch) -> KvResult<()>; + } +} + +#[cfg(feature = "mock")] +mockall::mock! { + pub Backend {} + trait Clone { + fn clone(&self) -> Self; + } + trait Backend: 'static + Clone + Sized { + const NAME: &'static str = "mock"; + type Col = MockBackendCol; + type Conf = (); + + fn open(conf: &()) -> KvResult<Self>; + fn open_col(&mut self, conf: &(), col_name: &str) -> KvResult<MockBackendCol>; + } +} diff --git a/rust-libs/tools/kv_typed/src/backend/sled.rs b/rust-libs/tools/kv_typed/src/backend/sled.rs new file mode 100644 index 0000000000000000000000000000000000000000..348ca2bdd04a24866fac3bf8c3f688ceca0e628d --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend/sled.rs @@ -0,0 +1,145 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Sled backend for KV Typed, + +mod transactional; + +pub use sled::Config; + +use crate::*; + +#[derive(Clone, Debug)] +pub struct Sled { + db: sled::Db, + trees: Vec<sled::Tree>, +} + +impl Backend for Sled { + const NAME: &'static str = "sled"; + type Col = SledCol; + type Conf = Config; + + fn open(conf: &Self::Conf) -> KvResult<Self> { + Ok(Sled { + db: conf.open()?, + trees: Vec::new(), + }) + } + fn open_col(&mut self, _conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col> { + let tree = self.db.open_tree(col_name)?; + self.trees.push(tree.clone()); + Ok(SledCol(tree)) + } +} + +impl BackendBatch for sled::Batch { + fn upsert(&mut self, k: &[u8], v: &[u8]) { + self.insert(k, v) + } + + fn remove(&mut self, k: &[u8]) { + self.remove(k) + } +} + +#[derive(Clone, Debug)] +pub struct SledCol(sled::Tree); + +impl BackendCol for SledCol { + type Batch = sled::Batch; + type KeyBytes = IVec; + type ValueBytes = IVec; + type Iter = SledIter; + + #[inline(always)] + fn new_batch() -> Self::Batch { + sled::Batch::default() + } + #[inline(always)] + fn count(&self) -> KvResult<usize> { + Ok(self.0.len()) + } + #[inline(always)] + fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> { + k.as_bytes(|k_bytes| { + self.0 + .get(k_bytes)? + .map(|bytes| { + V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e))) + }) + .transpose() + }) + } + #[inline(always)] + fn delete<K: Key>(&self, k: &K) -> KvResult<()> { + k.as_bytes(|k_bytes| self.0.remove(k_bytes))?; + Ok(()) + } + #[inline(always)] + fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> { + value.as_bytes(|value_bytes| { + k.as_bytes(|k_bytes| self.0.insert(k_bytes, value_bytes))?; + Ok(()) + }) + } + #[inline(always)] + fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> { + self.0.apply_batch(inner_batch)?; + Ok(()) + } + #[inline(always)] + fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter { + SledIter { + iter: self.0.range(range), + reversed: false, + } + } +} + +pub struct SledIter { + iter: sled::Iter, + reversed: bool, +} + +impl Debug for SledIter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SledIter") + .field("0", &"sled::Iter") + .finish() + } +} +impl Iterator for SledIter { + type Item = Result<(IVec, IVec), DynErr>; + + #[inline(always)] + fn next(&mut self) -> Option<Self::Item> { + if self.reversed { + self.iter.next_back() + } else { + self.iter.next() + } + .map(|res| res.map_err(Box::new).map_err(Into::into)) + } +} +impl ReversableIterator for SledIter { + #[inline(always)] + fn reverse(self) -> Self { + SledIter { + iter: self.iter, + reversed: !self.reversed, + } + } +} diff --git a/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs b/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ec9b43bbb8166b28c1472f8a322996805504484 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs @@ -0,0 +1,83 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Sled backend for KV Typed, + +/*use crate::*; +use sled::transaction::{ConflictableTransactionError, Transactional, TransactionalTree}; + +enum AbortType<A> { + User(A), + Kv(KvError), +} + +#[allow(missing_copy_implementations)] +pub struct SledTxCol(&'static TransactionalTree); + +impl Debug for SledTxCol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SledTxCol") + .field("0", &"TransactionalTree") + .finish() + } +} + +impl<DbReader: From<Vec<SledTxCol>>, DbWriter> TransactionalBackend<DbReader, DbWriter> for Sled { + type Err = sled::Error; + type TxCol = SledTxCol; + + fn read<A: Debug, D, F: Fn(&DbReader) -> TransactionResult<D, A, Self::Err>>( + &self, + f: F, + ) -> TransactionResult<D, A, Self::Err> { + match self + .trees + .transaction::<_, D>(|tx_trees: &Vec<TransactionalTree>| { + let reader = DbReader::from( + tx_trees + .iter() + .map(|tx_tree| SledTxCol(unsafe { to_static_ref(tx_tree) })) + .collect(), + ); + f(&reader).map_err(|e| match e { + TransactionError::Abort(a) => { + ConflictableTransactionError::Abort(AbortType::User(a)) + } + TransactionError::BackendErr(e) => ConflictableTransactionError::Storage(e), + TransactionError::KvError(e) => { + ConflictableTransactionError::Abort(AbortType::Kv(e)) + } + }) + }) { + Ok(t) => Ok(t), + Err(e) => match e { + sled::transaction::TransactionError::Abort(a) => match a { + AbortType::User(a) => Err(TransactionError::Abort(a)), + AbortType::Kv(e) => Err(TransactionError::KvError(e)), + }, + sled::transaction::TransactionError::Storage(e) => { + Err(TransactionError::BackendErr(e)) + } + }, + } + } + + fn write<A: Debug, F: Fn(&DbWriter) -> TransactionResult<(), A, Self::Err>>( + &self, + _f: F, + ) -> TransactionResult<(), A, Self::Err> { + todo!() + } +}*/ diff --git a/rust-libs/tools/kv_typed/src/batch.rs b/rust-libs/tools/kv_typed/src/batch.rs new file mode 100644 index 0000000000000000000000000000000000000000..c7d3d8c64e7e70dc603be1418a89dee5b5cbcf2c --- /dev/null +++ b/rust-libs/tools/kv_typed/src/batch.rs @@ -0,0 +1,74 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; +use std::collections::{BTreeSet, HashMap, HashSet}; + +#[derive(Debug)] +pub struct Batch<BC: BackendCol, C: DbCollectionRw> { + backend_batch: BC::Batch, + upsert_keys_bytes: BTreeSet<IVec>, + upsert_ops: HashMap<C::K, C::V>, + delete_ops: HashSet<C::K>, +} + +impl<BC: BackendCol, C: DbCollectionRw> Default for Batch<BC, C> { + fn default() -> Self { + Batch { + backend_batch: BC::Batch::default(), + upsert_keys_bytes: BTreeSet::default(), + upsert_ops: HashMap::default(), + delete_ops: HashSet::default(), + } + } +} + +impl<BC: BackendCol, C: DbCollectionRw> Batch<BC, C> { + pub fn get(&self, k: &C::K) -> Option<&C::V> { + if self.delete_ops.contains(k) { + None + } else { + self.upsert_ops.get(k) + } + } + pub fn upsert(&mut self, k: C::K, v: C::V) { + let _ = k.as_bytes(|k_bytes| { + self.upsert_keys_bytes.insert(k_bytes.into()); + v.as_bytes(|v_bytes| { + self.backend_batch.upsert(k_bytes, v_bytes); + Ok(()) + }) + }); + self.upsert_ops.insert(k, v); + } + pub fn remove(&mut self, k: C::K) { + let _ = k.as_bytes(|k_bytes| self.backend_batch.remove(k_bytes)); + self.delete_ops.insert(k); + } + #[cfg(not(feature = "subscription"))] + pub fn into_backend_batch(self) -> BC::Batch { + self.backend_batch + } + #[cfg(feature = "subscription")] + pub fn into_backend_batch_and_events(self) -> (BC::Batch, SmallVec<[C::Event; 4]>) { + let mut events: SmallVec<[C::Event; 4]> = self + .upsert_ops + .into_iter() + .map(|(k, v)| C::Event::upsert(k, v)) + .collect(); + events.extend(self.delete_ops.into_iter().map(C::Event::remove)); + (self.backend_batch, events) + } +} diff --git a/rust-libs/tools/kv_typed/src/collection_ro.rs b/rust-libs/tools/kv_typed/src/collection_ro.rs new file mode 100644 index 0000000000000000000000000000000000000000..9161e8df1065458f628f32e37588d77174a052e0 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/collection_ro.rs @@ -0,0 +1,85 @@ +use crate::*; + +pub trait DbCollectionRo: Sized { + type BackendCol: BackendCol; + type K: Key; + type V: Value; + type Event: EventTrait<K = Self::K, V = Self::V>; + + fn count(&self) -> KvResult<usize>; + fn get(&self, k: &Self::K) -> KvResult<Option<Self::V>>; + fn iter<R: 'static + RangeBounds<Self::K>>( + &self, + range: R, + ) -> KvIter<Self::BackendCol, Self::K, Self::V>; + #[cfg(feature = "subscription")] + fn subscribe(&self, subscriber_sender: Subscriber<Self::Event>) -> KvResult<()>; +} + +#[cfg(feature = "mock")] +mockall::mock! { + pub ColRo<E: EventTrait> {} + trait DbCollectionRo { + type BackendCol = MockBackendCol; + type K = E::K; + type V = E::V; + type Event = E; + + fn count(&self) -> KvResult<usize>; + fn get(&self, k: &E::K) -> KvResult<Option<E::V>>; + fn iter<R: 'static + RangeBounds<E::K>>(&self, range: R) + -> KvIter<MockBackendCol, E::K, E::V>; + #[cfg(feature = "subscription")] + fn subscribe(&self, subscriber_sender: Subscriber<E>) -> KvResult<()>; + } +} + +#[derive(Debug)] +pub struct ColRo<BC: BackendCol, E: EventTrait> { + pub(crate) inner: BC, + #[cfg(not(feature = "subscription"))] + pub(crate) phantom: PhantomData<E>, + #[cfg(feature = "subscription")] + pub(crate) subscription_sender: crate::subscription::SubscriptionsSender<E>, +} +impl<BC: BackendCol, E: EventTrait> Clone for ColRo<BC, E> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + #[cfg(not(feature = "subscription"))] + phantom: PhantomData, + #[cfg(feature = "subscription")] + subscription_sender: self.subscription_sender.clone(), + } + } +} +impl<BC: BackendCol, E: EventTrait> DbCollectionRo for ColRo<BC, E> { + type BackendCol = BC; + type K = E::K; + type V = E::V; + type Event = E; + + #[inline(always)] + fn count(&self) -> KvResult<usize> { + self.inner.count() + } + #[inline(always)] + fn get(&self, k: &Self::K) -> KvResult<Option<Self::V>> { + self.inner.get(k) + } + #[inline(always)] + fn iter<R: 'static + RangeBounds<Self::K>>( + &self, + range: R, + ) -> KvIter<Self::BackendCol, Self::K, Self::V> { + let range: RangeBytes = KvIter::<BC, Self::K, Self::V>::convert_range::<R>(range); + KvIter::new(self.inner.iter::<Self::K, Self::V>(range.clone()), range) + } + #[cfg(feature = "subscription")] + #[inline(always)] + fn subscribe(&self, subscriber_sender: Subscriber<Self::Event>) -> KvResult<()> { + self.subscription_sender + .try_send(subscriber_sender) + .map_err(|_| KvError::FailToSubscribe) + } +} diff --git a/rust-libs/tools/kv_typed/src/collection_rw.rs b/rust-libs/tools/kv_typed/src/collection_rw.rs new file mode 100644 index 0000000000000000000000000000000000000000..962e9428912f607f6a064e1da701c49b12759c0a --- /dev/null +++ b/rust-libs/tools/kv_typed/src/collection_rw.rs @@ -0,0 +1,121 @@ +use crate::*; +#[cfg(feature = "subscription")] +use parking_lot::Mutex; + +pub trait DbCollectionRw { + type K: Key; + type V: Value; + type Event: EventTrait<K = Self::K, V = Self::V>; + + fn remove(&self, k: Self::K) -> KvResult<()>; + fn upsert(&self, k: Self::K, v: Self::V) -> KvResult<()>; +} + +#[derive(Debug)] +pub struct ColRw<BC: BackendCol, E: EventTrait> { + inner: ColRo<BC, E>, + #[cfg(feature = "subscription")] + pending_events_receiver: Receiver<Events<E>>, + #[cfg(feature = "subscription")] + pending_events_sender: Sender<Events<E>>, + #[cfg(feature = "subscription")] + subscribers: Arc<Mutex<ColSubscribers<E>>>, +} + +impl<BC: BackendCol, E: EventTrait> Clone for ColRw<BC, E> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + #[cfg(feature = "subscription")] + pending_events_receiver: self.pending_events_receiver.clone(), + #[cfg(feature = "subscription")] + pending_events_sender: self.pending_events_sender.clone(), + #[cfg(feature = "subscription")] + subscribers: self.subscribers.clone(), + } + } +} + +impl<BC: BackendCol, E: EventTrait> DbCollectionRw for ColRw<BC, E> { + type K = E::K; + type V = E::V; + type Event = E; + + fn remove(&self, k: Self::K) -> KvResult<()> { + self.inner.inner.delete(&k)?; + #[cfg(feature = "subscription")] + { + let events = smallvec::smallvec![E::remove(k)]; + self.notify_subscribers(events); + } + Ok(()) + } + fn upsert(&self, k: Self::K, v: Self::V) -> KvResult<()> { + self.inner.inner.put(&k, &v)?; + #[cfg(feature = "subscription")] + { + let events = smallvec::smallvec![E::upsert(k, v)]; + self.notify_subscribers(events); + } + Ok(()) + } +} + +impl<BC: BackendCol, E: EventTrait> ColRw<BC, E> { + #[cfg(not(feature = "subscription"))] + pub fn new(col_backend: BC) -> Self { + Self { + inner: ColRo { + inner: col_backend, + phantom: PhantomData, + }, + } + } + #[cfg(feature = "subscription")] + pub fn new(col_backend: BC) -> Self { + let subscribers = ColSubscribers::<E>::default(); + let subscription_sender = subscribers.get_subscription_sender(); + let inner = ColRo { + inner: col_backend, + subscription_sender, + }; + let (pending_events_sender, pending_events_receiver) = unbounded(); + Self { + inner, + pending_events_sender, + pending_events_receiver, + subscribers: Arc::new(Mutex::new(subscribers)), + } + } + pub fn to_ro(&self) -> &ColRo<BC, E> { + &self.inner + } + #[cfg(feature = "subscription")] + fn notify_subscribers(&self, mut events: Events<E>) { + if let Some(mut subscribers_guard) = self.subscribers.try_lock() { + // Take pending events + while let Ok(pending_events) = self.pending_events_receiver.try_recv() { + events.extend(pending_events); + } + // Add new subscribers, notify all subscribers them prune died subscribers + subscribers_guard.add_new_subscribers(); + let died_subscribers = subscribers_guard.notify_subscribers(Arc::new(events)); + subscribers_guard.prune_subscribers(died_subscribers); + } else if !events.is_empty() { + // Push pending events into the queue + let _ = self.pending_events_sender.try_send(events); + } + } + #[cfg(not(feature = "subscription"))] + pub fn write_batch(&self, batch: Batch<BC, Self>) -> KvResult<()> { + self.inner.inner.write_batch(batch.into_backend_batch())?; + Ok(()) + } + #[cfg(feature = "subscription")] + pub fn write_batch(&self, batch: Batch<BC, Self>) -> KvResult<()> { + let (backend_batch, events) = batch.into_backend_batch_and_events(); + self.inner.inner.write_batch(backend_batch)?; + self.notify_subscribers(events); + Ok(()) + } +} diff --git a/rust-libs/tools/kv_typed/src/error.rs b/rust-libs/tools/kv_typed/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd9a362f531fc00fb55600d1d8d033ca0736ab45 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/error.rs @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed error type + +use crate::*; + +#[derive(Clone, Debug, Error, PartialEq)] +#[error("{0}")] +pub struct StringErr(pub String); + +pub type DynErr = Box<dyn Error + Send + Sync + 'static>; + +/// KV Typed error +pub type KvResult<T> = Result<T, KvError>; + +/// KV Typed error +#[derive(Debug, Error)] +pub enum KvError { + /// Backend error + #[error("Backend error: {0}")] + BackendError(DynErr), + // Error at serialisation or deserialisation + #[error("DeserError: {0}")] + DeserError(String), + /// FailToCreateDbFolder + #[error("FailToCreateDbFolder: {0}")] + FailToCreateDbFolder(std::io::Error), + /// FailToSubscribe + #[error("FailToSubscribe")] + FailToSubscribe, +} + +#[cfg(feature = "leveldb_backend")] +impl From<crate::backend::leveldb::LevelDbError> for KvError { + fn from(e: crate::backend::leveldb::LevelDbError) -> Self { + KvError::BackendError(Box::new(e).into()) + } +} +#[cfg(feature = "sled_backend")] +impl From<sled::Error> for KvError { + fn from(e: sled::Error) -> Self { + KvError::BackendError(Box::new(e).into()) + } +} + +pub type TransactionResult<D, A, BE> = Result<D, TransactionError<A, BE>>; +#[derive(Debug)] +pub enum TransactionError<A: Debug, BE: Error + Send + Sync + 'static> { + Abort(A), + BackendErr(BE), + KvError(KvError), +} diff --git a/rust-libs/tools/kv_typed/src/event.rs b/rust-libs/tools/kv_typed/src/event.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2faa43959ba135d951cc9296023102a510cfa37 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/event.rs @@ -0,0 +1,30 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed event + +use crate::*; + +/// Database events +pub type Events<E> = SmallVec<[E; 4]>; + +/// Event trait +pub trait EventTrait: 'static + Debug + PartialEq + Send + Sync { + type K: Key; + type V: Value; + + fn upsert(k: Self::K, v: Self::V) -> Self; + fn remove(k: Self::K) -> Self; +} diff --git a/rust-libs/tools/kv_typed/src/explorer.rs b/rust-libs/tools/kv_typed/src/explorer.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d912b836aed6aa5dc7264643ea208c1d9da6179 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/explorer.rs @@ -0,0 +1,371 @@ +use crate::*; +#[cfg(not(feature = "async"))] +use rayon::{iter::ParallelBridge, prelude::*}; +use std::num::NonZeroUsize; + +pub trait DbExplorable { + fn explore<'a>( + &self, + collection_name: &str, + action: ExplorerAction<'a>, + stringify_json_value: fn(serde_json::Value) -> serde_json::Value, + ) -> KvResult<Result<ExplorerActionResponse, StringErr>>; + fn list_collections(&self) -> Vec<(&'static str, &'static str, &'static str)>; +} + +pub trait ExplorableKey: Sized { + fn from_explorer_str(source: &str) -> Result<Self, StringErr>; + fn to_explorer_string(&self) -> KvResult<String>; +} + +impl ExplorableKey for String { + fn from_explorer_str(source: &str) -> Result<Self, StringErr> { + Ok(source.to_owned()) + } + + fn to_explorer_string(&self) -> KvResult<String> { + Ok(self.clone()) + } +} + +macro_rules! impl_explorable_key_for_numbers { + ($($T:ty),*) => {$( + impl ExplorableKey for $T { + fn from_explorer_str(source: &str) -> Result<Self, StringErr> { + source.parse().map_err(|e| StringErr(format!("{}", e))) + } + + fn to_explorer_string(&self) -> KvResult<String> { + Ok(format!("{}", self)) + } + } + )*}; +} + +impl_explorable_key_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); + +pub trait ExplorableValue: Sized { + fn from_explorer_str(source: &str) -> Result<Self, StringErr>; + fn to_explorer_json(&self) -> KvResult<serde_json::Value>; +} + +impl ExplorableValue for String { + fn from_explorer_str(source: &str) -> Result<Self, StringErr> { + Ok(source.to_owned()) + } + + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + Ok(serde_json::Value::String(self.clone())) + } +} + +macro_rules! impl_explorable_value_for_numbers { + ($($T:ty),*) => {$( + impl ExplorableValue for $T { + fn from_explorer_str(source: &str) -> Result<Self, StringErr> { + source.parse().map_err(|e| StringErr(format!("{}", e))) + } + + #[allow(trivial_numeric_casts)] + fn to_explorer_json(&self) -> KvResult<serde_json::Value> { + Ok(serde_json::Value::Number(serde_json::Number::from_f64(*self as f64).expect("too large number"))) + } + } + )*}; +} + +impl_explorable_value_for_numbers!( + usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64 +); + +#[derive(Debug)] +pub enum ExplorerAction<'a> { + Count, + Get { + key: &'a str, + }, + Find { + key_min: Option<String>, + key_max: Option<String>, + key_regex: Option<regex::Regex>, + value_regex: Option<regex::Regex>, + limit: Option<usize>, + reverse: bool, + step: NonZeroUsize, + }, + Put { + key: &'a str, + value: &'a str, + }, + Delete { + key: &'a str, + }, +} + +#[derive(Debug, PartialEq)] +pub struct EntryFound { + pub key: String, + pub value: serde_json::Value, + pub captures: Option<ValueCaptures>, +} + +#[derive(Debug, PartialEq)] +pub struct ValueCaptures(pub SmallVec<[SmallVec<[Option<String>; 8]>; 8]>); + +#[derive(Debug, PartialEq)] +pub enum ExplorerActionResponse { + Count(usize), + Get(Option<serde_json::Value>), + Find(Vec<EntryFound>), + PutOk, + DeleteOk, +} + +impl<'a> ExplorerAction<'a> { + pub fn exec<BC: BackendCol, E: EventTrait>( + self, + col: &ColRw<BC, E>, + stringify_json_value: fn(serde_json::Value) -> serde_json::Value, + ) -> KvResult<Result<ExplorerActionResponse, StringErr>> { + Ok(match self { + Self::Count => Ok(ExplorerActionResponse::Count(col.to_ro().count()?)), + Self::Get { key } => match E::K::from_explorer_str(key) { + Ok(k) => Ok(ExplorerActionResponse::Get( + col.to_ro() + .get(&k)? + .map(|v| v.to_explorer_json()) + .transpose()?, + )), + Err(e) => Err(e), + }, + Self::Find { + key_min, + key_max, + key_regex, + value_regex, + limit, + reverse, + step, + } => match define_range::<E::K>(key_min, key_max) { + Ok(range) => Ok(ExplorerActionResponse::Find(match range { + Range::Full => Self::get_range_inner( + col.to_ro(), + .., + key_regex, + value_regex, + limit, + reverse, + step, + stringify_json_value, + )?, + Range::From(range) => Self::get_range_inner( + col.to_ro(), + range, + key_regex, + value_regex, + limit, + reverse, + step, + stringify_json_value, + )?, + Range::FromTo(range) => Self::get_range_inner( + col.to_ro(), + range, + key_regex, + value_regex, + limit, + reverse, + step, + stringify_json_value, + )?, + Range::To(range) => Self::get_range_inner( + col.to_ro(), + range, + key_regex, + value_regex, + limit, + reverse, + step, + stringify_json_value, + )?, + })), + Err(e) => Err(e), + }, + Self::Put { key, value } => match E::K::from_explorer_str(key) { + Ok(k) => match E::V::from_explorer_str(value) { + Ok(v) => { + col.upsert(k, v)?; + Ok(ExplorerActionResponse::PutOk) + } + Err(e) => Err(e), + }, + Err(e) => Err(e), + }, + Self::Delete { key } => match E::K::from_explorer_str(key) { + Ok(k) => { + col.remove(k)?; + Ok(ExplorerActionResponse::DeleteOk) + } + Err(e) => Err(e), + }, + }) + } + #[allow(clippy::too_many_arguments)] + fn get_range_inner<BC: BackendCol, E: EventTrait, R: 'static + RangeBounds<E::K>>( + col: &ColRo<BC, E>, + range: R, + key_regex: Option<regex::Regex>, + value_regex: Option<regex::Regex>, + limit: Option<usize>, + reverse: bool, + step: NonZeroUsize, + stringify_json_value: fn(serde_json::Value) -> serde_json::Value, + ) -> KvResult<Vec<EntryFound>> { + let filter_map_closure = move |entry_res| { + stringify_and_filter_entry_res::<E::K, E::V>( + entry_res, + key_regex.as_ref(), + value_regex.as_ref(), + stringify_json_value, + ) + }; + + if let Some(limit) = limit { + let iter = col.iter(range); + + if reverse { + iter.reverse() + .step_by(step.get()) + .filter_map(filter_map_closure) + .take(limit) + .collect() + } else { + iter.step_by(step.get()) + .filter_map(filter_map_closure) + .take(limit) + .collect() + } + } else { + #[cfg(feature = "async")] + { + if reverse { + col.iter(range) + .reverse() + .step_by(step.get()) + .filter_map(filter_map_closure) + .collect() + } else { + col.iter(range) + .step_by(step.get()) + .filter_map(filter_map_closure) + .collect() + } + } + #[cfg(not(feature = "async"))] + { + let (send, recv) = unbounded(); + + let handler = std::thread::spawn(move || { + let iter = recv.into_iter().step_by(step.get()).par_bridge(); + + iter.filter_map(filter_map_closure).collect() + }); + + if reverse { + for entry_res in col.iter(range).reverse() { + if send.try_send(entry_res).is_err() { + return handler.join().expect("child thread panic"); + } + } + } else { + for entry_res in col.iter(range) { + if send.try_send(entry_res).is_err() { + return handler.join().expect("child thread panic"); + } + } + } + drop(send); + + handler.join().expect("child thread panic") + } + } + } +} + +enum Range<K> { + Full, + From(core::ops::RangeFrom<K>), + To(core::ops::RangeToInclusive<K>), + FromTo(core::ops::RangeInclusive<K>), +} + +fn define_range<K: Key>( + key_min_opt: Option<String>, + key_max_opt: Option<String>, +) -> Result<Range<K>, StringErr> { + if let Some(key_min) = key_min_opt { + let k_min = K::from_explorer_str(&key_min)?; + if let Some(key_max) = key_max_opt { + let k_max = K::from_explorer_str(&key_max)?; + Ok(Range::FromTo(core::ops::RangeInclusive::new(k_min, k_max))) + } else { + Ok(Range::From(core::ops::RangeFrom { start: k_min })) + } + } else if let Some(key_max) = key_max_opt { + let k_max = K::from_explorer_str(&key_max)?; + Ok(Range::To(core::ops::RangeToInclusive { end: k_max })) + } else { + Ok(Range::Full) + } +} + +fn stringify_and_filter_entry_res<K: Key, V: Value>( + entry_res: KvResult<(K, V)>, + key_regex_opt: Option<®ex::Regex>, + value_regex_opt: Option<®ex::Regex>, + stringify_json_value: fn(serde_json::Value) -> serde_json::Value, +) -> Option<KvResult<EntryFound>> { + match entry_res { + Ok((k, v)) => match k.to_explorer_string() { + Ok(key_string) => { + if let Some(key_regex) = key_regex_opt { + if !key_regex.is_match(&key_string) { + return None; + } + } + match v.to_explorer_json() { + Ok(mut value_json) => { + value_json = stringify_json_value(value_json); + let captures = if let Some(value_regex) = value_regex_opt { + let value_string = value_json.to_string(); + if !value_regex.is_match(&value_string) { + return None; + } + Some(ValueCaptures( + value_regex + .captures_iter(&value_string) + .map(|caps| { + caps.iter() + .skip(1) + .map(|m_opt| m_opt.map(|m| m.as_str().to_owned())) + .collect::<SmallVec<[Option<String>; 8]>>() + }) + .collect(), + )) + } else { + None + }; + Some(Ok(EntryFound { + key: key_string, + value: value_json, + captures, + })) + } + Err(e) => Some(Err(e)), + } + } + Err(e) => Some(Err(e)), + }, + Err(e) => Some(Err(e)), + } +} diff --git a/rust-libs/tools/kv_typed/src/from_bytes.rs b/rust-libs/tools/kv_typed/src/from_bytes.rs new file mode 100644 index 0000000000000000000000000000000000000000..41329b14a1bc159ab76c63d5ae77988489c6bcf7 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/from_bytes.rs @@ -0,0 +1,22 @@ +use crate::*; + +pub trait FromBytes: Sized { + type Err: Error; + + /// Create Self from bytes. + fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err>; +} + +macro_rules! impl_from_bytes_for_numbers { + ($($T:ty),*) => {$( + impl FromBytes for $T { + type Err = std::array::TryFromSliceError; + + fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> { + Ok(<$T>::from_be_bytes(bytes.try_into()?)) + } + } + )*}; +} + +impl_from_bytes_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); diff --git a/rust-libs/tools/kv_typed/src/iter.rs b/rust-libs/tools/kv_typed/src/iter.rs new file mode 100644 index 0000000000000000000000000000000000000000..b177bb65d422de75b3917a68b77edd9196e907c0 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/iter.rs @@ -0,0 +1,116 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed iterators + +pub mod keys; +mod range; +pub mod values; + +use crate::*; + +pub trait ReversableIterator: Iterator + Sized { + fn reverse(self) -> Self; + + #[inline(always)] + fn last(self) -> Option<Self::Item> { + self.reverse().next() + } +} + +pub trait ResultIter<T, E>: Iterator<Item = Result<T, E>> + Sized { + #[inline(always)] + fn next_res(&mut self) -> Result<Option<T>, E> { + self.next().transpose() + } +} +impl<I, T, E> ResultIter<T, E> for I where I: Iterator<Item = Result<T, E>> + Sized {} + +pub type RangeBytes = (Bound<IVec>, Bound<IVec>); + +#[derive(Debug)] +pub struct KvIter<C: BackendCol, K: Key, V: Value> { + range_iter: range::RangeIter<C>, + phantom_key: PhantomData<K>, + phantom_value: PhantomData<V>, +} + +impl<C: BackendCol, K: Key, V: Value> Iterator for KvIter<C, K, V> { + type Item = KvResult<(K, V)>; + + fn next(&mut self) -> Option<Self::Item> { + match self.range_iter.next() { + Some(Ok((key_bytes, value_bytes))) => match K::from_bytes(key_bytes.as_ref()) { + Ok(key) => match V::from_bytes(value_bytes.as_ref()) { + Ok(value) => Some(Ok((key, value))), + Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))), + }, + Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))), + }, + Some(Err(e)) => Some(Err(KvError::BackendError(e))), + None => None, + } + } +} + +impl<C: BackendCol, K: Key, V: Value> ReversableIterator for KvIter<C, K, V> { + #[inline(always)] + fn reverse(self) -> Self { + Self { + range_iter: self.range_iter.reverse(), + phantom_key: PhantomData, + phantom_value: PhantomData, + } + } +} + +impl<C: BackendCol, K: Key, V: Value> KvIter<C, K, V> { + pub fn keys(self) -> KvIterKeys<C, K> { + KvIterKeys::new(self.range_iter) + } + pub fn values(self) -> KvIterValues<C, K, V> { + KvIterValues::new(self.range_iter) + } + pub(crate) fn convert_range<RK: RangeBounds<K>>(range: RK) -> RangeBytes { + let range_start = convert_bound(range.start_bound()); + let range_end = convert_bound(range.end_bound()); + (range_start, range_end) + } + #[cfg(feature = "mock")] + pub fn new(backend_iter: C::Iter, range: RangeBytes) -> Self { + Self { + range_iter: range::RangeIter::new(backend_iter, range.0, range.1), + phantom_key: PhantomData, + phantom_value: PhantomData, + } + } + #[cfg(not(feature = "mock"))] + pub(crate) fn new(backend_iter: C::Iter, range: RangeBytes) -> Self { + Self { + range_iter: range::RangeIter::new(backend_iter, range.0, range.1), + phantom_key: PhantomData, + phantom_value: PhantomData, + } + } +} + +#[inline(always)] +fn convert_bound<K: Key>(bound_key: Bound<&K>) -> Bound<IVec> { + match bound_key { + Bound::Included(key) => Bound::Included(key.as_bytes(|key_bytes| key_bytes.into())), + Bound::Excluded(key) => Bound::Excluded(key.as_bytes(|key_bytes| key_bytes.into())), + Bound::Unbounded => Bound::Unbounded, + } +} diff --git a/rust-libs/tools/kv_typed/src/iter/keys.rs b/rust-libs/tools/kv_typed/src/iter/keys.rs new file mode 100644 index 0000000000000000000000000000000000000000..bbe9a0d74e5f6231ff022471f9d0e2d30f9b3564 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/iter/keys.rs @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed iterators + +use crate::*; + +#[derive(Debug)] +pub struct KvIterKeys<C: BackendCol, K: Key> { + range_iter: super::range::RangeIter<C>, + phantom_key: PhantomData<K>, +} + +impl<C: BackendCol, K: Key> Iterator for KvIterKeys<C, K> { + type Item = KvResult<K>; + + fn next(&mut self) -> Option<Self::Item> { + match self.range_iter.next() { + Some(Ok((key_bytes, _value_bytes))) => match K::from_bytes(key_bytes.as_ref()) { + Ok(key) => Some(Ok(key)), + Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))), + }, + Some(Err(e)) => Some(Err(KvError::BackendError(e))), + None => None, + } + } +} + +impl<C: BackendCol, K: Key> ReversableIterator for KvIterKeys<C, K> { + #[inline(always)] + fn reverse(self) -> Self { + Self { + range_iter: self.range_iter.reverse(), + phantom_key: PhantomData, + } + } +} + +impl<C: BackendCol, K: Key> KvIterKeys<C, K> { + pub(super) fn new(range_iter: super::range::RangeIter<C>) -> Self { + Self { + range_iter, + phantom_key: PhantomData, + } + } +} diff --git a/rust-libs/tools/kv_typed/src/iter/range.rs b/rust-libs/tools/kv_typed/src/iter/range.rs new file mode 100644 index 0000000000000000000000000000000000000000..58afce651111c2bb4ffdc0e6782cd1db0df90c53 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/iter/range.rs @@ -0,0 +1,110 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed iterators + +use crate::*; + +// V2 +pub(super) struct RangeIter<C: BackendCol> { + backend_iter: C::Iter, + reversed: bool, + range_start: Bound<IVec>, + range_end: Bound<IVec>, +} + +impl<C: BackendCol> Debug for RangeIter<C> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LevelDbCol") + .field("backend_iter", &"BackendIter") + .field("reversed", &format!("{:?}", self.reversed)) + .field("range_start", &format!("{:?}", self.range_start)) + .field("range_end", &format!("{:?}", self.range_end)) + .finish() + } +} + +impl<C: BackendCol> RangeIter<C> { + #[inline(always)] + pub(crate) fn new( + backend_iter: C::Iter, + range_start: Bound<IVec>, + range_end: Bound<IVec>, + ) -> Self { + RangeIter { + backend_iter, + reversed: false, + range_start, + range_end, + } + } +} + +impl<C: BackendCol> Iterator for RangeIter<C> { + type Item = <C::Iter as Iterator>::Item; + + fn next(&mut self) -> Option<Self::Item> { + loop { + match self.backend_iter.next() { + Some(Ok((key_bytes, value_bytes))) => { + let start_bound_ok = match &self.range_start { + Bound::Included(start_bytes) => key_bytes.as_ref() >= start_bytes.as_ref(), + Bound::Excluded(start_bytes) => key_bytes.as_ref() > start_bytes.as_ref(), + Bound::Unbounded => true, + }; + let end_bound_ok = match &self.range_end { + Bound::Included(end_bytes) => key_bytes.as_ref() <= end_bytes.as_ref(), + Bound::Excluded(end_bytes) => key_bytes.as_ref() < end_bytes.as_ref(), + Bound::Unbounded => true, + }; + if start_bound_ok { + if end_bound_ok { + break Some(Ok((key_bytes, value_bytes))); + } else if self.reversed { + // The interval has not yet begun. + continue; + } else { + // The range has been fully traversed, the iterator is finished. + break None; + } + } else if end_bound_ok { + if self.reversed { + // The range has been fully traversed, the iterator is finished. + break None; + } else { + // The interval has not yet begun. + continue; + } + } else { + // Empty range, the iterator is finished. + break None; + } + } + other => break other, + } + } + } +} +impl<C: BackendCol> ReversableIterator for RangeIter<C> { + #[inline(always)] + fn reverse(self) -> Self { + RangeIter { + backend_iter: self.backend_iter.reverse(), + reversed: !self.reversed, + range_start: self.range_start, + range_end: self.range_end, + } + } +} diff --git a/rust-libs/tools/kv_typed/src/iter/values.rs b/rust-libs/tools/kv_typed/src/iter/values.rs new file mode 100644 index 0000000000000000000000000000000000000000..d75986e46ddb7b3a492ac3f80451108a76672d65 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/iter/values.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed iterators + +use crate::*; + +#[derive(Debug)] +pub struct KvIterValues<C: BackendCol, K: Key, V: Value> { + range_iter: super::range::RangeIter<C>, + phantom_key: PhantomData<K>, + phantom_value: PhantomData<V>, +} + +impl<C: BackendCol, K: Key, V: Value> Iterator for KvIterValues<C, K, V> { + type Item = KvResult<V>; + + fn next(&mut self) -> Option<Self::Item> { + match self.range_iter.next() { + Some(Ok((_key_bytes, value_bytes))) => match V::from_bytes(value_bytes.as_ref()) { + Ok(value) => Some(Ok(value)), + Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))), + }, + Some(Err(e)) => Some(Err(KvError::BackendError(e))), + None => None, + } + } +} + +impl<C: BackendCol, K: Key, V: Value> ReversableIterator for KvIterValues<C, K, V> { + #[inline(always)] + fn reverse(self) -> Self { + Self { + range_iter: self.range_iter.reverse(), + phantom_key: PhantomData, + phantom_value: PhantomData, + } + } +} + +impl<C: BackendCol, K: Key, V: Value> KvIterValues<C, K, V> { + pub(super) fn new(range_iter: super::range::RangeIter<C>) -> Self { + Self { + range_iter, + phantom_key: PhantomData, + phantom_value: PhantomData, + } + } +} diff --git a/rust-libs/tools/kv_typed/src/key.rs b/rust-libs/tools/kv_typed/src/key.rs new file mode 100644 index 0000000000000000000000000000000000000000..7d820dfa7b4453454f5a0642e0e76bd8dd96d929 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/key.rs @@ -0,0 +1,62 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed Key trait + +use crate::*; + +/// Trait to be implemented by the collection key + +#[cfg(not(feature = "explorer"))] +pub trait Key: + 'static + KeyAsBytes + Debug + Eq + FromBytes + std::hash::Hash + Send + Sync + Sized +{ +} + +#[cfg(feature = "explorer")] +pub trait Key: + 'static + + KeyAsBytes + + Debug + + Eq + + ExplorableKey + + FromBytes + + std::hash::Hash + + Send + + Sync + + Sized +{ +} + +#[cfg(not(feature = "explorer"))] +impl<T> Key for T where + T: 'static + KeyAsBytes + Debug + Eq + FromBytes + std::hash::Hash + Send + Sync + Sized +{ +} + +#[cfg(feature = "explorer")] +impl<T> Key for T where + T: 'static + + KeyAsBytes + + Debug + + Eq + + ExplorableKey + + FromBytes + + std::hash::Hash + + Send + + Sync + + Sized +{ +} diff --git a/rust-libs/tools/kv_typed/src/lib.rs b/rust-libs/tools/kv_typed/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..2920e4c00d9cd4dfc221e91728f2b7d09330aa1f --- /dev/null +++ b/rust-libs/tools/kv_typed/src/lib.rs @@ -0,0 +1,111 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Strongly typed key-value storage + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + missing_debug_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces, + unused_qualifications +)] + +mod as_bytes; +pub mod backend; +mod batch; +mod collection_ro; +mod collection_rw; +mod error; +mod event; +#[cfg(feature = "explorer")] +pub mod explorer; +mod from_bytes; +mod iter; +mod key; +#[cfg(feature = "subscription")] +mod subscription; +mod utils; +mod value; + +// Re-export dependencies +#[cfg(feature = "async")] +pub use async_channel as channel; +#[cfg(all(not(feature = "async"), feature = "sync"))] +pub use crossbeam_channel as channel; +#[cfg(feature = "explorer")] +pub use regex; + +/// Kv Typed prelude +pub mod prelude { + pub use crate::as_bytes::{KeyAsBytes, ValueAsBytes}; + #[cfg(feature = "leveldb_backend")] + pub use crate::backend::leveldb::{LevelDb, LevelDbConf}; + #[cfg(feature = "memory_backend")] + pub use crate::backend::memory::{Mem, MemConf}; + #[cfg(feature = "mock")] + pub use crate::backend::mock::{MockBackend, MockBackendCol, MockBackendIter}; + #[cfg(feature = "sled_backend")] + pub use crate::backend::sled::{Config as SledConf, Sled}; + pub use crate::backend::{Backend, BackendCol, TransactionalBackend}; + pub use crate::batch::Batch; + #[cfg(feature = "mock")] + pub use crate::collection_ro::MockColRo; + pub use crate::collection_ro::{ColRo, DbCollectionRo}; + pub use crate::collection_rw::{ColRw, DbCollectionRw}; + pub use crate::error::{ + DynErr, KvError, KvResult, StringErr, TransactionError, TransactionResult, + }; + pub use crate::event::{EventTrait, Events}; + #[cfg(feature = "explorer")] + pub use crate::explorer::{ExplorableKey, ExplorableValue}; + pub use crate::from_bytes::FromBytes; + pub use crate::iter::{ + keys::KvIterKeys, values::KvIterValues, KvIter, ResultIter, ReversableIterator, + }; + pub use crate::key::Key; + #[cfg(feature = "subscription")] + pub use crate::subscription::{NewSubscribers, Subscriber, Subscribers}; + pub use crate::value::Value; + pub use kv_typed_code_gen::db_schema; +} + +// Internal crate imports +pub(crate) use crate::backend::BackendBatch; +#[cfg(feature = "explorer")] +pub(crate) use crate::explorer::{ExplorableKey, ExplorableValue}; +pub(crate) use crate::iter::RangeBytes; +pub(crate) use crate::prelude::*; +#[cfg(feature = "subscription")] +pub(crate) use crate::subscription::ColSubscribers; +pub(crate) use crate::utils::arc::Arc; +pub(crate) use crate::utils::ivec::IVec; +#[cfg(feature = "async")] +use async_channel::{unbounded, Receiver, Sender, TrySendError}; +#[cfg(all(not(feature = "async"), feature = "sync"))] +#[allow(unused_imports)] +use crossbeam_channel::{unbounded, Receiver, Sender, TrySendError}; +pub(crate) use smallvec::SmallVec; +pub(crate) use std::{ + convert::TryInto, + error::Error, + fmt::Debug, + marker::PhantomData, + ops::{Bound, RangeBounds}, +}; +pub(crate) use thiserror::Error; diff --git a/rust-libs/tools/kv_typed/src/subscription.rs b/rust-libs/tools/kv_typed/src/subscription.rs new file mode 100644 index 0000000000000000000000000000000000000000..3053328907df3907704235fa313d10b847aeb641 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/subscription.rs @@ -0,0 +1,108 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed subscription + +use crate::*; + +/// Subscriber +pub type Subscriber<E> = Sender<Arc<Events<E>>>; +/// Subscriptions sender +pub(crate) type SubscriptionsSender<E> = Sender<Subscriber<E>>; +/// Subscribers +pub type Subscribers<E> = std::collections::BTreeMap<usize, Subscriber<E>>; +/// New subscribers +pub type NewSubscribers<E> = SmallVec<[Subscriber<E>; 4]>; + +#[derive(Debug)] +#[doc(hidden)] +pub struct ColSubscribers<E: EventTrait> { + subscription_sender: SubscriptionsSender<E>, + subscription_receiver: Receiver<Subscriber<E>>, + subscribers: Subscribers<E>, + subscriber_index: usize, +} + +impl<E: EventTrait> Default for ColSubscribers<E> { + fn default() -> Self { + let (subscription_sender, subscription_receiver) = unbounded(); + ColSubscribers { + subscription_sender, + subscription_receiver, + subscribers: std::collections::BTreeMap::new(), + subscriber_index: 0, + } + } +} + +impl<E: EventTrait> ColSubscribers<E> { + pub(crate) fn get_subscription_sender(&self) -> Sender<Subscriber<E>> { + self.subscription_sender.clone() + } + #[inline(always)] + pub(crate) fn get_new_subscribers(&self) -> NewSubscribers<E> { + if !self.subscription_receiver.is_empty() { + let mut new_subscribers = SmallVec::new(); + while let Ok(subscriber) = self.subscription_receiver.try_recv() { + new_subscribers.push(subscriber) + } + new_subscribers + } else { + SmallVec::new() + } + } + pub(crate) fn notify_subscribers(&self, events: Arc<Events<E>>) -> Vec<usize> { + let mut died_subscribers = Vec::with_capacity(self.subscribers.len()); + let mut unsend_events_opt = None; + for (id, subscriber) in &self.subscribers { + if let Err(e) = subscriber.try_send( + unsend_events_opt + .take() + .unwrap_or_else(|| Arc::clone(&events)), + ) { + match e { + #[cfg(feature = "async")] + TrySendError::Closed(events_) => { + unsend_events_opt = Some(events_); + died_subscribers.push(*id); + } + #[cfg(not(feature = "async"))] + TrySendError::Disconnected(events_) => { + unsend_events_opt = Some(events_); + died_subscribers.push(*id); + } + TrySendError::Full(events_) => { + unsend_events_opt = Some(events_); + } + } + } + } + died_subscribers + } + #[inline(always)] + pub(crate) fn add_new_subscribers(&mut self) { + for new_subscriber in self.get_new_subscribers() { + self.subscribers + .insert(self.subscriber_index, new_subscriber); + self.subscriber_index += 1; + } + } + #[inline(always)] + pub(crate) fn prune_subscribers(&mut self, died_subscribers: Vec<usize>) { + for died_subscriber in died_subscribers { + self.subscribers.remove(&died_subscriber); + } + } +} diff --git a/rust-libs/tools/kv_typed/src/utils.rs b/rust-libs/tools/kv_typed/src/utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..68bd74c49c3a93239ea3bb150c947044037b820e --- /dev/null +++ b/rust-libs/tools/kv_typed/src/utils.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed utils + +pub mod arc; +#[cfg(not(feature = "sled_backend"))] +pub mod ivec; +#[cfg(feature = "sled_backend")] +pub mod ivec { + pub use sled::IVec; +} diff --git a/rust-libs/tools/kv_typed/src/utils/arc.rs b/rust-libs/tools/kv_typed/src/utils/arc.rs new file mode 100644 index 0000000000000000000000000000000000000000..6eef0caad58526107af45b9e14d24632496d3a15 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/utils/arc.rs @@ -0,0 +1,267 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV typed Arc + +#![allow(clippy::unwrap_used, dead_code, unsafe_code)] +/// We use this because we never use the weak count on the std +/// `Arc`, but we use a LOT of `Arc`'s, so the extra 8 bytes +/// turn into a huge overhead. +use std::{ + alloc::{alloc, dealloc, Layout}, + convert::TryFrom, + fmt::{self, Debug}, + mem, + ops::Deref, + ptr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +// we make this repr(C) because we do a raw +// write to the beginning where we expect +// the rc to be. +#[repr(C)] +struct ArcInner<T: ?Sized> { + rc: AtomicUsize, + inner: T, +} + +pub struct Arc<T: ?Sized> { + ptr: *mut ArcInner<T>, +} + +unsafe impl<T: Send + Sync + ?Sized> Send for Arc<T> {} +unsafe impl<T: Send + Sync + ?Sized> Sync for Arc<T> {} + +impl<T: Debug + ?Sized> Debug for Arc<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Debug::fmt(&**self, f) + } +} + +impl<T> Arc<T> { + pub fn new(inner: T) -> Arc<T> { + let bx = Box::new(ArcInner { + inner, + rc: AtomicUsize::new(1), + }); + let ptr = Box::into_raw(bx); + Arc { ptr } + } + + // See std::sync::arc::Arc::copy_from_slice, + // "Unsafe because the caller must either take ownership or bind `T: Copy`" + unsafe fn copy_from_slice(s: &[T]) -> Arc<[T]> { + let align = std::cmp::max(mem::align_of::<T>(), mem::align_of::<AtomicUsize>()); + + let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>()); + let data_width = mem::size_of::<T>().checked_mul(s.len()).unwrap(); + + let size_unpadded = rc_width.checked_add(data_width).unwrap(); + // Pad size out to alignment + let size_padded = (size_unpadded + align - 1) & !(align - 1); + + let layout = Layout::from_size_align(size_padded, align).unwrap(); + + let ptr = alloc(layout); + + assert!(!ptr.is_null(), "failed to allocate Arc"); + #[allow(clippy::cast_ptr_alignment)] + ptr::write(ptr as _, AtomicUsize::new(1)); + + let data_ptr = ptr.add(rc_width); + ptr::copy_nonoverlapping(s.as_ptr(), data_ptr as _, s.len()); + + let fat_ptr: *const ArcInner<[T]> = Arc::fatten(ptr, s.len()); + + Arc { + ptr: fat_ptr as *mut _, + } + } + + /// <https://users.rust-lang.org/t/construct-fat-pointer-to-struct/29198/9> + #[allow(trivial_casts)] + fn fatten(data: *const u8, len: usize) -> *const ArcInner<[T]> { + // Requirements of slice::from_raw_parts. + assert!(!data.is_null()); + assert!(isize::try_from(len).is_ok()); + + let slice = unsafe { core::slice::from_raw_parts(data as *const (), len) }; + slice as *const [()] as *const _ + } + + pub fn into_raw(arc: Arc<T>) -> *const T { + let ptr = unsafe { &(*arc.ptr).inner }; + #[allow(clippy::mem_forget)] + mem::forget(arc); + ptr + } + + pub unsafe fn from_raw(ptr: *const T) -> Arc<T> { + let align = std::cmp::max(mem::align_of::<T>(), mem::align_of::<AtomicUsize>()); + + let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>()); + + let sub_ptr = (ptr as *const u8).sub(rc_width) as *mut ArcInner<T>; + + Arc { ptr: sub_ptr } + } +} + +impl<T: ?Sized> Arc<T> { + pub fn strong_count(arc: &Arc<T>) -> usize { + unsafe { (*arc.ptr).rc.load(Ordering::Acquire) } + } + + pub fn get_mut(arc: &mut Arc<T>) -> Option<&mut T> { + if Arc::strong_count(arc) == 1 { + Some(unsafe { &mut arc.ptr.as_mut().unwrap().inner }) + } else { + None + } + } +} + +impl<T: ?Sized + Clone> Arc<T> { + pub fn make_mut(arc: &mut Arc<T>) -> &mut T { + if Arc::strong_count(arc) != 1 { + *arc = Arc::new((**arc).clone()); + assert_eq!(Arc::strong_count(arc), 1); + } + Arc::get_mut(arc).unwrap() + } +} + +impl<T: Default> Default for Arc<T> { + fn default() -> Arc<T> { + Arc::new(T::default()) + } +} + +impl<T: ?Sized> Clone for Arc<T> { + fn clone(&self) -> Arc<T> { + // safe to use Relaxed ordering below because + // of the required synchronization for passing + // any objects to another thread. + let last_count = unsafe { (*self.ptr).rc.fetch_add(1, Ordering::Relaxed) }; + + if last_count == usize::max_value() { + #[cold] + std::process::abort(); + } + + Arc { ptr: self.ptr } + } +} + +impl<T: ?Sized> Drop for Arc<T> { + fn drop(&mut self) { + unsafe { + let rc = (*self.ptr).rc.fetch_sub(1, Ordering::Release) - 1; + if rc == 0 { + std::sync::atomic::fence(Ordering::Acquire); + Box::from_raw(self.ptr); + } + } + } +} + +impl<T: Copy> From<&[T]> for Arc<[T]> { + #[inline] + fn from(s: &[T]) -> Arc<[T]> { + unsafe { Arc::copy_from_slice(s) } + } +} + +#[allow(clippy::fallible_impl_from)] +impl<T> From<Box<[T]>> for Arc<[T]> { + #[inline] + fn from(b: Box<[T]>) -> Arc<[T]> { + let len = b.len(); + unsafe { + let src = Box::into_raw(b); + let value_layout = Layout::for_value(&*src); + let align = std::cmp::max(value_layout.align(), mem::align_of::<AtomicUsize>()); + let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>()); + let unpadded_size = rc_width.checked_add(value_layout.size()).unwrap(); + // pad the total `Arc` allocation size to the alignment of + // `max(value, AtomicUsize)` + let size = (unpadded_size + align - 1) & !(align - 1); + let dst_layout = Layout::from_size_align(size, align).unwrap(); + let dst = alloc(dst_layout); + assert!(!dst.is_null(), "failed to allocate Arc"); + + #[allow(clippy::cast_ptr_alignment)] + ptr::write(dst as _, AtomicUsize::new(1)); + let data_ptr = dst.add(rc_width); + ptr::copy_nonoverlapping(src as *const u8, data_ptr, value_layout.size()); + + // free the old box memory without running Drop + if value_layout.size() != 0 { + dealloc(src as *mut u8, value_layout); + } + + let fat_ptr: *const ArcInner<[T]> = Arc::fatten(dst, len); + + Arc { + ptr: fat_ptr as *mut _, + } + } + } +} + +#[test] +fn boxed_slice_to_arc_slice() { + let box1: Box<[u8]> = Box::new([1, 2, 3]); + let arc1: Arc<[u8]> = box1.into(); + assert_eq!(&*arc1, &*vec![1, 2, 3]); + let box2: Box<[u64]> = Box::new([1, 2, 3]); + let arc2: Arc<[u64]> = box2.into(); + assert_eq!(&*arc2, &*vec![1, 2, 3]); +} + +impl<T> From<Vec<T>> for Arc<[T]> { + #[inline] + fn from(mut v: Vec<T>) -> Arc<[T]> { + unsafe { + let arc = Arc::copy_from_slice(&v); + + // Allow the Vec to free its memory, but not destroy its contents + v.set_len(0); + + arc + } + } +} + +impl<T: ?Sized> Deref for Arc<T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &(*self.ptr).inner } + } +} + +impl<T: ?Sized> std::borrow::Borrow<T> for Arc<T> { + fn borrow(&self) -> &T { + &**self + } +} + +impl<T: ?Sized> AsRef<T> for Arc<T> { + fn as_ref(&self) -> &T { + &**self + } +} diff --git a/rust-libs/tools/kv_typed/src/utils/ivec.rs b/rust-libs/tools/kv_typed/src/utils/ivec.rs new file mode 100644 index 0000000000000000000000000000000000000000..50a8d7528ea75e7f59b839a7326f7eff7a2ba104 --- /dev/null +++ b/rust-libs/tools/kv_typed/src/utils/ivec.rs @@ -0,0 +1,314 @@ +use std::{ + convert::TryFrom, + fmt, + hash::{Hash, Hasher}, + ops::{Deref, DerefMut}, + sync::Arc, +}; + +const CUTOFF: usize = 22; + +type Inner = [u8; CUTOFF]; + +/// A buffer that may either be inline or remote and protected +/// by an Arc +#[derive(Clone)] +pub struct IVec(IVecInner); + +impl Default for IVec { + fn default() -> Self { + Self::from(&[]) + } +} + +impl Hash for IVec { + fn hash<H: Hasher>(&self, state: &mut H) { + self.deref().hash(state); + } +} + +#[derive(Clone)] +enum IVecInner { + Inline(u8, Inner), + Remote(Arc<[u8]>), + Subslice { + base: Arc<[u8]>, + offset: usize, + len: usize, + }, +} + +const fn is_inline_candidate(length: usize) -> bool { + length <= CUTOFF +} + +impl IVec { + pub fn subslice(&self, slice_offset: usize, len: usize) -> Self { + assert!(self.len().checked_sub(slice_offset).expect("") >= len); + + let inner = match self.0 { + IVecInner::Remote(ref base) => IVecInner::Subslice { + base: base.clone(), + offset: slice_offset, + len, + }, + IVecInner::Inline(_, old_inner) => { + // old length already checked above in assertion + let mut new_inner = Inner::default(); + new_inner[..len].copy_from_slice(&old_inner[slice_offset..slice_offset + len]); + + IVecInner::Inline(u8::try_from(len).expect(""), new_inner) + } + IVecInner::Subslice { + ref base, + ref offset, + .. + } => IVecInner::Subslice { + base: base.clone(), + offset: offset + slice_offset, + len, + }, + }; + + IVec(inner) + } + + fn inline(slice: &[u8]) -> Self { + assert!(is_inline_candidate(slice.len())); + + let mut data = Inner::default(); + + #[allow(unsafe_code)] + unsafe { + std::ptr::copy_nonoverlapping(slice.as_ptr(), data.as_mut_ptr(), slice.len()); + } + + Self(IVecInner::Inline( + u8::try_from(slice.len()).expect(""), + data, + )) + } + + fn remote(arc: Arc<[u8]>) -> Self { + Self(IVecInner::Remote(arc)) + } + + fn make_mut(&mut self) { + match self.0 { + IVecInner::Remote(ref mut buf) if Arc::strong_count(buf) != 1 => { + self.0 = IVecInner::Remote(buf.to_vec().into()); + } + IVecInner::Subslice { + ref mut base, + offset, + len, + } if Arc::strong_count(base) != 1 => { + self.0 = IVecInner::Remote(base[offset..offset + len].to_vec().into()); + } + _ => {} + } + } +} + +impl From<Box<[u8]>> for IVec { + fn from(b: Box<[u8]>) -> Self { + if is_inline_candidate(b.len()) { + Self::inline(&b) + } else { + Self::remote(Arc::from(b)) + } + } +} + +impl From<&[u8]> for IVec { + fn from(slice: &[u8]) -> Self { + if is_inline_candidate(slice.len()) { + Self::inline(slice) + } else { + Self::remote(Arc::from(slice)) + } + } +} + +impl From<Arc<[u8]>> for IVec { + fn from(arc: Arc<[u8]>) -> Self { + if is_inline_candidate(arc.len()) { + Self::inline(&arc) + } else { + Self::remote(arc) + } + } +} + +impl From<&IVec> for IVec { + fn from(v: &Self) -> Self { + v.clone() + } +} + +impl std::borrow::Borrow<[u8]> for IVec { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +impl std::borrow::Borrow<[u8]> for &IVec { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +macro_rules! from_array { + ($($s:expr),*) => { + $( + impl From<&[u8; $s]> for IVec { + fn from(v: &[u8; $s]) -> Self { + Self::from(&v[..]) + } + } + )* + } +} + +from_array!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32 +); + +impl Into<Arc<[u8]>> for IVec { + fn into(self) -> Arc<[u8]> { + match self.0 { + IVecInner::Inline(..) => Arc::from(self.as_ref()), + IVecInner::Remote(arc) => arc, + IVecInner::Subslice { .. } => self.deref().into(), + } + } +} + +impl Deref for IVec { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + self.as_ref() + } +} + +impl AsRef<[u8]> for IVec { + #[inline] + #[allow(unsafe_code)] + fn as_ref(&self) -> &[u8] { + match &self.0 { + IVecInner::Inline(sz, buf) => unsafe { buf.get_unchecked(..*sz as usize) }, + IVecInner::Remote(buf) => buf, + IVecInner::Subslice { + ref base, + offset, + len, + } => &base[*offset..*offset + *len], + } + } +} + +impl DerefMut for IVec { + #[inline] + fn deref_mut(&mut self) -> &mut [u8] { + self.as_mut() + } +} + +impl AsMut<[u8]> for IVec { + #[inline] + #[allow(unsafe_code)] + fn as_mut(&mut self) -> &mut [u8] { + self.make_mut(); + + match &mut self.0 { + IVecInner::Inline(ref sz, ref mut buf) => unsafe { + std::slice::from_raw_parts_mut(buf.as_mut_ptr(), *sz as usize) + }, + IVecInner::Remote(ref mut buf) => Arc::get_mut(buf).expect(""), + IVecInner::Subslice { + ref mut base, + offset, + len, + } => &mut Arc::get_mut(base).expect("")[*offset..*offset + *len], + } + } +} + +impl Ord for IVec { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_ref().cmp(other.as_ref()) + } +} + +impl PartialOrd for IVec { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl<T: AsRef<[u8]>> PartialEq<T> for IVec { + fn eq(&self, other: &T) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl PartialEq<[u8]> for IVec { + fn eq(&self, other: &[u8]) -> bool { + self.as_ref() == other + } +} + +impl Eq for IVec {} + +impl fmt::Debug for IVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ivec_usage() { + let iv2 = IVec::from(&[4; 128][..]); + assert_eq!(iv2, vec![4; 128]); + } + + #[test] + fn boxed_slice_conversion() { + let boite1: Box<[u8]> = Box::new([1, 2, 3]); + let iv1: IVec = boite1.into(); + assert_eq!(iv1, vec![1, 2, 3]); + let boite2: Box<[u8]> = Box::new([4; 128]); + let iv2: IVec = boite2.into(); + assert_eq!(iv2, vec![4; 128]); + } + + #[test] + #[should_panic] + fn subslice_usage_00() { + let iv1 = IVec::from(&[1, 2, 3][..]); + let _subslice = iv1.subslice(0, 4); + } + + #[test] + #[should_panic] + fn subslice_usage_01() { + let iv1 = IVec::from(&[1, 2, 3][..]); + let _subslice = iv1.subslice(3, 1); + } + + #[test] + fn ivec_as_mut_identity() { + let initial = &[1]; + let mut iv = IVec::from(initial); + assert_eq!(&*initial, &*iv); + assert_eq!(&*initial, &mut *iv); + assert_eq!(&*initial, iv.as_mut()); + } +} diff --git a/rust-libs/tools/kv_typed/src/value.rs b/rust-libs/tools/kv_typed/src/value.rs new file mode 100644 index 0000000000000000000000000000000000000000..2681c1457ac3100cb96231dc6f2e524e5c67bd8e --- /dev/null +++ b/rust-libs/tools/kv_typed/src/value.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! KV Typed Value trait + +use crate::*; + +/// Trait to be implemented by the collection value +#[cfg(not(feature = "explorer"))] +pub trait Value: + 'static + ValueAsBytes + Debug + FromBytes + PartialEq + Send + Sync + Sized +{ +} + +#[cfg(feature = "explorer")] +pub trait Value: + 'static + ValueAsBytes + Debug + ExplorableValue + FromBytes + PartialEq + Send + Sync + Sized +{ +} + +#[cfg(not(feature = "explorer"))] +impl<T> Value for T where + T: 'static + ValueAsBytes + Debug + FromBytes + PartialEq + Send + Sync + Sized +{ +} + +#[cfg(feature = "explorer")] +impl<T> Value for T where + T: 'static + + ValueAsBytes + + Debug + + ExplorableValue + + FromBytes + + PartialEq + + Send + + Sync + + Sized +{ +} + +impl FromBytes for String { + type Err = std::str::Utf8Error; + + fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> { + Ok(std::str::from_utf8(bytes)?.to_owned()) + } +} diff --git a/rust-libs/tools/kv_typed/tests/db_schema.rs b/rust-libs/tools/kv_typed/tests/db_schema.rs new file mode 100644 index 0000000000000000000000000000000000000000..c3594138496e66f37582cfcd5bbe3b649aa39e96 --- /dev/null +++ b/rust-libs/tools/kv_typed/tests/db_schema.rs @@ -0,0 +1,73 @@ +#[cfg(feature = "memory_backend")] +mod tests { + use kv_typed::prelude::*; + use std::fmt::Debug; + + db_schema!( + TestV1, + [ + ["c1", col_1, i32, String], + ["c2", col_2, usize, i128], + ["c2", col_3, u64, u128], + ] + ); + + #[maybe_async::test(not(feature = "async"), async(feature = "async", async_std::test))] + async fn test_db_schema() -> KvResult<()> { + let db = TestV1Db::<Mem>::open(MemConf::default())?; + + #[cfg(feature = "subscription")] + let (sender, recv) = kv_typed::channel::unbounded(); + #[cfg(feature = "subscription")] + db.col_1().subscribe(sender)?; + + let db2 = db.clone(); + + let handler = std::thread::spawn(move || db2.col_1_write().upsert(3, "toto".to_owned())); + handler.join().expect("thread panic")?; + + #[cfg(feature = "subscription")] + { + let expected_events: Events<Col1Event> = smallvec::smallvec![Col1Event::Upsert { + key: 3, + value: "toto".to_owned(), + }]; + #[allow(unused_parens)] + if let Ok(msg) = recv.recv().await { + assert_eq!(msg.as_ref(), &expected_events,) + } else { + panic!("must be receive event") + } + } + + assert_eq!(db.col_1().get(&3)?, Some("toto".to_owned()),); + + assert_eq!(db.col_2().get(&3)?, None,); + + db.col_1_write().upsert(5, "tutu".to_owned())?; + + { + let mut iter = db.col_1().iter(..); + + assert_eq!(iter.next_res()?, Some((3, "toto".to_owned()))); + assert_eq!(iter.next_res()?, Some((5, "tutu".to_owned()))); + assert_eq!(iter.next_res()?, None); + + let mut iter = db.col_1().iter(..).values().reverse(); + + assert_eq!(iter.next_res()?, Some("tutu".to_owned())); + assert_eq!(iter.next_res()?, Some("toto".to_owned())); + assert_eq!(iter.next_res()?, None); + } + + db.col_1_write().upsert(7, "titi".to_owned())?; + + let mut iter = db.col_1().iter(..).values().reverse().step_by(2); + + assert_eq!(iter.next_res()?, Some("titi".to_owned())); + assert_eq!(iter.next_res()?, Some("toto".to_owned())); + assert_eq!(iter.next_res()?, None); + + Ok(()) + } +} diff --git a/rust-libs/tools/kv_typed/tests/test_mock.rs b/rust-libs/tools/kv_typed/tests/test_mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..8f68164a295009d6fc9c5f0e2ce2dbfa7cdad678 --- /dev/null +++ b/rust-libs/tools/kv_typed/tests/test_mock.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "mock")] +mod tests { + + use kv_typed::prelude::*; + use mockall::predicate::*; + use std::fmt::Debug; + use std::ops::{Bound, RangeFull}; + + db_schema!( + Test, + [["c1", col_1, u32, String], ["c2", col_2, String, u64],] + ); + + fn use_readable_db<DB: TestDbReadable>(db: &DB) -> KvResult<()> { + let col1_reader = db.col_1(); + assert_eq!(col1_reader.count()?, 899); + assert_eq!(col1_reader.get(&42)?, Some("toto".to_owned())); + let mut iter = col1_reader.iter(..); + assert_eq!(iter.next_res()?, Some((42, "toto".to_owned()))); + assert_eq!(iter.next_res()?, None); + Ok(()) + } + + #[test] + fn test_mock_db() -> KvResult<()> { + let mut db = MockTestDbReadable::new(); + db.expect_col_1().times(1).returning(|| { + let mut col_1 = MockColRo::<Col1Event>::new(); + col_1.expect_count().times(1).returning(|| Ok(899)); + col_1 + .expect_get() + .times(1) + .returning(|_| Ok(Some("toto".to_owned()))); + col_1.expect_iter::<RangeFull>().times(1).returning(|_| { + let mut b_iter = MockBackendIter::new(); + #[allow(clippy::string_lit_as_bytes)] + let mut items = vec![ + None, + Some(Ok((vec![0u8, 0, 0, 42], "toto".as_bytes().to_vec()))), + ]; + b_iter + .expect_next() + .times(2) + .returning(move || items.pop().unwrap_or(None)); + KvIter::new(b_iter, (Bound::Unbounded, Bound::Unbounded)) + }); + col_1 + }); + + use_readable_db(&db)?; + + Ok(()) + } +} diff --git a/rust-libs/tools/kv_typed_code_gen/Cargo.toml b/rust-libs/tools/kv_typed_code_gen/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6f09757d612f13252c5a3c8aab458c5e680f93cc --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "kv_typed_code_gen" +version = "0.1.0" +authors = ["elois <c@elo.tf>"] +description = "Macros to generate code for kv_typed users" +repository = "https://git.duniter.org/nodes/typescript/duniter" +keywords = ["database", "macro", "key", "sled"] +license = "AGPL-3.0" +edition = "2018" + +[lib] +proc-macro = true +path = "src/lib.rs" + +[dependencies] +Inflector = "0.11.4" +proc-macro2 = "1.0.20" +quote = "1.0" +syn = { version = "1.0.39", features = ["full"] } diff --git a/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs b/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs new file mode 100644 index 0000000000000000000000000000000000000000..085d0839a085a7ad9dd5ff47c1d853425f5cf697 --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs @@ -0,0 +1,81 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +pub(crate) struct ColSchema { + pub(crate) name_class: Ident, + pub(crate) name_snake: Ident, + pub(crate) path: LitStr, + pub(crate) key_ty: Ident, + pub(crate) value_ty: Ident, +} + +impl From<ExprArray> for ColSchema { + fn from(expr_array: ExprArray) -> Self { + let mut expr_iter = expr_array.elems.into_iter(); + + let col_path = if let Some(Expr::Lit(ExprLit { lit, .. })) = expr_iter.next() { + if let Lit::Str(lit_str) = lit { + lit_str + } else { + panic!("Collection must be defined by array of one literal followed by three identifiers"); + } + } else { + panic!( + "Collection must be defined by array of one literal followed by three identifiers" + ); + }; + let col_name = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() { + path.get_ident() + .expect("Collection name must be a plain identifier") + .to_owned() + } else { + panic!( + "Collection must be defined by array of one literal followed by three identifiers" + ); + }; + let key_ty = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() { + path.get_ident() + .expect("Collection key type must be a plain identifier") + .to_owned() + } else { + panic!( + "Collection must be defined by array of one literal followed by three identifiers" + ); + }; + let value_ty = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() { + path.get_ident() + .expect("Collection value type must be a plain identifier") + .to_owned() + } else { + panic!( + "Collection must be defined by array of one literal followed by three identifiers" + ); + }; + if expr_iter.next().is_some() { + panic!( + "Collection must be defined by array of one literal followed by three identifiers" + ); + } + ColSchema { + path: col_path, + name_class: format_ident!("{}", ident_to_class_case_string(&col_name)), + name_snake: format_ident!("{}", ident_to_snake_case_string(&col_name)), + key_ty, + value_ty, + } + } +} diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs b/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs new file mode 100644 index 0000000000000000000000000000000000000000..9773269a951f47c9ca1f2adf5ff71652bf4c4535 --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn impl_db_readable( + db: &Ident, + db_ro: &Ident, + db_readable: &Ident, + col_name_class: &[Ident], + col_field: &[Ident], + col_event_type: &[Ident], + col_key_type: &[Ident], + col_value_type: &[Ident], +) -> proc_macro2::TokenStream { + quote! { + pub trait #db_readable: Sized { + type Backend: Backend; + #(type #col_name_class: DbCollectionRo<Event=#col_event_type, K=#col_key_type, V=#col_value_type>;)* + + #(fn #col_field(&self) -> Self::#col_name_class;)* + } + impl<B: Backend> #db_readable for #db<B> { + type Backend = B; + #(type #col_name_class = ColRo<B::Col, #col_event_type>;)* + + #(fn #col_field(&self) -> Self::#col_name_class { self.collections.#col_field.to_ro().clone() })* + } + impl<B: Backend> #db_readable for #db_ro<B>{ + type Backend = B; + #(type #col_name_class = ColRo<B::Col, #col_event_type>;)* + + #(fn #col_field(&self) -> Self::#col_name_class { self.collections.#col_field.clone() })* + } + } +} diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs b/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs new file mode 100644 index 0000000000000000000000000000000000000000..c60ee621f3f3793847619f36fad00597df205152 --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +/// Parses the following syntax +/// +/// DbVersion, [collection_name, ColKeyType, ColValueType] +/// +pub(crate) struct DbSchema { + pub(crate) version: Ident, + pub(crate) collections: VecExprArray, +} + +pub(crate) struct VecExprArray(pub(crate) Vec<ExprArray>); + +impl Parse for VecExprArray { + fn parse(input: ParseStream) -> Result<Self> { + let tmp = Punctuated::<ExprArray, Token![,]>::parse_terminated(input)?; + Ok(VecExprArray(tmp.into_iter().collect())) + } +} + +impl Parse for DbSchema { + fn parse(input: ParseStream) -> Result<Self> { + let version: Ident = input.parse()?; + input.parse::<Token![,]>()?; + let collections_group: Group = input.parse()?; + let collections: VecExprArray = syn::parse(collections_group.stream().into())?; + + Ok(DbSchema { + version, + collections, + }) + } +} diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs b/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs new file mode 100644 index 0000000000000000000000000000000000000000..01c7e157bf49e0d11de7f1372fc58ed99ba42737 --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs @@ -0,0 +1,85 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn impl_db_writable( + db: &Ident, + db_ro: &Ident, + db_readable: &Ident, + db_writable: &Ident, + col_name_class: &[Ident], + col_field: &[Ident], + col_path: &[LitStr], + col_event_type: &[Ident], +) -> proc_macro2::TokenStream { + let db_batch = format_ident!("{}Batch", db); + let db_cols_rw = format_ident!("{}ColsRw", db); + let col_method_rw: Vec<Ident> = col_field + .iter() + .map(|field_name| format_ident!("{}_write", field_name)) + .collect(); + quote! { + pub trait #db_writable: #db_readable { + type Backend: Backend; + type Batch; + #(type #col_name_class: DbCollectionRw;)* + type DbRo: Sized; + + fn get_ro_handler(&self) -> Self::DbRo; + fn open( + backend_conf: <<Self as #db_writable>::Backend as kv_typed::backend::Backend>::Conf, + ) -> KvResult <Self>; + fn new_batch(&self) -> Self::Batch; + fn write_batch(&self, batch: Self::Batch) -> KvResult<()>; + #(fn #col_method_rw(&self) -> &Self::#col_name_class;)* + } + impl<B: Backend> #db_writable for #db<B> { + type Backend = B; + type Batch = #db_batch<B>; + #(type #col_name_class = ColRw<B::Col, #col_event_type>;)* + type DbRo = #db_ro<B>; + + #[inline(always)] + fn get_ro_handler(&self) -> Self::DbRo { + #db_ro { + collections: self.collections.to_ro(), + } + } + #[inline(always)] + fn new_batch(&self) -> Self::Batch { + <#db_batch::<B>>::default() + } + fn write_batch(&self, batch: Self::Batch) -> KvResult<()> { + #(self.collections.#col_field.write_batch(batch.#col_field)?;)* + Ok(()) + } + fn open( + backend_conf: <<Self as #db_writable>::Backend as kv_typed::backend::Backend>::Conf, + ) -> KvResult <Self> { + let mut db = B::open(&backend_conf)?; + Ok(#db { + collections: #db_cols_rw { + #(#col_field: <ColRw<B::Col, #col_event_type>>::new( + db.open_col(&backend_conf, #col_path)? + ),)* + }, + }) + } + #(fn #col_method_rw(&self) -> &ColRw<B::Col, #col_event_type> { &self.collections.#col_field })* + } + } +} diff --git a/rust-libs/tools/kv_typed_code_gen/src/lib.rs b/rust-libs/tools/kv_typed_code_gen/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..b93df173fd8d2978f71744ae1ef4494bb86a9c3c --- /dev/null +++ b/rust-libs/tools/kv_typed_code_gen/src/lib.rs @@ -0,0 +1,236 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Strongly typed key-value storage +//! +//! Macro to generate database code from their schema + +mod col_schema; +mod db_readable; +mod db_schema; +mod db_writable; + +use inflector::Inflector; +use proc_macro::TokenStream; +use proc_macro2::Group; +use quote::{format_ident, quote}; +use syn::{ + parse::{Parse, ParseStream, Result}, + parse_macro_input, + punctuated::Punctuated, + Expr, ExprArray, ExprLit, ExprPath, Ident, Lit, LitStr, Token, +}; + +use crate::col_schema::ColSchema; +use crate::db_readable::impl_db_readable; +use crate::db_schema::DbSchema; +use crate::db_writable::impl_db_writable; + +fn ident_to_class_case_string(ident: &Ident) -> String { + Inflector::to_class_case(&format!("{}", ident)) +} +fn ident_to_snake_case_string(ident: &Ident) -> String { + Inflector::to_snake_case(&format!("{}", ident)) +} + +#[allow(clippy::let_and_return)] +#[proc_macro] +pub fn db_schema(input: TokenStream) -> TokenStream { + let db_schema = parse_macro_input!(input as DbSchema); + + let version = db_schema.version; + let name = ident_to_snake_case_string(&version); + let collections: Vec<ColSchema> = db_schema + .collections + .0 + .into_iter() + .map(Into::into) + .collect(); + + let db = format_ident!("{}Db", version); + let db_batch = format_ident!("{}Batch", db); + let db_ro = format_ident!("{}Ro", db); + let db_cols_ro = format_ident!("{}ColsRo", db); + let db_cols_rw = format_ident!("{}ColsRw", db); + + let col_path: Vec<LitStr> = collections.iter().map(|col| col.path.clone()).collect(); + let col_field: Vec<Ident> = collections + .iter() + .map(|col| col.name_snake.clone()) + .collect(); + let col_event_type: Vec<Ident> = collections + .iter() + .map(|col| format_ident!("{}Event", &col.name_class)) + .collect(); + let col_key_type: Vec<Ident> = collections.iter().map(|col| col.key_ty.clone()).collect(); + let col_name_class: Vec<Ident> = collections + .iter() + .map(|col| col.name_class.clone()) + .collect(); + let col_name_class_rw: Vec<Ident> = collections + .iter() + .map(|col| format_ident!("{}Rw", &col.name_class)) + .collect(); + let col_value_type: Vec<Ident> = collections.iter().map(|col| col.value_ty.clone()).collect(); + + /*let define_each_db_collection: Vec<proc_macro2::TokenStream> = + collections.iter().map(define_db_collection).collect();*/ + let db_readable = format_ident!("{}Readable", db); + let impl_db_readable = impl_db_readable( + &db, + &db_ro, + &db_readable, + &col_name_class, + &col_field, + &col_event_type, + &col_key_type, + &col_value_type, + ); + let db_writable = format_ident!("{}Writable", db); + let impl_db_writable = impl_db_writable( + &db, + &db_ro, + &db_readable, + &db_writable, + &col_name_class_rw, + &col_field, + &col_path, + &col_event_type, + ); + + let expanded = quote! { + pub use __inner::{#db, #db_batch, #db_ro, #db_readable, #db_writable}; + #( + // Define each collection event type + #[derive(Debug, PartialEq)] + pub enum #col_event_type { + Upsert { key: #col_key_type, value: #col_value_type }, + Remove { key: #col_key_type }, + } + impl EventTrait for #col_event_type { + type K = #col_key_type; + type V = #col_value_type; + + fn upsert(k: Self::K, v: Self::V) -> Self { Self::Upsert { key: k, value: v, } } + fn remove(k: Self::K) -> Self { Self::Remove { key: k } } + } + )* + // Mocks + #[cfg(feature = "mock")] + mockall::mock! { + pub #db_readable {} + + trait #db_readable { + type Backend = kv_typed::backend::mock::MockBackend; + #(type #col_name_class = kv_typed::prelude::MockColRo<#col_event_type>;)* + + #(fn #col_field(&self) -> kv_typed::prelude::MockColRo<#col_event_type>;)* + } + } + // Inner module used to hide internals types that must not be exposed on public api + mod __inner { + use super::*; + // DbCollections + #[derive(Clone, Debug)] + pub struct #db_cols_ro<BC: BackendCol> { + #(#col_field: ColRo<BC, #col_event_type>,)* + } + #[derive(Clone, Debug)] + pub struct #db_cols_rw<BC: BackendCol> { + #(#col_field: ColRw<BC, #col_event_type>,)* + } + impl<BC: BackendCol> #db_cols_rw<BC> { + fn to_ro(&self) -> #db_cols_ro<BC> { + #db_cols_ro { + #(#col_field: self.#col_field.to_ro().clone(),)* + } + } + } + // Db + #[derive(Debug)] + pub struct #db<B: Backend> { + collections: #db_cols_rw<B::Col>, + } + impl<B: Backend> #db<B> { + pub const NAME: &'static str = #name; + } + impl<B: Backend> Clone for #db<B> { + fn clone(&self) -> Self { + #db { + collections: self.collections.clone(), + } + } + } + #[cfg(feature = "explorer")] + use kv_typed::prelude::StringErr; + #[cfg(feature = "explorer")] + impl<B: Backend> kv_typed::explorer::DbExplorable for #db<B> { + fn explore<'a>( + &self, + collection_name: &str, + action: kv_typed::explorer::ExplorerAction<'a>, + stringify_json_value: fn(serde_json::Value) -> serde_json::Value, + ) -> KvResult<std::result::Result<kv_typed::explorer::ExplorerActionResponse, StringErr>> { + #( if stringify!(#col_field) == collection_name { + return action.exec(&self.collections.#col_field, stringify_json_value); + } )* + Ok(Err(StringErr(format!("collection '{}' not exist in database '{}'.", collection_name, stringify!(#db))))) + } + fn list_collections(&self) -> Vec<(&'static str, &'static str, &'static str)> { + vec![ + #((stringify!(#col_field), stringify!(#col_key_type), stringify!(#col_value_type)),)* + ] + } + } + pub struct #db_batch<B: Backend> { + #(#col_field: Batch<B::Col, ColRw<B::Col, #col_event_type>>,)* + } + impl<B: Backend> Default for #db_batch<B> { + fn default() -> Self { + #db_batch { + #(#col_field: Batch::default(),)* + } + } + } + impl<B: Backend> #db_batch<B> { + #(pub fn #col_field(&mut self) -> &mut Batch<B::Col, ColRw<B::Col, #col_event_type>> { &mut self.#col_field })* + } + // DbRo + #[derive(Debug)] + pub struct #db_ro<B: Backend> { + collections: #db_cols_ro<B::Col>, + } + impl<B: Backend> #db_ro<B> { + pub const NAME: &'static str = #name; + } + impl<B: Backend> Clone for #db_ro<B> { + fn clone(&self) -> Self { + #db_ro { + collections: self.collections.clone(), + } + } + } + // Read operations + #impl_db_readable + // Write operations + #impl_db_writable + } + }; + + //let tokens = TokenStream::from(expanded); + //eprintln!("TOKENS: {:#}", tokens); + + TokenStream::from(expanded) +}