diff --git a/Cargo.lock b/Cargo.lock index 9be07ac..58336f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,60 +2,2576 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[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 = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arrow" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e833808ff2d94ed40d9379848a950d995043c7fb3e81a30b383f4c6033821cc" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad08897b81588f60ba983e3ca39bda2b179bdd84dced378e7df81a5313802ef8" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num", +] + +[[package]] +name = "arrow-array" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8548ca7c070d8db9ce7aa43f37393e4bfcf3f2d3681df278490772fd1673d08d" +dependencies = [ + "ahash 0.8.12", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "hashbrown 0.16.1", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e003216336f70446457e280807a73899dd822feaf02087d31febca1363e2fccc" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919418a0681298d3a77d1a315f625916cb5678ad0d74b9c60108eb15fd083023" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-data" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c64fff1d142f833d78897a772f2e5b55b36cb3e6320376f0961ab0db7bd6d0" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ord" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8f82583eb4f8d84d4ee55fd1cb306720cddead7596edce95b50ee418edf66f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", +] + +[[package]] +name = "arrow-row" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d07ba24522229d9085031df6b94605e0f4b26e099fb7cdeec37abd941a73753" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3aa9e59c611ebc291c28582077ef25c97f1975383f1479b12f3b9ffee2ffabe" +dependencies = [ + "bitflags", +] + +[[package]] +name = "arrow-select" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c41dbbd1e97bfcaee4fcb30e29105fb2c75e4d82ae4de70b792a5d3f66b2e7a" +dependencies = [ + "ahash 0.8.12", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f5183c150fbc619eede22b861ea7c0eebed8eaac0333eaa7f6da5205fd504d" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "comfy-table" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d05af1e006a2407bedef5af410552494ce5be9090444dbbcb57258c1af3d56" +dependencies = [ + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-width", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "duckdb" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8685352ce688883098b61a361e86e87df66fc8c444f4a2411e884c16d5243a65" +dependencies = [ + "arrow", + "cast", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libduckdb-sys", + "num-integer", + "rust_decimal", + "strum 0.27.2", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[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-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 = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +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 = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" + +[[package]] +name = "libduckdb-sys" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78bacb8933586cee3b550c39b610d314f9b7a48701ac7a914a046165a4ad8da" +dependencies = [ + "cc", + "flate2", + "pkg-config", + "reqwest", + "serde", + "serde_json", + "tar", + "vcpkg", + "zip", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[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-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "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 = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "oxide-sql-core" version = "0.1.0" dependencies = [ - "oxide-sql-derive", + "duckdb", + "oxide-sql-derive", +] + +[[package]] +name = "oxide-sql-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "oxide-sql-sqlite" +version = "0.1.0" +dependencies = [ + "oxide-sql-core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[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 = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[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-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.114", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[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.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] -name = "oxide-sql-derive" -version = "0.1.0" +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 = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ - "proc-macro2", "quote", - "syn", + "wasm-bindgen-macro-support", ] [[package]] -name = "oxide-sql-sqlite" -version = "0.1.0" +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ - "oxide-sql-core", + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "wasm-bindgen-shared" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.44" +name = "web-sys" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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 = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "syn" -version = "2.0.114" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.114", ] [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[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.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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 0.52.6", + "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-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" + +[[package]] +name = "zmij" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/crates/oxide-sql-core/Cargo.toml b/crates/oxide-sql-core/Cargo.toml index 4ae7ea1..e455b1a 100644 --- a/crates/oxide-sql-core/Cargo.toml +++ b/crates/oxide-sql-core/Cargo.toml @@ -14,3 +14,4 @@ categories = ["database", "parser-implementations"] [dev-dependencies] oxide-sql-derive.workspace = true +duckdb = { version = "1.1", features = ["bundled"] } diff --git a/crates/oxide-sql-core/src/builder/typed.rs b/crates/oxide-sql-core/src/builder/typed.rs index 3f6adb7..12e4540 100644 --- a/crates/oxide-sql-core/src/builder/typed.rs +++ b/crates/oxide-sql-core/src/builder/typed.rs @@ -197,7 +197,7 @@ impl Select { #[must_use] pub fn build(self) -> (String, Vec) { let mut sql = String::from("SELECT "); - let params = vec![]; + let mut params = vec![]; // Columns sql.push_str(&self.columns.join(", ")); @@ -212,6 +212,7 @@ impl Select { if let Some(ref where_expr) = self.where_clause { sql.push_str(" WHERE "); sql.push_str(where_expr.sql()); + params.extend(where_expr.params().iter().cloned()); } // ORDER BY @@ -452,12 +453,14 @@ impl Update { .collect(); sql.push_str(&set_clauses.join(", ")); + let mut params: Vec = self.sets.into_iter().map(|(_, v)| v).collect(); + if let Some(ref where_expr) = self.where_clause { sql.push_str(" WHERE "); sql.push_str(where_expr.sql()); + params.extend(where_expr.params().iter().cloned()); } - let params: Vec = self.sets.into_iter().map(|(_, v)| v).collect(); (sql, params) } @@ -518,13 +521,15 @@ impl Delete { pub fn build(self) -> (String, Vec) { let mut sql = String::from("DELETE FROM "); sql.push_str(T::NAME); + let mut params = vec![]; if let Some(ref where_expr) = self.where_clause { sql.push_str(" WHERE "); sql.push_str(where_expr.sql()); + params.extend(where_expr.params().iter().cloned()); } - (sql, vec![]) + (sql, params) } /// Builds the query and returns only the SQL string. diff --git a/crates/oxide-sql-core/src/lib.rs b/crates/oxide-sql-core/src/lib.rs index eba6128..a8dd1e9 100644 --- a/crates/oxide-sql-core/src/lib.rs +++ b/crates/oxide-sql-core/src/lib.rs @@ -8,33 +8,268 @@ //! - Protection against SQL injection through parameterized queries //! - A type-safe migrations system (Django-like) //! -//! ## Type-Safe SQL Building (Recommended) +//! ## Defining Tables with `#[derive(Table)]` //! -//! Use `Select`, `Insert`, `Update`, `Delete` with `#[derive(Table)]` for -//! compile-time validated queries: +//! The `#[derive(Table)]` macro (from [`oxide-sql-derive`]) turns a plain +//! struct into a full schema definition with compile-time checked column +//! names, types, and metadata. //! //! ```rust -//! use oxide_sql_core::builder::Select; -//! use oxide_sql_core::schema::Table; +//! # #![allow(clippy::needless_doctest_main)] +//! use oxide_sql_derive::Table; +//! use oxide_sql_core::schema::{Column, Table}; +//! +//! #[derive(Table)] +//! #[table(name = "users")] +//! pub struct User { +//! #[column(primary_key)] +//! id: i64, +//! name: String, +//! #[column(nullable)] +//! email: Option, +//! } +//! +//! fn main() { +//! // The macro generates all of the following: +//! // +//! // UserTable – unit struct implementing the Table trait +//! // UserColumns – module with typed column structs (Id, Name, Email) +//! // User::id() – accessor returning UserColumns::Id +//! // User::name() – accessor returning UserColumns::Name +//! // User::email() – accessor returning UserColumns::Email +//! // UserTable::id(), UserTable::name(), ... (same accessors) +//! +//! // Table metadata +//! assert_eq!(UserTable::NAME, "users"); +//! assert_eq!(UserTable::COLUMNS, &["id", "name", "email"]); +//! assert_eq!(UserTable::PRIMARY_KEY, Some("id")); +//! +//! // Column metadata +//! assert_eq!(UserColumns::Id::NAME, "id"); +//! assert!(UserColumns::Id::PRIMARY_KEY); +//! assert!(!UserColumns::Id::NULLABLE); +//! +//! assert_eq!(UserColumns::Email::NAME, "email"); +//! assert!(UserColumns::Email::NULLABLE); +//! } +//! ``` +//! +//! ### Attributes +//! +//! | Attribute | Level | Effect | +//! |---|---|---| +//! | `#[table(name = "...")]` | struct | Sets the SQL table name (default: `snake_case` of struct name) | +//! | `#[column(primary_key)]` | field | Marks the column as the primary key | +//! | `#[column(nullable)]` | field | Marks the column as nullable | +//! | `#[column(name = "...")]` | field | Overrides the SQL column name (default: field name) | //! -//! // Schema types (normally generated by #[derive(Table)] from oxide-sql-derive) -//! struct User { id: i32, name: String, active: bool } -//! struct UserTable; +//! ### What the macro generates — under the hood +//! +//! Given the `User` struct above, `#[derive(Table)]` expands to roughly: +//! +//! ```rust +//! use oxide_sql_core::schema::{Column, Table, TypedColumn}; +//! +//! pub struct User { id: i64, name: String, email: Option } +//! +//! // 1. A table unit struct that implements the Table trait. +//! #[derive(Debug, Clone, Copy)] +//! pub struct UserTable; //! //! impl Table for UserTable { //! type Row = User; //! const NAME: &'static str = "users"; -//! const COLUMNS: &'static [&'static str] = &["id", "name", "active"]; +//! const COLUMNS: &'static [&'static str] = &["id", "name", "email"]; //! const PRIMARY_KEY: Option<&'static str> = Some("id"); //! } //! -//! // Compile-time validated query - invalid columns won't compile +//! // 2. A columns module with one zero-sized struct per field. +//! // Each struct implements Column (with the table, Rust type, +//! // name, nullable, and primary_key metadata) and TypedColumn. +//! #[allow(non_snake_case)] +//! mod UserColumns { +//! use super::*; +//! +//! #[derive(Debug, Clone, Copy)] +//! pub struct Id; +//! impl Column for Id { +//! type Table = super::UserTable; +//! type Type = i64; +//! const NAME: &'static str = "id"; +//! const NULLABLE: bool = false; +//! const PRIMARY_KEY: bool = true; +//! } +//! impl TypedColumn for Id {} +//! +//! #[derive(Debug, Clone, Copy)] +//! pub struct Name; +//! impl Column for Name { +//! type Table = super::UserTable; +//! type Type = String; +//! const NAME: &'static str = "name"; +//! const NULLABLE: bool = false; +//! const PRIMARY_KEY: bool = false; +//! } +//! impl TypedColumn for Name {} +//! +//! #[derive(Debug, Clone, Copy)] +//! pub struct Email; +//! impl Column for Email { +//! type Table = super::UserTable; +//! type Type = Option; +//! const NAME: &'static str = "email"; +//! const NULLABLE: bool = true; +//! const PRIMARY_KEY: bool = false; +//! } +//! impl TypedColumn> for Email {} +//! } +//! +//! // 3. Const accessor methods on both UserTable and User so you +//! // can write `User::id()` or `UserTable::id()` to obtain +//! // the zero-sized column type for use in query builders. +//! impl UserTable { +//! pub const fn id() -> UserColumns::Id { UserColumns::Id } +//! pub const fn name() -> UserColumns::Name { UserColumns::Name } +//! pub const fn email() -> UserColumns::Email { UserColumns::Email } +//! } +//! // (User also gets the same accessors and a `table()` method) +//! # fn main() {} +//! ``` +//! +//! Because every column is a distinct zero-sized type that carries its +//! table association via `Column::Table`, the typed query builders can +//! verify at compile time that you only reference columns that belong to +//! the table you are querying. +//! +//! ## Type-Safe Queries +//! +//! The typed builders — [`Select`], [`Insert`], [`Update`], [`Delete`] — +//! use the typestate pattern so that incomplete queries (missing columns, +//! missing table, missing SET values) simply do not compile. +//! +//! All examples below reuse the `User` / `UserTable` definition from +//! the section above. +//! +//! ### SELECT +//! +//! ```rust +//! # #![allow(clippy::needless_doctest_main)] +//! use oxide_sql_derive::Table; +//! use oxide_sql_core::builder::{Select, col}; +//! use oxide_sql_core::schema::Table; +//! +//! #[derive(Table)] +//! #[table(name = "users")] +//! pub struct User { +//! #[column(primary_key)] +//! id: i64, +//! name: String, +//! #[column(nullable)] +//! email: Option, +//! } +//! +//! fn main() { +//! // SELECT all columns +//! let (sql, _params) = Select::::new() +//! .select_all() +//! .from_table() +//! .build(); +//! assert_eq!(sql, "SELECT id, name, email FROM users"); +//! +//! // SELECT with WHERE, ORDER BY, LIMIT //! let (sql, params) = Select::::new() //! .select_all() //! .from_table() +//! .where_col(User::id(), col(User::id()).gt(100)) +//! .order_by(User::name(), true) +//! .limit(10) //! .build(); +//! assert_eq!( +//! sql, +//! "SELECT id, name, email FROM users \ +//! WHERE id > ? ORDER BY name LIMIT 10" +//! ); +//! } +//! ``` +//! +//! ### INSERT +//! +//! ```rust +//! # #![allow(clippy::needless_doctest_main)] +//! use oxide_sql_derive::Table; +//! use oxide_sql_core::builder::Insert; +//! use oxide_sql_core::schema::Table; +//! +//! #[derive(Table)] +//! #[table(name = "users")] +//! pub struct User { +//! #[column(primary_key)] +//! id: i64, +//! name: String, +//! #[column(nullable)] +//! email: Option, +//! } +//! +//! fn main() { +//! let (sql, params) = Insert::::new() +//! .set(User::name(), "Alice") +//! .set(User::email(), "alice@example.com") +//! .build(); +//! assert_eq!(sql, "INSERT INTO users (name, email) VALUES (?, ?)"); +//! } +//! ``` +//! +//! ### UPDATE +//! +//! ```rust +//! # #![allow(clippy::needless_doctest_main)] +//! use oxide_sql_derive::Table; +//! use oxide_sql_core::builder::{Update, col}; +//! use oxide_sql_core::schema::Table; +//! +//! #[derive(Table)] +//! #[table(name = "users")] +//! pub struct User { +//! #[column(primary_key)] +//! id: i64, +//! name: String, +//! #[column(nullable)] +//! email: Option, +//! } +//! +//! fn main() { +//! let (sql, params) = Update::::new() +//! .set(User::name(), "Bob") +//! .where_col(User::id(), col(User::id()).eq(42)) +//! .build(); +//! assert_eq!(sql, "UPDATE users SET name = ? WHERE id = ?"); +//! } +//! ``` +//! +//! ### DELETE +//! +//! ```rust +//! # #![allow(clippy::needless_doctest_main)] +//! use oxide_sql_derive::Table; +//! use oxide_sql_core::builder::{Delete, col}; +//! use oxide_sql_core::schema::Table; +//! +//! #[derive(Table)] +//! #[table(name = "users")] +//! pub struct User { +//! #[column(primary_key)] +//! id: i64, +//! name: String, +//! #[column(nullable)] +//! email: Option, +//! } //! -//! assert_eq!(sql, "SELECT id, name, active FROM users"); +//! fn main() { +//! let (sql, params) = Delete::::new() +//! .where_col(User::id(), col(User::id()).eq(1)) +//! .build(); +//! assert_eq!(sql, "DELETE FROM users WHERE id = ?"); +//! } //! ``` //! //! ## Dynamic SQL Building @@ -75,8 +310,12 @@ //! //! ## Type-Safe Migrations //! -//! The migrations module provides Django-like database migrations with compile-time -//! validation: +//! The migrations module provides a Django-like system for evolving +//! database schemas. Each migration is a struct implementing the +//! [`Migration`] trait with `up()` (apply) and `down()` (rollback) +//! methods that return a list of [`Operation`]s. +//! +//! ### Defining a migration //! //! ```rust //! use oxide_sql_core::migrations::{ @@ -95,17 +334,216 @@ //! .name("users") //! .column(bigint("id").primary_key().autoincrement().build()) //! .column(varchar("username", 255).not_null().unique().build()) -//! .column(timestamp("created_at").not_null().default_expr("CURRENT_TIMESTAMP").build()) +//! .column( +//! timestamp("created_at") +//! .not_null() +//! .default_expr("CURRENT_TIMESTAMP") +//! .build(), +//! ) +//! .build() +//! .into(), +//! ] +//! } +//! +//! fn down() -> Vec { +//! vec![Operation::drop_table("users")] +//! } +//! } +//! ``` +//! +//! ### Column helpers +//! +//! Shorthand functions create a [`ColumnBuilder`](migrations::ColumnBuilder) +//! for each SQL type. Chain constraints then call `.build()`: +//! +//! | Function | SQL type | +//! |---|---| +//! | [`bigint`](migrations::bigint), [`integer`](migrations::integer), [`smallint`](migrations::smallint) | `BIGINT`, `INTEGER`, `SMALLINT` | +//! | [`varchar`](migrations::varchar), [`text`](migrations::text), [`char`](migrations::char) | `VARCHAR(n)`, `TEXT`, `CHAR(n)` | +//! | [`boolean`](migrations::boolean) | `BOOLEAN` | +//! | [`timestamp`](migrations::timestamp), [`datetime`](migrations::datetime), [`date`](migrations::date), [`time`](migrations::time) | date/time types | +//! | [`decimal`](migrations::decimal), [`numeric`](migrations::numeric), [`real`](migrations::real), [`double`](migrations::double) | floating-point/decimal types | +//! | [`blob`](migrations::blob), [`binary`](migrations::binary), [`varbinary`](migrations::varbinary) | binary types | +//! +//! ```rust +//! use oxide_sql_core::migrations::{bigint, varchar, boolean, timestamp}; +//! +//! // Primary key with auto-increment +//! let id = bigint("id").primary_key().autoincrement().build(); +//! +//! // NOT NULL + UNIQUE +//! let email = varchar("email", 255).not_null().unique().build(); +//! +//! // Default value +//! let active = boolean("active").not_null().default_bool(true).build(); +//! +//! // Default expression +//! let ts = timestamp("created_at") +//! .not_null() +//! .default_expr("CURRENT_TIMESTAMP") +//! .build(); +//! ``` +//! +//! ### Operations +//! +//! [`Operation`] covers all DDL changes. Beyond `CreateTable`, the most +//! common factory methods are: +//! +//! ```rust +//! use oxide_sql_core::migrations::{Operation, varchar}; +//! +//! // Drop a table +//! let _ = Operation::drop_table("users"); +//! +//! // Rename a table +//! let _ = Operation::rename_table("users", "accounts"); +//! +//! // Add a column to an existing table +//! let _ = Operation::add_column( +//! "users", +//! varchar("bio", 1000).nullable().build(), +//! ); +//! +//! // Drop a column +//! let _ = Operation::drop_column("users", "bio"); +//! +//! // Rename a column +//! let _ = Operation::rename_column("users", "name", "full_name"); +//! +//! // Raw SQL (with optional reverse for rollback) +//! let _ = Operation::run_sql_reversible( +//! "CREATE VIEW active_users AS SELECT * FROM users WHERE active", +//! "DROP VIEW active_users", +//! ); +//! ``` +//! +//! ### Dependencies between migrations +//! +//! Migrations can declare dependencies via `DEPENDENCIES`. The runner +//! topologically sorts them so dependees always run first: +//! +//! ```rust +//! use oxide_sql_core::migrations::{ +//! Migration, Operation, CreateTableBuilder, bigint, varchar, +//! }; +//! +//! pub struct Migration0001; +//! impl Migration for Migration0001 { +//! const ID: &'static str = "0001_create_users"; +//! fn up() -> Vec { +//! vec![ +//! CreateTableBuilder::new() +//! .name("users") +//! .column(bigint("id").primary_key().build()) +//! .column(varchar("name", 255).not_null().build()) //! .build() //! .into(), //! ] //! } +//! fn down() -> Vec { +//! vec![Operation::drop_table("users")] +//! } +//! } //! +//! pub struct Migration0002; +//! impl Migration for Migration0002 { +//! const ID: &'static str = "0002_create_posts"; +//! // This migration depends on 0001 +//! const DEPENDENCIES: &'static [&'static str] = &["0001_create_users"]; +//! fn up() -> Vec { +//! vec![ +//! CreateTableBuilder::new() +//! .name("posts") +//! .column(bigint("id").primary_key().build()) +//! .column(bigint("user_id").not_null().build()) +//! .column(varchar("title", 255).not_null().build()) +//! .build() +//! .into(), +//! ] +//! } +//! fn down() -> Vec { +//! vec![Operation::drop_table("posts")] +//! } +//! } +//! ``` +//! +//! ### Running migrations — under the hood +//! +//! [`MigrationRunner`](migrations::MigrationRunner) registers migrations, +//! resolves dependencies, and generates dialect-specific SQL. +//! [`MigrationState`](migrations::MigrationState) tracks which migrations +//! have already been applied (backed by the `_oxide_migrations` table in +//! your database). +//! +//! ```rust +//! use oxide_sql_core::migrations::{ +//! Migration, MigrationRunner, MigrationState, +//! SqliteDialect, Operation, CreateTableBuilder, +//! bigint, varchar, +//! }; +//! +//! pub struct Mig0001; +//! impl Migration for Mig0001 { +//! const ID: &'static str = "0001_create_users"; +//! fn up() -> Vec { +//! vec![ +//! CreateTableBuilder::new() +//! .name("users") +//! .column(bigint("id").primary_key().build()) +//! .column(varchar("name", 255).not_null().build()) +//! .build() +//! .into(), +//! ] +//! } //! fn down() -> Vec { //! vec![Operation::drop_table("users")] //! } //! } +//! +//! // 1. Create a runner with a dialect (SQLite, Postgres, DuckDB) +//! let mut runner = MigrationRunner::new(SqliteDialect::new()); +//! +//! // 2. Register all migrations +//! runner.register::(); +//! +//! // 3. Validate dependencies (detects cycles / missing deps) +//! runner.validate().expect("dependency graph is valid"); +//! +//! // 4. Build state from the database (here: empty = fresh DB) +//! let state = MigrationState::new(); +//! +//! // 5. Generate SQL for pending migrations +//! let pending_sql = runner.sql_for_pending(&state).unwrap(); +//! for (id, statements) in &pending_sql { +//! for sql in statements { +//! // execute `sql` against your database connection +//! assert!(!sql.is_empty()); +//! } +//! // then mark applied: state.mark_applied(id); +//! } +//! +//! // 6. Rollback the last N migrations +//! let mut applied_state = MigrationState::from_applied( +//! vec!["0001_create_users".to_string()], +//! ); +//! let rollback_sql = runner.sql_for_rollback(&applied_state, 1).unwrap(); +//! for (id, statements) in &rollback_sql { +//! for sql in statements { +//! assert!(!sql.is_empty()); +//! } +//! } //! ``` +//! +//! ### Dialects +//! +//! The same migration operations produce different SQL depending on the +//! dialect: +//! +//! | Dialect | Auto-increment strategy | Notes | +//! |---|---|---| +//! | [`SqliteDialect`](migrations::SqliteDialect) | `AUTOINCREMENT` keyword | Limited `ALTER TABLE`; dates stored as `TEXT` | +//! | [`PostgresDialect`](migrations::PostgresDialect) | `SERIAL` / `BIGSERIAL` types | Full `ALTER COLUMN` support | +//! | [`DuckDbDialect`](migrations::DuckDbDialect) | `CREATE SEQUENCE` + `DEFAULT nextval(...)` | Sequence name: `seq__` | pub mod ast; pub mod builder; diff --git a/crates/oxide-sql-core/src/migrations/dialect/duckdb.rs b/crates/oxide-sql-core/src/migrations/dialect/duckdb.rs new file mode 100644 index 0000000..c0be99e --- /dev/null +++ b/crates/oxide-sql-core/src/migrations/dialect/duckdb.rs @@ -0,0 +1,633 @@ +//! DuckDB dialect for migrations. + +use super::MigrationDialect; +use crate::ast::DataType; +use crate::migrations::column_builder::{ColumnDefinition, DefaultValue}; +use crate::migrations::operation::{ + AlterColumnChange, AlterColumnOp, CreateTableOp, DropIndexOp, RenameColumnOp, RenameTableOp, +}; + +/// DuckDB dialect for migration SQL generation. +/// +/// DuckDB does not support `AUTOINCREMENT` or `SERIAL`/`BIGSERIAL`. +/// Instead, auto-increment is implemented via `CREATE SEQUENCE` + +/// `DEFAULT nextval('seq_
_')`. The [`create_table`] +/// override emits the sequence DDL automatically for every column +/// marked with `autoincrement`. +#[derive(Debug, Clone, Copy, Default)] +pub struct DuckDbDialect; + +impl DuckDbDialect { + /// Creates a new DuckDB dialect. + #[must_use] + pub const fn new() -> Self { + Self + } + + /// Generates a column definition with sequence-backed default for + /// autoincrement columns, using the given table name to build the + /// sequence name. + fn column_def_with_table(&self, col: &ColumnDefinition, table: &str) -> String { + let data_type = self.map_data_type(&col.data_type); + let mut sql = format!("{} {}", self.quote_identifier(&col.name), data_type); + + if col.primary_key { + sql.push_str(" PRIMARY KEY"); + } else { + if !col.nullable { + sql.push_str(" NOT NULL"); + } + if col.unique { + sql.push_str(" UNIQUE"); + } + } + + if col.autoincrement && col.default.is_none() { + sql.push_str(&format!(" DEFAULT nextval('seq_{}_{}')", table, col.name,)); + } else if let Some(ref default) = col.default { + sql.push_str(" DEFAULT "); + sql.push_str(&self.render_default(default)); + } + + if let Some(ref fk) = col.references { + sql.push_str(" REFERENCES "); + sql.push_str(&self.quote_identifier(&fk.table)); + sql.push_str(" ("); + sql.push_str(&self.quote_identifier(&fk.column)); + sql.push(')'); + if let Some(action) = fk.on_delete { + sql.push_str(" ON DELETE "); + sql.push_str(action.as_sql()); + } + if let Some(action) = fk.on_update { + sql.push_str(" ON UPDATE "); + sql.push_str(action.as_sql()); + } + } + + if let Some(ref check) = col.check { + sql.push_str(&format!(" CHECK ({})", check)); + } + + if let Some(ref collation) = col.collation { + sql.push_str(&format!(" COLLATE \"{}\"", collation)); + } + + sql + } +} + +impl MigrationDialect for DuckDbDialect { + fn name(&self) -> &'static str { + "duckdb" + } + + fn map_data_type(&self, dt: &DataType) -> String { + match dt { + DataType::Smallint => "SMALLINT".to_string(), + DataType::Integer => "INTEGER".to_string(), + DataType::Bigint => "BIGINT".to_string(), + DataType::Real => "REAL".to_string(), + DataType::Double => "DOUBLE".to_string(), + DataType::Decimal { precision, scale } => match (precision, scale) { + (Some(p), Some(s)) => format!("DECIMAL({p}, {s})"), + (Some(p), None) => format!("DECIMAL({p})"), + _ => "DECIMAL".to_string(), + }, + DataType::Numeric { precision, scale } => match (precision, scale) { + (Some(p), Some(s)) => format!("NUMERIC({p}, {s})"), + (Some(p), None) => format!("NUMERIC({p})"), + _ => "NUMERIC".to_string(), + }, + DataType::Char(len) => match len { + Some(n) => format!("CHAR({n})"), + None => "CHAR".to_string(), + }, + DataType::Varchar(len) => match len { + Some(n) => format!("VARCHAR({n})"), + None => "VARCHAR".to_string(), + }, + DataType::Text => "TEXT".to_string(), + DataType::Blob => "BLOB".to_string(), + DataType::Binary(len) => match len { + Some(n) => format!("BLOB({n})"), + None => "BLOB".to_string(), + }, + DataType::Varbinary(len) => match len { + Some(n) => format!("BLOB({n})"), + None => "BLOB".to_string(), + }, + DataType::Date => "DATE".to_string(), + DataType::Time => "TIME".to_string(), + DataType::Timestamp => "TIMESTAMP".to_string(), + DataType::Datetime => "TIMESTAMP".to_string(), + DataType::Boolean => "BOOLEAN".to_string(), + DataType::Custom(name) => name.clone(), + } + } + + fn autoincrement_keyword(&self) -> String { + // DuckDB uses CREATE SEQUENCE + DEFAULT nextval() instead. + String::new() + } + + fn create_table(&self, op: &CreateTableOp) -> String { + // Emit CREATE SEQUENCE for every autoincrement column. + let mut sql = String::new(); + for col in &op.columns { + if col.autoincrement { + sql.push_str(&format!( + "CREATE SEQUENCE IF NOT EXISTS \ + \"seq_{table}_{col}\" START 1;\n", + table = op.name, + col = col.name, + )); + } + } + + sql.push_str("CREATE TABLE "); + if op.if_not_exists { + sql.push_str("IF NOT EXISTS "); + } + sql.push_str(&self.quote_identifier(&op.name)); + sql.push_str(" (\n"); + + let column_defs: Vec = op + .columns + .iter() + .map(|c| format!(" {}", self.column_def_with_table(c, &op.name))) + .collect(); + sql.push_str(&column_defs.join(",\n")); + + if !op.constraints.is_empty() { + sql.push_str(",\n"); + let constraint_defs: Vec = op + .constraints + .iter() + .map(|c| format!(" {}", self.table_constraint(c))) + .collect(); + sql.push_str(&constraint_defs.join(",\n")); + } + + sql.push_str("\n)"); + sql + } + + fn render_default(&self, default: &DefaultValue) -> String { + match default { + DefaultValue::Boolean(b) => { + if *b { + "TRUE".to_string() + } else { + "FALSE".to_string() + } + } + _ => default.to_sql(), + } + } + + fn rename_table(&self, op: &RenameTableOp) -> String { + format!( + "ALTER TABLE {} RENAME TO {}", + self.quote_identifier(&op.old_name), + self.quote_identifier(&op.new_name) + ) + } + + fn rename_column(&self, op: &RenameColumnOp) -> String { + format!( + "ALTER TABLE {} RENAME COLUMN {} TO {}", + self.quote_identifier(&op.table), + self.quote_identifier(&op.old_name), + self.quote_identifier(&op.new_name) + ) + } + + fn alter_column(&self, op: &AlterColumnOp) -> String { + let table = self.quote_identifier(&op.table); + let column = self.quote_identifier(&op.column); + + match &op.change { + AlterColumnChange::SetDataType(dt) => { + format!( + "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}", + table, + column, + self.map_data_type(dt) + ) + } + AlterColumnChange::SetNullable(nullable) => { + if *nullable { + format!( + "ALTER TABLE {} ALTER COLUMN {} DROP NOT NULL", + table, column + ) + } else { + format!("ALTER TABLE {} ALTER COLUMN {} SET NOT NULL", table, column) + } + } + AlterColumnChange::SetDefault(default) => { + format!( + "ALTER TABLE {} ALTER COLUMN {} SET DEFAULT {}", + table, + column, + self.render_default(default) + ) + } + AlterColumnChange::DropDefault => { + format!("ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT", table, column) + } + } + } + + fn drop_index(&self, op: &DropIndexOp) -> String { + let mut sql = String::from("DROP INDEX "); + if op.if_exists { + sql.push_str("IF EXISTS "); + } + sql.push_str(&self.quote_identifier(&op.name)); + sql + } + + fn drop_foreign_key(&self, op: &super::super::operation::DropForeignKeyOp) -> String { + format!( + "ALTER TABLE {} DROP CONSTRAINT {}", + self.quote_identifier(&op.table), + self.quote_identifier(&op.name) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::migrations::column_builder::{integer, varchar}; + use crate::migrations::operation::{DropTableOp, Operation, RenameColumnOp, RenameTableOp}; + use crate::migrations::table_builder::CreateTableBuilder; + + #[test] + fn test_duckdb_data_types() { + let d = DuckDbDialect::new(); + assert_eq!(d.map_data_type(&DataType::Integer), "INTEGER"); + assert_eq!(d.map_data_type(&DataType::Bigint), "BIGINT"); + assert_eq!(d.map_data_type(&DataType::Text), "TEXT"); + assert_eq!( + d.map_data_type(&DataType::Varchar(Some(255))), + "VARCHAR(255)" + ); + assert_eq!(d.map_data_type(&DataType::Blob), "BLOB"); + assert_eq!(d.map_data_type(&DataType::Boolean), "BOOLEAN"); + assert_eq!(d.map_data_type(&DataType::Timestamp), "TIMESTAMP"); + assert_eq!(d.map_data_type(&DataType::Double), "DOUBLE"); + assert_eq!(d.map_data_type(&DataType::Real), "REAL"); + assert_eq!(d.map_data_type(&DataType::Date), "DATE"); + assert_eq!(d.map_data_type(&DataType::Time), "TIME"); + assert_eq!( + d.map_data_type(&DataType::Decimal { + precision: Some(10), + scale: Some(2) + }), + "DECIMAL(10, 2)" + ); + } + + #[test] + fn test_create_table_basic() { + let d = DuckDbDialect::new(); + let op = CreateTableBuilder::new() + .name("users") + .column(varchar("username", 255).not_null().unique().build()) + .build(); + + let sql = d.create_table(&op); + assert_eq!( + sql, + "CREATE TABLE \"users\" (\n\ + \x20 \"username\" VARCHAR(255) NOT NULL UNIQUE\n\ + )" + ); + } + + #[test] + fn test_create_table_if_not_exists() { + let d = DuckDbDialect::new(); + let op = CreateTableBuilder::new() + .if_not_exists() + .name("users") + .column(varchar("username", 255).not_null().build()) + .build(); + + let sql = d.create_table(&op); + assert!(sql.contains("CREATE TABLE IF NOT EXISTS \"users\"")); + } + + #[test] + fn test_autoincrement_generates_sequence() { + let d = DuckDbDialect::new(); + let op = CreateTableBuilder::new() + .name("users") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("username", 255).not_null().unique().build()) + .build(); + + let sql = d.create_table(&op); + + assert!( + sql.contains( + "CREATE SEQUENCE IF NOT EXISTS \ + \"seq_users_id\" START 1;" + ), + "Missing sequence DDL in:\n{sql}" + ); + assert!( + sql.contains("DEFAULT nextval('seq_users_id')"), + "Missing nextval default in:\n{sql}" + ); + assert!( + !sql.contains("AUTOINCREMENT"), + "Should not contain AUTOINCREMENT keyword" + ); + } + + #[test] + fn test_varchar_unique_not_null() { + let d = DuckDbDialect::new(); + let op = CreateTableBuilder::new() + .name("items") + .column(varchar("domain", 255).not_null().unique().build()) + .build(); + + let sql = d.create_table(&op); + assert!( + sql.contains("\"domain\" VARCHAR(255) NOT NULL UNIQUE"), + "Expected NOT NULL UNIQUE in:\n{sql}" + ); + } + + #[test] + fn test_drop_table() { + let d = DuckDbDialect::new(); + + let op = DropTableOp { + name: "users".to_string(), + if_exists: false, + cascade: false, + }; + assert_eq!(d.drop_table(&op), "DROP TABLE \"users\""); + + let op = DropTableOp { + name: "users".to_string(), + if_exists: true, + cascade: true, + }; + assert_eq!(d.drop_table(&op), "DROP TABLE IF EXISTS \"users\" CASCADE"); + } + + #[test] + fn test_rename_table() { + let d = DuckDbDialect::new(); + let op = RenameTableOp { + old_name: "old_users".to_string(), + new_name: "users".to_string(), + }; + assert_eq!( + d.rename_table(&op), + "ALTER TABLE \"old_users\" RENAME TO \"users\"" + ); + } + + #[test] + fn test_add_column() { + let d = DuckDbDialect::new(); + let op = Operation::add_column("users", varchar("email", 255).not_null().build()); + if let Operation::AddColumn(ref add_op) = op { + let sql = d.add_column(add_op); + assert_eq!( + sql, + "ALTER TABLE \"users\" ADD COLUMN \ + \"email\" VARCHAR(255) NOT NULL" + ); + } + } + + #[test] + fn test_drop_column() { + let d = DuckDbDialect::new(); + let op = Operation::drop_column("users", "email"); + if let Operation::DropColumn(ref drop_op) = op { + let sql = d.drop_column(drop_op); + assert_eq!(sql, "ALTER TABLE \"users\" DROP COLUMN \"email\""); + } + } + + #[test] + fn test_rename_column() { + let d = DuckDbDialect::new(); + let op = RenameColumnOp { + table: "users".to_string(), + old_name: "name".to_string(), + new_name: "full_name".to_string(), + }; + assert_eq!( + d.rename_column(&op), + "ALTER TABLE \"users\" RENAME COLUMN \ + \"name\" TO \"full_name\"" + ); + } + + #[test] + fn test_alter_column_set_data_type() { + let d = DuckDbDialect::new(); + let op = AlterColumnOp { + table: "users".to_string(), + column: "age".to_string(), + change: AlterColumnChange::SetDataType(DataType::Bigint), + }; + assert_eq!( + d.alter_column(&op), + "ALTER TABLE \"users\" ALTER COLUMN \"age\" \ + SET DATA TYPE BIGINT" + ); + } + + #[test] + fn test_alter_column_set_not_null() { + let d = DuckDbDialect::new(); + let op = AlterColumnOp { + table: "users".to_string(), + column: "email".to_string(), + change: AlterColumnChange::SetNullable(false), + }; + assert_eq!( + d.alter_column(&op), + "ALTER TABLE \"users\" ALTER COLUMN \"email\" SET NOT NULL" + ); + } + + #[test] + fn test_alter_column_drop_not_null() { + let d = DuckDbDialect::new(); + let op = AlterColumnOp { + table: "users".to_string(), + column: "email".to_string(), + change: AlterColumnChange::SetNullable(true), + }; + assert_eq!( + d.alter_column(&op), + "ALTER TABLE \"users\" ALTER COLUMN \"email\" DROP NOT NULL" + ); + } + + #[test] + fn test_alter_column_set_default() { + let d = DuckDbDialect::new(); + let op = AlterColumnOp { + table: "users".to_string(), + column: "active".to_string(), + change: AlterColumnChange::SetDefault(DefaultValue::Boolean(true)), + }; + assert_eq!( + d.alter_column(&op), + "ALTER TABLE \"users\" ALTER COLUMN \"active\" \ + SET DEFAULT TRUE" + ); + } + + #[test] + fn test_alter_column_drop_default() { + let d = DuckDbDialect::new(); + let op = AlterColumnOp { + table: "users".to_string(), + column: "active".to_string(), + change: AlterColumnChange::DropDefault, + }; + assert_eq!( + d.alter_column(&op), + "ALTER TABLE \"users\" ALTER COLUMN \"active\" DROP DEFAULT" + ); + } + + #[test] + fn test_create_index() { + let d = DuckDbDialect::new(); + let op = crate::migrations::operation::CreateIndexOp { + name: "idx_users_email".to_string(), + table: "users".to_string(), + columns: vec!["email".to_string()], + unique: true, + index_type: crate::migrations::operation::IndexType::BTree, + if_not_exists: true, + condition: None, + }; + assert_eq!( + d.create_index(&op), + "CREATE UNIQUE INDEX IF NOT EXISTS \"idx_users_email\" \ + ON \"users\" (\"email\")" + ); + } + + #[test] + fn test_drop_index() { + let d = DuckDbDialect::new(); + + let op = crate::migrations::operation::DropIndexOp { + name: "idx_users_email".to_string(), + table: None, + if_exists: false, + }; + assert_eq!(d.drop_index(&op), "DROP INDEX \"idx_users_email\""); + + let op = crate::migrations::operation::DropIndexOp { + name: "idx_users_email".to_string(), + table: None, + if_exists: true, + }; + assert_eq!( + d.drop_index(&op), + "DROP INDEX IF EXISTS \"idx_users_email\"" + ); + } + + #[test] + fn test_drop_foreign_key() { + let d = DuckDbDialect::new(); + let op = crate::migrations::operation::DropForeignKeyOp { + table: "invoices".to_string(), + name: "fk_invoices_user".to_string(), + }; + assert_eq!( + d.drop_foreign_key(&op), + "ALTER TABLE \"invoices\" DROP CONSTRAINT \ + \"fk_invoices_user\"" + ); + } + + #[test] + fn test_consumer_scenario_two_tables_with_sequences() { + let d = DuckDbDialect::new(); + + let ops: Vec = vec![ + CreateTableBuilder::new() + .if_not_exists() + .name("excluded_domains") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("domain", 255).not_null().unique().build()) + .build() + .into(), + CreateTableBuilder::new() + .if_not_exists() + .name("excluded_ips") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("cidr", 255).not_null().unique().build()) + .build() + .into(), + ]; + + let sqls: Vec = ops.iter().map(|op| d.generate_sql(op)).collect(); + + // First table + assert!( + sqls[0].contains( + "CREATE SEQUENCE IF NOT EXISTS \ + \"seq_excluded_domains_id\" START 1;" + ), + "Missing sequence for excluded_domains:\n{}", + sqls[0] + ); + assert!( + sqls[0].contains("CREATE TABLE IF NOT EXISTS \"excluded_domains\""), + "Missing CREATE TABLE:\n{}", + sqls[0] + ); + assert!( + sqls[0].contains("DEFAULT nextval('seq_excluded_domains_id')"), + "Missing nextval default:\n{}", + sqls[0] + ); + assert!( + sqls[0].contains("\"domain\" VARCHAR(255) NOT NULL UNIQUE"), + "Missing domain column:\n{}", + sqls[0] + ); + + // Second table + assert!( + sqls[1].contains( + "CREATE SEQUENCE IF NOT EXISTS \ + \"seq_excluded_ips_id\" START 1;" + ), + "Missing sequence for excluded_ips:\n{}", + sqls[1] + ); + assert!( + sqls[1].contains("DEFAULT nextval('seq_excluded_ips_id')"), + "Missing nextval default:\n{}", + sqls[1] + ); + assert!( + sqls[1].contains("\"cidr\" VARCHAR(255) NOT NULL UNIQUE"), + "Missing cidr column:\n{}", + sqls[1] + ); + } +} diff --git a/crates/oxide-sql-core/src/migrations/dialect/mod.rs b/crates/oxide-sql-core/src/migrations/dialect/mod.rs index 1648c94..3acfc83 100644 --- a/crates/oxide-sql-core/src/migrations/dialect/mod.rs +++ b/crates/oxide-sql-core/src/migrations/dialect/mod.rs @@ -4,9 +4,11 @@ //! This module provides dialect implementations for generating //! database-specific migration SQL. +mod duckdb; mod postgres; mod sqlite; +pub use duckdb::DuckDbDialect; pub use postgres::PostgresDialect; pub use sqlite::SqliteDialect; diff --git a/crates/oxide-sql-core/src/migrations/mod.rs b/crates/oxide-sql-core/src/migrations/mod.rs index a00aace..5c20c8a 100644 --- a/crates/oxide-sql-core/src/migrations/mod.rs +++ b/crates/oxide-sql-core/src/migrations/mod.rs @@ -52,7 +52,7 @@ pub use column_builder::{ smallint, text, time, timestamp, varbinary, varchar, ColumnBuilder, ColumnDefinition, DefaultValue, ForeignKeyRef, }; -pub use dialect::{MigrationDialect, PostgresDialect, SqliteDialect}; +pub use dialect::{DuckDbDialect, MigrationDialect, PostgresDialect, SqliteDialect}; pub use migration::{Migration, MigrationRunner, MigrationStatus}; pub use operation::{ AddColumnOp, AddForeignKeyOp, AlterColumnOp, CreateIndexOp, CreateTableOp, DropColumnOp, diff --git a/crates/oxide-sql-core/tests/duckdb_e2e.rs b/crates/oxide-sql-core/tests/duckdb_e2e.rs new file mode 100644 index 0000000..e3e59b7 --- /dev/null +++ b/crates/oxide-sql-core/tests/duckdb_e2e.rs @@ -0,0 +1,355 @@ +//! End-to-end integration tests for the DuckDB dialect. +//! +//! These tests create an in-memory DuckDB database, execute DDL/DML +//! generated by oxide-sql typed builders and the DuckDB dialect, and +//! verify correctness by querying the database. + +use duckdb::{types::ToSql, Connection}; + +use oxide_sql_core::builder::value::SqlValue; +use oxide_sql_core::builder::{col, Delete, Insert, Select, Update}; +use oxide_sql_core::migrations::dialect::DuckDbDialect; +use oxide_sql_core::migrations::dialect::MigrationDialect; +use oxide_sql_core::migrations::{integer, varchar, CreateTableBuilder}; +use oxide_sql_derive::Table; + +// ------------------------------------------------------------------ +// Table definitions +// ------------------------------------------------------------------ + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "items")] +pub struct Item { + #[column(primary_key)] + pub id: i64, + pub name: String, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "counters")] +pub struct Counter { + #[column(primary_key)] + pub id: i64, + pub label: String, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "excluded_domains")] +pub struct ExcludedDomain { + #[column(primary_key)] + pub id: i64, + pub domain: String, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "excluded_ips")] +pub struct ExcludedIp { + #[column(primary_key)] + pub id: i64, + pub cidr: String, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "products")] +pub struct Product { + #[column(primary_key)] + pub id: i64, + pub name: String, + pub price: i64, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Table)] +#[table(name = "users")] +pub struct User { + #[column(primary_key)] + pub id: i64, + pub name: String, +} + +// ------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------ + +/// Converts `&[SqlValue]` into boxed `dyn ToSql` for DuckDB. +fn to_duckdb_params(values: &[SqlValue]) -> Vec> { + values + .iter() + .map(|v| -> Box { + match v { + SqlValue::Null => Box::new(duckdb::types::Null), + SqlValue::Bool(b) => Box::new(*b), + SqlValue::Int(i) => Box::new(*i), + SqlValue::Float(f) => Box::new(*f), + SqlValue::Text(s) => Box::new(s.clone()), + SqlValue::Blob(b) => Box::new(b.clone()), + } + }) + .collect() +} + +/// Execute a parameterised statement against a DuckDB connection. +fn execute_sql(conn: &Connection, sql: &str, params: &[SqlValue]) -> duckdb::Result { + let boxed = to_duckdb_params(params); + let refs: Vec<&dyn ToSql> = boxed.iter().map(|b| b.as_ref()).collect(); + conn.execute(sql, refs.as_slice()) +} + +/// Execute a batch of statements separated by `;` (no params). +fn execute_batch(conn: &Connection, sql: &str) -> duckdb::Result<()> { + conn.execute_batch(sql) +} + +/// Query rows as `(i64, String)` tuples. +fn query_id_str(conn: &Connection, sql: &str, params: &[SqlValue]) -> Vec<(i64, String)> { + let boxed = to_duckdb_params(params); + let refs: Vec<&dyn ToSql> = boxed.iter().map(|b| b.as_ref()).collect(); + let mut stmt = conn.prepare(sql).unwrap(); + stmt.query_map(refs.as_slice(), |row| { + Ok(( + row.get::<_, i64>(0).unwrap(), + row.get::<_, String>(1).unwrap(), + )) + }) + .unwrap() + .map(Result::unwrap) + .collect() +} + +// ------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------ + +#[test] +fn test_create_table_and_insert() { + let conn = Connection::open_in_memory().unwrap(); + let dialect = DuckDbDialect::new(); + + let create_op = CreateTableBuilder::new() + .name("items") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("name", 255).not_null().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&create_op)).unwrap(); + + let (sql, params) = Insert::::new() + .set(Item::name(), "widget") + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + + let (sql, params) = Select::::new() + .select_all() + .from_table() + .build(); + + let rows = query_id_str(&conn, &sql, ¶ms); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].0, 1); + assert_eq!(rows[0].1, "widget"); +} + +#[test] +fn test_autoincrement_sequence() { + let conn = Connection::open_in_memory().unwrap(); + let dialect = DuckDbDialect::new(); + + let create_op = CreateTableBuilder::new() + .name("counters") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("label", 100).not_null().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&create_op)).unwrap(); + + for label in &["alpha", "beta", "gamma"] { + let (sql, params) = Insert::::new() + .set(Counter::label(), *label) + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + } + + let (sql, params) = Select::::new() + .select_all() + .from_table() + .order_by(Counter::id(), true) + .build(); + + let rows = query_id_str(&conn, &sql, ¶ms); + assert_eq!(rows.len(), 3); + assert_eq!(rows[0], (1, "alpha".to_string())); + assert_eq!(rows[1], (2, "beta".to_string())); + assert_eq!(rows[2], (3, "gamma".to_string())); +} + +#[test] +fn test_consumer_scenario() { + let conn = Connection::open_in_memory().unwrap(); + let dialect = DuckDbDialect::new(); + + let op1 = CreateTableBuilder::new() + .if_not_exists() + .name("excluded_domains") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("domain", 255).not_null().unique().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&op1)).unwrap(); + + let op2 = CreateTableBuilder::new() + .if_not_exists() + .name("excluded_ips") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("cidr", 255).not_null().unique().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&op2)).unwrap(); + + for domain in &["example.com", "test.org"] { + let (sql, params) = Insert::::new() + .set(ExcludedDomain::domain(), *domain) + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + } + + for cidr in &["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] { + let (sql, params) = Insert::::new() + .set(ExcludedIp::cidr(), *cidr) + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + } + + let (sql, params) = Select::::new() + .select_all() + .from_table() + .order_by(ExcludedDomain::id(), true) + .build(); + let domains = query_id_str(&conn, &sql, ¶ms); + assert_eq!(domains.len(), 2); + assert_eq!(domains[0].1, "example.com"); + assert_eq!(domains[1].1, "test.org"); + + let (sql, params) = Select::::new() + .select_all() + .from_table() + .order_by(ExcludedIp::id(), true) + .build(); + let ips = query_id_str(&conn, &sql, ¶ms); + assert_eq!(ips.len(), 3); + assert_eq!(ips[0].1, "10.0.0.0/8"); + assert_eq!(ips[1].1, "192.168.0.0/16"); + assert_eq!(ips[2].1, "172.16.0.0/12"); +} + +#[test] +fn test_select_with_where() { + let conn = Connection::open_in_memory().unwrap(); + let dialect = DuckDbDialect::new(); + + let create_op = CreateTableBuilder::new() + .name("products") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("name", 255).not_null().build()) + .column(integer("price").not_null().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&create_op)).unwrap(); + + let products = vec![ + ("apple", 100_i64), + ("banana", 50), + ("cherry", 200), + ("date", 150), + ]; + for (name, price) in &products { + let (sql, params) = Insert::::new() + .set(Product::name(), *name) + .set(Product::price(), *price) + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + } + + // Typed Select with WHERE price > 100 + let (sql, params) = Select::::new() + .select_all() + .from_table() + .where_col(Product::price(), col(Product::price()).gt(100_i64)) + .order_by(Product::price(), true) + .build(); + + let boxed = to_duckdb_params(¶ms); + let refs: Vec<&dyn ToSql> = boxed.iter().map(|b| b.as_ref()).collect(); + let mut stmt = conn.prepare(&sql).unwrap(); + let rows: Vec<(i64, String, i64)> = stmt + .query_map(refs.as_slice(), |row| { + Ok(( + row.get::<_, i64>(0).unwrap(), + row.get::<_, String>(1).unwrap(), + row.get::<_, i64>(2).unwrap(), + )) + }) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(rows.len(), 2); + assert_eq!(rows[0].1, "date"); + assert_eq!(rows[0].2, 150); + assert_eq!(rows[1].1, "cherry"); + assert_eq!(rows[1].2, 200); +} + +#[test] +fn test_update_and_delete() { + let conn = Connection::open_in_memory().unwrap(); + let dialect = DuckDbDialect::new(); + + let create_op = CreateTableBuilder::new() + .name("users") + .column(integer("id").primary_key().autoincrement().build()) + .column(varchar("name", 255).not_null().build()) + .build(); + execute_batch(&conn, &dialect.create_table(&create_op)).unwrap(); + + for name in &["alice", "bob", "charlie"] { + let (sql, params) = Insert::::new() + .set(User::name(), *name) + .build(); + execute_sql(&conn, &sql, ¶ms).unwrap(); + } + + // Typed UPDATE: bob -> robert + let (sql, params) = Update::::new() + .set(User::name(), "robert") + .where_col(User::name(), col(User::name()).eq("bob")) + .build(); + let affected = execute_sql(&conn, &sql, ¶ms).unwrap(); + assert_eq!(affected, 1); + + // Verify update + let (sql, params) = Select::::new() + .select_all() + .from_table() + .order_by(User::id(), true) + .build(); + let rows = query_id_str(&conn, &sql, ¶ms); + assert_eq!(rows[1].1, "robert"); + + // Typed DELETE: charlie (id=3) + let (sql, params) = Delete::::new() + .where_col(User::id(), col(User::id()).eq(3_i64)) + .build(); + let affected = execute_sql(&conn, &sql, ¶ms).unwrap(); + assert_eq!(affected, 1); + + // Verify deletion — only 2 rows remain + let (sql, params) = Select::::new() + .select_all() + .from_table() + .order_by(User::id(), true) + .build(); + let rows = query_id_str(&conn, &sql, ¶ms); + assert_eq!(rows.len(), 2); + assert_eq!(rows[0].0, 1); + assert_eq!(rows[1].0, 2); +}