diff --git a/.changelogs/2021-09-10-sqlstore.md b/.changelogs/2021-09-10-sqlstore.md new file mode 100644 index 000000000..b0151816c --- /dev/null +++ b/.changelogs/2021-09-10-sqlstore.md @@ -0,0 +1 @@ +- Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) diff --git a/Cargo.lock b/Cargo.lock index 316c167e6..28bed3b3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded 0.7.0", - "sha-1 0.9.4", + "sha-1 0.9.6", "slab", - "time 0.2.26", + "time 0.2.27", ] [[package]] @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "actix-macros" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb2b608f0accc2f5bcf3dd872194ce13d94ee45b571487035864cf966b04ef" +checksum = "c2f86cd6857c135e6e9fe57b1619a88d1f94a7df34c00e11fe13e64fd3438837" dependencies = [ "quote", "syn", @@ -136,9 +136,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" dependencies = [ - "actix-macros 0.2.0", + "actix-macros 0.2.1", "futures-core", - "tokio 1.5.0", + "tokio 1.6.2", ] [[package]] @@ -266,7 +266,7 @@ dependencies = [ "serde_json", "serde_urlencoded 0.7.0", "socket2", - "time 0.2.26", + "time 0.2.27", "tinyvec", "url", ] @@ -289,10 +289,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aho-corasick" -version = "0.7.15" +name = "ahash" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -323,9 +334,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" [[package]] name = "arrayref" @@ -339,17 +350,11 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "ascii" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" - [[package]] name = "assert_cmd" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2475b58cd94eb4f70159f4fd8844ba3b807532fe3131b3373fae060bbe30396" +checksum = "a88b6bd5df287567ffdf4ddf4d33060048e1068308e5f62d81c6f9824a045a48" dependencies = [ "bstr", "doc-comment", @@ -361,9 +366,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.49" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" dependencies = [ "proc-macro2", "quote", @@ -525,9 +530,9 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "lazy_static", "memchr", @@ -547,9 +552,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" [[package]] name = "byte-tools" @@ -598,9 +603,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" dependencies = [ "jobserver", ] @@ -654,15 +659,12 @@ dependencies = [ [[package]] name = "combine" -version = "3.8.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e" dependencies = [ - "ascii", - "byteorder", - "either", + "bytes 1.0.1", "memchr", - "unreachable", ] [[package]] @@ -682,9 +684,9 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" [[package]] name = "constant_time_eq" @@ -705,7 +707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", - "time 0.2.26", + "time 0.2.27", "version_check", ] @@ -716,10 +718,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "cpufeatures" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] [[package]] name = "crc32fast" @@ -732,11 +737,10 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "autocfg", "cfg-if 1.0.0", "lazy_static", ] @@ -765,9 +769,9 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.13" +version = "0.99.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" +checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" dependencies = [ "convert_case", "proc-macro2", @@ -869,9 +873,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "elasticlunr-rs" -version = "2.3.11" +version = "2.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959fbc9a6ebced545cbe365fdce5e25c6ab7683f2ca4ecc9fb9d0db663bf73d5" +checksum = "2f8cf73b19a7aece6942f5745a2fc1ae3c8b0533569707d596b5d6baa7d6c600" dependencies = [ "lazy_static", "regex", @@ -924,9 +928,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "atty", "humantime 2.1.0", @@ -941,6 +945,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "filetime" version = "0.2.14" @@ -949,7 +965,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.6", + "redox_syscall 0.2.8", "winapi 0.3.9", ] @@ -1043,9 +1059,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" dependencies = [ "futures-channel", "futures-core", @@ -1058,9 +1074,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" dependencies = [ "futures-core", "futures-sink", @@ -1068,15 +1084,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" [[package]] name = "futures-executor" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" dependencies = [ "futures-core", "futures-task", @@ -1085,16 +1101,17 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" [[package]] name = "futures-macro" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" dependencies = [ + "autocfg", "proc-macro-hack", "proc-macro2", "quote", @@ -1103,22 +1120,23 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" [[package]] name = "futures-task" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" [[package]] name = "futures-util" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" dependencies = [ + "autocfg", "futures-channel", "futures-core", "futures-io", @@ -1183,9 +1201,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1249,7 +1267,7 @@ dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.0", + "quick-error 2.0.1", "serde", "serde_json", ] @@ -1260,6 +1278,24 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", +] + [[package]] name = "headers" version = "0.3.4" @@ -1272,7 +1308,7 @@ dependencies = [ "headers-core", "http", "mime", - "sha-1 0.9.4", + "sha-1 0.9.6", "time 0.1.43", ] @@ -1287,9 +1323,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] @@ -1351,9 +1387,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" [[package]] name = "httpdate" @@ -1418,7 +1454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.9.1", ] [[package]] @@ -1506,9 +1542,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" dependencies = [ "wasm-bindgen", ] @@ -1523,19 +1559,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "kv" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" -dependencies = [ - "lmdb-rkv 0.12.3", - "rmp-serde", - "serde", - "thiserror", - "toml", -] - [[package]] name = "language-tags" version = "0.2.2" @@ -1556,9 +1579,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lexical-core" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec", "bitflags", @@ -1569,9 +1592,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" [[package]] name = "libgit2-sys" @@ -1585,6 +1608,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libsqlite3-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.3" @@ -1603,57 +1637,11 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" -[[package]] -name = "lmdb-rkv" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452" -dependencies = [ - "bitflags", - "byteorder", - "libc", - "lmdb-rkv-sys 0.9.6", -] - -[[package]] -name = "lmdb-rkv" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" -dependencies = [ - "bitflags", - "byteorder", - "libc", - "lmdb-rkv-sys 0.11.0", -] - -[[package]] -name = "lmdb-rkv-sys" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "lmdb-rkv-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27470ac25167b3afdfb6af8fcd3bc1be67de50ffbdaf4073378cfded6ae24a5" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "lock_api" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" dependencies = [ "scopeguard", ] @@ -1760,9 +1748,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "mime" @@ -1811,9 +1799,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", "log", @@ -1971,9 +1959,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "opaque-debug" @@ -2017,7 +2005,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.6", + "redox_syscall 0.2.8", "smallvec", "winapi 0.3.9", ] @@ -2187,9 +2175,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" dependencies = [ "difference", "float-cmp", @@ -2242,9 +2230,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -2260,9 +2248,9 @@ dependencies = [ "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.0", + "quick-error 2.0.1", "rand 0.8.3", - "rand_chacha 0.3.0", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -2271,9 +2259,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.22.1" +version = "2.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92" +checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267" [[package]] name = "pulldown-cmark" @@ -2295,9 +2283,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-error" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" @@ -2335,7 +2323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha 0.3.0", + "rand_chacha 0.3.1", "rand_core 0.6.2", "rand_hc 0.3.0", ] @@ -2352,9 +2340,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.2", @@ -2375,7 +2363,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom 0.2.2", + "getrandom 0.2.3", ] [[package]] @@ -2422,9 +2410,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] @@ -2446,15 +2434,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.2", - "redox_syscall 0.2.6", + "getrandom 0.2.3", + "redox_syscall 0.2.8", ] [[package]] name = "regex" -version = "1.4.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -2463,18 +2451,15 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -dependencies = [ - "byteorder", -] +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" @@ -2510,27 +2495,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "rmp" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" -dependencies = [ - "byteorder", - "num-traits", -] - -[[package]] -name = "rmp-serde" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - [[package]] name = "rstest" version = "0.10.0" @@ -2544,6 +2508,21 @@ dependencies = [ "syn", ] +[[package]] +name = "rusqlite" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2576,9 +2555,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", "log", @@ -2680,18 +2659,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", @@ -2747,13 +2726,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" +checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -2772,9 +2751,9 @@ checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -2787,9 +2766,9 @@ checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" [[package]] name = "slab" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "smallvec" @@ -2953,9 +2932,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote", @@ -2974,11 +2953,10 @@ version = "0.3.0" dependencies = [ "anyhow", "chrono", - "kv", - "lmdb-rkv 0.14.0", "log", "proptest", "rstest", + "rusqlite", "serde", "serde_json", "strum 0.21.0", @@ -3001,7 +2979,7 @@ dependencies = [ "chrono", "dialoguer", "dirs-next", - "env_logger 0.8.3", + "env_logger 0.8.4", "iso8601-duration", "lazy_static", "log", @@ -3028,12 +3006,14 @@ dependencies = [ "actix-web", "anyhow", "clap", - "env_logger 0.8.3", + "env_logger 0.8.4", "futures", - "kv", "log", + "rusqlite", "serde", + "serde_json", "tempfile", + "thiserror", "uuid", ] @@ -3046,7 +3026,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.3", - "redox_syscall 0.2.6", + "redox_syscall 0.2.8", "remove_dir_all", "winapi 0.3.9", ] @@ -3084,9 +3064,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" dependencies = [ "libc", "winapi 0.3.9", @@ -3114,18 +3094,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" dependencies = [ "proc-macro2", "quote", @@ -3153,9 +3133,9 @@ dependencies = [ [[package]] name = "time" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" dependencies = [ "const_fn", "libc", @@ -3178,9 +3158,9 @@ dependencies = [ [[package]] name = "time-macros-impl" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -3240,13 +3220,13 @@ dependencies = [ [[package]] name = "tokio" -version = "1.5.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" +checksum = "aea337f72e96efe29acc234d803a5981cd9a2b6ed21655cd7fc21cfe021e8ec7" dependencies = [ "autocfg", "libc", - "mio 0.7.11", + "mio 0.7.13", "once_cell", "parking_lot", "pin-project-lite 0.2.6", @@ -3303,9 +3283,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09391a441b373597cf0888d2b052dcf82c5be4fee05da3636ae30fb57aad8484" +checksum = "dbbdcf4f749dd33b1f1ea19b547bf789d87442ec40767d6015e5e2d39158d69a" dependencies = [ "chrono", "combine", @@ -3320,9 +3300,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ "cfg-if 1.0.0", "log", @@ -3332,9 +3312,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" dependencies = [ "lazy_static", ] @@ -3414,7 +3394,7 @@ dependencies = [ "input_buffer", "log", "rand 0.7.3", - "sha-1 0.9.4", + "sha-1 0.9.6", "url", "utf-8", ] @@ -3451,9 +3431,9 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -3472,18 +3452,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "untrusted" @@ -3493,9 +3464,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" +checksum = "2475a6781e9bc546e7b64f4013d2f4032c8c6a40fcffd7c6f4ee734a890972ab" dependencies = [ "base64 0.13.0", "chunked_transfer", @@ -3509,9 +3480,9 @@ dependencies = [ [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", @@ -3537,7 +3508,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.2", + "getrandom 0.2.3", "serde", ] @@ -3559,12 +3530,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "wait-timeout" version = "0.2.0" @@ -3636,9 +3601,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3646,9 +3611,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" dependencies = [ "bumpalo", "lazy_static", @@ -3661,9 +3626,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3671,9 +3636,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ "proc-macro2", "quote", @@ -3684,15 +3649,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index 1748b86d0..ad7cbd61a 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -72,7 +72,6 @@ mod test { ); } - #[test] fn test_cleaning_command_name() { assert_eq!( diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 2a42298be..6c66f6726 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -10,12 +10,14 @@ edition = "2018" uuid = { version = "^0.8.2", features = ["serde", "v4"] } actix-web = "^3.3.2" anyhow = "1.0" +thiserror = "1.0" futures = "^0.3.8" serde = "^1.0.125" -kv = {version = "^0.10.0", features = ["msgpack-value"]} +serde_json = "^1.0" clap = "^2.33.0" log = "^0.4.14" env_logger = "^0.8.3" +rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] actix-rt = "^2.2.0" diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 2ae65ae5c..394031738 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,6 +1,6 @@ #![deny(clippy::all)] -use crate::storage::{KvStorage, Storage}; +use crate::storage::{SqliteStorage, Storage}; use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; use clap::Arg; @@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(KvStorage::new(data_dir)?); + let server_box: Box = Box::new(SqliteStorage::new(data_dir)?); let server_state = ServerState::new(server_box); log::warn!("Serving on port {}", port); diff --git a/sync-server/src/storage/kv.rs b/sync-server/src/storage/kv.rs deleted file mode 100644 index 19218a235..000000000 --- a/sync-server/src/storage/kv.rs +++ /dev/null @@ -1,241 +0,0 @@ -use super::{Client, Storage, StorageTxn, Uuid, Version}; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; -use std::path::Path; - -/// DB Key for versions: concatenation of client_key and parent_version_id -type VersionDbKey = [u8; 32]; - -fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey { - let mut key = [0u8; 32]; - key[..16].clone_from_slice(client_key.as_bytes()); - key[16..].clone_from_slice(parent_version_id.as_bytes()); - key -} - -/// Key for clients: just the client_key -type ClientDbKey = [u8; 16]; - -fn client_db_key(client_key: Uuid) -> ClientDbKey { - *client_key.as_bytes() -} - -/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub(crate) struct KvStorage<'t> { - store: Store, - clients_bucket: Bucket<'t, ClientDbKey, ValueBuf>>, - versions_bucket: Bucket<'t, VersionDbKey, ValueBuf>>, -} - -impl<'t> KvStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("clients", None); - config.bucket("versions", None); - - let store = Store::new(config)?; - - let clients_bucket = - store.bucket::>>(Some("clients"))?; - let versions_bucket = - store.bucket::>>(Some("versions"))?; - - Ok(KvStorage { - store, - clients_bucket, - versions_bucket, - }) - } -} - -impl<'t> Storage for KvStorage<'t> { - fn txn<'a>(&'a self) -> anyhow::Result> { - Ok(Box::new(Txn { - storage: self, - txn: Some(self.store.write_txn()?), - })) - } -} - -struct Txn<'t> { - storage: &'t KvStorage<'t>, - txn: Option>, -} - -impl<'t> Txn<'t> { - // get the underlying kv Txn - fn kvtxn(&mut self) -> &mut kv::Txn<'t> { - if let Some(ref mut txn) = self.txn { - txn - } else { - panic!("cannot use transaction after commit"); - } - } - - fn clients_bucket(&self) -> &'t Bucket<'t, ClientDbKey, ValueBuf>> { - &self.storage.clients_bucket - } - fn versions_bucket(&self) -> &'t Bucket<'t, VersionDbKey, ValueBuf>> { - &self.storage.versions_bucket - } -} - -impl<'t> StorageTxn for Txn<'t> { - fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { - let key = client_db_key(client_key); - let bucket = self.clients_bucket(); - let kvtxn = self.kvtxn(); - - let client = match kvtxn.get(bucket, key) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(client)) - } - - fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { - let key = client_db_key(client_key); - let bucket = self.clients_bucket(); - let kvtxn = self.kvtxn(); - let client = Client { latest_version_id }; - kvtxn.set(bucket, key, Msgpack::to_value_buf(client)?)?; - Ok(()) - } - - fn set_client_latest_version_id( - &mut self, - client_key: Uuid, - latest_version_id: Uuid, - ) -> anyhow::Result<()> { - // implementation is the same as new_client.. - self.new_client(client_key, latest_version_id) - } - - fn get_version_by_parent( - &mut self, - client_key: Uuid, - parent_version_id: Uuid, - ) -> anyhow::Result> { - let key = version_db_key(client_key, parent_version_id); - let bucket = self.versions_bucket(); - let kvtxn = self.kvtxn(); - let version = match kvtxn.get(bucket, key) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(version)) - } - - fn add_version( - &mut self, - client_key: Uuid, - version_id: Uuid, - parent_version_id: Uuid, - history_segment: Vec, - ) -> anyhow::Result<()> { - let key = version_db_key(client_key, parent_version_id); - let bucket = self.versions_bucket(); - let kvtxn = self.kvtxn(); - let version = Version { - version_id, - parent_version_id, - history_segment, - }; - kvtxn.set(bucket, key, Msgpack::to_value_buf(version)?)?; - Ok(()) - } - - fn commit(&mut self) -> anyhow::Result<()> { - if let Some(kvtxn) = self.txn.take() { - kvtxn.commit()?; - } else { - panic!("transaction already committed"); - } - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use tempfile::TempDir; - - #[test] - fn test_get_client_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - let maybe_client = txn.get_client(Uuid::new_v4())?; - assert!(maybe_client.is_none()); - Ok(()) - } - - #[test] - fn test_client_storage() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - - let client_key = Uuid::new_v4(); - let latest_version_id = Uuid::new_v4(); - txn.new_client(client_key, latest_version_id)?; - - let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(client.latest_version_id, latest_version_id); - - let latest_version_id = Uuid::new_v4(); - txn.set_client_latest_version_id(client_key, latest_version_id)?; - - let client = txn.get_client(client_key)?.unwrap(); - assert_eq!(client.latest_version_id, latest_version_id); - - Ok(()) - } - - #[test] - fn test_gvbp_empty() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; - assert!(maybe_version.is_none()); - Ok(()) - } - - #[test] - fn test_add_version_and_gvbp() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let storage = KvStorage::new(&tmp_dir.path())?; - let mut txn = storage.txn()?; - - let client_key = Uuid::new_v4(); - let version_id = Uuid::new_v4(); - let parent_version_id = Uuid::new_v4(); - let history_segment = b"abc".to_vec(); - txn.add_version( - client_key, - version_id, - parent_version_id, - history_segment.clone(), - )?; - let version = txn - .get_version_by_parent(client_key, parent_version_id)? - .unwrap(); - - assert_eq!( - version, - Version { - version_id, - parent_version_id, - history_segment, - } - ); - Ok(()) - } -} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 1d1cbe139..f9a2fc699 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -6,8 +6,8 @@ mod inmemory; #[cfg(test)] pub(crate) use inmemory::InMemoryStorage; -mod kv; -pub(crate) use self::kv::KvStorage; +mod sqlite; +pub(crate) use self::sqlite::SqliteStorage; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub(crate) struct Client { diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs new file mode 100644 index 000000000..34cdd05b7 --- /dev/null +++ b/sync-server/src/storage/sqlite.rs @@ -0,0 +1,291 @@ +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use anyhow::Context; +use rusqlite::types::{FromSql, ToSql}; +use rusqlite::{params, Connection, OptionalExtension}; +use std::path::Path; + +#[derive(Debug, thiserror::Error)] +enum SqliteError { + #[error("Failed to create SQLite transaction")] + CreateTransactionFailed, +} + +/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` +struct StoredUuid(Uuid); + +/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) +impl FromSql for StoredUuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = Uuid::parse_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredUuid(u)) + } +} + +/// Store Uuid as string in database +impl ToSql for StoredUuid { + fn to_sql(&self) -> rusqlite::Result> { + let s = self.0.to_string(); + Ok(s.into()) + } +} + +/// Stores [`Client`] in SQLite +impl FromSql for Client { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: Client = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(o) + } +} + +/// Parses Operation stored as JSON in string column +impl ToSql for Client { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + +/// An on-disk storage backend which uses SQLite +pub(crate) struct SqliteStorage { + db_file: std::path::PathBuf, +} + +impl SqliteStorage { + fn new_connection(&self) -> anyhow::Result { + Ok(Connection::open(&self.db_file)?) + } + + pub fn new>(directory: P) -> anyhow::Result { + std::fs::create_dir_all(&directory)?; + let db_file = directory.as_ref().join("taskchampion-sync-server.sqlite3"); + + let o = SqliteStorage { db_file }; + + { + let mut con = o.new_connection()?; + let txn = con.transaction()?; + + let queries = vec![ + "CREATE TABLE IF NOT EXISTS clients (client_key STRING PRIMARY KEY, latest_version_id STRING);", + "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment BLOB);", + ]; + for q in queries { + txn.execute(q, []).context("Creating table")?; + } + txn.commit()?; + } + + Ok(o) + } +} + +impl Storage for SqliteStorage { + fn txn<'a>(&'a self) -> anyhow::Result> { + let con = self.new_connection()?; + let t = Txn { con }; + Ok(Box::new(t)) + } +} + +struct Txn { + con: Connection, +} + +impl Txn { + fn get_txn(&mut self) -> Result { + self.con + .transaction() + .map_err(|_e| SqliteError::CreateTransactionFailed) + } +} + +impl StorageTxn for Txn { + fn get_client(&mut self, client_key: Uuid) -> anyhow::Result> { + let t = self.get_txn()?; + let result: Option = t + .query_row( + "SELECT latest_version_id FROM clients WHERE client_key = ? LIMIT 1", + [&StoredUuid(client_key)], + |r| { + let latest_version_id: StoredUuid = r.get(0)?; + Ok(Client { + latest_version_id: latest_version_id.0, + }) + }, + ) + .optional() + .context("Get client query")?; + + Ok(result) + } + + fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { + let t = self.get_txn()?; + + t.execute( + "INSERT OR REPLACE INTO clients (client_key, latest_version_id) VALUES (?, ?)", + params![&StoredUuid(client_key), &StoredUuid(latest_version_id)], + ) + .context("Create client query")?; + t.commit()?; + Ok(()) + } + + fn set_client_latest_version_id( + &mut self, + client_key: Uuid, + latest_version_id: Uuid, + ) -> anyhow::Result<()> { + // Implementation is same as new_client + self.new_client(client_key, latest_version_id) + } + + fn get_version_by_parent( + &mut self, + client_key: Uuid, + parent_version_id: Uuid, + ) -> anyhow::Result> { + let t = self.get_txn()?; + let r = t.query_row( + "SELECT version_id, parent_version_id, history_segment FROM versions WHERE parent_version_id = ? AND client_key = ?", + params![&StoredUuid(parent_version_id), &StoredUuid(client_key)], + |r| { + let version_id: StoredUuid = r.get("version_id")?; + let parent_version_id: StoredUuid = r.get("parent_version_id")?; + + Ok(Version{ + version_id: version_id.0, + parent_version_id: parent_version_id.0, + history_segment: r.get("history_segment")?, + })} + ) + .optional() + .context("Get version query") + ?; + Ok(r) + } + + fn add_version( + &mut self, + client_key: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> anyhow::Result<()> { + let t = self.get_txn()?; + + t.execute( + "INSERT INTO versions (version_id, client_key, parent_version_id, history_segment) VALUES(?, ?, ?, ?)", + params![ + StoredUuid(version_id), + StoredUuid(client_key), + StoredUuid(parent_version_id), + history_segment + ], + ) + .context("Add version query")?; + t.commit()?; + Ok(()) + } + + fn commit(&mut self) -> anyhow::Result<()> { + // FIXME: Note the queries aren't currently run in a + // transaction, as storing the transaction object and a pooled + // connection in the `Txn` object is complex. + // https://github.com/taskchampion/taskchampion/pull/206#issuecomment-860336073 + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_emtpy_dir() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let non_existant = tmp_dir.path().join("subdir"); + let storage = SqliteStorage::new(&non_existant)?; + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_get_client_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_client = txn.get_client(Uuid::new_v4())?; + assert!(maybe_client.is_none()); + Ok(()) + } + + #[test] + fn test_client_storage() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let latest_version_id = Uuid::new_v4(); + txn.new_client(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + let latest_version_id = Uuid::new_v4(); + txn.set_client_latest_version_id(client_key, latest_version_id)?; + + let client = txn.get_client(client_key)?.unwrap(); + assert_eq!(client.latest_version_id, latest_version_id); + + Ok(()) + } + + #[test] + fn test_gvbp_empty() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + let maybe_version = txn.get_version_by_parent(Uuid::new_v4(), Uuid::new_v4())?; + assert!(maybe_version.is_none()); + Ok(()) + } + + #[test] + fn test_add_version_and_gvbp() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let storage = SqliteStorage::new(&tmp_dir.path())?; + let mut txn = storage.txn()?; + + let client_key = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abc".to_vec(); + txn.add_version( + client_key, + version_id, + parent_version_id, + history_segment.clone(), + )?; + let version = txn + .get_version_by_parent(client_key, parent_version_id)? + .unwrap(); + + assert_eq!( + version, + Version { + version_id, + parent_version_id, + history_segment, + } + ); + Ok(()) + } +} diff --git a/taskchampion/Cargo.toml b/taskchampion/Cargo.toml index df9a7b52f..fe51874c9 100644 --- a/taskchampion/Cargo.toml +++ b/taskchampion/Cargo.toml @@ -17,11 +17,10 @@ serde_json = "^1.0" chrono = { version = "^0.4.10", features = ["serde"] } anyhow = "1.0" thiserror = "1.0" -kv = {version = "^0.10.0", features = ["msgpack-value"]} -lmdb-rkv = {version = "^0.14.0"} ureq = "^2.1.0" log = "^0.4.14" tindercrypt = { version = "^0.2.2", default-features = false } +rusqlite = { version = "0.25", features = ["bundled"] } strum = "0.21" strum_macros = "0.21" diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index bdd3c5dbd..657d60881 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -1,9 +1,10 @@ use crate::server::{ AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, }; -use crate::utils::Key; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; +use crate::storage::sqlite::StoredUuid; +use anyhow::Context; +use rusqlite::params; +use rusqlite::OptionalExtension; use serde::{Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; @@ -15,58 +16,54 @@ struct Version { history_segment: HistorySegment, } -pub struct LocalServer<'t> { - store: Store, - // NOTE: indexed by parent_version_id! - versions_bucket: Bucket<'t, Key, ValueBuf>>, - latest_version_bucket: Bucket<'t, Integer, ValueBuf>>, +pub struct LocalServer { + con: rusqlite::Connection, } -impl<'t> LocalServer<'t> { - /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("versions", None); - config.bucket("numbers", None); - config.bucket("latest_version", None); - config.bucket("operations", None); - config.bucket("working_set", None); - let store = Store::new(config)?; +impl LocalServer { + fn txn(&mut self) -> anyhow::Result { + let txn = self.con.transaction()?; + Ok(txn) + } - // versions are stored indexed by VersionId (uuid) - let versions_bucket = store.bucket::>>(Some("versions"))?; + /// A server which has no notion of clients, signatures, encryption, etc. + pub fn new>(directory: P) -> anyhow::Result { + let db_file = directory + .as_ref() + .join("taskchampion-local-sync-server.sqlite3"); + let con = rusqlite::Connection::open(&db_file)?; - // this bucket contains the latest version at key 0 - let latest_version_bucket = - store.int_bucket::>>(Some("latest_version"))?; + let queries = vec![ + "CREATE TABLE IF NOT EXISTS data (key STRING PRIMARY KEY, value STRING);", + "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, parent_version_id STRING, data STRING);", + ]; + for q in queries { + con.execute(q, []).context("Creating table")?; + } - Ok(LocalServer { - store, - versions_bucket, - latest_version_bucket, - }) + Ok(LocalServer { con }) } fn get_latest_version_id(&mut self) -> anyhow::Result { - let txn = self.store.read_txn()?; - let base_version = match txn.get(&self.latest_version_bucket, 0.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(NO_VERSION_ID), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(base_version as VersionId) + let t = self.txn()?; + let result: Option = t + .query_row( + "SELECT value FROM data WHERE key = 'latest_version_id' LIMIT 1", + rusqlite::params![], + |r| r.get(0), + ) + .optional()?; + Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID)) } fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { - let mut txn = self.store.write_txn()?; - txn.set( - &self.latest_version_bucket, - 0.into(), - Msgpack::to_value_buf(version_id as Uuid)?, - )?; - txn.commit()?; + let t = self.txn()?; + t.execute( + "INSERT OR REPLACE INTO data (key, value) VALUES ('latest_version_id', ?)", + params![&StoredUuid(version_id)], + ) + .context("Update task query")?; + t.commit()?; Ok(()) } @@ -74,31 +71,42 @@ impl<'t> LocalServer<'t> { &mut self, parent_version_id: VersionId, ) -> anyhow::Result> { - let txn = self.store.read_txn()?; + let t = self.txn()?; + let r = t.query_row( + "SELECT version_id, parent_version_id, data FROM versions WHERE parent_version_id = ?", + params![&StoredUuid(parent_version_id)], + |r| { + let version_id: StoredUuid = r.get("version_id")?; + let parent_version_id: StoredUuid = r.get("parent_version_id")?; - let version = match txn.get(&self.versions_bucket, parent_version_id.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(Some(version)) + Ok(Version{ + version_id: version_id.0, + parent_version_id: parent_version_id.0, + history_segment: r.get("data")?, + })} + ) + .optional() + .context("Get version query") + ?; + Ok(r) } fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> { - let mut txn = self.store.write_txn()?; - txn.set( - &self.versions_bucket, - version.parent_version_id.into(), - Msgpack::to_value_buf(version)?, + let t = self.txn()?; + t.execute( + "INSERT INTO versions (version_id, parent_version_id, data) VALUES (?, ?, ?)", + params![ + StoredUuid(version.version_id), + StoredUuid(version.parent_version_id), + version.history_segment + ], )?; - txn.commit()?; + t.commit()?; Ok(()) } } -impl<'t> Server for LocalServer<'t> { +impl Server for LocalServer { // TODO: better transaction isolation for add_version (gets and sets should be in the same // transaction) diff --git a/taskchampion/src/storage/config.rs b/taskchampion/src/storage/config.rs index 773baa035..a802e4a09 100644 --- a/taskchampion/src/storage/config.rs +++ b/taskchampion/src/storage/config.rs @@ -1,4 +1,4 @@ -use super::{InMemoryStorage, KvStorage, Storage}; +use super::{InMemoryStorage, SqliteStorage, Storage}; use std::path::PathBuf; /// The configuration required for a replica's storage. @@ -15,7 +15,7 @@ pub enum StorageConfig { impl StorageConfig { pub fn into_storage(self) -> anyhow::Result> { Ok(match self { - StorageConfig::OnDisk { taskdb_dir } => Box::new(KvStorage::new(taskdb_dir)?), + StorageConfig::OnDisk { taskdb_dir } => Box::new(SqliteStorage::new(taskdb_dir)?), StorageConfig::InMemory => Box::new(InMemoryStorage::new()), }) } diff --git a/taskchampion/src/storage/mod.rs b/taskchampion/src/storage/mod.rs index b263fd56f..18e64a9bb 100644 --- a/taskchampion/src/storage/mod.rs +++ b/taskchampion/src/storage/mod.rs @@ -11,12 +11,12 @@ use uuid::Uuid; mod config; mod inmemory; -mod kv; mod operation; +pub(crate) mod sqlite; -pub use self::kv::KvStorage; pub use config::StorageConfig; pub use inmemory::InMemoryStorage; +pub use sqlite::SqliteStorage; pub use operation::Operation; diff --git a/taskchampion/src/storage/kv.rs b/taskchampion/src/storage/sqlite.rs similarity index 50% rename from taskchampion/src/storage/kv.rs rename to taskchampion/src/storage/sqlite.rs index 505e3f095..0e56479fe 100644 --- a/taskchampion/src/storage/kv.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -1,355 +1,348 @@ use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use crate::utils::Key; -use kv::msgpack::Msgpack; -use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf}; +use anyhow::Context; +use rusqlite::types::{FromSql, ToSql}; +use rusqlite::{params, Connection, OptionalExtension}; use std::path::Path; use uuid::Uuid; -/// KvStorage is an on-disk storage backend which uses LMDB via the `kv` crate. -pub struct KvStorage<'t> { - store: Store, - tasks_bucket: Bucket<'t, Key, ValueBuf>>, - numbers_bucket: Bucket<'t, Integer, ValueBuf>>, - uuids_bucket: Bucket<'t, Integer, ValueBuf>>, - operations_bucket: Bucket<'t, Integer, ValueBuf>>, - working_set_bucket: Bucket<'t, Integer, ValueBuf>>, +#[derive(Debug, thiserror::Error)] +enum SqliteError { + #[error("SQLite transaction already committted")] + TransactionAlreadyCommitted, } -const BASE_VERSION: u64 = 1; -const NEXT_OPERATION: u64 = 2; -const NEXT_WORKING_SET_INDEX: u64 = 3; +/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` +pub(crate) struct StoredUuid(pub(crate) Uuid); -impl<'t> KvStorage<'t> { - pub fn new>(directory: P) -> anyhow::Result> { - let mut config = Config::default(directory); - config.bucket("tasks", None); - config.bucket("numbers", None); - config.bucket("uuids", None); - config.bucket("operations", None); - config.bucket("working_set", None); - let store = Store::new(config)?; - - // tasks are stored indexed by uuid - let tasks_bucket = store.bucket::>>(Some("tasks"))?; - - // this bucket contains various u64s, indexed by constants above - let numbers_bucket = store.int_bucket::>>(Some("numbers"))?; - - // this bucket contains various Uuids, indexed by constants above - let uuids_bucket = store.int_bucket::>>(Some("uuids"))?; - - // this bucket contains operations, numbered consecutively; the NEXT_OPERATION number gives - // the index of the next operation to insert - let operations_bucket = - store.int_bucket::>>(Some("operations"))?; - - // this bucket contains operations, numbered consecutively; the NEXT_WORKING_SET_INDEX - // number gives the index of the next operation to insert - let working_set_bucket = - store.int_bucket::>>(Some("working_set"))?; - - Ok(KvStorage { - store, - tasks_bucket, - numbers_bucket, - uuids_bucket, - operations_bucket, - working_set_bucket, - }) +/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) +impl FromSql for StoredUuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let u = Uuid::parse_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredUuid(u)) } } -impl<'t> Storage for KvStorage<'t> { - fn txn<'a>(&'a mut self) -> anyhow::Result> { - Ok(Box::new(Txn { - storage: self, - txn: Some(self.store.write_txn()?), - })) +/// Store Uuid as string in database +impl ToSql for StoredUuid { + fn to_sql(&self) -> rusqlite::Result> { + let s = self.0.to_string(); + Ok(s.into()) + } +} + +/// Wraps [`TaskMap`] (type alias for HashMap) so we can implement rusqlite conversion traits for it +struct StoredTaskMap(TaskMap); + +/// Parses TaskMap stored as JSON in string column +impl FromSql for StoredTaskMap { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: TaskMap = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(StoredTaskMap(o)) + } +} + +/// Stores TaskMap in string column +impl ToSql for StoredTaskMap { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self.0) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + +/// Stores [`Operation`] in SQLite +impl FromSql for Operation { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + let o: Operation = serde_json::from_str(value.as_str()?) + .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; + Ok(o) + } +} + +/// Parsers Operation stored as JSON in string column +impl ToSql for Operation { + fn to_sql(&self) -> rusqlite::Result> { + let s = serde_json::to_string(&self) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + Ok(s.into()) + } +} + +/// SqliteStorage is an on-disk storage backed by SQLite3. +pub struct SqliteStorage { + con: Connection, +} + +impl SqliteStorage { + pub fn new>(directory: P) -> anyhow::Result { + // Ensure parent folder exists + std::fs::create_dir_all(&directory)?; + + // Open (or create) database + let db_file = directory.as_ref().join("taskchampion.sqlite3"); + let con = Connection::open(db_file)?; + + // Initialize database + let queries = vec![ + "CREATE TABLE IF NOT EXISTS operations (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", + "CREATE TABLE IF NOT EXISTS sync_meta (key STRING PRIMARY KEY, value STRING);", + "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", + "CREATE TABLE IF NOT EXISTS working_set (id INTEGER PRIMARY KEY, uuid STRING);", + ]; + for q in queries { + con.execute(q, []).context("Creating table")?; + } + + Ok(SqliteStorage { con }) } } struct Txn<'t> { - storage: &'t KvStorage<'t>, - txn: Option>, + txn: Option>, } impl<'t> Txn<'t> { - // get the underlying kv Txn - fn kvtxn(&mut self) -> &mut kv::Txn<'t> { - if let Some(ref mut txn) = self.txn { - txn - } else { - panic!("cannot use transaction after commit"); - } + fn get_txn(&self) -> Result<&rusqlite::Transaction<'t>, SqliteError> { + self.txn + .as_ref() + .ok_or(SqliteError::TransactionAlreadyCommitted) } - // Access to buckets - fn tasks_bucket(&self) -> &'t Bucket<'t, Key, ValueBuf>> { - &self.storage.tasks_bucket + fn get_next_working_set_number(&self) -> anyhow::Result { + let t = self.get_txn()?; + let next_id: Option = t + .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", [], |r| { + r.get(0) + }) + .optional() + .context("Getting highest working set ID")?; + + Ok(next_id.unwrap_or(0)) } - fn numbers_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.numbers_bucket - } - fn uuids_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.uuids_bucket - } - fn operations_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.operations_bucket - } - fn working_set_bucket(&self) -> &'t Bucket<'t, Integer, ValueBuf>> { - &self.storage.working_set_bucket +} + +impl Storage for SqliteStorage { + fn txn<'a>(&'a mut self) -> anyhow::Result> { + let txn = self.con.transaction()?; + Ok(Box::new(Txn { txn: Some(txn) })) } } impl<'t> StorageTxn for Txn<'t> { fn get_task(&mut self, uuid: Uuid) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let buf = match self.kvtxn().get(bucket, uuid.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(None), - Err(e) => return Err(e.into()), - }; - let value = buf.inner()?.to_serde(); - Ok(Some(value)) + let t = self.get_txn()?; + let result: Option = t + .query_row( + "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", + [&StoredUuid(uuid)], + |r| r.get("data"), + ) + .optional()?; + + // Get task from "stored" wrapper + Ok(result.map(|t| t.0)) } fn create_task(&mut self, uuid: Uuid) -> anyhow::Result { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - match kvtxn.get(bucket, uuid.into()) { - Err(Error::NotFound) => { - kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(TaskMap::new())?)?; - Ok(true) - } - Err(e) => Err(e.into()), - Ok(_) => Ok(false), + let t = self.get_txn()?; + let count: usize = t.query_row( + "SELECT count(uuid) FROM tasks WHERE uuid = ?", + [&StoredUuid(uuid)], + |x| x.get(0), + )?; + if count > 0 { + return Ok(false); } + + let data = TaskMap::default(); + t.execute( + "INSERT INTO tasks (uuid, data) VALUES (?, ?)", + params![&StoredUuid(uuid), &StoredTaskMap(data)], + ) + .context("Create task query")?; + Ok(true) } fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> anyhow::Result<()> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - kvtxn.set(bucket, uuid.into(), Msgpack::to_value_buf(task)?)?; + let t = self.get_txn()?; + t.execute( + "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", + params![&StoredUuid(uuid), &StoredTaskMap(task)], + ) + .context("Update task query")?; Ok(()) } fn delete_task(&mut self, uuid: Uuid) -> anyhow::Result { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - match kvtxn.del(bucket, uuid.into()) { - Err(Error::NotFound) => Ok(false), - Err(e) => Err(e.into()), - Ok(_) => Ok(true), - } + let t = self.get_txn()?; + let changed = t + .execute("DELETE FROM tasks WHERE uuid = ?", [&StoredUuid(uuid)]) + .context("Delete task query")?; + Ok(changed > 0) } fn all_tasks(&mut self) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - let all_tasks: Result, Error> = kvtxn - .read_cursor(bucket)? - .iter() - .map(|(k, v)| Ok((k.into(), v.inner()?.to_serde()))) - .collect(); - Ok(all_tasks?) + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT uuid, data FROM tasks")?; + let rows = q.query_map([], |r| { + let uuid: StoredUuid = r.get("uuid")?; + let data: StoredTaskMap = r.get("data")?; + Ok((uuid.0, data.0)) + })?; + + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn all_task_uuids(&mut self) -> anyhow::Result> { - let bucket = self.tasks_bucket(); - let kvtxn = self.kvtxn(); - Ok(kvtxn - .read_cursor(bucket)? - .iter() - .map(|(k, _)| k.into()) - .collect()) + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT uuid FROM tasks")?; + let rows = q.query_map([], |r| { + let uuid: StoredUuid = r.get("uuid")?; + Ok(uuid.0) + })?; + + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn base_version(&mut self) -> anyhow::Result { - let bucket = self.uuids_bucket(); - let base_version = match self.kvtxn().get(bucket, BASE_VERSION.into()) { - Ok(buf) => buf, - Err(Error::NotFound) => return Ok(DEFAULT_BASE_VERSION), - Err(e) => return Err(e.into()), - } - .inner()? - .to_serde(); - Ok(base_version as VersionId) + let t = self.get_txn()?; + + let version: Option = t + .query_row( + "SELECT value FROM sync_meta WHERE key = 'base_version'", + [], + |r| r.get("value"), + ) + .optional()?; + Ok(version.map(|u| u.0).unwrap_or(DEFAULT_BASE_VERSION)) } fn set_base_version(&mut self, version: VersionId) -> anyhow::Result<()> { - let uuids_bucket = self.uuids_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.set( - uuids_bucket, - BASE_VERSION.into(), - Msgpack::to_value_buf(version as Uuid)?, - )?; + let t = self.get_txn()?; + t.execute( + "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)", + params!["base_version", &StoredUuid(version)], + ) + .context("Set base version")?; Ok(()) } fn operations(&mut self) -> anyhow::Result> { - let bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); - let all_ops: Result, Error> = kvtxn - .read_cursor(bucket)? - .iter() - .map(|(i, v)| Ok((i.into(), v.inner()?.to_serde()))) - .collect(); - let mut all_ops = all_ops?; - // sort by key.. - all_ops.sort_by(|a, b| a.0.cmp(&b.0)); - // and return the values.. - Ok(all_ops.iter().map(|(_, v)| v.clone()).collect()) + let t = self.get_txn()?; + + let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; + let rows = q.query_map([], |r| { + let data: Operation = r.get("data")?; + Ok(data) + })?; + + let mut ret = vec![]; + for r in rows { + ret.push(r?); + } + Ok(ret) } fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { - let numbers_bucket = self.numbers_bucket(); - let operations_bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); + let t = self.get_txn()?; - let next_op = match kvtxn.get(numbers_bucket, NEXT_OPERATION.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 0, - Err(e) => return Err(e.into()), - }; - - kvtxn.set( - operations_bucket, - next_op.into(), - Msgpack::to_value_buf(op)?, - )?; - kvtxn.set( - numbers_bucket, - NEXT_OPERATION.into(), - Msgpack::to_value_buf(next_op + 1)?, - )?; + t.execute("INSERT INTO operations (data) VALUES (?)", params![&op]) + .context("Add operation query")?; Ok(()) } fn set_operations(&mut self, ops: Vec) -> anyhow::Result<()> { - let numbers_bucket = self.numbers_bucket(); - let operations_bucket = self.operations_bucket(); - let kvtxn = self.kvtxn(); + let t = self.get_txn()?; + t.execute("DELETE FROM operations", []) + .context("Clear all existing operations")?; + t.execute("DELETE FROM sqlite_sequence WHERE name = 'operations'", []) + .context("Clear all existing operations")?; - kvtxn.clear_db(operations_bucket)?; - - let mut i = 0u64; - for op in ops { - kvtxn.set(operations_bucket, i.into(), Msgpack::to_value_buf(op)?)?; - i += 1; + for o in ops { + self.add_operation(o)?; } - - kvtxn.set( - numbers_bucket, - NEXT_OPERATION.into(), - Msgpack::to_value_buf(i)?, - )?; - Ok(()) } fn get_working_set(&mut self) -> anyhow::Result>> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); + let t = self.get_txn()?; - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; + let mut q = t.prepare("SELECT id, uuid FROM working_set ORDER BY id ASC")?; + let rows = q + .query_map([], |r| { + let id: usize = r.get("id")?; + let uuid: StoredUuid = r.get("uuid")?; + Ok((id, uuid.0)) + }) + .context("Get working set query")?; - let mut res = Vec::with_capacity(next_index as usize); - for _ in 0..next_index { - res.push(None) + let rows: Vec> = rows.collect(); + let mut res = Vec::with_capacity(rows.len()); + for _ in 0..self.get_next_working_set_number().context("Getting working set number")? { + res.push(None); + } + for r in rows { + let (id, uuid) = r?; + res[id as usize] = Some(uuid); } - for (i, u) in kvtxn.read_cursor(working_set_bucket)?.iter() { - let i: u64 = i.into(); - res[i as usize] = Some(u.inner()?.to_serde()); - } Ok(res) } fn add_to_working_set(&mut self, uuid: Uuid) -> anyhow::Result { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); + let t = self.get_txn()?; - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; + let next_working_id = self.get_next_working_set_number()?; - kvtxn.set( - working_set_bucket, - next_index.into(), - Msgpack::to_value_buf(uuid)?, - )?; - kvtxn.set( - numbers_bucket, - NEXT_WORKING_SET_INDEX.into(), - Msgpack::to_value_buf(next_index + 1)?, - )?; - Ok(next_index as usize) + t.execute( + "INSERT INTO working_set (id, uuid) VALUES (?, ?)", + params![next_working_id, &StoredUuid(uuid)], + ) + .context("Create task query")?; + + Ok(next_working_id) } fn set_working_set_item(&mut self, index: usize, uuid: Option) -> anyhow::Result<()> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - let index = index as u64; - - let next_index = match kvtxn.get(numbers_bucket, NEXT_WORKING_SET_INDEX.into()) { - Ok(buf) => buf.inner()?.to_serde(), - Err(Error::NotFound) => 1, - Err(e) => return Err(e.into()), - }; - - if index < 1 || index >= next_index { - anyhow::bail!("Index {} is not in the working set", index); + let t = self.get_txn()?; + match uuid { + // Add or override item + Some(uuid) => t.execute( + "INSERT OR REPLACE INTO working_set (id, uuid) VALUES (?, ?)", + params![index, &StoredUuid(uuid)], + ), + // Setting to None removes the row from database + None => t.execute("DELETE FROM working_set WHERE id = ?", [index]), } - - if let Some(uuid) = uuid { - kvtxn.set( - working_set_bucket, - index.into(), - Msgpack::to_value_buf(uuid)?, - )?; - } else { - match kvtxn.del(working_set_bucket, index.into()) { - Ok(_) => {} - Err(Error::NotFound) => {} - Err(e) => return Err(e.into()), - }; - } - + .context("Set working set item query")?; Ok(()) } fn clear_working_set(&mut self) -> anyhow::Result<()> { - let working_set_bucket = self.working_set_bucket(); - let numbers_bucket = self.numbers_bucket(); - let kvtxn = self.kvtxn(); - - kvtxn.clear_db(working_set_bucket)?; - kvtxn.set( - numbers_bucket, - NEXT_WORKING_SET_INDEX.into(), - Msgpack::to_value_buf(1)?, - )?; - + let t = self.get_txn()?; + t.execute("DELETE FROM working_set", []) + .context("Clear working set query")?; Ok(()) } fn commit(&mut self) -> anyhow::Result<()> { - if let Some(kvtxn) = self.txn.take() { - kvtxn.commit()?; - } else { - panic!("transaction already committed"); - } + let t = self + .txn + .take() + .ok_or(SqliteError::TransactionAlreadyCommitted)?; + t.commit().context("Committing transaction")?; Ok(()) } } @@ -360,10 +353,58 @@ mod test { use crate::storage::taskmap_with; use tempfile::TempDir; + #[test] + fn test_empty_dir() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let non_existant = tmp_dir.path().join("subdir"); + let mut storage = SqliteStorage::new(&non_existant)?; + let uuid = Uuid::new_v4(); + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid)?); + txn.commit()?; + } + { + let mut txn = storage.txn()?; + let task = txn.get_task(uuid)?; + assert_eq!(task, Some(taskmap_with(vec![]))); + } + Ok(()) + } + + #[test] + fn drop_transaction() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid1)?); + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + assert!(txn.create_task(uuid2)?); + std::mem::drop(txn); // Unnecessary explicit drop of transaction + } + + { + let mut txn = storage.txn()?; + let uuids = txn.all_task_uuids()?; + + assert_eq!(uuids, [uuid1]); + } + + Ok(()) + } + #[test] fn test_create() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -381,7 +422,7 @@ mod test { #[test] fn test_create_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -399,7 +440,7 @@ mod test { #[test] fn test_get_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -412,7 +453,7 @@ mod test { #[test] fn test_set_task() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -433,7 +474,7 @@ mod test { #[test] fn test_delete_task_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -445,7 +486,7 @@ mod test { #[test] fn test_delete_task_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -462,7 +503,7 @@ mod test { #[test] fn test_all_tasks_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; let tasks = txn.all_tasks()?; @@ -474,7 +515,7 @@ mod test { #[test] fn test_all_tasks_and_uuids() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); { @@ -528,7 +569,7 @@ mod test { #[test] fn test_base_version_default() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); @@ -539,7 +580,7 @@ mod test { #[test] fn test_base_version_setting() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let u = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -556,7 +597,7 @@ mod test { #[test] fn test_operations() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); let uuid3 = Uuid::new_v4(); @@ -620,7 +661,7 @@ mod test { #[test] fn get_working_set_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; { let mut txn = storage.txn()?; @@ -634,7 +675,7 @@ mod test { #[test] fn add_to_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -654,86 +695,10 @@ mod test { Ok(()) } - #[test] - fn set_working_set_item() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.set_working_set_item(1, Some(uuid2))?; - txn.set_working_set_item(2, None)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid2), None]); - } - - Ok(()) - } - - #[test] - fn set_working_set_item_nonexistent() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.set_working_set_item(1, None)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - // set it to None again, to check idempotency - txn.set_working_set_item(1, None)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, None]); - } - - Ok(()) - } - - #[test] - fn set_working_set_item_zero() -> anyhow::Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; - let uuid1 = Uuid::new_v4(); - - let mut txn = storage.txn()?; - assert!(txn.set_working_set_item(0, Some(uuid1)).is_err()); - - Ok(()) - } - #[test] fn clear_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = KvStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -760,4 +725,53 @@ mod test { Ok(()) } + + #[test] + fn set_working_set_item() -> anyhow::Result<()> { + let tmp_dir = TempDir::new()?; + let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + { + let mut txn = storage.txn()?; + txn.add_to_working_set(uuid1)?; + txn.add_to_working_set(uuid2)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); + } + + // Clear one item + { + let mut txn = storage.txn()?; + txn.set_working_set_item(1, None)?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid2)]); + } + + // Override item + { + let mut txn = storage.txn()?; + txn.set_working_set_item(2, Some(uuid1))?; + txn.commit()?; + } + + { + let mut txn = storage.txn()?; + let ws = txn.get_working_set()?; + assert_eq!(ws, vec![None, None, Some(uuid1)]); + } + + Ok(()) + } }