From 61b35ad3ffc03431228fa5040634c14d7e36d586 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 25 Jun 2026 06:20:26 +0200 Subject: [PATCH] feat(sql): migrate engine rusqlite -> gluesql-core, drop bundled SQLite (REQ-231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces rusqlite (bundled SQLite C) with gluesql-core (pure Rust) behind the unchanged `sql::query`/`plan_write` API. The ephemeral staging db is now gluesql memory-storage populated from the live store; gluesql's async `execute` is bridged via `futures::executor::block_on` (no runtime). The CI build no longer compiles the SQLite C amalgamation — the per-job weight that compounded #567 runner contention (DD-069). Behavior is identical — all existing tests pass unchanged: core read+write (sql:: 7/7), cli (sql_* 3/3 incl. round-trip fidelity + atomic rejection), serve (sql_endpoint 1/1). rusqlite + libsqlite3-sys removed from the dependency tree. Live V-closure query confirmed on the repo. Follow-on (now enabled): a native gluesql `Store`/`StoreMut` over rivet's store (true virtual tables; INSERT/DELETE/fields/links writes without the staging-diff). Implements: REQ-231 Refs: DD-069, REQ-229, REQ-230 Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 724 +++++++++++++++++++++++++++++++++++------- Cargo.toml | 11 +- rivet-core/Cargo.toml | 15 +- rivet-core/src/sql.rs | 400 ++++++++++++----------- 4 files changed, 836 insertions(+), 314 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4e5a0d..10b8d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,14 +13,13 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.12" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "cfg-if", + "getrandom 0.2.17", "once_cell", "version_check", - "zerocopy", ] [[package]] @@ -121,6 +120,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -131,6 +136,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -139,7 +155,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -212,6 +228,20 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -229,7 +259,7 @@ dependencies = [ "regex", "rustc-hash 2.1.2", "shlex", - "syn", + "syn 2.0.117", ] [[package]] @@ -259,6 +289,27 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -274,10 +325,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ + "borsh-derive", "bytes", "cfg_aliases", ] +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "boxcar" version = "0.2.14" @@ -293,6 +358,28 @@ dependencies = [ "allocator-api2", ] +[[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" @@ -507,10 +594,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -678,7 +765,7 @@ dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", "cranelift-srcgen", - "heck", + "heck 0.5.0", "pulley-interpreter", ] @@ -868,7 +955,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -879,7 +966,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -909,6 +996,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "derive_utils" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "digest" version = "0.10.7" @@ -948,7 +1057,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1030,18 +1139,6 @@ dependencies = [ "petgraph 0.7.1", ] -[[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 = "fastrand" version = "2.4.1" @@ -1070,6 +1167,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "find-crate" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b8e09277be3b89fccfc23ddbcbf496c6890f0cbaeefcdbe76aa747ec38cdd2" +dependencies = [ + "toml 1.1.2+spec-1.1.0", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1159,6 +1265,12 @@ dependencies = [ "libc", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.32" @@ -1190,6 +1302,18 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-enum" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888490b0e2e137dd4c1fae622efe7160e484e5fd0cc47c2a880e4614d90e48cc" +dependencies = [ + "derive_utils 0.11.2", + "find-crate", + "quote", + "syn 1.0.109", +] + [[package]] name = "futures-executor" version = "0.3.32" @@ -1215,7 +1339,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1316,7 +1440,7 @@ checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" dependencies = [ "fnv", "hashbrown 0.16.1", - "indexmap", + "indexmap 2.14.0", "stable_deref_trait", ] @@ -1326,6 +1450,60 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gluesql-core" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee257ac35040c55f631ca6c570379d3fa680a7ed2bc103bdf56ca4a03753eb2" +dependencies = [ + "async-recursion", + "async-trait", + "bigdecimal", + "cfg-if", + "chrono", + "futures", + "futures-enum", + "gluesql-utils", + "hex", + "im", + "iter-enum", + "itertools 0.12.1", + "md-5", + "ordered-float", + "rand 0.8.6", + "regex", + "rust_decimal", + "serde", + "serde_json", + "sqlparser", + "strum_macros", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "gluesql-utils" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a59f20843516bc76a90faf8ce63355643425f2a47961d067811cec412921dd1" +dependencies = [ + "futures", + "indexmap 1.9.3", + "pin-project", +] + +[[package]] +name = "gluesql_memory_storage" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e69388bee5db3c1fc1ad5678553542a5193449529cc12214c57c7f413d3e2a7" +dependencies = [ + "async-trait", + "futures", + "gluesql-core", + "serde", +] + [[package]] name = "good_lp" version = "1.15.1" @@ -1348,7 +1526,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1368,13 +1546,19 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1406,15 +1590,6 @@ dependencies = [ "foldhash 0.2.0", ] -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "hashlink" version = "0.10.0" @@ -1424,6 +1599,12 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1436,6 +1617,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "highs" version = "2.1.0" @@ -1724,6 +1911,30 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1832,6 +2043,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "iter-enum" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e701a443ecfdedddeea9b7975a1875c933f4001f12defa8a7a53e959611308" +dependencies = [ + "derive_utils 0.15.1", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1841,6 +2061,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1897,7 +2126,7 @@ checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2000,17 +2229,6 @@ dependencies = [ "redox_syscall 0.7.4", ] -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2091,6 +2309,16 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.8.0" @@ -2216,6 +2444,25 @@ dependencies = [ "instant", ] +[[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-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2243,7 +2490,7 @@ checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "crc32fast", "hashbrown 0.17.0", - "indexmap", + "indexmap 2.14.0", "memchr", ] @@ -2288,7 +2535,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2309,6 +2556,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", + "rand 0.8.6", + "serde", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2351,7 +2609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap", + "indexmap 2.14.0", ] [[package]] @@ -2361,7 +2619,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap", + "indexmap 2.14.0", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2462,7 +2740,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", ] [[package]] @@ -2481,7 +2768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap", + "indexmap 2.14.0", "nix", "tokio", "tracing", @@ -2507,6 +2794,26 @@ dependencies = [ "unarray", ] +[[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 = "pulldown-cmark" version = "0.12.2" @@ -2545,7 +2852,7 @@ checksum = "6c60fb1c885bdb1efd7c50e8e973714de558b75a65f20c3e9a41398c652aa44b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2585,6 +2892,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[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.6" @@ -2594,6 +2907,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -2644,6 +2958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.17", + "serde", ] [[package]] @@ -2670,6 +2985,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.12.0" @@ -2736,7 +3060,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2782,6 +3106,15 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[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" @@ -2871,6 +3204,9 @@ dependencies = [ "anyhow", "criterion", "etch", + "futures", + "gluesql-core", + "gluesql_memory_storage", "log", "petgraph 0.7.1", "proptest", @@ -2879,7 +3215,6 @@ dependencies = [ "regex", "reqwest", "rowan 0.16.2", - "rusqlite", "salsa", "serde", "serde_json", @@ -2897,6 +3232,35 @@ dependencies = [ "wiremock", ] +[[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 = "rmcp" version = "1.5.0" @@ -2939,7 +3303,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn", + "syn 2.0.117", ] [[package]] @@ -2966,17 +3330,20 @@ dependencies = [ ] [[package]] -name = "rusqlite" -version = "0.32.1" +name = "rust_decimal" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" dependencies = [ - "bitflags 2.11.1", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink 0.9.1", - "libsqlite3-sys", - "smallvec", + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.6", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", ] [[package]] @@ -3100,8 +3467,8 @@ dependencies = [ "crossbeam-queue", "crossbeam-utils", "hashbrown 0.15.5", - "hashlink 0.10.0", - "indexmap", + "hashlink", + "indexmap 2.14.0", "intrusive-collections", "inventory", "parking_lot", @@ -3129,7 +3496,7 @@ checksum = "978e5d5c9533ce19b6a58ad91024e1d136f6eec83c4ba98b5ce94c87986c41d8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3183,7 +3550,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.117", ] [[package]] @@ -3198,6 +3565,12 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "3.7.0" @@ -3258,7 +3631,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3269,7 +3642,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3304,7 +3677,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3334,7 +3707,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -3364,7 +3737,7 @@ checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3394,6 +3767,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.12" @@ -3515,6 +3904,17 @@ dependencies = [ "spar-parser", ] +[[package]] +name = "sqlparser" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a875d8cd437cc8a97e9aeaeea352ec9a19aea99c23e9effb17757291de80b08" +dependencies = [ + "bigdecimal", + "log", + "serde", +] + [[package]] name = "sse-stream" version = "0.2.2" @@ -3540,12 +3940,36 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + [[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.117" @@ -3574,7 +3998,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3614,6 +4038,12 @@ dependencies = [ "winx", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.13.5" @@ -3680,7 +4110,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3691,7 +4121,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3714,6 +4144,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.52.1" @@ -3739,7 +4184,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3792,15 +4237,30 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -3810,6 +4270,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" @@ -3901,7 +4382,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4081,6 +4562,7 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -4114,7 +4596,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -4134,8 +4616,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05a2b3bad87cc1ce45b63425ec09a854cc4cb369231c9fed1fee31538103efb" dependencies = [ "anyhow", - "heck", - "indexmap", + "heck 0.5.0", + "indexmap 2.14.0", "log", "petgraph 0.6.5", "smallvec", @@ -4181,7 +4663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.14.0", "wasm-encoder 0.244.0", "wasmparser 0.244.0", ] @@ -4194,7 +4676,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", "semver", ] @@ -4206,7 +4688,7 @@ checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" dependencies = [ "bitflags 2.11.1", "hashbrown 0.16.1", - "indexmap", + "indexmap 2.14.0", "semver", "serde", ] @@ -4218,7 +4700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" dependencies = [ "bitflags 2.11.1", - "indexmap", + "indexmap 2.14.0", "semver", ] @@ -4299,7 +4781,7 @@ dependencies = [ "cranelift-entity", "gimli", "hashbrown 0.16.1", - "indexmap", + "indexmap 2.14.0", "log", "object", "postcard", @@ -4331,7 +4813,7 @@ dependencies = [ "serde", "serde_derive", "sha2", - "toml", + "toml 0.9.12+spec-1.1.0", "wasmtime-environ", "windows-sys 0.61.2", "zstd", @@ -4346,7 +4828,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", "wit-parser 0.246.2", @@ -4457,7 +4939,7 @@ checksum = "910e5393af4aca456113581a5913b8d499cd2189013e983f8764d06ebc42b2ed" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4485,8 +4967,8 @@ checksum = "d890c3804d0e46000fa901c86ac1a9fdedf684e72dfd64e582b56bb4a78a6746" dependencies = [ "anyhow", "bitflags 2.11.1", - "heck", - "indexmap", + "heck 0.5.0", + "indexmap 2.14.0", "wit-parser 0.246.2", ] @@ -4594,10 +5076,10 @@ version = "44.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7f63d06fdf2e9a133407b318574574f66ea6590ea7a42c4a7554c029824454a" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasmtime-environ", "witx", ] @@ -4610,7 +5092,7 @@ checksum = "1b976b7285ca32a637e21721021442691a2d7938ab4650cb99472a672e9def6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wiggle-generate", ] @@ -4717,7 +5199,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4728,7 +5210,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4887,6 +5369,9 @@ name = "winnow" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] [[package]] name = "winx" @@ -4943,7 +5428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser 0.244.0", ] @@ -4954,10 +5439,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", - "indexmap", + "heck 0.5.0", + "indexmap 2.14.0", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4973,7 +5458,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4986,7 +5471,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.1", - "indexmap", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -5005,7 +5490,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", "semver", "serde", @@ -5024,7 +5509,7 @@ dependencies = [ "anyhow", "hashbrown 0.16.1", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", "semver", "serde", @@ -5052,6 +5537,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yoke" version = "0.8.2" @@ -5071,7 +5565,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -5092,7 +5586,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5112,7 +5606,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -5152,7 +5646,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9e22361..b37a991 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,9 +126,14 @@ rowan = { git = "https://github.com/pulseengine/rowan.git", branch = "fix/miri-s # Markdown rendering pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] } -# SQL facade over the artifact store (REQ-229 / DD-068). `bundled` compiles -# SQLite from source so there is no system libsqlite3 dependency. -rusqlite = { version = "0.32", features = ["bundled"] } +# SQL facade over the artifact store (REQ-229 / DD-068 / REQ-231). gluesql-core +# is pure Rust (sqlparser + execution engine) — NO bundled SQLite C compile. +# Use gluesql-core + the memory storage directly; the umbrella `gluesql` crate +# pulls every storage backend (sled/parquet/mongo/redis/…) and a C compiler. +gluesql-core = "0.19" +gluesql_memory_storage = "0.19" +# block_on bridge for gluesql's async execute API (no runtime needed). +futures = "0.3" # Benchmarking criterion = { version = "0.5", features = ["html_reports"] } diff --git a/rivet-core/Cargo.toml b/rivet-core/Cargo.toml index 26adb8d..3ff549e 100644 --- a/rivet-core/Cargo.toml +++ b/rivet-core/Cargo.toml @@ -14,10 +14,10 @@ rowan-yaml = [] oslc = ["dep:reqwest", "dep:urlencoding"] wasm = ["dep:wasmtime", "dep:wasmtime-wasi"] aadl = ["dep:spar-hir", "dep:spar-analysis"] -# REQ-229 / DD-068: SQL query facade over the artifact store. Kept in `default` -# so `rivet sql` ships, but gated so the minimal `--no-default-features` build -# (REQ-202) excludes the bundled SQLite C compile. -sql = ["dep:rusqlite"] +# REQ-229 / DD-068 / REQ-231: SQL query facade over the artifact store. Kept in +# `default` so `rivet sql` ships, but gated so the minimal `--no-default-features` +# build (REQ-202) excludes it. Engine is gluesql-core (pure Rust) — no SQLite C. +sql = ["dep:gluesql-core", "dep:gluesql_memory_storage", "dep:futures"] [dependencies] serde = { workspace = true } @@ -35,8 +35,11 @@ rowan = { workspace = true } pulldown-cmark = { workspace = true } etch = { path = "../etch" } -# SQL facade (optional, behind "sql" feature) — REQ-229 / DD-068 -rusqlite = { workspace = true, optional = true } +# SQL facade (optional, behind "sql" feature) — REQ-229 / DD-068 / REQ-231. +# gluesql-core: pure-Rust SQL engine (no bundled SQLite C). +gluesql-core = { workspace = true, optional = true } +gluesql_memory_storage = { workspace = true, optional = true } +futures = { workspace = true, optional = true } # OSLC client (optional, behind "oslc" feature) reqwest = { workspace = true, optional = true } diff --git a/rivet-core/src/sql.rs b/rivet-core/src/sql.rs index 5d7145c..f69fd16 100644 --- a/rivet-core/src/sql.rs +++ b/rivet-core/src/sql.rs @@ -1,9 +1,9 @@ -//! SQL query + write facade over the artifact store (REQ-229 / DD-068). +//! SQL query + write facade over the artifact store (REQ-229 / REQ-230 / DD-068). //! -//! Each call builds an **ephemeral in-memory SQLite** from the live store and +//! Engine: **gluesql-core** (pure Rust — no bundled SQLite C; REQ-231/DD-069). +//! Each call builds an **ephemeral in-memory gluesql db** from the live store and //! runs against it. Because the CLI reloads the project on every invocation, -//! results are never stale — DD-068's "no stale snapshot" guarantee without a -//! full `vtab` module (which rivet's scale doesn't need). +//! results are never stale — DD-068's "no stale snapshot" guarantee. //! //! Tables projected from the [`Store`]: //! - `artifacts(id, type, title, description, status, fields_json)` @@ -16,12 +16,17 @@ //! against the store and returns `ModifyParams` for the caller to validate and //! apply via the indentation-safe `modify_artifact_in_file` editor (no allowlist //! drop). Writes to `fields`/`links` and `INSERT`/`DELETE` are refused. +//! +//! gluesql's `execute` is async; it is bridged into these sync functions with +//! `futures::executor::block_on` (no async runtime required). #![cfg(feature = "sql")] use crate::mutate::ModifyParams; use crate::store::Store; -use rusqlite::Connection; +use futures::executor::block_on; +use gluesql_core::prelude::{Glue, Payload, Value}; +use gluesql_memory_storage::MemoryStorage; /// Tabular result of a SQL query: column names plus stringified rows. #[derive(Debug, Clone, PartialEq, Eq)] @@ -35,11 +40,7 @@ pub struct SqlResult { /// Returns an error string for write statements (anything not starting with /// `SELECT` or `WITH`) or on SQL errors. pub fn query(store: &Store, sql: &str) -> Result { - let head = sql - .split_whitespace() - .next() - .unwrap_or("") - .to_ascii_uppercase(); + let head = first_word_upper(sql); if head != "SELECT" && head != "WITH" { return Err(format!( "only read-only queries are supported (must start with SELECT or WITH; got `{head}`). \ @@ -47,145 +48,17 @@ pub fn query(store: &Store, sql: &str) -> Result { )); } - let conn = Connection::open_in_memory().map_err(|e| e.to_string())?; - build_schema(&conn).map_err(|e| e.to_string())?; - populate(&conn, store).map_err(|e| e.to_string())?; - run_query(&conn, sql) -} - -fn build_schema(conn: &Connection) -> rusqlite::Result<()> { - conn.execute_batch( - "CREATE TABLE artifacts ( - id TEXT PRIMARY KEY, - type TEXT NOT NULL, - title TEXT, - description TEXT, - status TEXT, - fields_json TEXT - ); - CREATE TABLE links ( - source TEXT NOT NULL, - link_type TEXT NOT NULL, - target TEXT NOT NULL, - external TEXT - ); - CREATE TABLE fields ( - artifact_id TEXT NOT NULL, - key TEXT NOT NULL, - value TEXT - ); - CREATE TABLE provenance ( - artifact_id TEXT NOT NULL, - created_by TEXT, - model TEXT, - session_id TEXT, - timestamp TEXT, - reviewed_by TEXT - );", - ) -} - -fn populate(conn: &Connection, store: &Store) -> rusqlite::Result<()> { - for a in store.iter_sorted() { - let fields_json = serde_json::to_string(&a.fields).ok(); - conn.execute( - "INSERT INTO artifacts (id, type, title, description, status, fields_json) - VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - rusqlite::params![ - a.id, - a.artifact_type, - a.title, - a.description, - a.status, - fields_json, - ], - )?; - - for l in &a.links { - // `external` is non-null only for `*-external` link types; serialize - // the structured payload to JSON so it stays queryable. - let external = l - .external - .as_ref() - .and_then(|e| serde_json::to_string(e).ok()); - conn.execute( - "INSERT INTO links (source, link_type, target, external) VALUES (?1, ?2, ?3, ?4)", - rusqlite::params![a.id, l.link_type, l.target, external], - )?; - } - - for (key, value) in &a.fields { - conn.execute( - "INSERT INTO fields (artifact_id, key, value) VALUES (?1, ?2, ?3)", - rusqlite::params![a.id, key, yaml_value_to_sql(value)], - )?; - } - - if let Some(p) = &a.provenance { - conn.execute( - "INSERT INTO provenance - (artifact_id, created_by, model, session_id, timestamp, reviewed_by) - VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - rusqlite::params![ - a.id, - p.created_by, - p.model, - p.session_id, - p.timestamp, - p.reviewed_by, - ], - )?; - } - } - Ok(()) -} - -/// Flatten a YAML field value to a SQL-friendly string: scalars become their -/// natural text, complex values (sequences/maps) become compact JSON so they -/// remain queryable with SQLite's `json_*` functions. -fn yaml_value_to_sql(value: &serde_yaml::Value) -> Option { - match value { - serde_yaml::Value::Null => None, - serde_yaml::Value::Bool(b) => Some(b.to_string()), - serde_yaml::Value::Number(n) => Some(n.to_string()), - serde_yaml::Value::String(s) => Some(s.clone()), - other => serde_json::to_string(other).ok(), - } -} - -fn run_query(conn: &Connection, sql: &str) -> Result { - let mut stmt = conn.prepare(sql).map_err(|e| e.to_string())?; - let columns: Vec = stmt.column_names().iter().map(|s| s.to_string()).collect(); - let col_count = columns.len(); - - let mut out_rows = Vec::new(); - let mut rows = stmt.query([]).map_err(|e| e.to_string())?; - while let Some(row) = rows.next().map_err(|e| e.to_string())? { - let mut cells = Vec::with_capacity(col_count); - for i in 0..col_count { - let cell = row.get_ref(i).map_err(|e| e.to_string())?; - cells.push(value_ref_to_string(cell)); - } - out_rows.push(cells); - } - + let mut glue = staging(store)?; + let (labels, rows) = select(&mut glue, sql)?; Ok(SqlResult { - columns, - rows: out_rows, + columns: labels, + rows: rows + .into_iter() + .map(|r| r.iter().map(value_to_string).collect()) + .collect(), }) } -fn value_ref_to_string(v: rusqlite::types::ValueRef<'_>) -> String { - use rusqlite::types::ValueRef; - match v { - ValueRef::Null => String::new(), - ValueRef::Integer(i) => i.to_string(), - ValueRef::Real(f) => f.to_string(), - ValueRef::Text(t) => String::from_utf8_lossy(t).into_owned(), - ValueRef::Blob(b) => format!("", b.len()), - } -} - /// A modification planned by a write query: the artifact id and the /// `ModifyParams` to apply. Pure data — the caller validates and applies each /// via `validate_modify` + `modify_artifact_in_file` (the indentation-safe @@ -199,68 +72,47 @@ pub struct PlannedWrite { /// Plan the artifact modifications implied by a write SQL statement WITHOUT /// touching any file. /// -/// The statement runs against the ephemeral staging SQLite; the `artifacts` -/// table is then diffed against the live store. Only `UPDATE artifacts SET +/// The statement runs against the ephemeral staging db; the `artifacts` table is +/// then diffed against the live store. Only `UPDATE artifacts SET /// {status|title|description}` is supported in this slice — writes that change /// the `fields`/`links` staging tables, or that `INSERT`/`DELETE` rows, are /// refused (clear error, never a silent partial write). pub fn plan_write(store: &Store, sql: &str) -> Result, String> { - let head = sql - .split_whitespace() - .next() - .unwrap_or("") - .to_ascii_uppercase(); + let head = first_word_upper(sql); if head == "SELECT" || head == "WITH" { return Err( "not a write statement — use `rivet sql` without a write verb for reads".into(), ); } - let conn = Connection::open_in_memory().map_err(|e| e.to_string())?; - build_schema(&conn).map_err(|e| e.to_string())?; - populate(&conn, store).map_err(|e| e.to_string())?; + let mut glue = staging(store)?; // Snapshot the tables this slice does NOT support, so a write that changes // them is refused rather than silently dropped. - let fields_before = snapshot( - &conn, - "SELECT artifact_id, key, value FROM fields ORDER BY 1, 2", - )?; - let links_before = snapshot( - &conn, - "SELECT source, link_type, target FROM links ORDER BY 1, 2, 3", - )?; + let fields_before = snapshot(&mut glue, "SELECT artifact_id, key, value FROM fields")?; + let links_before = snapshot(&mut glue, "SELECT source, link_type, target FROM links")?; - conn.execute(sql, []) - .map_err(|e| format!("SQL write failed: {e}"))?; + block_on(glue.execute(sql)).map_err(|e| format!("SQL write failed: {e}"))?; - if snapshot( - &conn, - "SELECT artifact_id, key, value FROM fields ORDER BY 1, 2", - )? != fields_before - { + if snapshot(&mut glue, "SELECT artifact_id, key, value FROM fields")? != fields_before { return Err("writes to `fields` are not supported in this slice (UPDATE artifacts SET status/title/description only); mapping SQL field writes to set-field is a follow-up".into()); } - if snapshot( - &conn, - "SELECT source, link_type, target FROM links ORDER BY 1, 2, 3", - )? != links_before - { + if snapshot(&mut glue, "SELECT source, link_type, target FROM links")? != links_before { return Err("writes to `links` are not supported in this slice".into()); } // Diff the artifacts table against the store. let mut planned = Vec::new(); let mut seen = std::collections::HashSet::new(); - let mut stmt = conn - .prepare("SELECT id, title, description, status FROM artifacts") - .map_err(|e| e.to_string())?; - let mut rows = stmt.query([]).map_err(|e| e.to_string())?; - while let Some(row) = rows.next().map_err(|e| e.to_string())? { - let id: String = row.get(0).map_err(|e| e.to_string())?; - let title: String = row.get(1).map_err(|e| e.to_string())?; - let description: Option = row.get(2).map_err(|e| e.to_string())?; - let status: Option = row.get(3).map_err(|e| e.to_string())?; + let (_, rows) = select( + &mut glue, + "SELECT id, title, description, status FROM artifacts", + )?; + for row in &rows { + let id = value_opt(&row[0]).unwrap_or_default(); + let title = value_opt(&row[1]).unwrap_or_default(); + let description = value_opt(&row[2]); + let status = value_opt(&row[3]); seen.insert(id.clone()); let orig = store.get(&id).ok_or_else(|| { @@ -323,14 +175,182 @@ pub fn plan_write(store: &Store, sql: &str) -> Result, String> Ok(planned) } +// ── engine internals ──────────────────────────────────────────────────────── + +fn first_word_upper(sql: &str) -> String { + sql.split_whitespace() + .next() + .unwrap_or("") + .to_ascii_uppercase() +} + +/// Build a fresh in-memory gluesql db and populate it from the store. +fn staging(store: &Store) -> Result, String> { + let mut glue = Glue::new(MemoryStorage::default()); + block_on(glue.execute(&build_setup_sql(store))) + .map_err(|e| format!("building SQL staging tables failed: {e}"))?; + Ok(glue) +} + +/// The CREATE TABLE + batched INSERT SQL that projects the store into staging. +fn build_setup_sql(store: &Store) -> String { + let mut s = String::new(); + s.push_str( + "CREATE TABLE artifacts (id TEXT, type TEXT, title TEXT, description TEXT, status TEXT, fields_json TEXT);\n\ + CREATE TABLE links (source TEXT, link_type TEXT, target TEXT, external TEXT);\n\ + CREATE TABLE fields (artifact_id TEXT, key TEXT, value TEXT);\n\ + CREATE TABLE provenance (artifact_id TEXT, created_by TEXT, model TEXT, session_id TEXT, timestamp TEXT, reviewed_by TEXT);\n", + ); + + let mut artifacts = Vec::new(); + let mut links = Vec::new(); + let mut fields = Vec::new(); + let mut provenance = Vec::new(); + + for a in store.iter_sorted() { + let fields_json = serde_json::to_string(&a.fields).ok(); + artifacts.push(format!( + "({}, {}, {}, {}, {}, {})", + lit(Some(&a.id)), + lit(Some(&a.artifact_type)), + lit(Some(&a.title)), + lit(a.description.as_deref()), + lit(a.status.as_deref()), + lit(fields_json.as_deref()), + )); + for l in &a.links { + // `external` is non-null only for `*-external` link types; serialize + // the structured payload to JSON so it stays queryable. + let external = l + .external + .as_ref() + .and_then(|e| serde_json::to_string(e).ok()); + links.push(format!( + "({}, {}, {}, {})", + lit(Some(&a.id)), + lit(Some(&l.link_type)), + lit(Some(&l.target)), + lit(external.as_deref()), + )); + } + for (key, value) in &a.fields { + fields.push(format!( + "({}, {}, {})", + lit(Some(&a.id)), + lit(Some(key)), + lit(yaml_value_to_sql(value).as_deref()), + )); + } + if let Some(p) = &a.provenance { + provenance.push(format!( + "({}, {}, {}, {}, {}, {})", + lit(Some(&a.id)), + lit(Some(&p.created_by)), + lit(p.model.as_deref()), + lit(p.session_id.as_deref()), + lit(p.timestamp.as_deref()), + lit(p.reviewed_by.as_deref()), + )); + } + } + + for (table, rows) in [ + ("artifacts", artifacts), + ("links", links), + ("fields", fields), + ("provenance", provenance), + ] { + if !rows.is_empty() { + s.push_str(&format!( + "INSERT INTO {table} VALUES {};\n", + rows.join(", ") + )); + } + } + s +} + +/// Escape an optional string into a SQL literal: `NULL`, or `'…'` with embedded +/// single-quotes doubled. The staging db is ephemeral, but values still must +/// parse cleanly. +fn lit(v: Option<&str>) -> String { + match v { + None => "NULL".to_string(), + Some(s) => format!("'{}'", s.replace('\'', "''")), + } +} + +/// Flatten a YAML field value to a SQL-friendly string: scalars become their +/// natural text, complex values (sequences/maps) become compact JSON. +fn yaml_value_to_sql(value: &serde_yaml::Value) -> Option { + match value { + serde_yaml::Value::Null => None, + serde_yaml::Value::Bool(b) => Some(b.to_string()), + serde_yaml::Value::Number(n) => Some(n.to_string()), + serde_yaml::Value::String(s) => Some(s.clone()), + other => serde_json::to_string(other).ok(), + } +} + +/// Execute a `SELECT` and return its labels + rows of gluesql `Value`s. +fn select( + glue: &mut Glue, + sql: &str, +) -> Result<(Vec, Vec>), String> { + let payload = block_on(glue.execute(sql)) + .map_err(|e| e.to_string())? + .into_iter() + .next() + .ok_or_else(|| "query produced no payload".to_string())?; + match payload { + Payload::Select { labels, rows } => Ok((labels, rows)), + // SELECT of a single value (e.g. count) or an empty table still yields + // a Select payload; anything else means the caller passed a non-query. + #[allow(clippy::wildcard_enum_match_arm)] + other => Err(format!("expected a SELECT result, got {other:?}")), + } +} + +/// Stringify a gluesql `Value` for the tabular `SqlResult` (NULL -> empty). +#[allow(clippy::wildcard_enum_match_arm)] +fn value_to_string(v: &Value) -> String { + match v { + Value::Null => String::new(), + Value::Str(s) => s.clone(), + Value::Bool(b) => b.to_string(), + Value::I64(n) => n.to_string(), + Value::F64(f) => f.to_string(), + other => format!("{other:?}"), + } +} + +/// Convert a gluesql `Value` to `Option`, preserving the NULL/empty +/// distinction the write-diff relies on (NULL -> None, '' -> Some("")). +#[allow(clippy::wildcard_enum_match_arm)] +fn value_opt(v: &Value) -> Option { + match v { + Value::Null => None, + Value::Str(s) => Some(s.clone()), + other => Some(value_to_string(other)), + } +} + /// Snapshot a query's rows as a single comparable string (for change detection). -fn snapshot(conn: &Connection, sql: &str) -> Result { - let r = run_query(conn, sql)?; - Ok(r.rows +fn snapshot(glue: &mut Glue, sql: &str) -> Result { + let (_, rows) = select(glue, sql)?; + let mut lines: Vec = rows .iter() - .map(|row| row.join("\u{1f}")) - .collect::>() - .join("\n")) + .map(|row| { + row.iter() + .map(value_to_string) + .collect::>() + .join("\u{1f}") + }) + .collect(); + // gluesql does not guarantee row order without ORDER BY; sort for a stable + // comparison key. + lines.sort(); + Ok(lines.join("\n")) } #[cfg(test)]