diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9f08903 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + push: + pull_request: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + +jobs: + check: + name: Lint, Format, and Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Cache Cargo + uses: Swatinem/rust-cache@v1 + + - uses: r7kamura/rust-problem-matchers@v1 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Run Formatter + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all -- -D warnings + + - name: Build + run: cargo build --release --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7e1678a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1388 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + +[[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 = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bigdecimal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[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.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.29", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "dashmap" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lz4_flex" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "mac_address" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "object" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scylla" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b9aa2b618718b5d4873926cecb99fe181a3b8c52b4983e2b20d6d833bfa9c1b" +dependencies = [ + "arc-swap", + "async-trait", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "dashmap", + "futures", + "histogram", + "itertools", + "lz4_flex", + "num-bigint", + "num_enum", + "rand", + "rand_pcg", + "scylla-cql", + "scylla-macros", + "smallvec", + "snap", + "socket2", + "strum", + "strum_macros", + "thiserror", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "scylla-cql" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048bdf0be96308ec0f5aeed2847bb2270f53355425b3afd67e64efc99d70b3e3" +dependencies = [ + "async-trait", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "lz4_flex", + "num-bigint", + "num_enum", + "scylla-macros", + "snap", + "thiserror", + "tokio", + "uuid", +] + +[[package]] +name = "scylla-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d777dadbf7163d1524ea4f5a095146298d263a686febb96d022cf46d06df32" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scyllax" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "getrandom", + "mac_address", + "once_cell", + "scylla", + "scyllax-macros", + "thiserror", + "tracing", + "uuid", +] + +[[package]] +name = "scyllax-examples" +version = "0.0.0" +dependencies = [ + "anyhow", + "scylla", + "scyllax", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "scyllax-macros" +version = "0.1.0" +dependencies = [ + "anyhow", + "darling", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "serde" +version = "1.0.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "snap" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" + +[[package]] +name = "strum_macros" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "parking_lot", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "atomic", + "getrandom", + "rand", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1327e31 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[workspace] +members = ["examples", "scyllax", "scyllax-macros"] +resolver = "2" + +[workspace.dependencies] +scyllax = { path = "./scyllax" } +scyllax-macros = { path = "./scyllax-macros" } + +anyhow = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json", "tracing-log", "parking_lot"] } +tokio = { version = "1", features = ["full", "tracing"] } +scylla = { version = "0.9" } +serde_json = { version = "1", features = ["preserve_order"] } +serde = { version = "1", features = ["derive"] } +uuid = { version = "1", features = ["serde", "v1", "std", "fast-rng"] } +darling = "0.20" + +[profile.release] +debug = 1 + +[profile.dev.package.backtrace] +opt-level = 3 diff --git a/README.md b/README.md new file mode 100644 index 0000000..68cc6aa --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# scyllax +A SQLx and Discord inspired query system for Scylla. + +## Features +- [x] Select Queries +- [ ] Insert Queries +- [ ] Update Queries +- [ ] Delete Queries +- [ ] Request Coalescing +- [ ] Compile-time Query Validation +- [ ] Runtime Query Validation (structure matches schema) + +## Usage +See [examples](examples) for more details. + +## References +1. https://www.reddit.com/r/rust/comments/11ki2n7/a_look_at_how_discord_uses_rust_for_their_data/jb8dmrx/ + +```rs +#[read_request( + query = "select * from foo where id = ? limit 1", + entity_type = "Foo" +)] +struct GetFooById { + #[shard_key] + id: i64 +} +``` + +```rs +handle.execute_read(GetFooById { ... }).await +``` + +**Messages from Jake** + +> the answer though is that unlike the scylla rust wrapper, we don't need the fields to be in the right order for our stuff to work. +> we do 2 clever things: + +> 1) `SELECT *` is actually a lie. Never use `SELECT *` in a prepared statement, **ever**. CQL suffers from a protocol level bug that can lead to data corruption on schema change when doing a `SELECT *` due to a schema de-sync condition that is possible between the client & server. So instead, what we do is, we look at the entity type struct, and we transform `SELECT *` into `SELECT col_a, col_b, col_c`. That means if a column present in the schema, but not in the struct we're going to de-serialize to, we don't actually query it. The gist of the bug is that, when a new column is added to a table, the database may start returning data for that column, without the client being aware of that. In the pathological case, this can cause a mis-aligned deserialziation of the data. https://docs.datastax.com/en/developer/java-driver/3.0/manual/statements/prepared/#avoid-preparing-select-queries - although this does look like it's finally fixed in native protocol v5, I'm unsure if scylla is using that yet. + +> 2) For binding of the query parameters as well, we essentially parse the SQL statement and figure out all of the bind positions, and then generate code that will bind the fields in the proper order (since on the wire level, they need to be specified in the order that they're defined in the query.) We do this at compile time in a proc macro to generate the code that does the serialization of the query, so we incur no runtime overhead of re-ordering things. + +> At startup we prepare everything and also type check the structs in code against what's in the db +Registering everything manually is fine +You can make it fail at compile time +If you try to use an unregistered query diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b1fc849 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: "3.8" + +services: + scylla: + image: scylladb/scylla:latest + volumes: + - scylla_storage:/var/lib/scylla + command: + - --smp 1 + ports: + - "7000:7000" + - "7001:7001" + - "7199:7199" + - "9042:9042" + - "9160:9160" + - "9180:9180" + +volumes: + scylla_storage: diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000..5aceaad --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "scyllax-examples" +edition = "2021" +version = "0.0.0" +publish = false + +[dependencies] + +anyhow = { workspace = true } +scylla = { workspace = true } +scyllax = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +uuid = { workspace = true } + +[[example]] +name = "simple" +path = "simple.rs" diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..6f5dea1 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,69 @@ +#![allow(dead_code)] + +use std::{str::FromStr, sync::Arc}; + +use scylla::{CachingSession, FromRow, SessionBuilder, ValueList}; +use scyllax::{executor::Executor, select_query, Entity, EntityExt, SelectQuery}; +use tracing_subscriber::prelude::*; +use uuid::Uuid; + +#[derive(Debug, Entity, ValueList, FromRow, Clone)] +struct Person { + id: Uuid, + email: String, + #[rename("createdAt")] + created_at: i64, +} + +#[select_query( + query = "select * from person where id = ? limit 1", + entity_type = "Person" +)] +struct GetPersonById { + id: Uuid, +} + +async fn load(db: &mut Executor) -> anyhow::Result<()> { + tracing::info!("loading queries"); + let _ = GetPersonById::prepare(db).await; + + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let session = create_db().await?; + let mut executor = Executor::with_session(session); + load(&mut executor).await?; + + let executor = Arc::new(executor); + let query = GetPersonById { + id: Uuid::from_str("e01ea1d2-414c-11ee-be56-0242ac120002")?, + }; + let person = executor.execute_select(query).await?; + + println!("Person: {:#?}", person); + + Ok(()) +} + +pub async fn create_db() -> anyhow::Result { + let session = CachingSession::from( + SessionBuilder::new() + .known_node("127.0.0.1:9042") + .build() + .await?, + 1_000, + ); + + session.get_session().use_keyspace("scyllax", false).await?; + + Ok(session) +} diff --git a/init.cql b/init.cql new file mode 100644 index 0000000..c68fea4 --- /dev/null +++ b/init.cql @@ -0,0 +1,34 @@ +create keyspace scyllax with replication = { + 'class': 'SimpleStrategy', + 'replication_factor': 1 +}; + +use scyllax; + +create table person ( + id timeuuid primary key, + email text + -- camel case for the sake of backwards compat + createdAt timestamp +); + +insert into person(id, email, "createdAt") values (e01e84d6-414c-11ee-be56-0242ac120002, 'foo1@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e880a-414c-11ee-be56-0242ac120002, 'foo2@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e89c2-414c-11ee-be56-0242ac120002, 'foo3@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e8b2a-414c-11ee-be56-0242ac120002, 'foo4@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e8db4-414c-11ee-be56-0242ac120002, 'foo5@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e911a-414c-11ee-be56-0242ac120002, 'foo6@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e9354-414c-11ee-be56-0242ac120002, 'foo7@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e94e4-414c-11ee-be56-0242ac120002, 'foo8@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e9854-414c-11ee-be56-0242ac120002, 'foo9@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e99ee-414c-11ee-be56-0242ac120002, 'foo10@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e9b60-414c-11ee-be56-0242ac120002, 'foo11@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e9cc8-414c-11ee-be56-0242ac120002, 'foo12@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01e9e3a-414c-11ee-be56-0242ac120002, 'foo13@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01ea1d2-414c-11ee-be56-0242ac120002, 'foo14@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01ea380-414c-11ee-be56-0242ac120002, 'foo15@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01ea4f2-414c-11ee-be56-0242ac120002, 'foo16@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01ea682-414c-11ee-be56-0242ac120002, 'foo17@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01ea81c-414c-11ee-be56-0242ac120002, 'foo18@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01eab14-414c-11ee-be56-0242ac120002, 'foo19@scyllax.local', toUnixTimestamp(now())); +insert into person(id, email, "createdAt") values (e01eaca4-414c-11ee-be56-0242ac120002, 'foo20@scyllax.local', toUnixTimestamp(now())); diff --git a/scyllax-macros/Cargo.toml b/scyllax-macros/Cargo.toml new file mode 100644 index 0000000..5585adf --- /dev/null +++ b/scyllax-macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "scyllax-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +anyhow = { workspace = true } +darling = { version = "0.20", features = ["suggestions"] } +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full", "derive", "extra-traits"] } diff --git a/scyllax-macros/src/entity.rs b/scyllax-macros/src/entity.rs new file mode 100644 index 0000000..55fb507 --- /dev/null +++ b/scyllax-macros/src/entity.rs @@ -0,0 +1,42 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Expr, Field}; + +pub fn expand(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let keys = match &input.data { + Data::Struct(s) => s.fields.iter().map(get_field_name).collect::>(), + _ => panic!("Entity can only be derived for structs."), + }; + + let expanded = quote! { + impl scyllax::EntityExt<#name> for #name { + fn keys() -> Vec { + vec![#(#keys.to_string()),*] + } + } + }; + + TokenStream::from(expanded) +} + +/// This is used to get the name of a field, taking into account the `#[rename]` attribute. +fn get_field_name(field: &Field) -> String { + let rename = field.attrs.iter().find(|a| a.path().is_ident("rename")); + if let Some(rename) = rename { + let expr = rename.parse_args::().expect("Expected an expression"); + if let Expr::Lit(lit) = expr { + if let syn::Lit::Str(s) = lit.lit { + return format!(r##""{}""##, s.value()); + } + } + } + + field + .ident + .as_ref() + .expect("Expected field to have a name") + .to_string() +} diff --git a/scyllax-macros/src/lib.rs b/scyllax-macros/src/lib.rs new file mode 100644 index 0000000..a3bfc44 --- /dev/null +++ b/scyllax-macros/src/lib.rs @@ -0,0 +1,25 @@ +use proc_macro::TokenStream; + +mod entity; +mod queries; + +#[proc_macro_attribute] +pub fn select_query(args: TokenStream, input: TokenStream) -> TokenStream { + queries::select::expand(args.into(), input.into()).into() +} + +/// Implement functions for the entity. +/// ```rs, ignore +/// #[derive(Debug, Entity)] +/// struct User { +/// id: Uuid, +/// email: String, +/// #[rename("createdAt")] +/// created_at: DateTime, +/// } +/// ``` +/// `User::keys()` will return `vec!["id", "email", "createdAt"]` +#[proc_macro_derive(Entity, attributes(rename))] +pub fn entity_derive(input: TokenStream) -> TokenStream { + entity::expand(input) +} diff --git a/scyllax-macros/src/queries/mod.rs b/scyllax-macros/src/queries/mod.rs new file mode 100644 index 0000000..0c305a7 --- /dev/null +++ b/scyllax-macros/src/queries/mod.rs @@ -0,0 +1 @@ +pub mod select; diff --git a/scyllax-macros/src/queries/select.rs b/scyllax-macros/src/queries/select.rs new file mode 100644 index 0000000..079637d --- /dev/null +++ b/scyllax-macros/src/queries/select.rs @@ -0,0 +1,109 @@ +use darling::{export::NestedMeta, FromMeta}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::ItemStruct; + +fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +#[derive(FromMeta)] +pub struct SelectQueryOptions { + query: String, + entity_type: syn::Type, +} + +pub fn expand(args: TokenStream, item: TokenStream) -> TokenStream { + let attr_args = match NestedMeta::parse_meta_list(args.clone()) { + Ok(args) => args, + Err(e) => return darling::Error::from(e).write_errors(), + }; + + let args = match SelectQueryOptions::from_list(&attr_args) { + Ok(o) => o, + Err(e) => return e.write_errors(), + }; + + let query = args.query.to_string(); + let entity_type = args.entity_type; + + let input: ItemStruct = match syn::parse2(item.clone()) { + Ok(it) => it, + Err(e) => return token_stream_with_error(item, e), + }; + let struct_ident = &input.ident; + + // if entity_type is a Vec, return type is Vec + // if entity_type is not a Vec, return type is Option + let return_type = if let syn::Type::Path(path) = entity_type.clone() { + let last_segment = path.path.segments.last().unwrap(); + let ident = &last_segment.ident; + + if ident == "Vec" { + quote! { + #entity_type + } + } else { + quote! { + Option<#entity_type> + } + } + } else { + return token_stream_with_error( + item, + syn::Error::new_spanned(entity_type, "entity_type must be a path"), + ); + }; + + // if entity_type is a Vec, we need to use the macro scyllax:match_rows!(res, entity_type) + // if entity_type is not a Vec, we need to use the macro scyllax:match_row!(res, entity_type) + // eg: Vec -> scyllax:match_rows!(res, OrgEntity) + // eg: OrgEntity -> scyllax:match_row!(res, OrgEntity) + let parser = if let syn::Type::Path(path) = entity_type.clone() { + let last_segment = path.path.segments.last().unwrap(); + let ident = &last_segment.ident; + + if ident == "Vec" { + quote! { + scyllax::match_rows!(res, #path) + } + } else { + quote! { + scyllax::match_row!(res, #path) + } + } + } else { + return token_stream_with_error( + item, + syn::Error::new_spanned(entity_type, "entity_type must be a path"), + ); + }; + + quote! { + #[scyllax::async_trait] + impl scyllax::SelectQuery<#entity_type, #return_type> for #struct_ident { + fn query() -> String { + #query.replace("*", &#entity_type::keys().join(", ")) + } + + async fn prepare(db: &Executor) -> Result { + tracing::debug!("preparing query {}", stringify!(#struct_ident)); + db.session.add_prepared_statement(&scylla::query::Query::new(Self::query())).await + } + + async fn execute(self, db: &scyllax::Executor) -> anyhow::Result { + let query = Self::query(); + + db.session.execute(query, self).await + } + + async fn parse_response(res: scylla::QueryResult) -> Result<#return_type, scyllax::ScyllaxError> { + #parser + } + } + + #[derive(scylla::ValueList, std::fmt::Debug, std::clone::Clone, PartialEq, Hash)] + #input + } +} diff --git a/scyllax/Cargo.toml b/scyllax/Cargo.toml new file mode 100644 index 0000000..22287c2 --- /dev/null +++ b/scyllax/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "scyllax" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = { workspace = true } +async-trait = "0.1" +getrandom = "0.2" +mac_address = "1" +once_cell = "1" +scylla = { workspace = true } +scyllax-macros = { workspace = true } +thiserror = "1" +tracing = { workspace = true } +uuid = { workspace = true } diff --git a/scyllax/src/error.rs b/scyllax/src/error.rs new file mode 100644 index 0000000..cda26d1 --- /dev/null +++ b/scyllax/src/error.rs @@ -0,0 +1,9 @@ +#[derive(thiserror::Error, Debug)] +pub enum ScyllaxError { + #[error("Scylla Query error: {0}")] + Scylla(#[from] scylla::transport::errors::QueryError), + #[error("Scylla single row typed error: {0}")] + SingleRowTyped(#[from] scylla::transport::query_result::SingleRowTypedError), + #[error("No rows found")] + NoRowsFound, +} diff --git a/scyllax/src/executor.rs b/scyllax/src/executor.rs new file mode 100644 index 0000000..02213d4 --- /dev/null +++ b/scyllax/src/executor.rs @@ -0,0 +1,37 @@ +use crate::{EntityExt, FromRow, ScyllaxError, SelectQuery, ValueList}; +use scylla::{ + prepared_statement::PreparedStatement, query::Query, transport::errors::QueryError, + CachingSession, +}; + +/// A structure that executes queries +pub struct Executor { + pub session: CachingSession, +} + +impl Executor { + /// Creates a new [`Executor`] with a provided [`scylla::CachingSession`]. + pub fn with_session(session: CachingSession) -> Executor { + Self { session } + } + + /// Prepares a query + pub async fn prepare_query(&self, query: String) -> Result { + self.session + .add_prepared_statement(&Query::new(query)) + .await + } + + /// Executes a [`SelectQuery`] and returns the result + pub async fn execute_select< + T: EntityExt + FromRow + ValueList, + R: Clone + std::fmt::Debug + Send + Sync, + E: SelectQuery, + >( + &self, + query: E, + ) -> Result { + let res = query.execute(self).await?; + E::parse_response(res).await + } +} diff --git a/scyllax/src/lib.rs b/scyllax/src/lib.rs new file mode 100644 index 0000000..f517cad --- /dev/null +++ b/scyllax/src/lib.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use scylla::{prepared_statement::PreparedStatement, transport::errors::QueryError, QueryResult}; + +pub use async_trait::async_trait; +pub use scylla::{frame::value::ValueList, FromRow}; +pub use scyllax_macros::*; + +pub mod error; +pub mod executor; +pub mod rows; +pub mod util; + +pub use {crate::error::ScyllaxError, crate::executor::Executor}; + +/// The traits of the entity +pub trait EntityExt { + /// Returns the keys of the entity as a vector of strings, keeping the order of the keys. + fn keys() -> Vec; +} + +/// Every query should implement this trait to be able to execute it +#[async_trait] +pub trait Executable< + T: EntityExt + ValueList + FromRow, + R: Clone + std::fmt::Debug + Send + Sync, +> +{ + async fn execute(self, db: &Executor) -> Result; +} + +/// The trait that's implemented on select/read queries +// R is the return type of the query +// It can be either Option or Vec +#[async_trait] +pub trait SelectQuery< + T: EntityExt + ValueList + FromRow, + R: Clone + std::fmt::Debug + Send + Sync, +> +{ + /// Returns the query as a string + fn query() -> String; + + /// Prepares the query + async fn prepare(db: &Executor) -> Result; + + /// Executes the query + async fn execute(self, db: &Executor) -> Result; + + /// Parses the response from the database + async fn parse_response(res: QueryResult) -> Result; +} diff --git a/scyllax/src/rows.rs b/scyllax/src/rows.rs new file mode 100644 index 0000000..d27d5b9 --- /dev/null +++ b/scyllax/src/rows.rs @@ -0,0 +1,43 @@ +#[macro_export] +/// Take a QueryResult and return a Result> +/// Example: +/// ```rust,ignore +/// match_row!(res, OrgEntity) +/// ``` +macro_rules! match_row { + ($res:ident, $type:ty) => { + match $res.single_row_typed::<$type>() { + Ok(data) => Ok(Some(data)), + Err(err) => { + use scylla::transport::query_result::SingleRowTypedError; + match err { + // tried to parse into type, but there are no rows + SingleRowTypedError::BadNumberOfRows(_) => Ok(None), + _ => { + tracing::error!("err: {:?}", err); + Err(scyllax::ScyllaxError::SingleRowTyped(err)) + } + } + } + } + }; +} + +#[macro_export] +/// Take a QueryResult and return a Result> +/// Example: +/// ```rust,ignore +/// match_rows!(res, OrgEntity) +/// ``` +macro_rules! match_rows { + ($res:ident, $type:ty) => { + match $res.rows_typed::<$type>() { + Ok(xs) => Ok(xs.filter_map(|x| x.ok()).collect::>()), + Err(e) => { + tracing::error!("err: {:?}", e); + + Ok(vec![]) + } + } + }; +} diff --git a/scyllax/src/util.rs b/scyllax/src/util.rs new file mode 100644 index 0000000..57f04f0 --- /dev/null +++ b/scyllax/src/util.rs @@ -0,0 +1,19 @@ +use once_cell::sync::Lazy; +use uuid::Uuid; + +/// Generate a v1 UUID +pub fn v1_uuid() -> Uuid { + static MAC: Lazy<[u8; 6]> = Lazy::new(get_mac_address); + Uuid::now_v1(&MAC) +} + +fn get_mac_address() -> [u8; 6] { + match mac_address::get_mac_address() { + Ok(Some(addr)) => addr.bytes(), + _ => { + let mut mac = [0u8; 6]; + getrandom::getrandom(&mut mac).ok(); + mac + } + } +}