From 6c54873ca2e01035137cbce425fb346f785f045a Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 28 Apr 2025 18:53:21 +0200 Subject: [PATCH] init --- .gitignore | 5 + .woodpecker/test.yml | 9 + Cargo.lock | 2730 ++++++++++++++++++++++++++++++++++ Cargo.toml | 27 + Dockerfile | 17 + README.md | 39 + examples/basic.rs | 81 + examples/basic_global.rs | 81 + examples/blob.rs | 17 + examples/friends.rs | 80 + examples/loan.rs | 55 + examples/parent.rs | 29 + examples/references.rs | 61 + examples/stock.rs | 43 + examples/watch.rs | 61 + owl_macro/Cargo.lock | 70 + owl_macro/Cargo.toml | 14 + owl_macro/src/lib.rs | 215 +++ src/cli.rs | 59 + src/db/field.rs | 102 ++ src/db/id.rs | 63 + src/db/mod.rs | 394 +++++ src/db/model/file.rs | 98 ++ src/db/model/location.rs | 36 + src/db/model/mail_address.rs | 17 + src/db/model/mod.rs | 6 + src/db/model/person.rs | 94 ++ src/db/model/phone_number.rs | 14 + src/db/model/vehicle.rs | 16 + src/db/relation.rs | 589 ++++++++ src/db/store.rs | 118 ++ src/lib.rs | 110 ++ src/main.rs | 128 ++ tests/basic.rs | 24 + 34 files changed, 5502 insertions(+) create mode 100644 .gitignore create mode 100644 .woodpecker/test.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 examples/basic.rs create mode 100644 examples/basic_global.rs create mode 100644 examples/blob.rs create mode 100644 examples/friends.rs create mode 100644 examples/loan.rs create mode 100644 examples/parent.rs create mode 100644 examples/references.rs create mode 100644 examples/stock.rs create mode 100644 examples/watch.rs create mode 100644 owl_macro/Cargo.lock create mode 100644 owl_macro/Cargo.toml create mode 100644 owl_macro/src/lib.rs create mode 100644 src/cli.rs create mode 100644 src/db/field.rs create mode 100644 src/db/id.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/model/file.rs create mode 100644 src/db/model/location.rs create mode 100644 src/db/model/mail_address.rs create mode 100644 src/db/model/mod.rs create mode 100644 src/db/model/person.rs create mode 100644 src/db/model/phone_number.rs create mode 100644 src/db/model/vehicle.rs create mode 100644 src/db/relation.rs create mode 100644 src/db/store.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 tests/basic.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52324ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +.env +/db +/files +/owl_macro/target \ No newline at end of file diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml new file mode 100644 index 0000000..ba3467e --- /dev/null +++ b/.woodpecker/test.yml @@ -0,0 +1,9 @@ +when: + - event: push + branch: main + +steps: + - name: "Cargo Test" + image: rust:alpine + commands: + - cargo test --all diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..28054a7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2730 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "argh" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" +dependencies = [ + "argh_derive", + "argh_shared", + "rust-fuzzy-search", +] + +[[package]] +name = "argh_derive" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" +dependencies = [ + "serde", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "geo-types" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddb1950450d67efee2bbc5e429c68d052a822de3aad010d28b351fbb705224" +dependencies = [ + "approx", + "num-traits", + "serde", +] + +[[package]] +name = "geojson" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26f3c45b36fccc9cf2805e61d4da6bc4bbd5a3a9589b01afa3a40eff703bd79" +dependencies = [ + "log", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "geozero" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f28f34864745eb2f123c990c6ffd92c1584bd39439b3f27ff2a0f4ea5b309b" +dependencies = [ + "geo-types", + "geojson", + "log", + "serde_json", + "thiserror 1.0.69", + "wkt", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libm" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owl" +version = "0.1.0" +dependencies = [ + "argh", + "chrono", + "crossbeam", + "dashmap", + "env_logger", + "geozero", + "log", + "once_cell", + "owl_macro", + "parking_lot", + "rayon", + "rmp-serde", + "rmpv", + "serde", + "serde_json", + "sha2", + "sqlx", + "tokio", + "ulid", + "uuid", + "vfs", +] + +[[package]] +name = "owl_macro" +version = "0.1.0" +dependencies = [ + "convert_case", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rmpv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9" +dependencies = [ + "num-traits", + "rmp", + "serde", + "serde_bytes", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-fuzzy-search" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" +dependencies = [ + "base64", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.12", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.1", + "serde", + "web-time", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.2", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vfs" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec343ec20aa715908fd028a4b8e7c99a349d13143224222e4d61c316d1e7f0a" +dependencies = [ + "filetime", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wkt" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f7f1ff4ea4c18936d6cd26a6fd24f0003af37e951a8e0e8b9e9a2d0bd0a46d" +dependencies = [ + "geo-types", + "log", + "num-traits", + "thiserror 1.0.69", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..14e5575 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "owl" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +log = "0.4.20" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" +tokio = { version = "1.35.1", features = ["full"] } +uuid = { version = "1.8.0", features = ["v4", "serde"] } +sha2 = "0.10.8" +sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-native-tls", "derive", "uuid", "chrono", "json"] } +geozero = "0.14.0" +ulid = { version = "1.2.1", features = ["serde"] } +owl_macro = { path = "./owl_macro" } +vfs = "0.12.1" +dashmap = "6.1.0" +rayon = "1.10.0" +argh = "0.1.13" +rmp-serde = "1.3.0" +rmpv = { version = "1.3.0", features = ["serde", "with-serde"] } +env_logger = "0.11.8" +parking_lot = { version = "0.12.3", features = ["send_guard"] } +crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } +once_cell = "1.21.3" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b17dc4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM rust:buster as builder + +COPY . /app +WORKDIR /app + +RUN cargo build --release + +FROM debian:buster + +RUN apt update && apt upgrade -y +RUN apt install -y ca-certificates openssl + +COPY --from=builder /app/target/release/owl /owl + +WORKDIR / + +CMD ["/owl"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc20d45 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# 🦉 owl +owl provides a model based database with references and relations. + +## Example +Simple embedded database: + +```rust +use owl::prelude::*; + +#[model] +#[derive(Debug)] +pub struct Item { + pub id: Id, + pub cost: f64, + pub strength: f64 +} + +pub fn main() { + // Init + let db = Database::in_memory(); + + // Save + let item = Item { + id: Id::new_ulid(), + cost: 1.20, + strength: 0.4, + }; + + dbg!(&item); + db.save(&item); + + // Get + let i: Item = db.get(&item.id.to_string()); + + dbg!(i); +} +``` + +For more usage examples look at the `./examples` directory. diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..dd81e6b --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,81 @@ +use owl::prelude::*; + +#[model] +#[derive(Debug)] +pub struct Item { + pub id: Id, + pub cost: f64, + pub strength: f64, +} + +pub fn main() { + // Init + let db = Database::in_memory(); + + // Save + let item = Item { + id: Id::new_ulid(), + cost: 1.80, + strength: 0.4, + }; + + let first_id = item.id.clone(); + + dbg!(&item); + let item = db.save(item); + + // Get + let i: Model = db.get(item.read().id.clone()).unwrap(); + + dbg!(&i.read()); + + db.save(Item { + id: Id::new_ulid(), + cost: 0.3, + strength: 2.4, + }); + + db.save(Item { + id: Id::new_ulid(), + cost: 3.4, + strength: 0.4, + }); + + db.save(Item { + id: Id::new_ulid(), + cost: 20.0, + strength: 200.5, + }); + + db.save(Item { + id: Id::new_ulid(), + cost: 4.2, + strength: 4.2, + }); + + // Query + let res = db.query(|x: &Item| x.cost > 1.5); + dbg!(&res + .into_iter() + .map(|x| format!("{:?}", x.read())) + .collect::>()); + + // Update + db.update(&mut db.query(|x: &Item| x.cost > 1.5), |x: &mut Item| { + x.cost += 1.0; + }); + + let item: Model = db.get(first_id.to_string().as_str()).unwrap(); + dbg!(&item.read()); + assert_eq!(item.read().cost, 2.80); + + // Aggregates + let count = db.query(|x: &Item| x.cost > 1.5).len(); + let sum: f64 = db + .query(|x: &Item| x.cost > 1.5) + .iter() + .map(|x| x.read().cost) + .sum(); + dbg!(count); + dbg!(sum); +} diff --git a/examples/basic_global.rs b/examples/basic_global.rs new file mode 100644 index 0000000..eaf493e --- /dev/null +++ b/examples/basic_global.rs @@ -0,0 +1,81 @@ +use owl::{get, prelude::*, query, save, set_global_db, update}; + +#[model] +#[derive(Debug)] +pub struct Item { + pub id: Id, + pub cost: f64, + pub strength: f64, +} + +pub fn main() { + // Init + let db = Database::in_memory(); + set_global_db!(db); + + // Save + let item = Item { + id: Id::new_ulid(), + cost: 1.80, + strength: 0.4, + }; + + let first_id = item.id.clone(); + + dbg!(&item); + let item = save!(item); + + // Get + let i: Model = get!(item.read().id.clone()).unwrap(); + + dbg!(&i.read()); + + save!(Item { + id: Id::new_ulid(), + cost: 0.3, + strength: 2.4, + }); + + save!(Item { + id: Id::new_ulid(), + cost: 3.4, + strength: 0.4, + }); + + save!(Item { + id: Id::new_ulid(), + cost: 20.0, + strength: 200.5, + }); + + save!(Item { + id: Id::new_ulid(), + cost: 4.2, + strength: 4.2, + }); + + // Query + let res = query!(|x: &Item| x.cost > 1.5); + dbg!(&res + .into_iter() + .map(|x| format!("{:?}", x.read())) + .collect::>()); + + // Update + update!(&mut query!(|x: &Item| x.cost > 1.5), |x: &mut Item| { + x.cost += 1.0; + }); + + let item: Model = get!(first_id.to_string().as_str()).unwrap(); + dbg!(&item.read()); + assert_eq!(item.read().cost, 2.80); + + // Aggregates + let count = query!(|x: &Item| x.cost > 1.5).len(); + let sum: f64 = query!(|x: &Item| x.cost > 1.5) + .iter() + .map(|x| x.read().cost) + .sum(); + dbg!(count); + dbg!(sum); +} diff --git a/examples/blob.rs b/examples/blob.rs new file mode 100644 index 0000000..78286c9 --- /dev/null +++ b/examples/blob.rs @@ -0,0 +1,17 @@ +use owl::{db::model::file::File, prelude::*, Identifiable}; + +pub fn main() { + // Init + let db = Database::in_memory(); + + let f = File::new( + include_bytes!("../Cargo.toml").to_vec(), + Some("Cargo.toml".to_string()), + &db, + ); + dbg!(&f); + + let f: Model = db.get(f.id()).unwrap(); + let data = String::from_utf8(f.read_file(&db)).unwrap(); + println!("Content: {data}"); +} diff --git a/examples/friends.rs b/examples/friends.rs new file mode 100644 index 0000000..254bd4a --- /dev/null +++ b/examples/friends.rs @@ -0,0 +1,80 @@ +use owl::{ + db::{ + model::person::Person, + relation::{clean_graph_traversal, find_path, get_other}, + }, + prelude::*, +}; + +#[relation("friend", Person, "friendy", Person, RelationKind::Unidirectional)] +pub struct Friendship; + +pub fn main() { + env_logger::init(); + + let db = Database::filesystem("./db"); + let alice = Person::new_id("alice", "", ""); + let alice = db.save(alice); + let bob = Person::new_id("bob", "", ""); + let bob = db.save(bob); + + Friendship {}.add(&alice, &bob, None, &db); + + let charizard = Person::new_id("charizard", "", ""); + let charizard = db.save(charizard); + + Friendship.add(&alice, &charizard, None, &db); + Friendship.add(&charizard, &bob, None, &db); + + let pika = db.save(Person::new_id("pika", "", "")); + + Friendship.add(&pika, &charizard, None, &db); + + let malice = db.save(Person::new_id("malice", "", "")); + + Friendship.add(&pika, &malice, None, &db); + + let drache = db.save(Person::new_id("drache", "", "")); + Friendship.add(&drache, &bob, None, &db); + Friendship.add(&drache, &malice, None, &db); + + let enid = db.save(Person::new_id("enid", "", "")); + + Friendship.add(&enid, &alice, None, &db); + + print_friends("person::alice", &db); + print_friends("person::bob", &db); + print_friends("person::charizard", &db); + print_friends("person::drache", &db); + print_friends("person::enid", &db); + print_friends("person::malice", &db); + + println!( + "alice to malice? - {:?}", + clean_graph_traversal( + "person::alice", + &find_path( + "person::alice".into(), + "person::malice".into(), + 6, + |id, db| get_friends_of(id, &db), + &db + ) + ) + ); +} + +pub fn print_friends(id: &str, db: &Database) { + let other: Vec<_> = get_friends_of(id, db); + println!("friends of {id} -- {other:?}"); +} + +pub fn get_friends_of>>(id: T, db: &Database) -> Vec> { + let id: IdRef = id.into(); + let refs = Friendship::get_friend_of(id.to_string(), db); + dbg!(&refs); + get_other::(id, refs, db) + .into_iter() + .map(|x| IdRef::::from(&x)) + .collect() +} diff --git a/examples/loan.rs b/examples/loan.rs new file mode 100644 index 0000000..30924f8 --- /dev/null +++ b/examples/loan.rs @@ -0,0 +1,55 @@ +use owl::{db::model::person::Person, prelude::*}; +#[relation("receiver", Person, "payer", Person, RelationKind::Bidirectional)] +pub struct LoanRelation { + debt: f64, +} + +pub fn lend_money(who: &Model, from: &Model, amount: f64, db: &Database) { + LoanRelation { debt: amount }.add(who, from, None, db) +} + +pub fn pay_off(who: &Model, to: &Model, amount: f64, db: &Database) { + let mut loan = LoanRelation::get(who, to, db).unwrap(); + loan.write(db, |loan| { + loan.alter_meta(|x: &mut _| { + x.debt -= amount; + }); + }); +} + +pub fn main() { + env_logger::init(); + + let db = Database::filesystem("./db"); + let p = db.save(Person::new_id("myperson", "first", "last")); + let p2 = db.save(Person::new_id("secperson", "second", "last")); + let banker = db.save(Person::new_id("banker", "boss", "bank")); + + lend_money(&p, &banker, 250.0, &db); + + lend_money(&p2, &p, 100.0, &db); + + lend_money(&p2, &banker, 150.0, &db); + + let financer: Vec<_> = LoanRelation::get_payer_of(&p2, &db) + .into_iter() + .map(|x| LoanRelation::payer(&x.dereference(&db), &db)) + .map(|x| x.read().first_name.current().cloned().unwrap()) + .collect(); + println!( + "{} is financed by {:?}", + p2.read().first_name.current().unwrap(), + financer + ); + + let brokers = LoanRelation::get_receiver_of(&p, &db); + dbg!(&brokers); + let brokers = dereference(&brokers, &db); + + brokers.iter().for_each(|x| println!("{:?}", x.read())); + + pay_off(&p2, &p, 100.0, &db); + + let brokers = dereference(&LoanRelation::get_receiver_of(&p, &db), &db); + brokers.iter().for_each(|x| println!("{:?}", x.read())); +} diff --git a/examples/parent.rs b/examples/parent.rs new file mode 100644 index 0000000..154044d --- /dev/null +++ b/examples/parent.rs @@ -0,0 +1,29 @@ +use owl::{db::model::person::Person, prelude::*}; + +#[relation("parent", Person, "child", Person, RelationKind::Unidirectional)] +pub struct ParentRelation; + +pub fn main() { + let db = Database::in_memory(); + let p = Person::new_id("myperson", "first", "last"); + db.save(p); + let p: Model = db.get("myperson").unwrap(); + + let p2 = Person::new_id("secperson", "second", "last"); + let p2 = db.save(p2); + + ParentRelation.add(&p, &p2, None, &db); + + let children_of = ParentRelation::get_child_of(&p, &db); + dbg!(&children_of); + + let children_of = dereference(&children_of, &db); + children_of + .iter() + .for_each(|x| println!("child: {:?}", x.read())); + + let my_parents = dereference(&ParentRelation::get_parent_of(&p2, &db), &db); + my_parents + .iter() + .for_each(|x| println!("parent: {:?}", x.read())); +} diff --git a/examples/references.rs b/examples/references.rs new file mode 100644 index 0000000..1b13f17 --- /dev/null +++ b/examples/references.rs @@ -0,0 +1,61 @@ +use owl::{db::model::person::Person, prelude::*}; + +#[model] +#[derive(Debug)] +pub struct Car { + pub id: Id, + pub price: u32, + pub driver: IdRef, +} + +pub fn main() { + env_logger::init(); + + // Init + let db = Database::in_memory(); + + let per = Person::new_id("perso", "P1", ""); + let per2: Person = Person::new_id("perso2", "P2", ""); + + // Save + let car = Car { + id: Id::new_ulid(), + price: 1000, + driver: per.reference(), + }; + db.save(per); + let per2 = db.save(per2); + let mut car = db.save(car); + + println!( + "P1 has {} cars", + db.query(|car: &Car| { + car.driver + .dereference(&db) + .read() + .first_name + .current() + .unwrap() + .as_str() + == "P1" + }) + .iter() + .count() + ); + + car.write(&db, |car| { + car.driver = per2.reference(); + }); + + println!( + "P1 has {} cars", + db.query(|car: &Car| { + car.driver + .try_dereference(&db) + .map(|x| x.read().first_name.current().unwrap().as_str() == "P1") + .unwrap_or(false) + }) + .iter() + .count() + ); +} diff --git a/examples/stock.rs b/examples/stock.rs new file mode 100644 index 0000000..f49e90d --- /dev/null +++ b/examples/stock.rs @@ -0,0 +1,43 @@ +use owl::prelude::*; + +#[model] +pub struct Stock { + pub id: Id, +} + +#[model] +pub struct Owner { + pub id: Id, +} + +#[relation("owner", Owner, "stock", Stock, RelationKind::Unidirectional)] +pub struct StockOrder { + amount: f64, +} + +pub fn main() { + env_logger::init(); + + let db = Database::filesystem("./db"); + + let o = Owner { id: Id::new_ulid() }; + let o = db.save(o); + + let apl = Stock { + id: Id::String("APL".to_string()), + }; + let apl = db.save(apl); + + StockOrder { amount: 1.0 }.add(&o, &apl, None, &db); + + for order in StockOrder::get_stock_of(&o, &db) { + let rel = db.get(order).unwrap(); + + println!( + "{} has {} {}", + StockOrder::owner(&rel, &db).read().id.to_string(), + StockOrder::meta(&rel).unwrap().amount, + StockOrder::stock(&rel, &db).read().id.to_string() + ); + } +} diff --git a/examples/watch.rs b/examples/watch.rs new file mode 100644 index 0000000..c5fc2d6 --- /dev/null +++ b/examples/watch.rs @@ -0,0 +1,61 @@ +use owl::prelude::*; + +#[model] +#[derive(Debug)] +pub struct Contact { + pub id: Id, + pub age: u16, +} + +pub fn age_one_year(p: &mut Model, db: &Database) { + p.write(db, |p| p.age += 1); +} + +pub fn main() { + env_logger::init(); + + // Init + let db = Database::in_memory(); + + let mut c1 = db.save(Contact { + id: Id::String("c1".to_string()), + age: 15, + }); + let mut c2 = db.save(Contact { + id: Id::String("c2".to_string()), + age: 17, + }); + let mut c3 = db.save(Contact { + id: Id::String("c3".to_string()), + age: 16, + }); + + let age_18_watcher = db.watch::( + |c| c.age == 18, + |c| { + println!("{} turned 18", c.id); + }, + ); + + std::thread::spawn(move || loop { + age_18_watcher.process(); + }); + + age_one_year(&mut c1, &db); + age_one_year(&mut c2, &db); + age_one_year(&mut c3, &db); + + age_one_year(&mut c1, &db); + age_one_year(&mut c2, &db); + age_one_year(&mut c3, &db); + + age_one_year(&mut c1, &db); + age_one_year(&mut c2, &db); + age_one_year(&mut c3, &db); + + age_one_year(&mut c1, &db); + age_one_year(&mut c2, &db); + age_one_year(&mut c3, &db); + + std::thread::sleep(std::time::Duration::from_secs(10)); +} diff --git a/owl_macro/Cargo.lock b/owl_macro/Cargo.lock new file mode 100644 index 0000000..b888106 --- /dev/null +++ b/owl_macro/Cargo.lock @@ -0,0 +1,70 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "owl_macro" +version = "0.1.0" +dependencies = [ + "convert_case", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" diff --git a/owl_macro/Cargo.toml b/owl_macro/Cargo.toml new file mode 100644 index 0000000..aedb7e4 --- /dev/null +++ b/owl_macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "owl_macro" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = { version = "2", features = ["full"] } +proc-macro2 = "1" +convert_case = "0.6" +heck = "0.5.0" diff --git a/owl_macro/src/lib.rs b/owl_macro/src/lib.rs new file mode 100644 index 0000000..f989319 --- /dev/null +++ b/owl_macro/src/lib.rs @@ -0,0 +1,215 @@ +extern crate proc_macro; +use convert_case::{Case, Casing}; +use heck::ToSnakeCase; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse::{Parse, ParseStream}, parse_macro_input, ItemStruct, LitStr, Path, Token}; + +#[proc_macro_attribute] +/// Create a relation. +/// +/// Syntax: `#[relation("TopName", Top, "SubName", Sub, RelationKind::*)]` +/// +/// # Examples +/// +/// ```ignore +/// #[relation("Lover", Person, "Loved", Person, RelationKind::Bidirectional)] +/// pub struct LoveRelation {} +/// ``` +pub fn relation(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as RelationArgs); + let input_struct = parse_macro_input!(input as ItemStruct); + + let struct_name = &input_struct.ident; + let role1 = &args.role1; + let role1_fn = format_ident!("get_{}_of", &args.role2); + let ty1 = &args.ty1; + let role2 = &args.role2; + let role2_fn = format_ident!("get_{}_of", &args.role1); + let ty2 = &args.ty2; + let kind = &args.kind; + + let struct_ref_name = format_ident!("{}Reference", struct_name); + + let ref_name = format!( + "{}-refs", + format_ident!("{}", struct_name).to_string().to_snake_case() + ); + + let fns = match quote!(#kind).to_string().as_str() { + "RelationKind :: Bidirectional" => { quote! { + pub fn #role1_fn(#role1: &owl::db::Model<#ty1>, db: &owl::db::Database) -> Vec> { + Self::relation().get_all_top(#role1, &*db.storage) + } + + pub fn #role2_fn(#role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Vec> { + Self::relation().get_all_sub(#role2, &*db.storage) + } + } } + "RelationKind :: Unidirectional" => { quote! { + pub fn #role1_fn>>(#role1: X, db: &owl::db::Database) -> Vec> { + Self::relation().get_all_with_t(#role1, &*db.storage) + } + + pub fn #role2_fn>>(#role2: X, db: &owl::db::Database) -> Vec> { + Self::relation().get_all_with_s(#role2, &*db.storage) + } + } } + _ => { unimplemented!() } + }; + + let relation_name = struct_name.to_string().to_case(Case::Snake); + + let expanded = quote! { + #[derive(owl::Serialize, owl::Deserialize, Default)] + #input_struct + + #[derive(owl::Deserialize, owl::Serialize, Debug)] + pub struct #struct_ref_name { + pub id: Id, + pub inner: owl::db::relation::RelationReference + } + + impl owl::Identifiable for #struct_ref_name { + fn id(&self) -> Id { + Id::String(format!("[{}->{}]", self.inner.top.id, self.inner.sub.id)) + } + + fn model_id() -> String { + #ref_name.to_string() + } + } + + impl #struct_ref_name { + pub fn alter_meta( + &mut self, + u: F + ) { + self.inner.alter_meta(u); + } + } + + impl owl::db::relation::RelationRef for #struct_ref_name { + fn top(&self) -> IdRef { + self.inner.top.clone() + } + fn sub(&self) -> IdRef { + self.inner.sub.clone() + } + + fn from_ref(r: owl::db::relation::RelationReference) -> Self { + Self { + id: owl::db::id::Id::String(r.id()), + inner: r + } + } + + fn update_meta(&mut self, weight: Option, meta: Option) { + self.inner.weight = weight; + self.inner.meta = meta; + } + } + + impl #struct_name { + pub fn relation() -> Relation<#ty1, #ty2, #struct_ref_name, #kind> { + owl::db::relation::Relation::new(#relation_name) + } + + #fns + + pub fn meta(reference: &Model<#struct_ref_name>) -> Option { + if let Some(meta) = reference.read().inner.meta.clone() { + return Some(serde_json::from_value(meta).unwrap()); + } + + None + } + + pub fn #role1(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty1> { + unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::top(reference, &db) } + } + + pub fn #role2(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty2> { + unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::sub(reference, &db) } + } + + pub fn get(#role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Option> { + unsafe { Self::relation().get(#role1, #role2, &db) } + } + + pub fn add(&self, #role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, weight: Option, db: &owl::db::Database) { + let meta = serde_json::to_value(self.clone()).ok(); + Self::relation().add(#role1, #role2, weight, meta, &db) + } + } + }; + + TokenStream::from(expanded) +} + +#[proc_macro_attribute] +/// Create a model. +/// +/// # Examples +/// +/// ```ignore +/// #[model] +/// pub struct MyTing { +/// pub id: Id +/// } +/// ``` +pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { + let input_struct = parse_macro_input!(input as ItemStruct); + + let struct_name = &input_struct.ident; + + let relation_name = struct_name.to_string().to_case(Case::Snake); + + let expanded = quote! { + #[derive(serde::Serialize, serde::Deserialize)] + #input_struct + impl owl::db::store::Saveable for #struct_name {} + + impl owl::Identifiable for #struct_name { + fn id(&self) -> owl::db::id::Id { + self.id.clone() + } + + fn model_id() -> String { + #relation_name.to_string() + } + } + }; + + TokenStream::from(expanded) +} + +struct RelationArgs { + role1: syn::Ident, + ty1: syn::Path, + role2: syn::Ident, + ty2: syn::Path, + kind: syn::Path, +} + +impl Parse for RelationArgs { + fn parse(input: ParseStream) -> syn::Result { + let role1: LitStr = input.parse()?; + input.parse::()?; + let ty1: Path = input.parse()?; + input.parse::()?; + let role2: LitStr = input.parse()?; + input.parse::()?; + let ty2: Path = input.parse()?; + input.parse::()?; + let kind: Path = input.parse()?; + + Ok(Self { + role1: syn::Ident::new(&role1.value(), role1.span()), + ty1, + role2: syn::Ident::new(&role2.value(), role2.span()), + ty2, + kind, + }) + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..947b269 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,59 @@ +use argh::FromArgs; + +#[derive(FromArgs, PartialEq, Debug)] +/// owl cli +pub struct OwlCLI { + #[argh(subcommand)] + pub nested: OwlCLICommands, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +pub enum OwlCLICommands { + List(ListCommand), + Get(GetCommand), + Store(StoreCommand), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// First subcommand. +#[argh(subcommand, name = "list")] +pub struct ListCommand { + #[argh(option)] + /// database + pub db: Option, + + #[argh(positional)] + /// collection + pub collection: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// First subcommand. +#[argh(subcommand, name = "store")] +pub struct StoreCommand { + #[argh(option)] + /// database + pub db: Option, + + #[argh(positional)] + /// collection + pub collection: String, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Second subcommand. +#[argh(subcommand, name = "get")] +pub struct GetCommand { + #[argh(option)] + /// database + pub db: Option, + + #[argh(positional)] + /// collection + pub collection: String, + + #[argh(positional)] + /// id + pub id: String, +} diff --git a/src/db/field.rs b/src/db/field.rs new file mode 100644 index 0000000..e389bd3 --- /dev/null +++ b/src/db/field.rs @@ -0,0 +1,102 @@ +use std::ops::Deref; + +use chrono::Utc; +use serde::{Deserialize, Deserializer, Serialize}; + +// historic field +#[derive(Deserialize, Serialize, Default, Debug, PartialEq)] +pub struct Historic { + pub values: Vec<(chrono::DateTime, T)>, +} + +impl Historic { + pub fn alter_or_default(&mut self, f: F) { + let mut c = if let Some(c) = self.current().cloned() { + c + } else { + T::default() + }; + f(&mut c); + self.add(c); + } +} + +impl Deref for Historic { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.current().unwrap() + } +} + +impl Historic { + pub fn new() -> Self { + Self { values: vec![] } + } + + pub fn current(&self) -> Option<&T> { + let x = self.values.iter().max_by_key(|(dt, _)| *dt)?; + + Some(&x.1) + } + + pub fn alter(&mut self, f: F) { + let mut c = self.current().unwrap().clone(); + f(&mut c); + self.add(c); + } + + pub fn add(&mut self, value: T) { + self.values.push((chrono::Utc::now(), value)); + } +} + +pub trait Historicable: Sized { + fn historic(self) -> Historic { + Historic { + values: vec![(chrono::Utc::now(), self)], + } + } +} + +impl Historicable for String {} + +/// a unique `Vec` +pub struct UniVec(Vec); + +impl UniVec { + pub fn push(&mut self, val: T) -> bool { + if self.0.iter().any(|x| *x == val) { + return false; + } + self.0.push(val); + true + } +} + +impl Serialize for UniVec { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + Vec::::serialize(&self.0, serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for UniVec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = Vec::deserialize(deserializer)?; + Ok(Self(s)) + } +} + +impl Deref for UniVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/db/id.rs b/src/db/id.rs new file mode 100644 index 0000000..50087bc --- /dev/null +++ b/src/db/id.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq)] +/// Database IDs +pub enum Id { + /// String ID + String(String), + /// ULID + ULID(ulid::Ulid), + /// UUID + UUID(uuid::Uuid), +} + +impl<'de> Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.parse::() { + Ok(ulid) => Ok(Id::ULID(ulid)), + Err(_) => match s.parse::() { + Ok(uuid) => Ok(Id::UUID(uuid)), + Err(_) => Ok(Id::String(s)), + }, + } + } +} + +impl Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Id::String(str) => str.serialize(serializer), + Id::ULID(ulid) => ulid.serialize(serializer), + Id::UUID(uuid) => uuid.serialize(serializer), + } + } +} + +impl Id { + /// Generate a new random ULID based ID + pub fn new_ulid() -> Self { + Self::ULID(ulid::Ulid::new()) + } +} + +impl Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Id::String(s) => s.clone(), + Id::ULID(ulid) => ulid.to_string(), + Id::UUID(uuid) => uuid.to_string(), + } + ) + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..96801ee --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,394 @@ +use parking_lot::{RwLock, RwLockReadGuard}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; + +use dashmap::DashMap; +use model::file::File; +use relation::{dereference, IdRef}; +use serde::{Deserialize, Serialize}; +use store::Store; + +use crate::Identifiable; + +pub mod field; +pub mod id; +pub mod model; +pub mod relation; +pub mod store; + +/// really simple query function. it iterates over every object and matches against `predicate`. +/// +/// # Examples +/// +/// ```ignore +/// let queried = query_t(|x: &MyType| { +/// x.id.to_string().starts_with("something") +/// }); +/// ``` +pub fn query_t< + T: Identifiable + Serialize + Send + Sync + for<'a> Deserialize<'a> + 'static, + F: Fn(&T) -> bool, +>( + predicate: F, + db: &Database, +) -> Vec> { + let collection = T::model_id(); + let all = Store::get_ids(&collection, &*db.storage); + let all: Vec> = all + .into_iter() + .map(|x| IdRef::new(format!("{collection}::{x}"))) + .collect(); + let all = dereference(&all, db); + all.into_iter() + .filter(|x| { + let r = x.read(); + let rf = r.deref(); + (predicate)(rf) + }) + .collect() +} + +/// Database Instance +pub struct Database { + /// Underlying storage layer + pub storage: Arc, + /// Write models with schema + pub named: bool, + /// Use memory caching + pub cached: bool, + /// Memory Cache + records: Arc>>>, + watcher: Arc>>>>, +} + +pub trait ModelObj: Send + Sync { + fn as_any(&self) -> &dyn std::any::Any; +} + +pub struct ModelWatcher { + filter: Box bool + 'static + Send + Sync>, + apply: Box, + recv: crossbeam::channel::Receiver>, +} + +impl ModelWatcher { + pub fn process(&self) { + if let Ok(val) = self.recv.recv() { + self.handle(val) + } + } + + pub fn recv(&self) -> Model { + let res = self.recv.recv().unwrap(); + res.as_any().downcast_ref::>().unwrap().clone() + } + + pub fn handle(&self, val: Box) { + log::trace!("Received watch event"); + let model: &Model = val.as_any().downcast_ref().unwrap(); + if (self.filter)(&model.read()) { + log::info!("Watcher processing {}", model.full_id()); + (self.apply)(&model.read()); + } + } + + pub fn try_process(&self) { + if let Ok(val) = self.recv.recv_timeout(std::time::Duration::from_secs(1)) { + self.handle(val); + } + } +} + +impl Clone for Database { + fn clone(&self) -> Self { + Self { + storage: Arc::clone(&self.storage), + named: self.named.clone(), + cached: self.cached.clone(), + records: Arc::clone(&self.records), + watcher: Arc::clone(&self.watcher), + } + } +} + +impl Database { + /// Create a new in memory database + pub fn in_memory() -> Self { + Self::ensure_init(Self { + storage: Arc::new(vfs::MemoryFS::new()), + cached: false, + named: false, + records: Arc::new(DashMap::new()), + watcher: Arc::new(DashMap::new()), + }) + } + + pub fn watch(&self, filter: F, then: G) -> ModelWatcher + where + T: Identifiable + 'static + Send + Sync, + F: Fn(&T) -> bool + 'static + Send + Sync, + G: Fn(&T) + 'static + Send + Sync, + { + let id = T::model_id(); + + let (send, recv) = crossbeam::channel::unbounded(); + + self.watcher.entry(id).or_default().push(send); + + ModelWatcher { + filter: Box::new(filter), + apply: Box::new(then), + recv, + } + } + + pub fn get_id Deserialize<'b>>(&self, collection: &str, id: &str) -> T { + unsafe { Store::get(collection, id, &*self.storage).unwrap() } + } + + pub fn named(mut self) -> Self { + self.named = true; + self + } + + pub fn with_cache(mut self, choice: bool) -> Self { + self.cached = choice; + self + } + + /// Query `Vec` which match `predicate` + pub fn query< + T: Identifiable + for<'b> Deserialize<'b> + Serialize + Send + Sync + 'static, + F: Fn(&T) -> bool, + >( + &self, + predicate: F, + ) -> Vec> { + query_t(predicate, self) + } + + /// Update every model in `entries` according to `u(_)` + pub fn update( + &self, + entries: &mut [Model], + u: F, + ) { + for e in entries { + e.write(self, &u); + } + } + + fn ensure_init(db: Self) -> Self { + let _ = db.storage.create_dir("/collection"); + let _ = db.storage.create_dir("/files"); + db + } + + /// Create a new fs backed database at `path` + pub fn filesystem(path: &str) -> Self { + Self::ensure_init(Self { + storage: Arc::new(vfs::PhysicalFS::new(path)), + named: false, + cached: true, + records: Arc::new(DashMap::new()), + watcher: Arc::new(DashMap::new()), + }) + } + + /// List all collections + pub fn list(&self) -> Vec { + Store::list(&*self.storage) + } + + /// List all entries in `collection` + pub fn list_entries(&self, collection: &str) -> Vec { + Store::get_ids(collection, &*self.storage) + } + + pub fn try_cached Deserialize<'b> + 'static>( + &self, + col: String, + id: String, + ) -> Option> { + // let id = id.trim_start_matches(&format!("{}::", T::model_id())); + let entry = self.records.get(&col)?; + let entry = entry.get(&id)?; + let model: &Model = entry.value().as_any().downcast_ref()?; + Some(model.clone()) + } + + pub fn save_cache( + &self, + id: String, + model: Model, + ) { + self.records + .entry(T::model_id()) + .or_default() + .insert(id, Box::new(model.clone())); + } + + /// Get a model `T` from `id` + pub fn get< + T: Identifiable + Serialize + Send + Sync + for<'b> Deserialize<'b> + 'static, + I: Into>, + >( + &self, + id: I, + ) -> Option> { + let id: IdRef = id.into(); + let col = parse_collection(&id.id).unwrap_or(T::model_id()); + log::trace!("Getting {id} as {col}"); + if self.cached { + if let Some(cached) = self.try_cached(col, id.to_string()) { + log::trace!("Returning {id} from cache"); + return Some(cached); + } + } + + log::trace!("Trying to load {id}"); + let col = parse_collection(&id.to_string()); + let col = col.unwrap_or(T::model_id()); + let m: T = unsafe { + Store::get( + &col, + &id.id.trim_start_matches(&format!("{}::", col)), + &*self.storage, + )? + }; + let model = Model { + inner: Arc::new(RwLock::new(m)), + }; + + if self.cached { + self.save_cache(id.to_string(), model.clone()); + log::trace!("Returning {id} and saved to cache"); + } + + Some(model) + } + + /// Save a raw model into the database + pub unsafe fn save_raw(&self, data: &T, collection: &str) { + Store::save(collection, data, self.named, &*self.storage); + } + + fn save_model(&self, data: &Model) { + if let Some(watchers) = self.watcher.get(&T::model_id()) { + log::trace!("Notifying watchers"); + for watch in watchers.iter() { + watch.send(Box::new(data.clone())).unwrap(); + } + } + + let data = data.read(); + + unsafe { + Store::save(&T::model_id(), data.deref(), self.named, &*self.storage); + } + } + + /// Save a model `T` into the database + pub fn save<'a, T: Serialize + Identifiable + Send + Sync + 'static>( + &'a self, + data: T, + ) -> Model { + let model = Model { + inner: Arc::new(RwLock::new(data)), + }; + self.save_model(&model); + let id = model.full_id().to_string(); + + if self.cached { + log::trace!("Saving {id} to cache"); + self.save_cache(id, model.clone()); + } + + model + } +} + +// TODO : cache eviction based on ref counts + +pub struct Model { + inner: Arc>, +} + +impl Model { + pub fn read_file(&self, db: &Database) -> Vec { + self.read().read(db) + } +} + +impl Model { + pub fn from_raw(raw: T) -> Model { + Model { + inner: Arc::new(RwLock::new(raw)), + } + } + + pub fn full_id(&self) -> String { + self.read().full_id() + } + + pub fn reference(&self) -> IdRef { + self.read().reference() + } + + pub fn read(&self) -> RwLockReadGuard { + self.inner.read() + } +} + +impl Model { + pub fn write(&mut self, db: &Database, u: F) { + let mut me = self.inner.write(); + u(me.deref_mut()); + drop(me); + db.save_model(self); + } + + /// Update the model inline without writing to `Database` + pub fn write_raw_inline(&mut self, u: F) { + let mut me = self.inner.write(); + u(me.deref_mut()); + drop(me); + } +} + +impl ModelObj for Model { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Clone for Model { + fn clone(&self) -> Self { + Self { + inner: Arc::clone(&self.inner), + } + } +} + +pub fn parse_collection(id: &str) -> Option { + if id.starts_with("[") { + return None; + } + + let splitted: Vec<_> = id.split("[").collect(); + if splitted.len() > 1 { + let cols = splitted.first().unwrap(); + let col = cols.trim_end_matches("::"); + return Some(col.to_string()); + } + + let mut splitted = id.split("::").collect::>(); + if splitted.len() == 1 { + None + } else { + splitted.pop().unwrap(); + Some(splitted.join("::")) + } +} diff --git a/src/db/model/file.rs b/src/db/model/file.rs new file mode 100644 index 0000000..275e911 --- /dev/null +++ b/src/db/model/file.rs @@ -0,0 +1,98 @@ +use std::{io::Write, process::Stdio}; + +use owl_macro::model; +use sha2::{Digest, Sha256}; + +pub use crate as owl; +use crate::{ + db::Database, + prelude::{Id, Saveable}, +}; + +fn sha256(input: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(input); + let result = hasher.finalize(); + format!("{:x}", result) +} + +/// A generic file +#[derive(Debug, Clone)] +#[model] +pub struct File { + id: Id, + pub name: Option, + pub size: usize, + pub mime: String, +} + +pub fn get_mime_type(data: &[u8]) -> String { + let mut child = std::process::Command::new("file") + .arg("--brief") + .arg("--mime-type") + .arg("-") // Read from stdin + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + { + let stdin = child.stdin.as_mut().ok_or("Failed to open stdin").unwrap(); + stdin.write_all(data).unwrap(); + } + + let output = child.wait_with_output().unwrap(); + + if !output.status.success() { + log::error!("Error getting MIME"); + } + + let mime_type = String::from_utf8(output.stdout).unwrap().trim().to_string(); + mime_type +} + +impl File { + pub fn new(data: Vec, name: Option, db: &Database) -> Self { + let hash = sha256(&data); + + let f = Self { + id: Id::String(hash.clone()), + size: data.len(), + mime: get_mime_type(&data), + name, + }; + + let first: String = hash.chars().take(2).collect(); + let second: String = hash.chars().skip(2).take(2).collect(); + + let _ = db.storage.create_dir(&format!("/files/{first}")); + let _ = db.storage.create_dir(&format!("/files/{first}/{second}")); + db.storage + .create_file(&format!("/files/{first}/{second}/{hash}")) + .unwrap() + .write(&data) + .unwrap(); + + unsafe { + f.save(false, &*db.storage); + } + + f + } + + pub fn read(&self, db: &Database) -> Vec { + let hash = &self.id.to_string(); + let first: String = hash.chars().take(2).collect(); + let second: String = hash.chars().skip(2).take(2).collect(); + + let mut buf = Vec::with_capacity(self.size as usize); + + db.storage + .open_file(&format!("/files/{first}/{second}/{hash}")) + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + + buf + } +} diff --git a/src/db/model/location.rs b/src/db/model/location.rs new file mode 100644 index 0000000..b710374 --- /dev/null +++ b/src/db/model/location.rs @@ -0,0 +1,36 @@ +use owl_macro::model; +use serde::{Deserialize, Serialize}; + +pub use crate as owl; + +use crate::db::id::Id; + +/// Represents a geographical location with an ID, coordinates, and optional address +#[derive(Debug, Clone, PartialEq)] +#[model] +pub struct Location { + pub id: Id, + /// A common name for the `Location` + pub location_name: Option, + /// Geographical coordinates of the location + pub geo: (f64, f64), + /// The street address + pub location_address: Option, + /// The city name + pub city: Option, + /// The state or province name + pub location_state: Option, + /// The postal or ZIP code + pub postal_code: Option, + /// The country name + pub country: Option, +} + +/// Represents geographical coordinates with latitude and longitude +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct GeoCoordinates { + /// The latitude coordinate + pub latitude: f64, + /// The longitude coordinate + pub longitude: f64, +} diff --git a/src/db/model/mail_address.rs b/src/db/model/mail_address.rs new file mode 100644 index 0000000..a5262b6 --- /dev/null +++ b/src/db/model/mail_address.rs @@ -0,0 +1,17 @@ +use owl_macro::model; + +pub use crate as owl; + +use crate::db::id::Id; + +/// Represents a mail address with an ID and the address itself +#[derive(Debug, Clone)] +#[model] +pub struct MailAddress { + /// Unique identifier for the mail address + pub id: Id, + /// The actual mail address + pub mail_address: String, + // The domain + pub domain: String, +} diff --git a/src/db/model/mod.rs b/src/db/model/mod.rs new file mode 100644 index 0000000..86259aa --- /dev/null +++ b/src/db/model/mod.rs @@ -0,0 +1,6 @@ +pub mod file; +pub mod location; +pub mod mail_address; +pub mod person; +pub mod phone_number; +pub mod vehicle; diff --git a/src/db/model/person.rs b/src/db/model/person.rs new file mode 100644 index 0000000..61d17bc --- /dev/null +++ b/src/db/model/person.rs @@ -0,0 +1,94 @@ +use owl_macro::model; +use serde::{Deserialize, Serialize}; + +pub use crate as owl; + +use crate::{ + db::{ + field::{Historic, Historicable}, + id::Id, + query_t, + relation::IdRef, + Database, Model, + }, + Identifiable, +}; + +use super::{location::Location, vehicle::Vehicle}; + +#[derive(Debug, PartialEq)] +#[model] +pub struct Person { + pub id: Id, + /// The person's first name + pub first_name: Historic, + /// The person's last name + pub last_name: Historic, + /// The person's date of birth. + pub date_of_birth: Option, + /// The person's gender. + pub gender: Option, + /// Associated locations + pub locations: Historic>>, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub enum Gender { + Male, + Female, + Other, +} + +impl Person { + pub fn new(first_name: &str, last_name: &str) -> Self { + Self { + id: Id::new_ulid(), + first_name: first_name.to_string().historic(), + last_name: last_name.to_string().historic(), + date_of_birth: None, + gender: None, + locations: Historic::new(), + } + } + + pub fn new_id(id: &str, first_name: &str, last_name: &str) -> Self { + Self { + id: Id::String(id.to_string()), + first_name: first_name.to_string().historic(), + last_name: last_name.to_string().historic(), + date_of_birth: None, + gender: None, + locations: Historic::new(), + } + } + + pub fn vehicles(&self, db: &Database) -> Vec> { + query_t( + |vehicle: &Vehicle| { + if let Some(owner) = &vehicle.owned_by { + if *owner == self.reference() { + return true; + } + } + + false + }, + db, + ) + } + + /* + pub async fn remove_email(&self, email: uuid::Uuid) { + Relation::remove("relate_person_email", &self.id, &email).await + }pub async fn add_email(&self, email: uuid::Uuid) { + Relation::add("relate_person_email", &self.id, &email).await + } + + pub async fn emails(&self) -> Vec { + Relation::find_relations("relate_person_email", "mail_address", &self.id).await + } + pub async fn phone_numbers(&self) -> Vec { + Relation::find_relations("relate_person_phone_number", "phone_number", &self.id).await + } + */ +} diff --git a/src/db/model/phone_number.rs b/src/db/model/phone_number.rs new file mode 100644 index 0000000..1027827 --- /dev/null +++ b/src/db/model/phone_number.rs @@ -0,0 +1,14 @@ +use owl_macro::model; + +pub use crate as owl; + +use crate::db::id::Id; + +/// Represents a phone number with an ID, the phone number itself, and the country code. +#[derive(Debug, Clone)] +#[model] +pub struct PhoneNumber { + id: Id, + number: String, + country_code: String, +} diff --git a/src/db/model/vehicle.rs b/src/db/model/vehicle.rs new file mode 100644 index 0000000..8a42ab6 --- /dev/null +++ b/src/db/model/vehicle.rs @@ -0,0 +1,16 @@ +use owl_macro::model; + +pub use crate as owl; + +use crate::db::{id::Id, relation::IdRef}; + +use super::person::Person; + +/// A vehicle +#[derive(Debug)] +#[model] +pub struct Vehicle { + pub id: Id, + pub owned_by: Option>, + pub license_plate: Option, +} diff --git a/src/db/relation.rs b/src/db/relation.rs new file mode 100644 index 0000000..da1f14a --- /dev/null +++ b/src/db/relation.rs @@ -0,0 +1,589 @@ +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt::{Debug, Display}; + +use crate::Identifiable; + +use super::{ + id::Id, + store::{Saveable, Store}, + Database, Model, +}; + +#[allow(non_snake_case)] +pub mod RelationKind { + /// Unidirectional relation kind + /// + /// The relation is the same for each node. + pub struct Unidirectional; + + /// Unidirectional relation kind + /// + /// Relations can be directed. + pub struct Bidirectional; +} + +/// A relation between `T` and `S` with kind `U`. +/// Additionally with `weight` and `meta` values. +pub struct Relation { + pub name: String, + pub top: std::marker::PhantomData, + pub sub: std::marker::PhantomData, + pub uni: std::marker::PhantomData, + pub references: std::marker::PhantomData, + pub weight: Option, + pub meta: Option, +} + +impl Relation { + /// Create a new relation with `name` + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + weight: None, + meta: None, + references: std::marker::PhantomData, + top: std::marker::PhantomData, + sub: std::marker::PhantomData, + uni: std::marker::PhantomData, + } + } +} + +#[derive(PartialEq)] +/// A typed reference type +pub struct IdRef { + pub id: String, + model: std::marker::PhantomData, +} + +impl Debug for IdRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("IdRef").field(&self.id).finish() + } +} + +impl Clone for IdRef { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + model: self.model.clone(), + } + } +} + +unsafe impl Send for IdRef {} +unsafe impl Sync for IdRef {} + +impl Display for IdRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.id) + } +} + +impl Deserialize<'a>> From for IdRef { + fn from(value: String) -> Self { + IdRef::new(value) + } +} + +impl Deserialize<'a>> From<&str> for IdRef { + fn from(value: &str) -> Self { + IdRef::new(value.to_string()) + } +} + +impl Deserialize<'a>> From for IdRef { + fn from(value: Id) -> Self { + IdRef::new(value.to_string()) + } +} + +impl Deserialize<'a>, X: for<'a> Deserialize<'a>> From<&IdRef> for IdRef { + fn from(value: &IdRef) -> Self { + IdRef::new(value.id.clone()) + } +} + +impl Deserialize<'a>> From<&Model> for IdRef { + fn from(value: &Model) -> Self { + IdRef::new(value.full_id()) + } +} + +impl Deserialize<'a>> From<&T> for IdRef { + fn from(value: &T) -> Self { + IdRef::new(value.full_id()) + } +} + +impl<'de, T> Deserialize<'de> for IdRef { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(IdRef { + id: s, + model: std::marker::PhantomData, + }) + } +} + +impl Serialize for IdRef { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.id.serialize(serializer) + } +} + +impl Deserialize<'a> + Send + Sync + Identifiable + Serialize + 'static> IdRef { + pub fn fetch_raw(&self, db: &Database) -> Model { + let id = self.id.clone(); + db.get::(id).unwrap() + } + + pub fn try_fetch(&self, db: &Database) -> Option> { + // todo : asset type + + Some(self.fetch_raw(db)) + } +} + +impl Deserialize<'a>> IdRef { + pub fn new(id: String) -> Self { + Self { + id, + model: std::marker::PhantomData, + } + } + + pub unsafe fn fetch_raw_vfs(&self, vfs: &dyn vfs::FileSystem) -> T { + Store::get_id(&self.id, vfs).unwrap() + } + + pub unsafe fn fetch_as Deserialize<'a>>(&self, vfs: &dyn vfs::FileSystem) -> O { + Store::get_id(&self.id, vfs).unwrap() + } +} + +impl Deserialize<'a>> IdRef { + /// Get the object this reference holds + pub unsafe fn get_raw(&self, vfs: &dyn vfs::FileSystem) -> Option { + let split: Vec<&str> = self.id.split("::").collect(); + if split.len() > 1 { + Store::get_id(&self.id, vfs) + } else { + Store::get(&T::model_id(), &self.id, vfs) + } + } + + pub fn dereference(&self, db: &Database) -> Model { + self.try_dereference(db).unwrap() + } + + pub fn try_dereference(&self, db: &Database) -> Option> { + db.get(self.id.clone()) + } +} + +impl< + T: Identifiable + Saveable + Send + Sync + 'static, + S: Identifiable + Saveable + Send + Sync + 'static, + R: RelationRef, + U, + > Relation +{ + pub unsafe fn top(reference: &Model, db: &Database) -> Model { + db.get(reference.read().top().id.clone()).unwrap() + } + + pub unsafe fn sub(reference: &Model, db: &Database) -> Model { + db.get(reference.read().sub().id.clone()).unwrap() + } + + /// Parse a relation ref id into the respective pointer structure. + /// + /// The ref id looks like: `[top_id->sub_id]` + pub fn parse_ref_id(id: &str) -> (String, String) { + // Expected format: "[top_id->sub_id]" + if let Some(stripped) = id.strip_prefix('[').and_then(|s| s.strip_suffix(']')) { + if let Some((top, sub)) = stripped.split_once("->") { + return (top.to_string(), sub.to_string()); + } + } + panic!("Invalid ref_id format: {}", id); + } + + /// Return a relation ref id for `top` and `sub` + pub fn ref_id(top: &Model, sub: &Model) -> String { + format!("[{}->{}]", top.reference(), sub.reference()) + } + + /// Return a collection id for this relation + pub fn collection_id(&self) -> String { + format!("{}-refs", self.name) + } +} + +impl< + T: Identifiable + Saveable + Send + Sync + 'static, + S: Identifiable + Saveable + Send + Sync + 'static, + R: RelationRef, + > Relation +{ + /// Get a `RelationReference` if a relation exists between `top` and `sub` + pub fn get(&self, top: &Model, sub: &Model, db: &Database) -> Option> { + let entry = db.get(Relation::::ref_id( + top, sub, + )); + let entry_rev = db.get(Relation::::ref_id( + sub, top, + )); + + if let Some(entry) = entry { + return Some(entry); + } + if let Some(entry) = entry_rev { + return Some(entry); + } + + None + } + + /// Get all `RelationReference`s with `node` + pub fn get_all_with_t>>( + &self, + node: X, + vfs: &dyn vfs::FileSystem, + ) -> Vec> { + let ids = Store::get_ids(&self.collection_id(), vfs); + let node: IdRef = node.into(); + let top_id = node.id; + ids.into_iter() + .filter_map(|x| { + let (t_id, s_id) = + Relation::::parse_ref_id(&x); + if top_id == t_id || top_id == s_id { + return Some(IdRef::new(x.clone())); + } else { + return None; + } + }) + .collect() + } + + /// Get all `RelationReference`s with `node` + pub fn get_all_with_s>>( + &self, + node: X, + vfs: &dyn vfs::FileSystem, + ) -> Vec> { + let ids = Store::get_ids(&self.collection_id(), vfs); + let node: IdRef = node.into(); + let top_id = node.id; + ids.into_iter() + .filter_map(|x| { + let (t_id, s_id) = + Relation::::parse_ref_id(&x); + if top_id == t_id || top_id == s_id { + return Some(IdRef::new(x.clone())); + } else { + return None; + } + }) + .collect() + } + + /// Check if a relation exists between `top` and `sub` + pub fn exists(&self, top: &Model, sub: &Model, vfs: &V) -> bool { + let entry: Option = unsafe { + Store::get_opt( + &self.collection_id(), + &Relation::::ref_id(&top, &sub), + vfs, + ) + }; + let entry_rev: Option = unsafe { + Store::get_opt( + &self.collection_id(), + &Relation::::ref_id(&sub, &top), + vfs, + ) + }; + if entry.is_some() || entry_rev.is_some() { + return true; + } + + false + } + + /// Add a relation for `top` and `sub` with optional `weight` and `meta` values + pub fn add( + &self, + top: &Model, + sub: &Model, + weight: Option, + meta: Option, + db: &Database, + ) { + let mut reference = self.get(&top, &sub, db).unwrap_or_else(|| { + Model::from_raw(R::from_ref(RelationReference { + top: IdRef::new(top.full_id()), + sub: IdRef::new(sub.full_id()), + uni: true, + weight: weight, + meta: None, + })) + }); + reference.write(db, |r| { + r.update_meta(weight, meta.clone()); + }); + } +} + +impl< + T: Identifiable + Saveable + Send + Sync + 'static, + S: Identifiable + Saveable + Send + Sync + 'static, + R: RelationRef, + > Relation +{ + /// Get a `RelationReference` if a relation exists between `top` and `sub` + pub unsafe fn get(&self, top: &Model, sub: &Model, db: &Database) -> Option> { + let entry = db.get(Relation::::ref_id( + top, sub, + )); + if let Some(entry) = entry { + return Some(entry); + } + + None + } + + /// Get all `RelationReference`s with `node` as top + pub fn get_all_top(&self, top: &Model, vfs: &dyn vfs::FileSystem) -> Vec> { + let ids = Store::get_ids(&self.collection_id(), vfs); + let top_id = top.full_id(); + ids.into_iter() + .filter_map(|x| { + let (t_id, _) = Relation::::parse_ref_id(&x); + if top_id == t_id { + return Some(IdRef::new(format!("{}::{x}", R::model_id()))); + } else { + return None; + } + }) + .collect() + } + + /// Get all `RelationReference`s with `node` as sub + pub fn get_all_sub(&self, sub: &Model, vfs: &dyn vfs::FileSystem) -> Vec> { + let ids = Store::get_ids(&self.collection_id(), vfs); + let sub_id = sub.full_id(); + ids.into_iter() + .filter_map(|x| { + let (_, s_id) = Relation::::parse_ref_id(&x); + if sub_id == s_id { + return Some(IdRef::new(x)); + } else { + return None; + } + }) + .collect() + } + + /// Check if a relation exists between `top` and `sub` + pub fn exists(&self, top: &Model, sub: &Model, vfs: &dyn vfs::FileSystem) -> bool { + let entry: Option = unsafe { + Store::get_opt( + &self.collection_id(), + &Relation::::ref_id(&top, &sub), + vfs, + ) + }; + if entry.is_some() { + return true; + } + + false + } + + /// Add a relation for `top` and `sub` with optional `weight` and `meta` values + pub fn add( + &self, + top: &Model, + sub: &Model, + weight: Option, + meta: Option, + db: &Database, + ) { + let mut reference = unsafe { + self.get(&top, &sub, db).unwrap_or_else(|| { + Model::from_raw(R::from_ref(RelationReference { + top: IdRef::new(top.full_id()), + sub: IdRef::new(sub.full_id()), + uni: true, + weight: weight, + meta: None, + })) + }) + }; + reference.write(db, |reference: &mut _| { + reference.update_meta(weight, meta.clone()); + }); + } +} + +pub trait RelationRef: + Identifiable + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static +{ + fn top(&self) -> IdRef; + fn sub(&self) -> IdRef; + fn from_ref(r: RelationReference) -> Self; + fn update_meta(&mut self, weight: Option, meta: Option); +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct RelationReference { + pub top: IdRef, + pub sub: IdRef, + pub uni: bool, + pub weight: Option, + pub meta: Option, +} + +impl RelationReference { + pub fn alter_meta Deserialize<'a> + Default + Serialize>( + &mut self, + u: F, + ) { + let mut meta = + serde_json::from_value(self.meta.clone().unwrap()).unwrap_or_else(|_| X::default()); + u(&mut meta); + self.meta = Some(serde_json::to_value(meta).unwrap()); + } + + pub fn id(&self) -> String { + format!("[{}->{}]", self.top.id, self.sub.id) + } + + /* + pub unsafe fn save(&self, vfs: &dyn vfs::FileSystem) { + Store::save_raw(&self.model_id_real(), &self.id(), self, false, vfs); + } + */ +} + +/// Dereference a `Vec<_>` with `IdRef`s into their objects +pub fn dereference< + T: for<'a> Deserialize<'a> + Serialize + Identifiable + Send + Sync + 'static, +>( + v: &[IdRef], + db: &Database, +) -> Vec> { + v.into_iter().map(|x| x.fetch_raw(db)).collect() +} + +pub unsafe fn dereference_raw Deserialize<'a>>(v: &[IdRef], db: &Database) -> Vec { + v.into_iter() + .map(|x| unsafe { x.fetch_raw_vfs(&*db.storage) }) + .collect() +} + +// TODO : generally clean up traversal loops +pub fn clean_graph_traversal(id: &str, route: &[IdRef]) -> Vec> { + let mut index = 0; + + for (i, node) in route.iter().enumerate() { + let node = node.id.to_string(); + if node == id { + index = i + 1; + } + } + + route[index..].to_vec() +} + +/// Find a path from `from` to `to` with max `reach` steps to get there. +/// +/// `fetch_relation` is a function for traversing the graph ( `Fn(IdRef, &Database) -> Vec>` ) +pub fn find_path( + from: IdRef, + to: IdRef, + reach: u32, + fetch_relation: F, + db: &Database, +) -> Vec> +where + F: Fn(&IdRef, &Database) -> Vec> + Sync + Send + Clone, +{ + log::info!("path from {from} r {reach}"); + let my_friends = fetch_relation(&from, db); + log::trace!("have children {my_friends:?}"); + + if reach == 0 { + log::trace!("no reach :("); + return vec![]; + } + + my_friends + .par_iter() + .filter_map(|friend| { + if friend.id == to.id { + log::trace!("found you {friend}"); + return Some(vec![friend.clone()]); + } + + log::trace!("nooo... looking deeper"); + let p = find_path( + friend.clone(), + to.clone(), + reach - 1, + fetch_relation.clone(), + db, + ); + if !p.is_empty() { + let mut ret = vec![friend.clone()]; + ret.extend(p.iter().cloned()); + return Some(ret); + } + + None + }) + .find_any(|_| true) // find any successful path in parallel + .unwrap_or_else(|| { + log::trace!("END SEARCH"); + vec![] + }) +} + +/// Get the other which isnt oneself from unidirectional `RelationReference`s +pub fn get_other< + X: Identifiable + Send + Sync + Serialize + for<'a> Deserialize<'a>, + T: Into>, + R: RelationRef, +>( + id: T, + refs: Vec>, + db: &Database, +) -> Vec> { + let mut other = Vec::new(); + let id: IdRef = id.into(); + let id = id.id; + + for r in refs { + if let Some(r) = r.try_dereference(&db) { + if r.read().top().id != id { + other.push(r.read().top()); + } else { + other.push(r.read().sub()); + } + } else { + log::error!("Could not get Model<{}> from {}", R::model_id(), r.id); + } + } + + other +} diff --git a/src/db/store.rs b/src/db/store.rs new file mode 100644 index 0000000..64094a8 --- /dev/null +++ b/src/db/store.rs @@ -0,0 +1,118 @@ +use serde::{Deserialize, Serialize}; + +use crate::Identifiable; + +fn read_to_vec(path: &str, vfs: &dyn vfs::FileSystem) -> Option> { + let mut f = vfs.open_file(path).ok()?; + let mut content = Vec::new(); + f.read_to_end(&mut content).ok()?; + Some(content) +} + +pub fn write(path: &str, data: &[u8], vfs: &dyn vfs::FileSystem) { + let mut f = vfs.create_file(path).unwrap(); + f.write_all(&data).unwrap(); +} + +pub struct Store {} + +impl Store { + pub unsafe fn get Deserialize<'a>>( + collection: &str, + id: &str, + vfs: &dyn vfs::FileSystem, + ) -> Option { + let path = std::path::Path::new(&format!("/collection/{collection}")) + .join(id) + .display() + .to_string(); + log::info!("IO READ {path}"); + let content = read_to_vec(&path, vfs)?; + rmp_serde::from_slice(&content).ok() + } + + pub unsafe fn get_opt Deserialize<'a>>( + collection: &str, + id: &str, + vfs: &dyn vfs::FileSystem, + ) -> Option { + let path = std::path::Path::new(&format!("/collection/{collection}")) + .join(id) + .display() + .to_string(); + rmp_serde::from_slice(&read_to_vec(&path, vfs)?).unwrap() + } + + pub unsafe fn save_raw( + collection: &str, + id: &str, + data: &T, + named: bool, + vfs: &dyn vfs::FileSystem, + ) { + let c = if named { + rmp_serde::to_vec_named(data).unwrap() + } else { + rmp_serde::to_vec(data).unwrap() + }; + + let path = std::path::Path::new(&format!("/collection/{collection}")) + .join(id) + .display() + .to_string(); + let _ = vfs.create_dir(&format!("/collection/{collection}")); + log::info!("IO WRITE {path}"); + write(&path, &c, vfs); + } + + pub unsafe fn save( + collection: &str, + data: &T, + named: bool, + vfs: &dyn vfs::FileSystem, + ) { + Self::save_raw(collection, &data.id().to_string(), &data, named, vfs); + } + + pub unsafe fn get_id Deserialize<'a>>( + id: &str, + vfs: &dyn vfs::FileSystem, + ) -> Option { + let split_d: Vec<_> = id.split("[").collect(); + let id = split_d.first()?; + let mut split: Vec<_> = id.split("::").collect(); + let sid: &str = split.pop()?; + let collection = split.join("::"); + let id = format!( + "{}{sid}{}", + if split_d.len() > 1 { "[" } else { "" }, + split_d + .iter() + .skip(1) + .map(|x| x.to_string()) + .collect::>() + .join("[") + ); + Store::get(&collection, &id, vfs) + } + + pub fn get_ids(collection: &str, vfs: &dyn vfs::FileSystem) -> Vec { + vfs.read_dir(&format!("/collection/{collection}")) + .unwrap() + .collect() + } + + pub fn list(vfs: &dyn vfs::FileSystem) -> Vec { + vfs.read_dir("/collection").unwrap().collect() + } +} + +pub trait Saveable: Identifiable + for<'a> Deserialize<'a> + Serialize { + unsafe fn save(&self, named: bool, vfs: &dyn vfs::FileSystem) { + Store::save(&Self::model_id(), self, named, vfs); + } + + unsafe fn get(id: &str, vfs: &dyn vfs::FileSystem) -> Option { + Store::get(&Self::model_id(), id, vfs) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f7e0dc2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,110 @@ +//mod model; + +use db::id::Id; + +pub mod db; +use once_cell::sync::OnceCell; +pub use owl_macro::model; +pub use owl_macro::relation; +pub use serde::{Deserialize, Serialize}; + +pub static DB: OnceCell = OnceCell::new(); + +/// Set a `Database` as global. +/// +/// All global macro actions are then done against that `Database`. +#[macro_export] +macro_rules! set_global_db { + ($db:ident) => { + assert!($crate::DB.set($db).is_ok()); + }; +} + +#[macro_export] +macro_rules! dereference { + ($id:ident) => { + $id.dereference(&$crate::DB.get().unwrap()) + }; + ($id:expr) => { + $id.dereference(&$crate::DB.get().unwrap()) + }; +} + +#[macro_export] +macro_rules! save { + ($model:ident) => { + $crate::DB.get().unwrap().save($model) + }; + ($model:expr) => { + $crate::DB.get().unwrap().save($model) + }; +} + +#[macro_export] +macro_rules! get { + ($id:ident) => { + $crate::DB.get().unwrap().get($id) + }; + ($id:expr) => { + $crate::DB.get().unwrap().get($id) + }; +} + +#[macro_export] +macro_rules! update { + ($ids:ident, $u:expr) => { + $crate::DB.get().unwrap().update($ids, $u) + }; + ($ids:expr, $u:expr) => { + $crate::DB.get().unwrap().update($ids, $u) + }; +} + +#[macro_export] +macro_rules! query { + ($id:ident) => { + $crate::DB.get().unwrap().query($id) + }; + ($id:expr) => { + $crate::DB.get().unwrap().query($id) + }; +} + +pub mod prelude { + pub use super::db::field::Historic; + pub use super::db::id::Id; + pub use super::db::relation::{dereference, IdRef, Relation, RelationKind}; + pub use super::db::store::{Saveable, Store}; + pub use super::db::Database; + pub use super::db::Model; + pub use super::Identifiable; + pub use super::{model, relation}; +} + +// TODO : wire format for endpoints +// TODO : auth + traceability +// TODO : deletion? +// TODO : make relations able to be inactive/active + +pub trait Identifiable: Sized + for<'a> Deserialize<'a> { + fn id(&self) -> Id; + + /// Get a typed `IdRef` for this ID + fn reference(&self) -> crate::db::relation::IdRef { + crate::db::relation::IdRef::new(self.full_id()) + } + fn model_id() -> String; + fn full_id(&self) -> String { + format!("{}::{}", Self::model_id(), self.id()) + } +} + +impl Identifiable for serde_json::Value { + fn id(&self) -> Id { + serde_json::from_value(self.as_object().unwrap().get("id").unwrap().clone()).unwrap() + } + + fn model_id() -> String { + "json".to_string() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..612bef5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,128 @@ +use std::io::Read; + +use owl::prelude::*; + +// data points +/* + +// events +struct Event { + pub ts: String, + pub content: String, + pub references: Vec, + pub attachments: Vec +} + +pub struct File(String); +*/ + +// TODO : document + +// TODO : serve api +// TODO : schemaless data storage (named) +// TODO : export/import? + +// pub static PG: OnceCell = OnceCell::const_new(); + +#[macro_export] +macro_rules! get_pg { + () => { + if let Some(client) = $crate::PG.get() { + client + } else { + let client = sqlx::postgres::PgPoolOptions::new() + .max_connections(5) + .connect(&std::env::var("DATABASE_URL").unwrap()) + .await + .unwrap(); + $crate::PG.set(client).unwrap(); + $crate::PG.get().unwrap() + } + }; +} + +/* + +#[rocket::main] +async fn main() { + let cors = rocket_cors::CorsOptions { + allowed_origins: rocket_cors::AllowedOrigins::all(), + allowed_methods: vec![Method::Get, Method::Post, Method::Options] + .into_iter() + .map(From::from) + .collect(), + allowed_headers: rocket_cors::AllowedHeaders::all(), + allow_credentials: true, + ..Default::default() + } + .to_cors() + .expect("error creating CORS options"); + + rocket::build() + .mount( + "/", + route![ + routes::model_api_create, + routes::model_api_info, + routes::model_api_update, + routes::model_overview + ], + ) + .attach(cors) + //.manage(pg) + .launch() + .await + .unwrap(); +} +*/ + +mod cli; + +pub fn main() { + let cli: cli::OwlCLI = argh::from_env(); + + match cli.nested { + cli::OwlCLICommands::List(list_command) => { + let db_path = list_command.db.unwrap_or("./db".to_string()); + let db = Database::filesystem(&db_path); + if let Some(collection) = list_command.collection { + let entries = db.list_entries(&collection); + println!("List {db_path} [{collection}]:"); + for t in entries { + println!(" - {t}"); + } + } else { + let collections = db.list(); + println!("List {db_path}:"); + for c in collections { + println!(" - {c}"); + } + } + } + cli::OwlCLICommands::Get(get_command) => { + let db_path = get_command.db.unwrap_or("./db".to_string()); + let db = Database::filesystem(&db_path); + + let x: rmpv::Value = db.get_id(&get_command.collection, &get_command.id); + println!("{}", serde_json::to_string(&x).unwrap()); + } + cli::OwlCLICommands::Store(store_command) => { + let db_path = store_command.db.unwrap_or("./db".to_string()); + let db = Database::filesystem(&db_path).named(); + + let mut buffer = String::new(); + std::io::stdin() + .read_to_string(&mut buffer) + .expect("Failed to read from stdin"); + let json_value: serde_json::Value = + serde_json::from_str(&buffer).expect("Failed to parse JSON"); + + let id = json_value.id(); + unsafe { + db.save_raw(&json_value, &store_command.collection); + } + + println!("{}::{id}", store_command.collection); + } + } +} diff --git a/tests/basic.rs b/tests/basic.rs new file mode 100644 index 0000000..7f4b2c9 --- /dev/null +++ b/tests/basic.rs @@ -0,0 +1,24 @@ +use owl::prelude::*; + +#[cfg(test)] +#[model] +pub struct TestModel { + pub id: Id, + pub data: String, +} + +#[test] +fn save_load_db() { + let data = "TestData".to_string(); + let db = Database::in_memory(); + + let m = TestModel { + id: Id::String(data.clone()), + data: data.clone(), + }; + + db.save(m); + + let get_model: Model = db.get(data.as_str()).unwrap(); + assert_eq!(get_model.read().id.to_string(), data); +}