diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d87b76b1..c98a9bf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,9 @@ jobs: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 with: - workspaces: ". -> target" + workspaces: | + . -> target + examples/rust-app-demo -> target - name: cargo fmt run: cargo fmt --all -- --check # awa-python is a separate cargo workspace, so the root `cargo fmt --all` @@ -64,6 +66,15 @@ jobs: working-directory: awa-python - name: cargo clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings + # examples/rust-app-demo is a third cargo workspace and depends on awa by + # path, so a change to awa's public API or its sqlx major can break it + # while every other job stays green. Same reasoning as awa-python above. + - name: cargo fmt (rust-app-demo workspace) + run: cargo fmt --all -- --check + working-directory: examples/rust-app-demo + - name: cargo clippy (rust-app-demo workspace) + run: cargo clippy --all-targets -- -D warnings + working-directory: examples/rust-app-demo - name: Check CI test-shard membership run: ./scripts/ci-test-shard.sh check diff --git a/CHANGELOG.md b/CHANGELOG.md index bc5801f5..7929db7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Notable changes between releases. Detailed migration notes for storage transitio ## [Unreleased] +### Changed + +- **Upgraded to sea-orm 2.0.2 stable and sqlx 0.9 ([#443](https://github.com/hardbyte/awa/issues/443)).** sea-orm moves from the 2.0.0 release candidates (sqlx 0.8) to the stable line built on sqlx 0.9, which unifies the dependency for `awa-seaorm` consumers. For library users the visible change is sqlx 0.9's injection guard: query functions only accept `&'static str` or an explicit assertion, so dynamically assembled SQL must opt in via [`awa::audited_sql`](https://docs.rs/awa) (new re-export). Awa's own call sites interpolate only validated identifiers — schema names are checked against `[a-z_][a-z0-9_]*` both where they are configured (`QueueStorage::new`) and where they are read back out of the transition state (`QueueStorage::active_schema`, which now re-validates so no unchecked name can reach a `format!`) — and pass all external values as bind parameters; the helper documents that invariant at its definition. The deliberate raw-SQL passthrough APIs (Python `Transaction.execute` / `fetch_*`, where the caller supplies the statement) opt in through a separate `caller_provided_sql` instead, so `audited_sql` stays greppable as the set of sites awa vouches for. sqlx's combined runtime+TLS features were removed in 0.9, so builds now select `runtime-tokio` + `tls-rustls-ring` explicitly (the same ring provider with webpki roots `runtime-tokio-rustls` selected previously). The upgrade also drops 20 transitive crates, among them `rsa` and its `pkcs1`/`pkcs8`/`spki`/`der`/`num-bigint-dig` chain: `sqlx-mysql` 0.8 depended on `rsa` and `sqlx-macros-core` pulled that driver in unconditionally, so a Postgres-only build still carried it — and `rsa` 0.9.x has no released fix for the RUSTSEC-2023-0071 Marvin timing advisory. `sqlx-mysql` 0.9 no longer needs it. No schema, migration, or behavioural changes. + ### Fixed - **Nightly flake gates now carry runner-contention margin ([#399](https://github.com/hardbyte/awa/issues/399), [#434](https://github.com/hardbyte/awa/issues/434)).** Four assertion shapes in the chaos and benchmark suites were tight enough that shared-runner CPU contention failed them while every invariant they exist for was intact, eroding the 14-consecutive-green-nightlies release gate. `awa/tests/ci_timing.rs` now holds the scaling for all of them, and it only ever loosens a bound, and only when `CI` is set: diff --git a/Cargo.lock b/Cargo.lock index 84ee29f9..959ed45b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,12 +36,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "allocator-api2" version = "0.2.21" @@ -604,12 +598,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "better_any" version = "0.2.1" @@ -899,12 +887,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-oid" version = "0.10.2" @@ -1107,23 +1089,23 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468", - "zeroize", + "serde_core", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "derive-where" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ - "serde_core", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -1161,9 +1143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", "crypto-common 0.1.7", - "subtle", ] [[package]] @@ -1173,7 +1153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -1252,13 +1232,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1301,9 +1280,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -1318,9 +1297,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -1522,21 +1501,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "hashbrown" version = "0.17.1" @@ -1545,11 +1518,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -1572,20 +1545,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "digest 0.10.7", + "hmac", ] [[package]] @@ -1597,15 +1561,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "http" version = "1.5.0" @@ -1895,17 +1850,6 @@ dependencies = [ "hashbrown 0.17.1", ] -[[package]] -name = "inherent" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee2c455ca60511a054699102d40ce7153621cb2429558f9eaa600e4499b5984" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "ipnet" version = "2.12.1" @@ -1949,9 +1893,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lexical-core" @@ -2028,10 +1969,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ - "bitflags", "libc", - "plain", - "redox_syscall 0.9.3", ] [[package]] @@ -2097,16 +2035,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - [[package]] name = "md-5" version = "0.11.0" @@ -2253,22 +2181,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -2293,16 +2205,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2478,30 +2380,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.119", -] - [[package]] name = "parking" version = "2.2.1" @@ -2526,7 +2404,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -2537,15 +2415,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2624,39 +2493,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "pluralizer" version = "0.5.0" @@ -2683,8 +2525,8 @@ dependencies = [ "byteorder", "bytes", "fallible-iterator", - "hmac 0.13.0", - "md-5 0.11.0", + "hmac", + "md-5", "memchr", "rand 0.10.2", "sha2 0.11.0", @@ -2775,19 +2617,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "version_check", - "yansi", -] - [[package]] name = "prost" version = "0.14.4" @@ -2952,15 +2781,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -3124,26 +2944,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rust-embed" version = "8.12.0" @@ -3291,20 +3091,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sea-orm" -version = "2.0.0-rc.38" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5428ce6a0c8f6b9858df21ad1aa00c2fb94e1c9f344a0436bc855391e5a225" +checksum = "a334e83ced3ae3ee44db0f84d1fcf8d2087a1ad9bb9036f00f9f6067156ea197" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", + "derive-where", "derive_more", "futures-util", "itertools", "log", "mac_address", - "ouroboros", "pgvector", "rust_decimal", "sea-orm-arrow", @@ -3315,12 +3115,14 @@ dependencies = [ "serde", "serde_json", "sqlx", + "sqlx-core", "strum", "thiserror 2.0.20", "time", "tracing", "url", "uuid", + "web-time", ] [[package]] @@ -3351,12 +3153,11 @@ dependencies = [ [[package]] name = "sea-query" -version = "1.0.0-rc.33" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04cdb0135c16e829504e93fbe7880513578d56f07aaea152283526590111828" +checksum = "546040c653a705e60ec65ecd3191a809603734bebbc225775916dea9ae409b31" dependencies = [ "chrono", - "inherent", "ordered-float", "sea-query-derive", "serde_json", @@ -3378,9 +3179,9 @@ dependencies = [ [[package]] name = "sea-query-sqlx" -version = "0.8.0-rc.15" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04aeecfe00614fece56336fd35dc385bb9ffed0c75660695ba925e42a3991ef" +checksum = "4eaa419cdb9157da1361186b1959983eb2ea0dcb9a3c69dc45c449ecb2af8fef" dependencies = [ "sea-query", "sqlx", @@ -3388,9 +3189,9 @@ dependencies = [ [[package]] name = "sea-schema" -version = "0.17.0-rc.17" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b363dd21c20fe4d1488819cb2bc7f8d4696c62dd9f39554f97639f54d57dd0ab" +checksum = "3553c77dceed56e95bece9ea876c4dd67ca879ef51055a0b97a7bb89a8ae4fed" dependencies = [ "async-trait", "sea-query", @@ -3523,13 +3324,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -3588,16 +3389,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "simdutf8" version = "0.1.5" @@ -3644,21 +3435,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3669,12 +3450,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -3684,12 +3466,11 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "rustls", "serde", @@ -3702,14 +3483,14 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -3720,15 +3501,15 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck 0.5.0", "hex", - "once_cell", "proc-macro2", "quote", "serde", @@ -3739,59 +3520,44 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror 2.0.20", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest 0.10.7", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac 0.12.1", - "itoa", "log", - "md-5 0.10.6", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.7", - "rsa", "serde", "sha1", - "sha2 0.10.9", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror 2.0.20", "tracing", "uuid", - "whoami 1.6.1", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -3806,35 +3572,34 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac 0.12.1", - "home", + "hmac", "itoa", "log", - "md-5 0.10.6", + "md-5", "memchr", - "once_cell", - "rand 0.8.7", + "rand 0.10.2", "serde", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.20", "tracing", "uuid", - "whoami 1.6.1", + "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -3844,7 +3609,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror 2.0.20", "tracing", @@ -3858,12 +3622,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stringprep" version = "0.1.5" @@ -4172,7 +3930,7 @@ dependencies = [ "socket2", "tokio", "tokio-util", - "whoami 2.1.3", + "whoami", ] [[package]] @@ -4628,12 +4386,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasite" version = "1.0.2" @@ -4719,15 +4471,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.9", -] - [[package]] name = "webpki-roots" version = "1.0.9" @@ -4737,16 +4480,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite 0.1.0", -] - [[package]] name = "whoami" version = "2.1.3" @@ -4756,7 +4489,7 @@ dependencies = [ "libc", "libredox", "objc2-system-configuration", - "wasite 1.0.2", + "wasite", "web-sys", ] @@ -4861,22 +4594,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4888,67 +4612,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4961,48 +4652,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5048,12 +4715,6 @@ dependencies = [ "tap", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 865b3f4f..913a0cf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,8 @@ awa-worker = { path = "awa-worker", version = "0.7.0-alpha.1" } awa = { path = "awa", version = "0.7.0-alpha.1" } # Database -sea-orm = { version = "=2.0.0-rc.38", default-features = false, features = ["sqlx-postgres", "runtime-tokio-rustls", "with-chrono", "with-json"] } -sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "chrono", "json", "migrate", "uuid"] } +sea-orm = { version = "2.0.2", default-features = false, features = ["sqlx-postgres", "runtime-tokio-rustls", "with-chrono", "with-json"] } +sqlx = { version = "0.9", features = ["runtime-tokio", "tls-rustls-ring", "postgres", "chrono", "json", "migrate", "uuid"] } # UUID uuid = { version = "1", features = ["v4", "serde"] } diff --git a/awa-cli/src/main.rs b/awa-cli/src/main.rs index f4892927..7b179128 100644 --- a/awa-cli/src/main.rs +++ b/awa-cli/src/main.rs @@ -1687,9 +1687,11 @@ async fn run() -> Result<(), Box> { ..Default::default() })?; if reset { - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await?; + sqlx::query(awa_model::audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await?; } store.prepare_schema(&pool).await?; println!( diff --git a/awa-cli/tests/migrate_cli_test.rs b/awa-cli/tests/migrate_cli_test.rs index e6bfe255..f6dfdde0 100644 --- a/awa-cli/tests/migrate_cli_test.rs +++ b/awa-cli/tests/migrate_cli_test.rs @@ -14,6 +14,7 @@ use std::sync::OnceLock; use std::time::Duration; use assert_cmd::Command; +use awa_model::audited_sql; use awa_model::migrations; use sqlx::postgres::{PgConnectOptions, PgConnection, PgPoolOptions}; use sqlx::{Connection, PgPool}; @@ -75,7 +76,7 @@ async fn ensure_test_database() { if !exists { // CREATE DATABASE takes no bind parameters. Tolerate the duplicate // race under per-test processes (cargo-nextest CI shards). - if let Err(err) = sqlx::raw_sql(&format!("CREATE DATABASE {TEST_DB_NAME}")) + if let Err(err) = sqlx::raw_sql(audited_sql(format!("CREATE DATABASE {TEST_DB_NAME}"))) .execute(&mut admin) .await { @@ -363,7 +364,7 @@ async fn rendered_sql_installs_the_current_schema_and_replays_cleanly() { let mut conn = PgConnection::connect(&test_database_url()) .await .expect("connect"); - sqlx::raw_sql(&rendered) + sqlx::raw_sql(audited_sql(rendered.clone())) .execute(&mut conn) .await .expect("rendered SQL should install the schema"); @@ -374,7 +375,7 @@ async fn rendered_sql_installs_the_current_schema_and_replays_cleanly() { .expect("read schema version"); assert_eq!(version, migrations::CURRENT_VERSION); - sqlx::raw_sql(&rendered) + sqlx::raw_sql(audited_sql(rendered.clone())) .execute(&mut conn) .await .expect("rendered SQL should be safe to re-apply"); @@ -504,7 +505,11 @@ async fn rendered_sql_is_atomic_only_with_the_transaction_wrapper() { let mut conn = connect().await; for (_, path) in &files { let sql = std::fs::read_to_string(path).expect("read extracted sql"); - if sqlx::raw_sql(&sql).execute(&mut conn).await.is_err() { + if sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&mut conn) + .await + .is_err() + { break; } } @@ -521,7 +526,9 @@ async fn rendered_sql_is_atomic_only_with_the_transaction_wrapper() { ) .expect("utf-8 sql"); let mut conn = connect().await; - let _ = sqlx::raw_sql(&wrapped).execute(&mut conn).await; + let _ = sqlx::raw_sql(audited_sql(wrapped.clone())) + .execute(&mut conn) + .await; conn.close().await.expect("close"); let wrapped_leftovers = awa_relation_count(&pool).await; diff --git a/awa-cli/tests/storage_finalize_cli_test.rs b/awa-cli/tests/storage_finalize_cli_test.rs index bc36979b..461caef9 100644 --- a/awa-cli/tests/storage_finalize_cli_test.rs +++ b/awa-cli/tests/storage_finalize_cli_test.rs @@ -13,6 +13,7 @@ use std::sync::OnceLock; use std::time::Duration; use assert_cmd::Command; +use awa_model::audited_sql; use awa_model::{migrations, storage, QueueStorage}; use sqlx::postgres::{PgConnectOptions, PgConnection, PgPoolOptions}; use sqlx::{Connection, PgPool}; @@ -74,7 +75,7 @@ async fn ensure_test_database() { .expect("check db existence"); if !exists { // CREATE DATABASE does not support bind parameters. - sqlx::raw_sql(&format!("CREATE DATABASE {TEST_DB_NAME}")) + sqlx::raw_sql(audited_sql(format!("CREATE DATABASE {TEST_DB_NAME}"))) .execute(&mut admin) .await .expect("create finalize cli test db"); @@ -123,10 +124,12 @@ async fn reset_schema(pool: &PgPool) { } async fn prepare_queue_storage_schema(pool: &PgPool, schema: &str) { - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await - .expect("drop qs schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await + .expect("drop qs schema"); let store = QueueStorage::from_existing_schema(schema).expect("qs schema validates"); store.prepare_schema(pool).await.expect("prepare qs schema"); } diff --git a/awa-model/src/admin.rs b/awa-model/src/admin.rs index 34c0dbfd..ab8ab1e6 100644 --- a/awa-model/src/admin.rs +++ b/awa-model/src/admin.rs @@ -1,3 +1,4 @@ +use crate::audited_sql; use crate::dlq::DlqMetadata; use crate::error::AwaError; use crate::job::{JobRow, JobState}; @@ -498,7 +499,7 @@ async fn list_queue_storage_jobs( let mut cursor = filter.before_id; loop { - let ids: Vec = sqlx::query_scalar(&sql) + let ids: Vec = sqlx::query_scalar(audited_sql(sql.clone())) .bind(filter.state) .bind(&filter.kind) .bind(&filter.queue) @@ -836,7 +837,7 @@ pub async fn cancel_by_unique_key( if let Some(store) = active_queue_storage(pool).await? { let sql = unique_key_candidate_sql(store.schema()); - let candidate: Option = sqlx::query_scalar(&sql) + let candidate: Option = sqlx::query_scalar(audited_sql(sql)) .bind(&unique_key) .fetch_optional(pool) .await?; @@ -903,7 +904,7 @@ async fn cancel_by_unique_key_in_tx<'a>( if let Some(store) = active_queue_storage_in_tx(tx).await? { let sql = unique_key_candidate_sql(store.schema()); - let candidate: Option = sqlx::query_scalar(&sql) + let candidate: Option = sqlx::query_scalar(audited_sql(sql)) .bind(&unique_key) .fetch_optional(tx.as_mut()) .await?; @@ -964,7 +965,10 @@ pub async fn retry_failed_by_kind( "#, schema = store.schema() ); - let ids: Vec = sqlx::query_scalar(&sql).bind(kind).fetch_all(pool).await?; + let ids: Vec = sqlx::query_scalar(audited_sql(sql)) + .bind(kind) + .fetch_all(pool) + .await?; let (retried, matched) = store.retry_jobs_by_ids(pool, &ids).await?; return Ok(RetryFailedOutcome { retried, @@ -1009,7 +1013,10 @@ pub async fn retry_failed_by_queue( "#, schema = store.schema() ); - let ids: Vec = sqlx::query_scalar(&sql).bind(queue).fetch_all(pool).await?; + let ids: Vec = sqlx::query_scalar(audited_sql(sql)) + .bind(queue) + .fetch_all(pool) + .await?; let (retried, matched) = store.retry_jobs_by_ids(pool, &ids).await?; let pruned_failed_count = store.pruned_failed_count_for_queue(pool, queue).await?; return Ok(RetryFailedOutcome { @@ -1180,7 +1187,10 @@ pub async fn drain_queue(pool: &PgPool, queue: &str) -> Result { ORDER BY job_id ASC", queue_storage_current_jobs_cte(store.schema()) ); - let ids: Vec = sqlx::query_scalar(&sql).bind(queue).fetch_all(pool).await?; + let ids: Vec = sqlx::query_scalar(audited_sql(sql)) + .bind(queue) + .fetch_all(pool) + .await?; return store .cancel_jobs_by_ids(pool, &ids) .await @@ -1349,7 +1359,7 @@ pub async fn sync_queue_descriptors( .map(|named| named.descriptor.descriptor_hash()) .collect(); let sql = build_descriptor_upsert("awa.queue_descriptors", "queue", chunk.len()); - let mut query = sqlx::query(&sql); + let mut query = sqlx::query(audited_sql(sql)); for (named, hash) in chunk.iter().zip(hashes.iter()) { query = query .bind(&named.queue) @@ -1386,7 +1396,7 @@ pub async fn sync_job_kind_descriptors( .map(|named| named.descriptor.descriptor_hash()) .collect(); let sql = build_descriptor_upsert("awa.job_kind_descriptors", "kind", chunk.len()); - let mut query = sqlx::query(&sql); + let mut query = sqlx::query(audited_sql(sql)); for (named, hash) in chunk.iter().zip(hashes.iter()) { query = query .bind(&named.kind) @@ -2032,7 +2042,10 @@ where // Table name is an authenticated literal from the match above — safe // to interpolate into the statement. let sql = format!("DELETE FROM {table} WHERE last_seen_at < now() - make_interval(secs => $1)"); - let result = sqlx::query(&sql).bind(seconds).execute(executor).await?; + let result = sqlx::query(audited_sql(sql)) + .bind(seconds) + .execute(executor) + .await?; Ok(result.rows_affected()) } @@ -2282,7 +2295,7 @@ pub async fn queue_overviews(pool: &PgPool) -> Result, AwaErr current_jobs_cte = queue_storage_current_jobs_cte(store.schema()) ); - let rows = sqlx::query_as::<_, QueueOverview>(&sql) + let rows = sqlx::query_as::<_, QueueOverview>(audited_sql(sql)) .fetch_all(pool) .await?; return Ok(rows); @@ -2776,7 +2789,8 @@ pub async fn state_counts(pool: &PgPool) -> Result, AwaEr failed, cancelled, waiting_external, - ): (i64, i64, i64, i64, i64, i64, i64, i64) = sqlx::query_as(&sql).fetch_one(pool).await?; + ): (i64, i64, i64, i64, i64, i64, i64, i64) = + sqlx::query_as(audited_sql(sql)).fetch_one(pool).await?; return Ok(HashMap::from([ (JobState::Scheduled, scheduled), @@ -2896,7 +2910,7 @@ pub async fn job_kind_overviews(pool: &PgPool) -> Result, A current_jobs_cte = queue_storage_current_jobs_cte(store.schema()) ); - let rows = sqlx::query_as::<_, JobKindOverview>(&sql) + let rows = sqlx::query_as::<_, JobKindOverview>(audited_sql(sql)) .fetch_all(pool) .await?; return Ok(rows); @@ -3007,7 +3021,7 @@ pub async fn distinct_kinds(pool: &PgPool) -> Result, AwaError> { ORDER BY kind", queue_storage_current_jobs_cte(store.schema()) ); - return sqlx::query_scalar(&sql) + return sqlx::query_scalar(audited_sql(sql)) .fetch_all(pool) .await .map_err(AwaError::from); @@ -3034,7 +3048,7 @@ pub async fn distinct_queues(pool: &PgPool) -> Result, AwaError> { ORDER BY queue", queue_storage_current_jobs_cte(store.schema()) ); - return sqlx::query_scalar(&sql) + return sqlx::query_scalar(audited_sql(sql)) .fetch_all(pool) .await .map_err(AwaError::from); @@ -3178,10 +3192,11 @@ pub async fn state_timeseries( ORDER BY bucket", queue_storage_current_jobs_cte(store.schema()) ); - let rows = sqlx::query_as::<_, (chrono::DateTime, JobState, i64)>(&sql) - .bind(minutes) - .fetch_all(pool) - .await?; + let rows = + sqlx::query_as::<_, (chrono::DateTime, JobState, i64)>(audited_sql(sql)) + .bind(minutes) + .fetch_all(pool) + .await?; return Ok(rows .into_iter() diff --git a/awa-model/src/batch_operations.rs b/awa-model/src/batch_operations.rs index 4e8842b1..19b4b780 100644 --- a/awa-model/src/batch_operations.rs +++ b/awa-model/src/batch_operations.rs @@ -1,3 +1,4 @@ +use crate::audited_sql; use crate::error::{map_sqlx_error, AwaError}; use crate::job::{JobRow, JobState}; use crate::queue_storage::QueueStorage; @@ -1023,7 +1024,7 @@ async fn max_matching_queue_storage_job_id( let schema = QueueStorage::active_schema(pool) .await? .ok_or_else(|| AwaError::Validation("queue storage is not active".to_string()))?; - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" WITH candidates AS ( SELECT @@ -1069,7 +1070,7 @@ async fn max_matching_queue_storage_job_id( AND ($6::timestamptz IS NULL OR created_at >= $6) AND ($7::timestamptz IS NULL OR created_at < $7) "# - )) + ))) .bind(filter.state) .bind(&filter.kind) .bind(&filter.queue) @@ -1165,7 +1166,7 @@ async fn load_matching_queue_storage_job_ids( let schema = QueueStorage::active_schema(pool) .await? .ok_or_else(|| AwaError::Validation("queue storage is not active".to_string()))?; - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" WITH candidates AS ( SELECT @@ -1215,7 +1216,7 @@ async fn load_matching_queue_storage_job_ids( ORDER BY id ASC LIMIT $10 "# - )) + ))) .bind(filter.state) .bind(&filter.kind) .bind(&filter.queue) @@ -1316,7 +1317,7 @@ async fn count_matching_queue_storage_jobs( let schema = QueueStorage::active_schema(pool) .await? .ok_or_else(|| AwaError::Validation("queue storage is not active".to_string()))?; - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" WITH candidates AS ( SELECT @@ -1364,7 +1365,7 @@ async fn count_matching_queue_storage_jobs( AND ($8::bigint IS NULL OR id > $8) AND ($9::bigint IS NULL OR id <= $9) "# - )) + ))) .bind(filter.state) .bind(&filter.kind) .bind(&filter.queue) @@ -1388,7 +1389,7 @@ async fn load_matching_queue_storage_jobs( limit: i64, ) -> Result, AwaError> { let schema = _store.schema(); - let ids: Vec = sqlx::query_scalar(&format!( + let ids: Vec = sqlx::query_scalar(audited_sql(format!( r#" WITH candidates AS ( SELECT @@ -1438,7 +1439,7 @@ async fn load_matching_queue_storage_jobs( ORDER BY id ASC LIMIT $10 "# - )) + ))) .bind(filter.state) .bind(&filter.kind) .bind(&filter.queue) diff --git a/awa-model/src/dlq.rs b/awa-model/src/dlq.rs index 3b144fdd..1c0d9ff8 100644 --- a/awa-model/src/dlq.rs +++ b/awa-model/src/dlq.rs @@ -4,6 +4,7 @@ //! active queue_storage schema from `awa.runtime_storage_backends` and operates //! against that backend. +use crate::audited_sql; use crate::error::AwaError; use crate::job::JobRow; use crate::queue_storage::QueueStorage; @@ -214,7 +215,7 @@ pub async fn bulk_move_failed_to_dlq( pub async fn list_dlq(pool: &PgPool, filter: &ListDlqFilter) -> Result, AwaError> { let store = active_queue_storage(pool).await?; let schema = store.schema(); - let rows: Vec = sqlx::query_as(&format!( + let rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -252,7 +253,7 @@ pub async fn list_dlq(pool: &PgPool, filter: &ListDlqFilter) -> Result Result Result, AwaError> { let store = active_queue_storage(pool).await?; let schema = store.schema(); - let row: Option = sqlx::query_as(&format!( + let row: Option = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -296,7 +297,7 @@ pub async fn get_dlq_job(pool: &PgPool, job_id: i64) -> Result, A ORDER BY dlq_at DESC LIMIT 1 "# - )) + ))) .bind(job_id) .fetch_optional(pool) .await?; @@ -306,10 +307,10 @@ pub async fn get_dlq_job(pool: &PgPool, job_id: i64) -> Result, A pub async fn dlq_depth(pool: &PgPool, queue: Option<&str>) -> Result { let store = active_queue_storage(pool).await?; - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.dlq_entries WHERE ($1::text IS NULL OR queue = $1)", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -318,7 +319,7 @@ pub async fn dlq_depth(pool: &PgPool, queue: Option<&str>) -> Result Result, AwaError> { let store = active_queue_storage(pool).await?; - sqlx::query_as::<_, (String, i64)>(&format!( + sqlx::query_as::<_, (String, i64)>(audited_sql(format!( r#" SELECT queue, count(*)::bigint FROM {}.dlq_entries @@ -326,7 +327,7 @@ pub async fn dlq_depth_by_queue(pool: &PgPool) -> Result, Awa ORDER BY count(*) DESC, queue ASC "#, store.schema() - )) + ))) .fetch_all(pool) .await .map_err(Into::into) @@ -391,7 +392,7 @@ pub async fn purge_dlq( } let store = active_queue_storage(pool).await?; - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" DELETE FROM {}.dlq_entries WHERE ($1::text IS NULL OR kind = $1) @@ -409,7 +410,7 @@ pub async fn purge_dlq( ) "#, store.schema() - )) + ))) .bind(&filter.kind) .bind(&filter.queue) .bind(&filter.tag) @@ -422,10 +423,10 @@ pub async fn purge_dlq( pub async fn purge_dlq_job(pool: &PgPool, job_id: i64) -> Result { let store = active_queue_storage(pool).await?; - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( "DELETE FROM {}.dlq_entries WHERE job_id = $1", store.schema() - )) + ))) .bind(job_id) .execute(pool) .await?; @@ -445,7 +446,7 @@ pub async fn cleanup_dlq( ) -> Result { let store = active_queue_storage(pool).await?; let retention_secs = retention.as_secs().min(i64::MAX as u64) as i64; - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" DELETE FROM {}.dlq_entries WHERE job_id IN ( @@ -459,7 +460,7 @@ pub async fn cleanup_dlq( "#, store.schema(), store.schema() - )) + ))) .bind(retention_secs) .bind(batch_size) .bind(queue) diff --git a/awa-model/src/insert.rs b/awa-model/src/insert.rs index 3acdb70b..81c9388d 100644 --- a/awa-model/src/insert.rs +++ b/awa-model/src/insert.rs @@ -1,3 +1,4 @@ +use crate::audited_sql; use crate::error::{map_sqlx_error, AwaError}; use crate::job::{InsertOpts, InsertParams, JobRow, JobState}; use crate::unique::compute_unique_key; @@ -416,7 +417,7 @@ where let rows = precompute_rows(jobs)?; let query = build_multi_insert_query(rows.len()); - let mut sql_query = sqlx::query_as::<_, JobRow>(&query); + let mut sql_query = sqlx::query_as::<_, JobRow>(audited_sql(query)); for row in &rows { sql_query = sql_query diff --git a/awa-model/src/lib.rs b/awa-model/src/lib.rs index 6484f90b..fd7b46ef 100644 --- a/awa-model/src/lib.rs +++ b/awa-model/src/lib.rs @@ -79,3 +79,61 @@ pub trait JobArgs: serde::Serialize { serde_json::to_value(self) } } + +/// Accept dynamically assembled SQL after manual audit, for sqlx >= 0.9. +/// +/// sqlx 0.9 only lets a query be built from `&'static str` unless the call +/// site asserts the text is safe. Awa assembles query text with `format!` in +/// order to name the configured schema and its partition children, so those +/// call sites opt in here. +/// +/// # Invariant +/// +/// Every use of this function satisfies both of: +/// +/// 1. The only interpolated values are SQL identifiers that passed +/// `validate_ident` (`[a-z_][a-z0-9_]*` — see `queue_storage`), integers +/// derived from slot/shard arithmetic, and fixed text fragments chosen by +/// awa itself (for example an allow-listed table name, or a `state` +/// literal typed as `&'static str`). +/// 2. Every value that originates from a job, queue, filter, or API caller +/// travels as a bind parameter. +/// +/// Schema names are validated both where they are configured +/// (`QueueStorage::new`) and where they are read back out of the database +/// (`QueueStorage::active_schema`), so no unchecked name reaches a `format!`. +/// +/// sqlx cannot see any of that, which is why the assertion is manual. Keep +/// the invariant true: `grep audited_sql` is how the audit is re-run, so a +/// site that does not satisfy it belongs in [`caller_provided_sql`] instead. +/// +/// One carve-out: `copy_in_raw` is not covered by sqlx's guard, so the two +/// dynamic `COPY ... FROM STDIN` statements in `queue_storage` hold the same +/// invariant by hand and are commented as such. A `grep audited_sql` audit +/// should sweep `copy_in_raw` alongside it. +/// +/// Downstream callers assembling their *own* query text get no audit from +/// awa by using this: the name records that awa vouched for awa's SQL. Reach +/// for `sqlx::AssertSqlSafe` directly there, so the assertion reads as +/// belonging to whoever made it. +/// +/// Note that the return type is a sqlx type, so this signature is tied to +/// awa's sqlx major version. +pub fn audited_sql(sql: impl Into) -> sqlx::AssertSqlSafe { + sqlx::AssertSqlSafe(sql.into()) +} + +/// Pass through SQL that an external caller supplied verbatim, for sqlx >= 0.9. +/// +/// This is the counterpart to [`audited_sql`] for awa's deliberate raw-SQL +/// APIs — the Python `Transaction.execute` / `fetch_*` family, whose whole +/// purpose is to run the caller's own statement inside awa's transaction. +/// The text is not awa's and awa makes no claim about it; the caller owns it +/// exactly as they would owning a `sqlx::query` call directly. Arguments are +/// still bound as parameters. +/// +/// Kept separate from [`audited_sql`] on purpose: mixing the two would make +/// the audit invariant documented there unverifiable by inspection. +pub fn caller_provided_sql(sql: impl Into) -> sqlx::AssertSqlSafe { + sqlx::AssertSqlSafe(sql.into()) +} diff --git a/awa-model/src/migrations.rs b/awa-model/src/migrations.rs index 017aa744..3bc56de7 100644 --- a/awa-model/src/migrations.rs +++ b/awa-model/src/migrations.rs @@ -495,7 +495,7 @@ async fn apply_migrations( info!(version, description, "Applying migration v{version}"); let started = std::time::Instant::now(); for step in steps { - sqlx::raw_sql(step).execute(&mut *conn).await?; + sqlx::raw_sql(*step).execute(&mut *conn).await?; } let elapsed_ms = started.elapsed().as_millis(); info!( diff --git a/awa-model/src/queue_storage.rs b/awa-model/src/queue_storage.rs index e904dd5b..3718cdb3 100644 --- a/awa-model/src/queue_storage.rs +++ b/awa-model/src/queue_storage.rs @@ -1,4 +1,5 @@ use crate::admin::{CallbackConfig, CallbackPollResult}; +use crate::audited_sql; use crate::dlq::{ListDlqFilter, RetryFromDlqOpts}; use crate::error::AwaError; use crate::insert::prepare_row_raw; @@ -530,6 +531,18 @@ fn validate_ident(ident: &str) -> Result<(), AwaError> { } } +/// Gate a schema name that came back from the database before it is +/// interpolated into query text. Mirrors the [`validate_ident`] check +/// [`QueueStorage::new`] applies to configured names. +fn validate_active_schema(schema: String) -> Result { + validate_ident(&schema).map_err(|_| { + AwaError::Validation(format!( + "active queue-storage schema name is not a valid SQL identifier: {schema:?}" + )) + })?; + Ok(schema) +} + fn ready_child_name(schema: &str, slot: usize) -> String { format!("{schema}.ready_entries_{slot}") } @@ -774,7 +787,7 @@ async fn queue_prune_has_active_leases_tx( slot: i32, generation: i64, ) -> Result { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" SELECT EXISTS ( SELECT 1 @@ -784,7 +797,7 @@ async fn queue_prune_has_active_leases_tx( LIMIT 1 ) "# - )) + ))) .bind(slot) .bind(generation) .fetch_one(tx.as_mut()) @@ -798,7 +811,7 @@ async fn queue_prune_has_pending_ready_tx( ready_child: &str, generation: i64, ) -> Result { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" WITH claim_cursors AS MATERIALIZED ( SELECT @@ -824,7 +837,7 @@ async fn queue_prune_has_pending_ready_tx( LIMIT 1 ) "# - )) + ))) .bind(generation) .fetch_one(tx.as_mut()) .await @@ -837,7 +850,7 @@ async fn queue_prune_has_unclosed_claim_refs_tx( slot: i32, generation: i64, ) -> Result { - let count_proves_claim_refs_closed: bool = sqlx::query_scalar(&format!( + let count_proves_claim_refs_closed: bool = sqlx::query_scalar(audited_sql(format!( r#" WITH claim_count AS ( SELECT count(*)::bigint AS total @@ -871,7 +884,7 @@ async fn queue_prune_has_unclosed_claim_refs_tx( explicit_count.total + compact_count.total FROM claim_count, compact_claim_count, explicit_count, compact_count "# - )) + ))) .bind(slot) .bind(generation) .fetch_one(tx.as_mut()) @@ -896,7 +909,7 @@ async fn claim_prune_has_open_claims_tx( closure_child: &str, closure_batch_child: &str, ) -> Result { - let count_proves_claims_closed: bool = sqlx::query_scalar(&format!( + let count_proves_claims_closed: bool = sqlx::query_scalar(audited_sql(format!( r#" WITH claim_count AS ( SELECT count(*)::bigint AS total FROM {claim_child} @@ -916,7 +929,7 @@ async fn claim_prune_has_open_claims_tx( explicit_count.total + compact_count.total FROM claim_count, compact_claim_count, explicit_count, compact_count "# - )) + ))) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -1093,6 +1106,24 @@ mod identifier_tests { } } + #[test] + fn active_schema_read_path_rejects_names_the_constructor_would_reject() { + // `active_schema` interpolates its result into query text, so the + // same gate the constructor applies has to hold on the read path. + for schema in ["Awa", "awa-queue", "123awa", "awa.queue", "awa\"; DROP"] { + assert!( + super::validate_active_schema(schema.to_string()).is_err(), + "active schema name should be rejected: {schema}" + ); + } + for schema in ["awa", "awa_shadow", "_awa123"] { + assert_eq!( + super::validate_active_schema(schema.to_string()).expect("should be accepted"), + schema + ); + } + } + #[test] fn default_queue_storage_schema_requires_default_physical_shape() { for config in [ @@ -2834,13 +2865,23 @@ impl QueueStorage { "attempt_state" } + /// Read the active queue-storage schema name from the transition state. + /// + /// The name is re-validated with [`validate_ident`] before it is handed + /// back. Callers interpolate it into query text, so the check has to + /// happen on the read path too and not only in [`Self::new`] — a name + /// that never passed the constructor (hand-edited row, restore from an + /// older or foreign database) must not reach a `format!`. No supported + /// deployment can store a rejected name, since every writer of this row + /// goes through the constructor first. pub async fn active_schema(pool: &PgPool) -> Result, AwaError> { - sqlx::query_scalar( + let schema: Option = sqlx::query_scalar( "SELECT schema_name FROM awa.runtime_storage_backends WHERE backend = 'queue_storage'", ) .fetch_optional(pool) .await - .map_err(map_sqlx_error) + .map_err(map_sqlx_error)?; + schema.map(validate_active_schema).transpose() } /// Transaction-aware variant of [`Self::active_schema`] — read the @@ -2849,12 +2890,13 @@ impl QueueStorage { pub async fn active_schema_in_tx( tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result, AwaError> { - sqlx::query_scalar( + let schema: Option = sqlx::query_scalar( "SELECT schema_name FROM awa.runtime_storage_backends WHERE backend = 'queue_storage'", ) .fetch_optional(tx.as_mut()) .await - .map_err(map_sqlx_error) + .map_err(map_sqlx_error)?; + schema.map(validate_active_schema).transpose() } fn materialize_runtime_payload( @@ -3082,7 +3124,7 @@ impl QueueStorage { // touch it and does NOT change storage-transition state, so a call // to `prepare_schema` remains activation-neutral. - sqlx::query(&format!("CREATE SCHEMA IF NOT EXISTS {schema}")) + sqlx::query(audited_sql(format!("CREATE SCHEMA IF NOT EXISTS {schema}"))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3107,9 +3149,9 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; if open_receipt_claims_exists { - let row_count: i64 = sqlx::query_scalar(&format!( + let row_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.open_receipt_claims" - )) + ))) .fetch_one(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3121,9 +3163,9 @@ impl QueueStorage { then re-run prepare_schema." ))); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE IF EXISTS {schema}.open_receipt_claims CASCADE" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3163,17 +3205,17 @@ impl QueueStorage { .map_err(map_sqlx_error)?; if lease_claims_relkind.as_deref() == Some("r") { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.lease_claims RENAME TO lease_claims_legacy" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; } if closures_relkind.as_deref() == Some("r") { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.lease_claim_closures RENAME TO lease_claim_closures_legacy" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3185,9 +3227,9 @@ impl QueueStorage { // snapshot. Drop it on every prepare_schema so an upgrade from an // older install reclaims the storage. Done before the helper // runs because the helper does not touch this legacy table. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE IF EXISTS {schema}.queue_count_snapshots" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3263,9 +3305,9 @@ impl QueueStorage { let legacy_claim_slot: Option = if lease_claims_legacy_exists || closures_legacy_exists { Some( - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(install_tx.as_mut()) .await .map_err(map_sqlx_error)?, @@ -3275,20 +3317,20 @@ impl QueueStorage { }; if lease_claims_legacy_exists { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.lease_claims_legacy ADD COLUMN IF NOT EXISTS enqueue_shard SMALLINT NOT NULL DEFAULT 0" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.lease_claims_legacy ADD COLUMN IF NOT EXISTS deadline_at TIMESTAMPTZ" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims ( claim_slot, job_id, run_lease, ready_slot, ready_generation, @@ -3303,22 +3345,22 @@ impl QueueStorage { FROM {schema}.lease_claims_legacy ON CONFLICT (claim_slot, job_id, run_lease) DO NOTHING "# - )) + ))) .bind(legacy_claim_slot.expect("legacy claim slot should be present")) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE {schema}.lease_claims_legacy" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; } if closures_legacy_exists { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claim_closures ( claim_slot, job_id, run_lease, outcome, closed_at @@ -3329,15 +3371,15 @@ impl QueueStorage { FROM {schema}.lease_claim_closures_legacy ON CONFLICT (claim_slot, job_id, run_lease) DO NOTHING "# - )) + ))) .bind(legacy_claim_slot.expect("legacy claim slot should be present")) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE {schema}.lease_claim_closures_legacy" - )) + ))) .execute(install_tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3450,21 +3492,21 @@ impl QueueStorage { // parents. `reset()` must clean these out, otherwise the next // `prepare_schema()` runs the legacy migration again on top of // the freshly-emptied parent and silently re-inserts old rows. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE IF EXISTS {schema}.lease_claims_legacy" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE IF EXISTS {schema}.lease_claim_closures_legacy" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" TRUNCATE {schema}.ready_entries, @@ -3496,14 +3538,14 @@ impl QueueStorage { {schema}.lease_ring_rotations, {schema}.claim_ring_rotations "# - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER SEQUENCE {schema}.job_id_seq RESTART WITH 1" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3514,13 +3556,13 @@ impl QueueStorage { (RingFamily::Claim, self.claim_slot_count()), ] { let state = family.state_relname(); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.{state} SET slot_count = $1 WHERE singleton = TRUE "# - )) + ))) .bind(slot_count as i32) .execute(tx.as_mut()) .await @@ -3529,18 +3571,18 @@ impl QueueStorage { // The rotation ledgers were truncated above; re-seed the // genesis cursor (slot 0, generation 0). let ledger = family.ledger_relname(); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.{ledger} (generation, slot) VALUES (0, 0)" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; } for slot in 0..self.queue_slot_count() { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_slots (slot) VALUES ($1)" - )) + ))) .bind(slot as i32) .execute(tx.as_mut()) .await @@ -3548,9 +3590,9 @@ impl QueueStorage { } for slot in 0..self.lease_slot_count() { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.lease_ring_slots (slot) VALUES ($1)" - )) + ))) .bind(slot as i32) .execute(tx.as_mut()) .await @@ -3558,9 +3600,9 @@ impl QueueStorage { } for slot in 0..self.claim_slot_count() { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.claim_ring_slots (slot) VALUES ($1)" - )) + ))) .bind(slot as i32) .execute(tx.as_mut()) .await @@ -3595,7 +3637,7 @@ impl QueueStorage { // head row is visible in this transaction before trusting the cache. if self.lane_is_cached(queue, priority, enqueue_shard) { let schema = self.schema(); - let visible: bool = sqlx::query_scalar(&format!( + let visible: bool = sqlx::query_scalar(audited_sql(format!( r#" SELECT EXISTS ( SELECT 1 @@ -3605,7 +3647,7 @@ impl QueueStorage { AND enqueue_shard = $3 ) "# - )) + ))) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3644,26 +3686,26 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_lanes (queue, priority) VALUES ($1, $2) ON CONFLICT (queue, priority) DO NOTHING "# - )) + ))) .bind(queue) .bind(priority) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_enqueue_heads (queue, priority, enqueue_shard) VALUES ($1, $2, $3) ON CONFLICT (queue, priority, enqueue_shard) DO NOTHING "# - )) + ))) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3671,13 +3713,13 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_claim_heads (queue, priority, enqueue_shard) VALUES ($1, $2, $3) ON CONFLICT (queue, priority, enqueue_shard) DO NOTHING "# - )) + ))) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3685,11 +3727,11 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" SELECT {schema}.ensure_lane_sequences($1, $2, $3) "# - )) + ))) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3824,7 +3866,7 @@ impl QueueStorage { "# ); - let maybe_start: Option = sqlx::query_scalar(&sql) + let maybe_start: Option = sqlx::query_scalar(audited_sql(sql.clone())) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3847,7 +3889,7 @@ impl QueueStorage { self.invalidate_cached_lane(queue, priority, enqueue_shard); self.ensure_lane_inserts(tx, queue, priority, enqueue_shard) .await?; - let start: i64 = sqlx::query_scalar(&sql) + let start: i64 = sqlx::query_scalar(audited_sql(sql)) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -3866,9 +3908,9 @@ impl QueueStorage { tx: &mut sqlx::Transaction<'a, sqlx::Postgres>, ) -> Result { let schema = self.schema(); - let authority: String = sqlx::query_scalar(&format!( + let authority: String = sqlx::query_scalar(audited_sql(format!( "SELECT authority FROM {schema}.ring_cursor_authority WHERE singleton = TRUE" - )) + ))) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -3905,7 +3947,7 @@ impl QueueStorage { ) } }; - sqlx::query_as(&sql) + sqlx::query_as(audited_sql(sql)) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error) @@ -3919,9 +3961,9 @@ impl QueueStorage { ) -> Result { let schema = self.schema(); let state = family.state_relname(); - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT slot_count FROM {schema}.{state} WHERE singleton = TRUE" - )) + ))) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error) @@ -3959,9 +4001,9 @@ impl QueueStorage { // Universal outer gate: serialize against the flip and against 0.6 // rotators, BEFORE reading the authority. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "SELECT 1 FROM {schema}.{state} WHERE singleton = TRUE FOR UPDATE" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -4010,10 +4052,10 @@ impl QueueStorage { match authority { RingAuthority::Ledger => { - let inserted = sqlx::query(&format!( + let inserted = sqlx::query(audited_sql(format!( "INSERT INTO {schema}.{ledger} (generation, slot) \ VALUES ($1, $2) ON CONFLICT (generation) DO NOTHING" - )) + ))) .bind(next_generation) .bind(next_slot) .execute(tx.as_mut()) @@ -4025,11 +4067,11 @@ impl QueueStorage { let state = family.state_relname(); let slots = family.slots_relname(); // CAS the authoritative compat columns (0.6 semantics). - let advanced = sqlx::query(&format!( + let advanced = sqlx::query(audited_sql(format!( "UPDATE {schema}.{state} \ SET current_slot = $1, generation = $2 \ WHERE singleton = TRUE AND generation = $2 - 1" - )) + ))) .bind(next_slot) .bind(next_generation) .execute(tx.as_mut()) @@ -4042,9 +4084,9 @@ impl QueueStorage { // Stamp the incoming slot's per-slot generation, as 0.6 did, // so a mixed-fleet 0.6 binary's sealed-slot logic stays // consistent. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.{slots} SET generation = $1 WHERE slot = $2" - )) + ))) .bind(next_generation) .bind(next_slot) .execute(tx.as_mut()) @@ -4056,7 +4098,7 @@ impl QueueStorage { // every missing generation (deriving each slot) so the // ledger is a faithful shadow ready for the flip. let slot_count = self.ring_slot_count_tx(tx, family).await?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.{ledger} (generation, slot) \ SELECT g, ((g % $2) + $2) % $2 \ FROM generate_series( \ @@ -4064,7 +4106,7 @@ impl QueueStorage { $1 \ ) AS g \ ON CONFLICT (generation) DO NOTHING" - )) + ))) .bind(next_generation) .bind(slot_count) .execute(tx.as_mut()) @@ -4101,7 +4143,7 @@ impl QueueStorage { self.job_id_sequence() ); - sqlx::query_scalar(&query) + sqlx::query_scalar(audited_sql(query)) .bind(count as i32) .fetch_all(tx.as_mut()) .await @@ -4127,7 +4169,7 @@ impl QueueStorage { aging_interval: Duration, ) -> Result, AwaError> { let schema = self.schema(); - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" SELECT ready_slot, @@ -4159,7 +4201,7 @@ impl QueueStorage { COALESCE(payload, '{{}}'::jsonb) AS payload FROM {schema}.claim_ready_runtime($1, $2, $3, $4) "# - )) + ))) .bind(queue) .bind(max_batch) .bind(deadline_duration.as_secs_f64()) @@ -4287,7 +4329,7 @@ impl QueueStorage { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; for advance in advances { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH head AS MATERIALIZED ( SELECT seq_name @@ -4302,7 +4344,7 @@ impl QueueStorage { WHERE $5::bigint IS NULL OR {schema}.sequence_next_value(seq_name) = $5 "# - )) + ))) .bind(&advance.queue) .bind(advance.priority) .bind(advance.enqueue_shard) @@ -4447,6 +4489,11 @@ impl QueueStorage { } let schema = self.schema(); + // `copy_in_raw` is outside sqlx 0.9's `SqlSafeStr` guard, so this + // site cannot opt in through `audited_sql`. It carries the same + // invariant by hand: `schema` passed `validate_ident`, the sentinel + // is a const, and every row value is CSV-encoded into the COPY + // stream rather than into the statement. let copy_sql = format!( "COPY {schema}.ready_entries (ready_slot, ready_generation, job_id, kind, queue, args, priority, attempt, run_lease, max_attempts, lane_seq, enqueue_shard, run_at, attempted_at, created_at, unique_key, unique_states, payload) FROM STDIN WITH (FORMAT csv, NULL '{COPY_NULL_SENTINEL}')" ); @@ -4834,6 +4881,11 @@ impl QueueStorage { self.sync_deferred_enqueue_unique_claims(tx, &rows).await?; let schema = self.schema(); + // `copy_in_raw` is outside sqlx 0.9's `SqlSafeStr` guard, so this + // site cannot opt in through `audited_sql`. It carries the same + // invariant by hand: `schema` passed `validate_ident`, the sentinel + // is a const, and every row value is CSV-encoded into the COPY + // stream rather than into the statement. let copy_sql = format!( "COPY {schema}.deferred_jobs (job_id, kind, queue, args, state, priority, attempt, run_lease, max_attempts, run_at, attempted_at, finalized_at, created_at, unique_key, unique_states, payload) FROM STDIN WITH (FORMAT csv, NULL '{COPY_NULL_SENTINEL}')" ); @@ -5092,7 +5144,7 @@ impl QueueStorage { } let schema = self.schema(); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_terminal_count_deltas ( ready_slot, @@ -5136,7 +5188,7 @@ impl QueueStorage { enqueue_shard, counter_bucket "# - )) + ))) .bind(&ready_slots) .bind(&ready_generations) .bind(&queues) @@ -5185,7 +5237,7 @@ impl QueueStorage { let schema = self.schema(); let job_ids: Vec = rows.iter().map(|row| row.job_id).collect(); let run_leases: Vec = rows.iter().map(|row| row.run_lease).collect(); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH refs(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -5222,7 +5274,7 @@ impl QueueStorage { ) SELECT count(*) FROM marked "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .execute(tx.as_mut()) @@ -5249,7 +5301,7 @@ impl QueueStorage { let lane_seqs: Vec = rows.iter().map(|row| row.lane_seq).collect(); let payload_rows: Vec<(i32, i64, String, i16, i16, i64, serde_json::Value)> = - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" WITH refs(ready_slot, ready_generation, queue, priority, enqueue_shard, lane_seq) AS ( SELECT * FROM unnest($1::int[], $2::bigint[], $3::text[], $4::smallint[], $5::smallint[], $6::bigint[]) @@ -5271,7 +5323,7 @@ impl QueueStorage { AND ready.enqueue_shard = refs.enqueue_shard AND ready.lane_seq = refs.lane_seq "# - )) + ))) .bind(&ready_slots) .bind(&ready_generations) .bind(&queues) @@ -5424,7 +5476,7 @@ impl QueueStorage { pruned_failed_deltas.push(pruned_failed_delta); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH deltas(queue, priority, pruned_completed_delta, pruned_failed_delta) AS ( SELECT * @@ -5457,7 +5509,7 @@ impl QueueStorage { rollups.pruned_failed_count + EXCLUDED.pruned_failed_count ) "# - )) + ))) .bind(&queues) .bind(&priorities) .bind(&pruned_completed_deltas) @@ -5508,7 +5560,7 @@ impl QueueStorage { pruned_failed_deltas.push(pruned_failed_delta); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_terminal_rollup_deltas ( queue, @@ -5524,7 +5576,7 @@ impl QueueStorage { $4::bigint[] ) "# - )) + ))) .bind(&queues) .bind(&priorities) .bind(&pruned_completed_deltas) @@ -6027,7 +6079,7 @@ impl QueueStorage { 0 }; - if let Some(owned) = sqlx::query_as::<_, QueueClaimerLeaseRow>(&format!( + if let Some(owned) = sqlx::query_as::<_, QueueClaimerLeaseRow>(audited_sql(format!( r#" SELECT claimer_slot, lease_epoch, last_claimed_at, expires_at FROM {schema}.queue_claimer_leases @@ -6037,7 +6089,7 @@ impl QueueStorage { ORDER BY claimer_slot LIMIT 1 "# - )) + ))) .bind(queue) .bind(instance_id) .bind(now) @@ -6050,7 +6102,7 @@ impl QueueStorage { for offset in 0..max_claimers { let slot = (probe_start + offset) % max_claimers; - if let Some(updated) = sqlx::query_as::<_, QueueClaimerLeaseRow>(&format!( + if let Some(updated) = sqlx::query_as::<_, QueueClaimerLeaseRow>(audited_sql(format!( r#" UPDATE {schema}.queue_claimer_leases SET owner_instance_id = $3, @@ -6070,7 +6122,7 @@ impl QueueStorage { ) RETURNING claimer_slot, lease_epoch, last_claimed_at, expires_at "# - )) + ))) .bind(queue) .bind(slot) .bind(instance_id) @@ -6084,7 +6136,7 @@ impl QueueStorage { return Ok(Some(updated)); } - if let Some(inserted) = sqlx::query_as::<_, QueueClaimerLeaseRow>(&format!( + if let Some(inserted) = sqlx::query_as::<_, QueueClaimerLeaseRow>(audited_sql(format!( r#" INSERT INTO {schema}.queue_claimer_leases ( queue, @@ -6099,7 +6151,7 @@ impl QueueStorage { ON CONFLICT (queue, claimer_slot) DO NOTHING RETURNING claimer_slot, lease_epoch, last_claimed_at, expires_at "# - )) + ))) .bind(queue) .bind(slot) .bind(instance_id) @@ -6131,7 +6183,7 @@ impl QueueStorage { + TimeDelta::from_std(lease_ttl) .map_err(|err| AwaError::Validation(format!("invalid claimer lease ttl: {err}")))?; - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" UPDATE {schema}.queue_claimer_leases SET last_claimed_at = $5, @@ -6141,7 +6193,7 @@ impl QueueStorage { AND owner_instance_id = $3 AND lease_epoch = $4 "# - )) + ))) .bind(queue) .bind(lease.claimer_slot) .bind(instance_id) @@ -6219,14 +6271,14 @@ impl QueueStorage { AwaError::Validation(format!("invalid claimer control interval: {err}")) })?; - if let Some(target) = sqlx::query_scalar::<_, i16>(&format!( + if let Some(target) = sqlx::query_scalar::<_, i16>(audited_sql(format!( r#" SELECT target_claimers FROM {schema}.queue_claimer_state WHERE queue = $1 AND updated_at > $2 "# - )) + ))) .bind(queue) .bind(stale_cutoff) .fetch_optional(pool) @@ -6236,13 +6288,13 @@ impl QueueStorage { return Ok(target.clamp(1, max_claimers.max(1))); } - let current_target = sqlx::query_scalar::<_, i16>(&format!( + let current_target = sqlx::query_scalar::<_, i16>(audited_sql(format!( r#" SELECT target_claimers FROM {schema}.queue_claimer_state WHERE queue = $1 "# - )) + ))) .bind(queue) .fetch_optional(pool) .await @@ -6251,7 +6303,7 @@ impl QueueStorage { let signal = self.queue_claimer_signal(pool, queue).await?; let desired = self.desired_queue_claimer_target(current_target, &signal, max_claimers); - if let Some(updated) = sqlx::query_scalar::<_, i16>(&format!( + if let Some(updated) = sqlx::query_scalar::<_, i16>(audited_sql(format!( r#" INSERT INTO {schema}.queue_claimer_state (queue, target_claimers, updated_at) VALUES ($1, $2, $3) @@ -6261,7 +6313,7 @@ impl QueueStorage { WHERE {schema}.queue_claimer_state.updated_at <= $4 RETURNING target_claimers "# - )) + ))) .bind(queue) .bind(desired) .bind(now) @@ -6298,7 +6350,7 @@ impl QueueStorage { ) -> Result { let schema = self.schema(); let queues = self.physical_queues_for_logical(queue); - let available: i64 = sqlx::query_scalar(&format!( + let available: i64 = sqlx::query_scalar(audited_sql(format!( r#" SELECT COALESCE( sum(GREATEST( @@ -6315,7 +6367,7 @@ impl QueueStorage { AND qc.enqueue_shard = qe.enqueue_shard WHERE qe.queue = ANY($1) "# - )) + ))) .bind(&queues) .fetch_one(pool) .await @@ -6421,7 +6473,7 @@ impl QueueStorage { let enqueue_shards: Vec = claimed.iter().map(|entry| entry.enqueue_shard).collect(); let lane_seqs: Vec = claimed.iter().map(|entry| entry.lane_seq).collect(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" WITH completed(lease_slot, queue, priority, enqueue_shard, lane_seq) AS ( SELECT * FROM unnest($1::int[], $2::text[], $3::smallint[], $4::smallint[], $5::bigint[]) @@ -6451,7 +6503,7 @@ impl QueueStorage { leases.callback_id, leases.callback_timeout_at "# - )) + ))) .bind(&lease_slots) .bind(&queues) .bind(&priorities) @@ -6595,7 +6647,7 @@ impl QueueStorage { group.iter().map(|entry| entry.job.attempted_at).collect(); let finalized_ats: Vec> = vec![finalized_at; group.len()]; - let completed: Vec<(i64, i64)> = match sqlx::query_as(&format!( + let completed: Vec<(i64, i64)> = match sqlx::query_as(audited_sql(format!( r#" WITH completed( claim_slot, @@ -6797,7 +6849,7 @@ impl QueueStorage { CROSS JOIN (SELECT count(*) FROM claim_closure_batches) AS closure_batch_write CROSS JOIN (SELECT count(*) FROM terminal) AS terminal_write "# - )) + ))) .bind(&claim_slots) .bind(&ready_slots) .bind(&ready_generations) @@ -6942,7 +6994,7 @@ impl QueueStorage { let closure_batch_rel = format!("{schema}.lease_claim_closure_batches"); let closed_evidence = receipt_closed_evidence_sql(schema, &closure_rel, &closure_batch_rel, "claims"); - let updated: Vec<(i64, i64)> = sqlx::query_as(&format!( + let updated: Vec<(i64, i64)> = sqlx::query_as(audited_sql(format!( r#" WITH completed(claim_slot, job_id, run_lease, receipt_id) AS ( SELECT * FROM unnest($1::int[], $2::bigint[], $3::bigint[], $4::bigint[]) @@ -7107,7 +7159,7 @@ impl QueueStorage { SELECT job_id, run_lease FROM closed_batch_pairs "# - )) + ))) .bind(&receipt_claim_slots) .bind(&receipt_job_ids) .bind(&receipt_run_leases) @@ -7180,7 +7232,7 @@ impl QueueStorage { // lease after the original claim, so the materialized lease can // live in a newer lease slot than the claim carried. Match on // the stable ready-lane and attempt identity instead. - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" WITH completed(ready_slot, ready_generation, job_id, queue, priority, enqueue_shard, lane_seq, run_lease) AS ( SELECT * FROM unnest($1::int[], $2::bigint[], $3::bigint[], $4::text[], $5::smallint[], $6::smallint[], $7::bigint[], $8::bigint[]) @@ -7240,7 +7292,7 @@ impl QueueStorage { callback_timeout_at FROM deleted "# - )) + ))) .bind(&ready_slots) .bind(&ready_generations) .bind(&job_ids) @@ -7300,7 +7352,7 @@ impl QueueStorage { // attempt-state delete and to the final SELECT), and `del_attempts` // hangs off it. Saves one round-trip per completion batch versus // issuing the attempt-state delete as a separate statement. - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" WITH completed(lease_slot, queue, priority, enqueue_shard, lane_seq, run_lease) AS ( SELECT * FROM unnest($1::int[], $2::text[], $3::smallint[], $4::smallint[], $5::bigint[], $6::bigint[]) @@ -7358,7 +7410,7 @@ impl QueueStorage { callback_timeout_at FROM deleted "# - )) + ))) .bind(&lease_slots) .bind(&queues) .bind(&priorities) @@ -7439,7 +7491,7 @@ impl QueueStorage { .map(|(_, run_lease)| *run_lease) .collect(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" WITH completed(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -7466,7 +7518,7 @@ impl QueueStorage { leases.callback_id, leases.callback_timeout_at "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .fetch_all(tx.as_mut()) @@ -7581,7 +7633,7 @@ impl QueueStorage { )" ) }; - let row: (i64, i64, i64, i64) = sqlx::query_as(&format!( + let row: (i64, i64, i64, i64) = sqlx::query_as(audited_sql(format!( r#" WITH lane_counts AS ( -- Exact count: a ready row is available iff its @@ -7690,7 +7742,7 @@ impl QueueStorage { CROSS JOIN live_running CROSS JOIN live_terminal "# - )) + ))) .bind(&queues) .fetch_one(pool) .await @@ -7761,14 +7813,14 @@ impl QueueStorage { // queue_counts_exact's strict definition. Receipt-plane claims // that haven't materialised a lease row yet are documented as // omitted in the method-level doc. - let running: i64 = sqlx::query_scalar(&format!( + let running: i64 = sqlx::query_scalar(audited_sql(format!( r#" SELECT COALESCE(count(*)::bigint, 0) FROM {schema}.leases WHERE queue = ANY($1) AND state = 'running' "# - )) + ))) .bind(&queues) .fetch_one(pool) .await @@ -7778,7 +7830,7 @@ impl QueueStorage { // see method-level docs. The GREATEST legacy dedupe applies to // the completed column only: queue_lanes never carried a failed // column. - let (pruned_completed, pruned_failed): (i64, i64) = sqlx::query_as(&format!( + let (pruned_completed, pruned_failed): (i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT COALESCE(sum( @@ -7815,7 +7867,7 @@ impl QueueStorage { ) AS pending USING (queue, priority) "# - )) + ))) .bind(&queues) .fetch_one(pool) .await @@ -7842,7 +7894,7 @@ impl QueueStorage { let queues = self.physical_queues_for_logical(queue); // Folded rollups plus unfolded prune deltas (#371), so the count // stays exact between maintenance folds. - let pruned_failed: i64 = sqlx::query_scalar(&format!( + let pruned_failed: i64 = sqlx::query_scalar(audited_sql(format!( r#" SELECT COALESCE(( @@ -7857,7 +7909,7 @@ impl QueueStorage { WHERE queue = ANY($1) ), 0)::bigint "# - )) + ))) .bind(&queues) .fetch_one(pool) .await @@ -7871,7 +7923,7 @@ impl QueueStorage { job_id: i64, ) -> Result, AwaError> { let schema = self.schema(); - let deleted_waiting: Vec = sqlx::query_as(&format!( + let deleted_waiting: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE job_id = $1 @@ -7894,7 +7946,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(job_id) .fetch_all(tx.as_mut()) .await @@ -7943,7 +7995,7 @@ impl QueueStorage { let done_projection = done_row_projection("done", "ready"); let ready_join = done_ready_join(schema, "done", "ready"); - let terminal: Option = sqlx::query_as(&format!( + let terminal: Option = sqlx::query_as(audited_sql(format!( r#" WITH deleted AS ( DELETE FROM {schema}.done_entries @@ -7962,7 +8014,7 @@ impl QueueStorage { FROM deleted AS done {ready_join} "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -8107,7 +8159,7 @@ impl QueueStorage { let closed_evidence = receipt_closed_evidence_sql(schema, &closure_rel, &closure_batch_rel, "claims"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH refs(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -8243,7 +8295,7 @@ impl QueueStorage { (SELECT count(*) FROM marked) + (SELECT count(*) FROM inserted_batches) "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .bind(outcome) @@ -8280,7 +8332,7 @@ impl QueueStorage { job_id: i64, ) -> Result, AwaError> { let schema = self.schema(); - let ready: Option = sqlx::query_as(&format!( + let ready: Option = sqlx::query_as(audited_sql(format!( r#" WITH target AS ( SELECT ready.* @@ -8333,7 +8385,7 @@ impl QueueStorage { COALESCE(payload, '{{}}'::jsonb) AS payload FROM target "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -8367,7 +8419,7 @@ impl QueueStorage { })); } - let deleted_lease: Vec = sqlx::query_as(&format!( + let deleted_lease: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE job_id = $1 @@ -8390,7 +8442,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(job_id) .fetch_all(tx.as_mut()) .await @@ -8448,7 +8500,7 @@ impl QueueStorage { i64, bool, ); - let receipt: Option = sqlx::query_as(&format!( + let receipt: Option = sqlx::query_as(audited_sql(format!( r#" WITH row_receipt AS ( SELECT @@ -8542,7 +8594,7 @@ impl QueueStorage { ORDER BY run_lease DESC LIMIT 1 "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -8565,7 +8617,7 @@ impl QueueStorage { { // Hydrate the ready row so we can synthesize the done // row with the original args/payload. - let ready_match: Option = sqlx::query_as(&format!( + let ready_match: Option = sqlx::query_as(audited_sql(format!( r#" SELECT ready_slot, @@ -8593,7 +8645,7 @@ impl QueueStorage { AND queue = $4 AND lane_seq = $5 "# - )) + ))) .bind(job_id) .bind(ready_slot) .bind(ready_generation) @@ -8641,7 +8693,7 @@ impl QueueStorage { // its explicit closure so the gate balances it against the // lease_claims row. if compact_batch { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claim_closure_batches ( claim_slot, @@ -8664,7 +8716,7 @@ impl QueueStorage { clock_timestamp() ) "# - )) + ))) .bind(claim_slot) .bind(ready_slot) .bind(ready_generation) @@ -8673,7 +8725,7 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; } else { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH inserted AS ( INSERT INTO {schema}.lease_claim_closures (claim_slot, job_id, run_lease, outcome, closed_at) @@ -8692,7 +8744,7 @@ impl QueueStorage { ) SELECT count(*) FROM marked "# - )) + ))) .bind(claim_slot) .bind(job_id) .bind(run_lease) @@ -8710,9 +8762,9 @@ impl QueueStorage { // committed first, that lease is now an orphan pointing // at a job we're about to mark `cancelled`. Sweep it // defensively. If no race occurred this is a no-op. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.leases WHERE job_id = $1 AND run_lease = $2" - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) @@ -8726,7 +8778,7 @@ impl QueueStorage { } } - let deferred: Option = sqlx::query_as(&format!( + let deferred: Option = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.deferred_jobs WHERE job_id = $1 @@ -8749,7 +8801,7 @@ impl QueueStorage { unique_states, COALESCE(payload, '{{}}'::jsonb) AS payload "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -8966,7 +9018,7 @@ impl QueueStorage { priority: Option, ) -> Result { let schema = self.schema(); - let row: Option = sqlx::query_as(&format!( + let row: Option = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -8990,7 +9042,7 @@ impl QueueStorage { AND state = 'scheduled' FOR UPDATE SKIP LOCKED "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -9031,7 +9083,7 @@ impl QueueStorage { .or_insert_with(|| serde_json::Value::from(i64::from(old_priority))); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.deferred_jobs SET queue = $2, @@ -9039,7 +9091,7 @@ impl QueueStorage { payload = $4 WHERE job_id = $1 "# - )) + ))) .bind(job_id) .bind(new_queue) .bind(new_priority) @@ -9058,7 +9110,7 @@ impl QueueStorage { priority: Option, ) -> Result { let schema = self.schema(); - let ready: Option = sqlx::query_as(&format!( + let ready: Option = sqlx::query_as(audited_sql(format!( r#" WITH target AS ( SELECT ready.* @@ -9103,7 +9155,7 @@ impl QueueStorage { COALESCE(payload, '{{}}'::jsonb) AS payload FROM target "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -9129,7 +9181,7 @@ impl QueueStorage { if new_queue == old_queue && new_priority == old_priority { return Ok(ReadyBatchMoveResult { moved: false }); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.ready_tombstones ( ready_slot, ready_generation, queue, priority, enqueue_shard, lane_seq, job_id @@ -9137,7 +9189,7 @@ impl QueueStorage { VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING "# - )) + ))) .bind(ready.ready_slot) .bind(ready.ready_generation) .bind(&ready.queue) @@ -9190,7 +9242,7 @@ impl QueueStorage { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let moved: Vec = sqlx::query_as(&format!( + let moved: Vec = sqlx::query_as(audited_sql(format!( r#" WITH target AS ( SELECT ready.* @@ -9244,7 +9296,7 @@ impl QueueStorage { COALESCE(payload, '{{}}'::jsonb) AS payload FROM target "# - )) + ))) .bind(cutoff) .bind(limit) .fetch_all(tx.as_mut()) @@ -9319,7 +9371,7 @@ impl QueueStorage { run_lease: i64, ) -> Result { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let mut row: Option = sqlx::query_as(&format!( + let mut row: Option = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -9336,7 +9388,7 @@ impl QueueStorage { FOR UPDATE "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .fetch_optional(tx.as_mut()) @@ -9359,20 +9411,20 @@ impl QueueStorage { && row.callback_on_fail.is_none() && row.callback_transform.is_none() { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {} WHERE job_id = $1 AND run_lease = $2", self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; } else { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {} SET callback_result = NULL, updated_at = clock_timestamp() WHERE job_id = $1 AND run_lease = $2", self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) @@ -9480,7 +9532,7 @@ impl QueueStorage { receipt_closed_evidence_sql(schema, &closure_rel, &closure_batch_rel, "claims"); let job_ids: Vec = jobs.iter().map(|(job_id, _)| *job_id).collect(); let run_leases: Vec = jobs.iter().map(|(_, run_lease)| *run_lease).collect(); - let inserted: i64 = sqlx::query_scalar(&format!( + let inserted: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH inflight(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -9669,7 +9721,7 @@ impl QueueStorage { SELECT job_id, run_lease FROM already_live ) AS moved "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .fetch_one(tx.as_mut()) @@ -9709,7 +9761,7 @@ impl QueueStorage { receipt_closed_evidence_sql(schema, &closure_rel, &closure_batch_rel, "claims"); let job_ids: Vec = jobs.iter().map(|(job_id, _)| *job_id).collect(); let run_leases: Vec = jobs.iter().map(|(_, run_lease)| *run_lease).collect(); - let updated: i64 = sqlx::query_scalar(&format!( + let updated: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH inflight(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -9792,7 +9844,7 @@ impl QueueStorage { ) SELECT count(*)::bigint FROM upserted "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .fetch_one(tx.as_mut()) @@ -9827,7 +9879,7 @@ impl QueueStorage { .iter() .map(|(_, _, progress)| progress.clone()) .collect(); - let updated: i64 = sqlx::query_scalar(&format!( + let updated: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH inflight(job_id, run_lease, progress) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[], $3::jsonb[]) @@ -9921,7 +9973,7 @@ impl QueueStorage { ) SELECT count(*)::bigint FROM upserted "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .bind(&progress) @@ -9949,7 +10001,7 @@ impl QueueStorage { let job_ids: Vec = deleted.iter().map(|row| row.job_id).collect(); let run_leases: Vec = deleted.iter().map(|row| row.run_lease).collect(); - let ready_rows: Vec = sqlx::query_as(&format!( + let ready_rows: Vec = sqlx::query_as(audited_sql(format!( r#" WITH refs(ready_slot, ready_generation, queue, enqueue_shard, lane_seq, job_id) AS ( SELECT * FROM unnest($1::int[], $2::bigint[], $3::text[], $4::smallint[], $5::bigint[], $6::bigint[]) @@ -9977,7 +10029,7 @@ impl QueueStorage { AND ready.lane_seq = refs.lane_seq AND ready.job_id = refs.job_id "# - )) + ))) .bind(&ready_slots) .bind(&ready_generations) .bind(&queues) @@ -9988,7 +10040,7 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; - let attempt_rows: Vec = sqlx::query_as(&format!( + let attempt_rows: Vec = sqlx::query_as(audited_sql(format!( r#" WITH refs(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -10007,7 +10059,7 @@ impl QueueStorage { attempt.callback_transform, attempt.callback_result "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .fetch_all(tx.as_mut()) @@ -10028,7 +10080,7 @@ impl QueueStorage { // retryable / failed / completed. Write the closure here so // the receipt plane mirrors the lease plane: when the lease // is gone, the receipt is gone too. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH refs(job_id, run_lease) AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) @@ -10125,7 +10177,7 @@ impl QueueStorage { (SELECT count(*) FROM marked) + (SELECT count(*) FROM inserted_batches) "# - )) + ))) .bind(&job_ids) .bind(&run_leases) .execute(tx.as_mut()) @@ -10215,7 +10267,7 @@ impl QueueStorage { .await?; let schema = self.schema(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" WITH row_target AS ( -- Target is the open claim identified from the @@ -10422,7 +10474,7 @@ impl QueueStorage { AND closed_target.job_id = target.job_id AND closed_target.run_lease = target.run_lease "# - )) + ))) .bind(job_id) .bind(run_lease) .bind(outcome) @@ -10453,7 +10505,7 @@ impl QueueStorage { } let schema = self.schema(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE job_id = $1 @@ -10477,7 +10529,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(job_id) .bind(run_lease) .fetch_all(tx.as_mut()) @@ -10544,7 +10596,7 @@ impl QueueStorage { let claim_batch_child = claim_batch_child_name(schema, slot as usize); let closure_child = closure_child_name(schema, slot as usize); let closure_batch_child = claim_closure_batch_child_name(schema, slot as usize); - let rescued: Vec = sqlx::query_as(&format!( + let rescued: Vec = sqlx::query_as(audited_sql(format!( r#" WITH cursor_row AS ( SELECT @@ -10949,7 +11001,7 @@ impl QueueStorage { AND closed_locked.run_lease = stale_locked.run_lease CROSS JOIN cursor_advance "# - )) + ))) .bind(slot) .bind(cutoff) .bind(RECEIPT_RESCUE_CURSOR_SCAN_LIMIT) @@ -11033,7 +11085,7 @@ impl QueueStorage { tx: &mut sqlx::Transaction<'a, sqlx::Postgres>, ) -> Result, AwaError> { let schema = self.schema(); - let preferred_slot = sqlx::query_as::<_, (i32, i64, i32)>(&format!( + let preferred_slot = sqlx::query_as::<_, (i32, i64, i32)>(audited_sql(format!( r#" SELECT ledger.slot, ledger.generation, state.slot_count FROM {schema}.claim_ring_state AS state @@ -11045,7 +11097,7 @@ impl QueueStorage { ) AS ledger WHERE state.singleton = TRUE "# - )) + ))) .fetch_optional(tx.as_mut()) .await .map_err(map_sqlx_error)? @@ -11074,7 +11126,7 @@ impl QueueStorage { let closure_batch_child = claim_closure_batch_child_name(schema, slot as usize); let closed_evidence = receipt_closed_evidence_sql(schema, &closure_child, &closure_batch_child, "claims"); - let rescued: Vec = sqlx::query_as(&format!( + let rescued: Vec = sqlx::query_as(audited_sql(format!( r#" WITH cursor_row AS ( SELECT @@ -11281,7 +11333,7 @@ impl QueueStorage { AND inserted.run_lease = expired_locked.run_lease CROSS JOIN cursor_advance "# - )) + ))) .bind(slot) .bind(RECEIPT_DEADLINE_RESCUE_CURSOR_SCAN_LIMIT) .bind(rescue_limit) @@ -11325,7 +11377,7 @@ impl QueueStorage { let claim_batch_child = claim_batch_child_name(schema, slot as usize); let closure_child = closure_child_name(schema, slot as usize); let closure_batch_child = claim_closure_batch_child_name(schema, slot as usize); - let rescued: Vec = sqlx::query_as(&format!( + let rescued: Vec = sqlx::query_as(audited_sql(format!( r#" WITH cursor_row AS ( SELECT @@ -11607,7 +11659,7 @@ impl QueueStorage { AND closed_locked.run_lease = expired_locked.run_lease CROSS JOIN cursor_advance "# - )) + ))) .bind(slot) .bind(RECEIPT_DEADLINE_RESCUE_CURSOR_SCAN_LIMIT) .bind(rescue_limit) @@ -11625,7 +11677,7 @@ impl QueueStorage { receipt_closed_evidence_sql(schema, &closure_rel, &closure_batch_rel, "claims"); let mut candidates = Vec::new(); - let ready_rows: Vec = sqlx::query_as(&format!( + let ready_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -11646,7 +11698,7 @@ impl QueueStorage { WHERE job_id = $1 ORDER BY run_lease DESC, attempted_at DESC NULLS LAST, run_at DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11655,7 +11707,7 @@ impl QueueStorage { candidates.push(row.into_job_row()?); } - let deferred_rows: Vec = sqlx::query_as(&format!( + let deferred_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -11677,7 +11729,7 @@ impl QueueStorage { FROM {schema}.deferred_jobs WHERE job_id = $1 "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11686,7 +11738,7 @@ impl QueueStorage { candidates.push(row.into_job_row()?); } - let lease_rows: Vec = sqlx::query_as(&format!( + let lease_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT lease.ready_slot, @@ -11732,7 +11784,7 @@ impl QueueStorage { WHERE lease.job_id = $1 ORDER BY lease.run_lease DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11743,7 +11795,7 @@ impl QueueStorage { // Report receipt-backed attempts as running by anti-joining // lease_claims against every durable closure evidence shape. - let lease_claim_rows: Vec = sqlx::query_as(&format!( + let lease_claim_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT claims.ready_slot, @@ -11798,7 +11850,7 @@ impl QueueStorage { ) ORDER BY claims.run_lease DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11811,7 +11863,7 @@ impl QueueStorage { // them only for this admin read, and report still-open items as // running until durable closure, terminal, or materialized-lease // evidence supersedes the claim. - let lease_claim_batch_rows: Vec = sqlx::query_as(&format!( + let lease_claim_batch_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT claim_batches.ready_slot, @@ -11899,7 +11951,7 @@ impl QueueStorage { ) ORDER BY items.run_lease DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11908,7 +11960,7 @@ impl QueueStorage { candidates.push(row.into_job_row()?); } - let done_rows: Vec = sqlx::query_as(&format!( + let done_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT ready_slot, @@ -11935,7 +11987,7 @@ impl QueueStorage { WHERE done.job_id = $1 ORDER BY done.run_lease DESC, done.finalized_at DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11944,7 +11996,7 @@ impl QueueStorage { candidates.push(row.into_job_row()?); } - let dlq_rows: Vec = sqlx::query_as(&format!( + let dlq_rows: Vec = sqlx::query_as(audited_sql(format!( r#" SELECT job_id, @@ -11970,7 +12022,7 @@ impl QueueStorage { WHERE job_id = $1 ORDER BY dlq_at DESC "#, - )) + ))) .bind(job_id) .fetch_all(pool) .await @@ -11999,7 +12051,7 @@ impl QueueStorage { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; self.ensure_mutable_running_attempt_tx(&mut tx, job_id, run_lease) .await?; - let updated = sqlx::query(&format!( + let updated = sqlx::query(audited_sql(format!( r#" UPDATE {} SET callback_id = $2, @@ -12009,7 +12061,7 @@ impl QueueStorage { AND run_lease = $4 "#, self.leases_table() - )) + ))) .bind(job_id) .bind(callback_id) .bind(timeout.as_secs_f64()) @@ -12023,7 +12075,7 @@ impl QueueStorage { return Err(AwaError::Validation("job is not in running state".into())); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {} SET callback_filter = NULL, @@ -12035,14 +12087,14 @@ impl QueueStorage { AND run_lease = $2 "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" DELETE FROM {} WHERE job_id = $1 @@ -12055,7 +12107,7 @@ impl QueueStorage { AND callback_transform IS NULL "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) @@ -12122,7 +12174,7 @@ impl QueueStorage { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; self.ensure_mutable_running_attempt_tx(&mut tx, job_id, run_lease) .await?; - let updated = sqlx::query(&format!( + let updated = sqlx::query(audited_sql(format!( r#" UPDATE {} SET callback_id = $2, @@ -12132,7 +12184,7 @@ impl QueueStorage { AND run_lease = $4 "#, self.leases_table() - )) + ))) .bind(job_id) .bind(callback_id) .bind(timeout.as_secs_f64()) @@ -12146,7 +12198,7 @@ impl QueueStorage { return Err(AwaError::Validation("job is not in running state".into())); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {} ( job_id, @@ -12167,7 +12219,7 @@ impl QueueStorage { updated_at = clock_timestamp() "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .bind(&config.filter) @@ -12189,7 +12241,7 @@ impl QueueStorage { run_lease: i64, ) -> Result { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" UPDATE {} SET callback_id = NULL, @@ -12200,7 +12252,7 @@ impl QueueStorage { AND run_lease = $2 "#, self.leases_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) @@ -12211,7 +12263,7 @@ impl QueueStorage { return Ok(false); } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {} SET callback_filter = NULL, @@ -12223,14 +12275,14 @@ impl QueueStorage { AND run_lease = $2 "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" DELETE FROM {} WHERE job_id = $1 @@ -12243,7 +12295,7 @@ impl QueueStorage { AND callback_transform IS NULL "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(run_lease) .execute(tx.as_mut()) @@ -12267,7 +12319,7 @@ impl QueueStorage { run_lease: i64, ) -> Result, AwaError> { let schema = self.schema(); - let row: Option = sqlx::query_as(&format!( + let row: Option = sqlx::query_as(audited_sql(format!( r#" SELECT lease.ready_slot, @@ -12313,7 +12365,7 @@ impl QueueStorage { WHERE lease.job_id = $1 AND lease.run_lease = $2 "#, - )) + ))) .bind(job_id) .bind(run_lease) .fetch_optional(tx.as_mut()) @@ -12346,7 +12398,7 @@ impl QueueStorage { run_lease: i64, callback_id: Uuid, ) -> Result { - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" UPDATE {} SET state = 'waiting_external', @@ -12358,7 +12410,7 @@ impl QueueStorage { AND callback_id = $3 "#, self.leases_table() - )) + ))) .bind(job_id) .bind(run_lease) .bind(callback_id) @@ -12375,7 +12427,7 @@ impl QueueStorage { callback_id: Uuid, ) -> Result { let row: Option<(JobState, Option, i64, Option)> = - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" SELECT lease.state, @@ -12392,7 +12444,7 @@ impl QueueStorage { "#, self.leases_table(), self.attempt_state_table() - )) + ))) .bind(job_id) .fetch_optional(pool) .await @@ -12463,7 +12515,7 @@ impl QueueStorage { } else { "" }; - let row: Option = sqlx::query_as(&format!( + let row: Option = sqlx::query_as(audited_sql(format!( r#" SELECT lease.ready_slot, @@ -12515,7 +12567,7 @@ impl QueueStorage { "#, self.leases_table(), schema = self.schema(), - )) + ))) .bind(callback_id) .bind(run_lease) .fetch_optional(tx.as_mut()) @@ -12557,7 +12609,7 @@ impl QueueStorage { resume: bool, ) -> Result { if resume { - let resumed: Option<(i64, i64)> = sqlx::query_as(&format!( + let resumed: Option<(i64, i64)> = sqlx::query_as(audited_sql(format!( r#" UPDATE {} SET state = 'running', @@ -12570,7 +12622,7 @@ impl QueueStorage { RETURNING job_id, run_lease "#, self.leases_table() - )) + ))) .bind(callback_id) .bind(run_lease) .fetch_optional(tx.as_mut()) @@ -12583,7 +12635,7 @@ impl QueueStorage { }); }; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {} ( job_id, @@ -12606,7 +12658,7 @@ impl QueueStorage { updated_at = clock_timestamp() "#, self.attempt_state_table() - )) + ))) .bind(job_id) .bind(resumed_run_lease) .bind(payload.unwrap_or(serde_json::Value::Null)) @@ -12623,7 +12675,7 @@ impl QueueStorage { } let schema = self.schema(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE callback_id = $1 @@ -12647,7 +12699,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(callback_id) .bind(run_lease) .fetch_all(tx.as_mut()) @@ -12725,7 +12777,7 @@ impl QueueStorage { run_lease: Option, ) -> Result { let schema = self.schema(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE callback_id = $1 @@ -12749,7 +12801,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(callback_id) .bind(run_lease) .fetch_all(tx.as_mut()) @@ -12824,7 +12876,7 @@ impl QueueStorage { run_lease: Option, ) -> Result { let schema = self.schema(); - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE callback_id = $1 @@ -12848,7 +12900,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(callback_id) .bind(run_lease) .fetch_all(tx.as_mut()) @@ -12907,7 +12959,7 @@ impl QueueStorage { callback_id: Uuid, timeout: Duration, ) -> Result { - let updated: Option<(i64, i64)> = sqlx::query_as(&format!( + let updated: Option<(i64, i64)> = sqlx::query_as(audited_sql(format!( r#" UPDATE {} SET callback_timeout_at = clock_timestamp() + make_interval(secs => $2) @@ -12916,7 +12968,7 @@ impl QueueStorage { RETURNING job_id, run_lease "#, self.leases_table() - )) + ))) .bind(callback_id) .bind(timeout.as_secs_f64()) .fetch_optional(pool) @@ -12951,7 +13003,7 @@ impl QueueStorage { ) .await?; } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {} (job_id, run_lease, progress, updated_at) SELECT lease.job_id, lease.run_lease, $3, clock_timestamp() @@ -12966,7 +13018,7 @@ impl QueueStorage { "#, self.attempt_state_table(), self.leases_table() - )) + ))) .bind(job_id) .bind(run_lease) .bind(progress) @@ -13007,7 +13059,7 @@ impl QueueStorage { // upsert_attempt_state path firing. let job_ids: Vec = jobs.iter().map(|(job_id, _)| *job_id).collect(); let run_leases: Vec = jobs.iter().map(|(_, run_lease)| *run_lease).collect(); - let result = sqlx::query(&format!( + let result = sqlx::query(audited_sql(format!( r#" WITH inflight AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[]) AS v(job_id, run_lease) @@ -13020,7 +13072,7 @@ impl QueueStorage { AND {table}.state = 'running' "#, table = self.leases_table(), - )) + ))) .bind(&job_ids) .bind(&run_leases) .execute(tx.as_mut()) @@ -13063,7 +13115,7 @@ impl QueueStorage { let run_leases: Vec = jobs.iter().map(|(_, run_lease, _)| *run_lease).collect(); let progress: Vec = jobs.iter().map(|(_, _, value)| value.clone()).collect(); - let lease_updated: i64 = sqlx::query_scalar(&format!( + let lease_updated: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH inflight AS ( SELECT * FROM unnest($1::bigint[], $2::bigint[], $3::jsonb[]) AS v(job_id, run_lease, progress) @@ -13089,7 +13141,7 @@ impl QueueStorage { SELECT count(*)::bigint FROM updated "#, table = self.leases_table(), - )) + ))) .bind(&job_ids) .bind(&run_leases) .bind(&progress) @@ -13558,7 +13610,7 @@ impl QueueStorage { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; let done_projection = done_row_projection("done", "ready"); let ready_join = done_ready_join(schema, "done", "ready"); - let moved: Option = sqlx::query_as(&format!( + let moved: Option = sqlx::query_as(audited_sql(format!( r#" WITH deleted AS ( DELETE FROM {schema}.done_entries @@ -13577,7 +13629,7 @@ impl QueueStorage { FROM deleted AS done {ready_join} "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -13622,7 +13674,7 @@ impl QueueStorage { let mut tx = pool.begin().await.map_err(map_sqlx_error)?; let done_projection = done_row_projection("done", "ready"); let ready_join = done_ready_join(schema, "done", "ready"); - let moved: Vec = sqlx::query_as(&format!( + let moved: Vec = sqlx::query_as(audited_sql(format!( r#" WITH deleted AS ( DELETE FROM {schema}.done_entries @@ -13635,7 +13687,7 @@ impl QueueStorage { FROM deleted AS done {ready_join} "# - )) + ))) .bind(kind) .bind(queue) .fetch_all(tx.as_mut()) @@ -13672,7 +13724,7 @@ impl QueueStorage { ) -> Result, AwaError> { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let moved: Option = sqlx::query_as(&format!( + let moved: Option = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE job_id = $1 @@ -13697,7 +13749,7 @@ impl QueueStorage { dlq_at, original_run_lease "# - )) + ))) .bind(job_id) .fetch_optional(tx.as_mut()) .await @@ -13760,7 +13812,7 @@ impl QueueStorage { ) -> Result { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let moved: Vec = sqlx::query_as(&format!( + let moved: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE ($1::text IS NULL OR kind = $1) @@ -13797,7 +13849,7 @@ impl QueueStorage { dlq_at, original_run_lease "# - )) + ))) .bind(&filter.kind) .bind(&filter.queue) .bind(&filter.tag) @@ -13843,7 +13895,7 @@ impl QueueStorage { let done_projection = done_row_projection("done", "ready"); let ready_join = done_ready_join(schema, "done", "ready"); - let deleted_done: Vec = sqlx::query_as(&format!( + let deleted_done: Vec = sqlx::query_as(audited_sql(format!( r#" WITH deleted AS ( DELETE FROM {schema}.done_entries @@ -13855,13 +13907,13 @@ impl QueueStorage { FROM deleted AS done {ready_join} "# - )) + ))) .bind(kind) .fetch_all(tx.as_mut()) .await .map_err(map_sqlx_error)?; - let deleted_dlq: Vec = sqlx::query_as(&format!( + let deleted_dlq: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE kind = $1 @@ -13886,7 +13938,7 @@ impl QueueStorage { dlq_at, original_run_lease "# - )) + ))) .bind(kind) .fetch_all(tx.as_mut()) .await @@ -14029,7 +14081,7 @@ impl QueueStorage { // `(state, callback_timeout_at)` indexes, followed by a heap // recheck of the COALESCE. Bounded by running-lease count and // called at 30s cadence — cheap. - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases AS target WHERE (target.job_id, target.run_lease) IN ( @@ -14062,7 +14114,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .bind(cutoff) .fetch_all(tx.as_mut()) .await @@ -14147,7 +14199,7 @@ impl QueueStorage { pub async fn rescue_expired_deadlines(&self, pool: &PgPool) -> Result, AwaError> { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE job_id IN ( @@ -14178,7 +14230,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .fetch_all(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -14242,7 +14294,7 @@ impl QueueStorage { pub async fn rescue_expired_callbacks(&self, pool: &PgPool) -> Result, AwaError> { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let deleted: Vec = sqlx::query_as(&format!( + let deleted: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.leases WHERE job_id IN ( @@ -14273,7 +14325,7 @@ impl QueueStorage { callback_id, callback_timeout_at "# - )) + ))) .fetch_all(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -14345,7 +14397,7 @@ impl QueueStorage { let schema = self.schema(); let mut tx = pool.begin().await.map_err(map_sqlx_error)?; - let moved: Vec = sqlx::query_as(&format!( + let moved: Vec = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.deferred_jobs WHERE job_id IN ( @@ -14379,7 +14431,7 @@ impl QueueStorage { unique_states, COALESCE(payload, '{{}}'::jsonb) AS payload "# - )) + ))) .bind(state) .bind(batch_size) .fetch_all(tx.as_mut()) @@ -14426,10 +14478,12 @@ impl QueueStorage { tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, relation: &str, ) -> Result { - sqlx::query_scalar(&format!("SELECT EXISTS (SELECT 1 FROM {relation} LIMIT 1)")) - .fetch_one(tx.as_mut()) - .await - .map_err(map_sqlx_error) + sqlx::query_scalar(audited_sql(format!( + "SELECT EXISTS (SELECT 1 FROM {relation} LIMIT 1)" + ))) + .fetch_one(tx.as_mut()) + .await + .map_err(map_sqlx_error) } /// Idle-gate probe (#371): true when every relation in `relations` is @@ -14755,26 +14809,26 @@ impl QueueStorage { .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {schema}.done_entries, \ {schema}.receipt_completion_batches, \ {schema}.receipt_completion_tombstones, \ {schema}.queue_terminal_count_deltas, \ {schema}.queue_terminal_live_counts \ IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "TRUNCATE TABLE {schema}.queue_terminal_live_counts, {schema}.queue_terminal_count_deltas" - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; - let inserted: i64 = sqlx::query_scalar(&format!( + let inserted: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH inserted AS ( INSERT INTO {schema}.queue_terminal_live_counts AS counts ( @@ -14797,7 +14851,7 @@ impl QueueStorage { ) SELECT COALESCE(count(*), 0)::bigint FROM inserted "# - )) + ))) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -14805,13 +14859,13 @@ impl QueueStorage { // Flip the trust marker. From this point the read path // (queue_counts_exact) uses the counter for done-entry terminal rows; // before this call, it falls back to scanning terminal_jobs. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.queue_ring_state SET terminal_counter_trusted_at = now() WHERE singleton = TRUE "# - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -14833,10 +14887,10 @@ impl QueueStorage { /// PK fetch; negligible cost per call. pub async fn terminal_counter_trusted(&self, pool: &PgPool) -> Result { let schema = self.schema(); - let trusted: Option = sqlx::query_scalar(&format!( + let trusted: Option = sqlx::query_scalar(audited_sql(format!( "SELECT terminal_counter_trusted_at IS NOT NULL \ FROM {schema}.queue_ring_state WHERE singleton = TRUE" - )) + ))) .fetch_optional(pool) .await .map_err(map_sqlx_error)?; @@ -14963,8 +15017,9 @@ impl QueueStorage { // Sealed slots and their last-open generations are derived from // the rotation-ledger cursor (#371); no per-slot generation rows // exist anymore. - let (current_slot, generation, slot_count): (i32, i64, i32) = sqlx::query_as(&format!( - r#" + let (current_slot, generation, slot_count): (i32, i64, i32) = + sqlx::query_as(audited_sql(format!( + r#" SELECT ledger.slot, ledger.generation, state.slot_count FROM {schema}.queue_ring_state AS state CROSS JOIN LATERAL ( @@ -14975,10 +15030,10 @@ impl QueueStorage { ) AS ledger WHERE state.singleton = TRUE "# - )) - .fetch_one(pool) - .await - .map_err(map_sqlx_error)?; + ))) + .fetch_one(pool) + .await + .map_err(map_sqlx_error)?; let sealed_slots = initialized_sealed_ring_slots(current_slot, generation, slot_count); let mut pending_slots = Vec::new(); @@ -14987,7 +15042,7 @@ impl QueueStorage { continue; }; let delta_child = terminal_delta_child_name(schema, slot_index); - let has_pending: bool = sqlx::query_scalar(&format!( + let has_pending: bool = sqlx::query_scalar(audited_sql(format!( r#" SELECT EXISTS ( SELECT 1 @@ -14996,7 +15051,7 @@ impl QueueStorage { LIMIT 1 ) "# - )) + ))) .bind(generation) .fetch_one(pool) .await @@ -15037,14 +15092,14 @@ impl QueueStorage { .await?; let slot_count = self.ring_slot_count_tx(&mut tx, RingFamily::Queue).await?; - let slot_locked: Option = sqlx::query_scalar(&format!( + let slot_locked: Option = sqlx::query_scalar(audited_sql(format!( r#" SELECT slot FROM {schema}.queue_ring_slots WHERE slot = $1 FOR UPDATE "# - )) + ))) .bind(slot) .fetch_optional(tx.as_mut()) .await @@ -15078,7 +15133,7 @@ impl QueueStorage { } let ready_child = ready_child_name(schema, slot as usize); - let pending_ready: bool = sqlx::query_scalar(&format!( + let pending_ready: bool = sqlx::query_scalar(audited_sql(format!( r#" WITH claim_cursors AS MATERIALIZED ( SELECT @@ -15104,7 +15159,7 @@ impl QueueStorage { LIMIT 1 ) "# - )) + ))) .bind(generation) .fetch_one(tx.as_mut()) .await @@ -15117,9 +15172,9 @@ impl QueueStorage { set_prune_lock_timeout_tx(&mut tx, self.prune_lock_timeout).await?; - let lock_delta = sqlx::query(&format!( + let lock_delta = sqlx::query(audited_sql(format!( "LOCK TABLE {delta_child} IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(tx.as_mut()) .await; @@ -15151,13 +15206,13 @@ impl QueueStorage { return Ok(TerminalDeltaSlotRollup::SkippedActive); } - let delta_rows: i64 = sqlx::query_scalar(&format!( + let delta_rows: i64 = sqlx::query_scalar(audited_sql(format!( r#" SELECT count(*)::bigint FROM {delta_child} WHERE ready_generation = $1 "# - )) + ))) .bind(generation) .fetch_one(tx.as_mut()) .await @@ -15168,7 +15223,7 @@ impl QueueStorage { return Ok(TerminalDeltaSlotRollup::Empty); } - let grouped_keys: i64 = sqlx::query_scalar(&format!( + let grouped_keys: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH grouped AS MATERIALIZED ( SELECT @@ -15234,13 +15289,13 @@ impl QueueStorage { ) SELECT count(*)::bigint FROM grouped "# - )) + ))) .bind(generation) .fetch_one(tx.as_mut()) .await .map_err(map_sqlx_error)?; - let truncate_delta = sqlx::query(&format!("TRUNCATE TABLE {delta_child}")) + let truncate_delta = sqlx::query(audited_sql(format!("TRUNCATE TABLE {delta_child}"))) .execute(tx.as_mut()) .await; @@ -15296,12 +15351,12 @@ impl QueueStorage { }); } - let deltas: Vec<(String, i16, i64, i64)> = sqlx::query_as(&format!( + let deltas: Vec<(String, i16, i64, i64)> = sqlx::query_as(audited_sql(format!( r#" DELETE FROM {schema}.queue_terminal_rollup_deltas RETURNING queue, priority, pruned_completed_delta, pruned_failed_delta "# - )) + ))) .fetch_all(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -15358,7 +15413,7 @@ impl QueueStorage { for family in [RingFamily::Queue, RingFamily::Lease, RingFamily::Claim] { let ledger = family.ledger_relname(); let state = family.state_relname(); - let deleted = sqlx::query(&format!( + let deleted = sqlx::query(audited_sql(format!( r#" DELETE FROM {schema}.{ledger} WHERE generation < ( @@ -15367,7 +15422,7 @@ impl QueueStorage { SELECT slot_count - 1 FROM {schema}.{state} WHERE singleton = TRUE )::bigint "# - )) + ))) .execute(tx.as_mut()) .await .map_err(map_sqlx_error)?; @@ -15443,14 +15498,14 @@ impl QueueStorage { // Slot row lock: second resource in the queue-ring lock order // (ring advisory lock → slot row → child ACCESS EXCLUSIVE). - let slot_locked: Option = sqlx::query_scalar(&format!( + let slot_locked: Option = sqlx::query_scalar(audited_sql(format!( r#" SELECT slot FROM {schema}.queue_ring_slots WHERE slot = $1 FOR UPDATE "# - )) + ))) .bind(slot) .fetch_optional(tx.as_mut()) .await @@ -15514,9 +15569,9 @@ impl QueueStorage { set_prune_lock_timeout_tx(&mut tx, self.prune_lock_timeout).await?; let lock_started = Instant::now(); - let lock_tables = sqlx::query(&format!( + let lock_tables = sqlx::query(audited_sql(format!( "LOCK TABLE {ready_child}, {claim_attempt_child}, {done_child}, {tomb_child}, {segment_child}, {receipt_batch_child}, {receipt_tomb_child}, {delta_child} IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(tx.as_mut()) .await; let lock_duration = lock_started.elapsed(); @@ -15583,7 +15638,7 @@ impl QueueStorage { // never-resetting sequences, so a PK collision means // corrupted terminal state and must abort the prune loudly // rather than silently drop a terminal fact. - let carried = sqlx::query(&format!( + let carried = sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, @@ -15624,7 +15679,7 @@ impl QueueStorage { WHERE done.state = 'failed' AND done.finalized_at >= now() - make_interval(secs => $1::bigint) "# - )) + ))) .bind(failed_retention_secs) .bind(state.0) .bind(current_generation) @@ -15639,7 +15694,7 @@ impl QueueStorage { // evidence moves with them: re-append positive deltas // under the current slot, grouped exactly like the // completion path's delta append. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_terminal_count_deltas ( ready_slot, @@ -15668,7 +15723,7 @@ impl QueueStorage { GROUP BY queue, priority, enqueue_shard, counter_bucket ORDER BY queue, priority, enqueue_shard, counter_bucket "# - )) + ))) .bind(failed_retention_secs) .bind(state.0) .bind(current_generation) @@ -15698,8 +15753,9 @@ impl QueueStorage { // Carried failed rows are excluded from both columns: they are // still live in `done_entries`, so folding them into the // permanent rollup would double-count them. - let pruned_terminal_counts: Vec<(String, i16, i64, i64)> = sqlx::query_as(&format!( - r#" + let pruned_terminal_counts: Vec<(String, i16, i64, i64)> = + sqlx::query_as(audited_sql(format!( + r#" WITH done_counts AS ( SELECT queue, @@ -15767,17 +15823,17 @@ impl QueueStorage { LEFT JOIN done_counts USING (queue, priority) LEFT JOIN batch_counts USING (queue, priority) "# - )) - .bind(failed_retention_secs) - .bind(retention_floor) - .fetch_all(tx.as_mut()) - .await - .map_err(map_sqlx_error)?; + ))) + .bind(failed_retention_secs) + .bind(retention_floor) + .fetch_all(tx.as_mut()) + .await + .map_err(map_sqlx_error)?; let truncate_started = Instant::now(); - let truncate = sqlx::query(&format!( + let truncate = sqlx::query(audited_sql(format!( "TRUNCATE TABLE {ready_child}, {claim_attempt_child}, {done_child}, {tomb_child}, {segment_child}, {receipt_batch_child}, {receipt_tomb_child}, {delta_child}" - )) + ))) .execute(tx.as_mut()) .await; let truncate_duration = truncate_started.elapsed(); @@ -15807,9 +15863,9 @@ impl QueueStorage { // the partition scan; this just cleans up the counter // index entries so a future insert into a re-rotated // slot starts from zero. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.queue_terminal_live_counts WHERE ready_slot = $1" - )) + ))) .bind(slot) .execute(tx.as_mut()) .await @@ -15900,13 +15956,13 @@ impl QueueStorage { tracing::Span::current().record("slot", slot); tracing::Span::current().record("generation", generation); - let slot_locked: Option = sqlx::query_scalar(&format!( + let slot_locked: Option = sqlx::query_scalar(audited_sql(format!( r#" SELECT slot FROM {schema}.lease_ring_slots WHERE slot = $1 FOR UPDATE "# - )) + ))) .bind(slot) .fetch_optional(tx.as_mut()) .await @@ -15922,9 +15978,9 @@ impl QueueStorage { set_prune_lock_timeout_tx(&mut tx, self.prune_lock_timeout).await?; let lock_started = Instant::now(); - let lock_table = sqlx::query(&format!( + let lock_table = sqlx::query(audited_sql(format!( "LOCK TABLE {lease_child} IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(tx.as_mut()) .await; let lock_duration = lock_started.elapsed(); @@ -15965,7 +16021,7 @@ impl QueueStorage { } let truncate_started = Instant::now(); - let truncate = sqlx::query(&format!("TRUNCATE TABLE {lease_child}")) + let truncate = sqlx::query(audited_sql(format!("TRUNCATE TABLE {lease_child}"))) .execute(tx.as_mut()) .await; let truncate_duration = truncate_started.elapsed(); @@ -15998,7 +16054,7 @@ impl QueueStorage { } pub async fn vacuum_leases(&self, pool: &PgPool) -> Result<(), AwaError> { - sqlx::query(&format!("VACUUM {}", self.leases_table())) + sqlx::query(audited_sql(format!("VACUUM {}", self.leases_table()))) .execute(pool) .await .map_err(map_sqlx_error)?; @@ -16215,13 +16271,13 @@ impl QueueStorage { // Lock the slot row so concurrent rotate/prune observe the same // state machine transition. - let slot_locked: Option = sqlx::query_scalar(&format!( + let slot_locked: Option = sqlx::query_scalar(audited_sql(format!( r#" SELECT slot FROM {schema}.claim_ring_slots WHERE slot = $1 FOR UPDATE "# - )) + ))) .bind(slot) .fetch_optional(tx.as_mut()) .await @@ -16266,9 +16322,9 @@ impl QueueStorage { set_prune_lock_timeout_tx(&mut tx, self.prune_lock_timeout).await?; let lock_started = Instant::now(); - let lock_tables = sqlx::query(&format!( + let lock_tables = sqlx::query(audited_sql(format!( "LOCK TABLE {claim_child}, {claim_batch_child}, {closure_child}, {closure_batch_child} IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(tx.as_mut()) .await; let lock_duration = lock_started.elapsed(); @@ -16317,16 +16373,16 @@ impl QueueStorage { } let truncate_started = Instant::now(); - let truncate = sqlx::query(&format!( + let truncate = sqlx::query(audited_sql(format!( "TRUNCATE TABLE {claim_child}, {claim_batch_child}, {closure_child}, {closure_batch_child}" - )) + ))) .execute(tx.as_mut()) .await; let truncate_duration = truncate_started.elapsed(); match truncate { Ok(_) => { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.claim_ring_slots SET rescue_cursor_claimed_at = '-infinity'::timestamptz, @@ -16339,7 +16395,7 @@ impl QueueStorage { batch_deadline_cursor_batch_id = 0 WHERE slot = $1 "# - )) + ))) .bind(slot) .execute(tx.as_mut()) .await diff --git a/awa-model/src/reschedule.rs b/awa-model/src/reschedule.rs index c4332651..3f7785d8 100644 --- a/awa-model/src/reschedule.rs +++ b/awa-model/src/reschedule.rs @@ -22,6 +22,7 @@ use chrono::{DateTime, Utc}; use sqlx::{PgConnection, PgPool, Postgres, Transaction}; +use crate::audited_sql; use crate::error::{map_sqlx_error, AwaError}; use crate::job::{JobRow, JobState}; use crate::queue_storage::QueueStorage; @@ -302,21 +303,22 @@ async fn migrate_to_queue_storage( // `finalized_at` comes from the same statement as `run_at`: retention and // cleanup compare it against the database clock, so stamping it from the // worker would let clock skew shift those decisions. - let (new_id, run_at, db_now): (i64, DateTime, DateTime) = sqlx::query_as(&format!( - r#" + let (new_id, run_at, db_now): (i64, DateTime, DateTime) = + sqlx::query_as(audited_sql(format!( + r#" SELECT nextval('{schema}.job_id_seq')::bigint, CASE WHEN $1 THEN now() + awa.backoff_duration($2::smallint, $3::smallint) ELSE now() + make_interval(secs => $4) END, now() "# - )) - .bind(use_backoff) - .bind(job.attempt) - .bind(job.max_attempts) - .bind(delay_secs) - .fetch_one(tx.as_mut()) - .await - .map_err(map_sqlx_error)?; + ))) + .bind(use_backoff) + .bind(job.attempt) + .bind(job.max_attempts) + .bind(delay_secs) + .fetch_one(tx.as_mut()) + .await + .map_err(map_sqlx_error)?; let mut errors: Vec = job.errors.clone().unwrap_or_default(); if let Some(error) = error { diff --git a/awa-model/tests/heartbeat_write_removal_test.rs b/awa-model/tests/heartbeat_write_removal_test.rs index 64ddcb7f..ae7b0468 100644 --- a/awa-model/tests/heartbeat_write_removal_test.rs +++ b/awa-model/tests/heartbeat_write_removal_test.rs @@ -7,6 +7,7 @@ //! (heartbeat_batch on a receipts-mode store does not write //! leases.heartbeat_at). +use awa_model::audited_sql; use awa_model::{QueueStorage, QueueStorageConfig}; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; @@ -71,10 +72,12 @@ async fn prepare_schema_does_not_create_state_hb_index() { }) .expect("construct QueueStorage"); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("clean any prior schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("clean any prior schema"); store .prepare_schema(&pool) .await @@ -86,7 +89,7 @@ async fn prepare_schema_does_not_create_state_hb_index() { "fresh prepare_schema must not create idx_*_state_hb" ); - sqlx::query(&format!("DROP SCHEMA {schema} CASCADE")) + sqlx::query(audited_sql(format!("DROP SCHEMA {schema} CASCADE"))) .execute(&pool) .await .expect("cleanup test schema"); diff --git a/awa-model/tests/least_privilege_migration_test.rs b/awa-model/tests/least_privilege_migration_test.rs index 43997d05..fd309a7c 100644 --- a/awa-model/tests/least_privilege_migration_test.rs +++ b/awa-model/tests/least_privilege_migration_test.rs @@ -16,6 +16,7 @@ //! old scan idiom (with `permission denied for schema pg_toast`) and passes //! once discovery uses catalog lookups gated on `has_schema_privilege()`. +use awa_model::audited_sql; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use std::str::FromStr; @@ -40,9 +41,9 @@ async fn migrations_apply_as_non_superuser_owner() { // A least-privilege login role: explicitly NOSUPERUSER so it is subject // to the same schema ACLs (pg_toast etc.) a managed-Postgres owner hits. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE ROLE \"{role}\" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '{password}'" - )) + ))) .execute(&admin) .await .expect("create least-privilege role"); @@ -50,18 +51,22 @@ async fn migrations_apply_as_non_superuser_owner() { // A fresh database owned by that role: it can CREATE the awa schema, and // every object it creates is owned by it — matching the real deployment. // (CREATE DATABASE cannot run inside a transaction.) - sqlx::query(&format!("CREATE DATABASE \"{db}\" OWNER \"{role}\"")) - .execute(&admin) - .await - .expect("create owned database"); + sqlx::query(audited_sql(format!( + "CREATE DATABASE \"{db}\" OWNER \"{role}\"" + ))) + .execute(&admin) + .await + .expect("create owned database"); let result = run_migrations_as(&database_url(), &db, &role, password).await; // Always tear down, regardless of the outcome. - let _ = sqlx::query(&format!("DROP DATABASE IF EXISTS \"{db}\" WITH (FORCE)")) - .execute(&admin) - .await; - let _ = sqlx::query(&format!("DROP ROLE IF EXISTS \"{role}\"")) + let _ = sqlx::query(audited_sql(format!( + "DROP DATABASE IF EXISTS \"{db}\" WITH (FORCE)" + ))) + .execute(&admin) + .await; + let _ = sqlx::query(audited_sql(format!("DROP ROLE IF EXISTS \"{role}\""))) .execute(&admin) .await; admin.close().await; diff --git a/awa-model/tests/queue_storage_copy_test.rs b/awa-model/tests/queue_storage_copy_test.rs index d53fd7b7..f16b01fd 100644 --- a/awa-model/tests/queue_storage_copy_test.rs +++ b/awa-model/tests/queue_storage_copy_test.rs @@ -1,3 +1,4 @@ +use awa_model::audited_sql; use awa_model::{ migrations, AwaError, InsertOpts, InsertParams, QueueStorage, QueueStorageConfig, UniqueOpts, }; @@ -28,10 +29,13 @@ async fn setup_store_with_config( migrations::run(&pool).await.expect("run migrations"); let store = QueueStorage::new(config).expect("create queue storage"); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema())) - .execute(&pool) - .await - .expect("drop queue storage schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {} CASCADE", + store.schema() + ))) + .execute(&pool) + .await + .expect("drop queue storage schema"); store .prepare_schema(&pool) .await @@ -106,7 +110,7 @@ fn bench_env_usize(name: &str, default: usize) -> usize { } async fn lane_available_count(pool: &PgPool, schema: &str, queues: Vec) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" SELECT COALESCE( sum(GREATEST( @@ -123,7 +127,7 @@ async fn lane_available_count(pool: &PgPool, schema: &str, queues: Vec) AND qc.enqueue_shard = qe.enqueue_shard WHERE qe.queue = ANY($1) "# - )) + ))) .bind(queues) .fetch_one(pool) .await @@ -175,10 +179,10 @@ async fn queue_storage_copy_enqueues_ready_and_deferred_rows() { .expect("copy enqueue"); assert_eq!(inserted, 3); - let ready: Vec<(i64, i64, Option)> = sqlx::query_as(&format!( + let ready: Vec<(i64, i64, Option)> = sqlx::query_as(audited_sql(format!( "SELECT lane_seq, (args->>'seq')::bigint, payload FROM {}.ready_entries WHERE queue = $1 ORDER BY lane_seq", store.schema() - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -200,13 +204,13 @@ async fn queue_storage_copy_enqueues_ready_and_deferred_rows() { "default payloads should persist as SQL NULL in COPY rows" ); - let (compact_payload_bytes, expanded_payload_bytes): (i32, i32) = sqlx::query_as(&format!( + let (compact_payload_bytes, expanded_payload_bytes): (i32, i32) = sqlx::query_as(audited_sql(format!( "SELECT COALESCE(pg_column_size(payload), 0)::int, \ pg_column_size('{{\"metadata\":{{}},\"tags\":[],\"errors\":[],\"progress\":null}}'::jsonb)::int \ FROM {}.ready_entries \ WHERE queue = $1 AND lane_seq = $2", store.schema() - )) + ))) .bind(queue) .bind(default.0) .fetch_one(&pool) @@ -221,10 +225,10 @@ async fn queue_storage_copy_enqueues_ready_and_deferred_rows() { "compact payload should use fewer JSONB bytes ({compact_payload_bytes} >= {expanded_payload_bytes})" ); - let deferred_count: i64 = sqlx::query_scalar(&format!( + let deferred_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.deferred_jobs WHERE queue = $1 AND state = 'scheduled'", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -269,10 +273,10 @@ async fn queue_storage_copy_rolls_back_on_unique_conflict() { "unexpected error: {err:?}" ); - let ready_count: i64 = sqlx::query_scalar(&format!( + let ready_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -316,10 +320,10 @@ async fn queue_storage_batch_rolls_back_on_batched_unique_conflict() { "unexpected error: {err:?}" ); - let ready_count: i64 = sqlx::query_scalar(&format!( + let ready_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -363,10 +367,10 @@ async fn queue_storage_copy_rolls_back_on_existing_unique_conflict() { "unexpected error: {err:?}" ); - let ready_count: i64 = sqlx::query_scalar(&format!( + let ready_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -415,10 +419,10 @@ async fn queue_storage_copy_concurrent_lane_seq_is_dense() { ); } - let lane_seqs: Vec = sqlx::query_scalar(&format!( + let lane_seqs: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT lane_seq FROM {}.ready_entries WHERE queue = $1 ORDER BY lane_seq", store.schema() - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -481,10 +485,10 @@ async fn queue_storage_copy_independent_stores_keep_lane_seq_unique() { ); } - let lane_seqs: Vec = sqlx::query_scalar(&format!( + let lane_seqs: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT lane_seq FROM {}.ready_entries WHERE queue = $1 ORDER BY lane_seq", store.schema() - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -517,19 +521,19 @@ async fn queue_storage_sequence_sync_does_not_rewind_hot_reservations() { .await .expect("seed lane"); - let seq_name: String = sqlx::query_scalar(&format!( + let seq_name: String = sqlx::query_scalar(audited_sql(format!( "SELECT seq_name FROM {}.queue_enqueue_heads WHERE queue = $1 AND priority = 2 AND enqueue_shard = 0", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await .expect("read enqueue sequence name"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE {}.sequence_sync_seen (lane_seq BIGINT PRIMARY KEY)", store.schema() - )) + ))) .execute(&pool) .await .expect("create sequence seen table"); @@ -549,10 +553,10 @@ async fn queue_storage_sequence_sync_does_not_rewind_hot_reservations() { .expect("connect sync pool"); sync_start.wait().await; for _ in 0..(reserver_count * rounds) { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "SELECT {}.set_sequence_next($1, 1::bigint)", sync_schema - )) + ))) .bind(&seq_name) .execute(&pool) .await @@ -575,19 +579,19 @@ async fn queue_storage_sequence_sync_does_not_rewind_hot_reservations() { start.wait().await; for _ in 0..rounds { let mut tx = pool.begin().await.expect("begin reserve tx"); - let range_start: i64 = sqlx::query_scalar(&format!( + let range_start: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT {}.reserve_enqueue_seq($1, 2::smallint, 0::smallint, $2::bigint)", schema - )) + ))) .bind(&queue) .bind(values_per_round) .fetch_one(tx.as_mut()) .await .expect("reserve enqueue sequence range"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {}.sequence_sync_seen (lane_seq) SELECT generate_series($1::bigint, $2::bigint)", schema - )) + ))) .bind(range_start) .bind(range_start + values_per_round - 1) .execute(tx.as_mut()) @@ -603,10 +607,10 @@ async fn queue_storage_sequence_sync_does_not_rewind_hot_reservations() { reserver.await.expect("join reserver task"); } - let (seen, distinct_seen): (i64, i64) = sqlx::query_as(&format!( + let (seen, distinct_seen): (i64, i64) = sqlx::query_as(audited_sql(format!( "SELECT count(*)::bigint, count(DISTINCT lane_seq)::bigint FROM {}.sequence_sync_seen", store.schema() - )) + ))) .fetch_one(&pool) .await .expect("count reserved sequence values"); @@ -630,10 +634,10 @@ async fn queue_storage_copy_distributes_across_stripes() { .expect("copy enqueue striped"); assert_eq!(inserted, 8); - let rows: Vec<(String, i64)> = sqlx::query_as(&format!( + let rows: Vec<(String, i64)> = sqlx::query_as(audited_sql(format!( "SELECT queue, lane_seq FROM {}.ready_entries ORDER BY queue, lane_seq", store.schema() - )) + ))) .fetch_all(&pool) .await .expect("read striped ready rows"); @@ -700,10 +704,10 @@ async fn queue_storage_copy_escapes_csv_special_values() { .expect("copy enqueue escape matrix"); assert_eq!(inserted, jobs.len()); - let ready: Vec<(String, serde_json::Value, serde_json::Value)> = sqlx::query_as(&format!( + let ready: Vec<(String, serde_json::Value, serde_json::Value)> = sqlx::query_as(audited_sql(format!( "SELECT kind, args, payload FROM {}.ready_entries WHERE queue = $1 ORDER BY (args->>'seq')::int", store.schema() - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -721,10 +725,10 @@ async fn queue_storage_copy_escapes_csv_special_values() { serde_json::Value, serde_json::Value, Option>, - ) = sqlx::query_as(&format!( + ) = sqlx::query_as(audited_sql(format!( "SELECT kind, args, payload, unique_key FROM {}.deferred_jobs WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await diff --git a/awa-model/tests/receipt_plane_fillfactor_test.rs b/awa-model/tests/receipt_plane_fillfactor_test.rs index 4d62526d..509b833d 100644 --- a/awa-model/tests/receipt_plane_fillfactor_test.rs +++ b/awa-model/tests/receipt_plane_fillfactor_test.rs @@ -6,6 +6,7 @@ //! `done_entries`, `lease_claim_closures`) intentionally stay at the //! default fillfactor=100. +use awa_model::audited_sql; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; @@ -111,11 +112,13 @@ async fn apply_receipt_plane_fillfactor_helper_restores_reset_partitions() { let pool = migrated_pool().await; let schema = format!("awa_fillfactor_test_{}", uuid::Uuid::new_v4().simple()); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("clean any prior schema"); - sqlx::query(&format!("CREATE SCHEMA {schema}")) + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("clean any prior schema"); + sqlx::query(audited_sql(format!("CREATE SCHEMA {schema}"))) .execute(&pool) .await .expect("create test schema"); @@ -150,14 +153,14 @@ async fn apply_receipt_plane_fillfactor_helper_restores_reset_partitions() { "expected at least one partition under {schema}" ); for name in &partition_names { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {name} RESET ( \ fillfactor, \ autovacuum_vacuum_scale_factor, \ autovacuum_vacuum_threshold, \ autovacuum_vacuum_cost_limit, \ autovacuum_vacuum_cost_delay)" - )) + ))) .execute(&pool) .await .expect("reset partition reloptions"); @@ -224,7 +227,7 @@ async fn apply_receipt_plane_fillfactor_helper_restores_reset_partitions() { ); } - sqlx::query(&format!("DROP SCHEMA {schema} CASCADE")) + sqlx::query(audited_sql(format!("DROP SCHEMA {schema} CASCADE"))) .execute(&pool) .await .expect("cleanup test schema"); @@ -265,10 +268,12 @@ async fn prepare_schema_reapplies_receipt_plane_fillfactor_on_reprepare() { }) .expect("construct QueueStorage"); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("clean any prior schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("clean any prior schema"); store .prepare_schema(&pool) @@ -296,14 +301,14 @@ async fn prepare_schema_reapplies_receipt_plane_fillfactor_on_reprepare() { .expect("list partitions"); assert!(!partition_names.is_empty(), "expected partitions"); for name in &partition_names { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {name} RESET ( \ fillfactor, \ autovacuum_vacuum_scale_factor, \ autovacuum_vacuum_threshold, \ autovacuum_vacuum_cost_limit, \ autovacuum_vacuum_cost_delay)" - )) + ))) .execute(&pool) .await .expect("reset partition reloptions"); @@ -370,7 +375,7 @@ async fn prepare_schema_reapplies_receipt_plane_fillfactor_on_reprepare() { ); } - sqlx::query(&format!("DROP SCHEMA {schema} CASCADE")) + sqlx::query(audited_sql(format!("DROP SCHEMA {schema} CASCADE"))) .execute(&pool) .await .expect("cleanup test schema"); @@ -388,41 +393,43 @@ async fn apply_receipt_plane_fillfactor_skips_non_awa_schemas() { let pool = migrated_pool().await; let schema = format!("app_owned_test_{}", uuid::Uuid::new_v4().simple()); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("clean any prior schema"); - sqlx::query(&format!("CREATE SCHEMA {schema}")) + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("clean any prior schema"); + sqlx::query(audited_sql(format!("CREATE SCHEMA {schema}"))) .execute(&pool) .await .expect("create app-owned schema"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE {schema}.leases ( \ lease_slot INT NOT NULL, id BIGINT NOT NULL, \ PRIMARY KEY (lease_slot, id) \ ) PARTITION BY LIST (lease_slot)" - )) + ))) .execute(&pool) .await .expect("create lookalike leases parent"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE {schema}.leases_0 PARTITION OF {schema}.leases FOR VALUES IN (0)" - )) + ))) .execute(&pool) .await .expect("create lookalike leases partition"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE {schema}.lease_claims ( \ claim_slot INT NOT NULL, id BIGINT NOT NULL, \ PRIMARY KEY (claim_slot, id) \ ) PARTITION BY LIST (claim_slot)" - )) + ))) .execute(&pool) .await .expect("create lookalike lease_claims parent"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE {schema}.lease_claims_0 PARTITION OF {schema}.lease_claims FOR VALUES IN (0)" - )) + ))) .execute(&pool) .await .expect("create lookalike lease_claims partition"); @@ -455,7 +462,7 @@ async fn apply_receipt_plane_fillfactor_skips_non_awa_schemas() { schema that lacks the AWA claim_ready_runtime sentinel" ); - sqlx::query(&format!("DROP SCHEMA {schema} CASCADE")) + sqlx::query(audited_sql(format!("DROP SCHEMA {schema} CASCADE"))) .execute(&pool) .await .expect("cleanup lookalike schema"); diff --git a/awa-model/tests/sql_only_storage_upgrade_test.rs b/awa-model/tests/sql_only_storage_upgrade_test.rs index 7710aa8c..6edace72 100644 --- a/awa-model/tests/sql_only_storage_upgrade_test.rs +++ b/awa-model/tests/sql_only_storage_upgrade_test.rs @@ -10,6 +10,7 @@ //! transition is driven by the SQL functions defined in v010/v013/v014. //! Tests below call those functions directly via raw SQL. +use awa_model::audited_sql; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; use std::sync::LazyLock; @@ -748,7 +749,7 @@ async fn seed_canonical_running_job_with_mask( run_lease: i64, mask: &str, ) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" INSERT INTO awa.jobs_hot ( kind, queue, args, state, priority, attempt, max_attempts, @@ -762,7 +763,7 @@ async fn seed_canonical_running_job_with_mask( ) RETURNING id "# - )) + ))) .bind(unique_key.to_vec()) .bind(run_lease) .fetch_one(pool) diff --git a/awa-python/Cargo.lock b/awa-python/Cargo.lock index e0e63785..0cb24c77 100644 --- a/awa-python/Cargo.lock +++ b/awa-python/Cargo.lock @@ -182,12 +182,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "better_any" version = "0.2.1" @@ -240,6 +234,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -292,6 +295,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -317,10 +331,10 @@ dependencies = [ ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "cmov" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "constant_time_eq" @@ -402,14 +416,21 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", ] [[package]] @@ -418,10 +439,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -468,13 +498,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -495,9 +524,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -512,9 +541,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -650,6 +679,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", ] [[package]] @@ -673,9 +703,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -690,11 +720,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -711,29 +741,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "windows-sys 0.61.2", + "digest 0.11.3", ] [[package]] @@ -775,6 +796,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.0" @@ -1007,9 +1037,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1017,24 +1044,6 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[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.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.3", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1068,12 +1077,12 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] @@ -1127,41 +1136,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1169,7 +1143,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1279,7 +1252,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1290,15 +1263,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1349,39 +1313,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "portable-atomic" version = "1.15.0" @@ -1559,35 +1496,25 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1600,15 +1527,6 @@ dependencies = [ "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" @@ -1619,19 +1537,16 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "redox_syscall" -version = "0.9.3" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -1710,26 +1625,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.43" @@ -1770,12 +1665,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1832,37 +1721,36 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "sha1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] -name = "sha1" -version = "0.10.7" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1890,16 +1778,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "siphasher" version = "1.0.3" @@ -1940,21 +1818,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1965,12 +1833,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -1980,17 +1849,16 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror 2.0.20", "tokio", @@ -1998,14 +1866,14 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -2016,78 +1884,63 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror 2.0.20", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.7", - "rsa", "serde", "sha1", - "sha2", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror 2.0.20", "tracing", "uuid", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -2103,16 +1956,14 @@ dependencies = [ "hex", "hkdf", "hmac", - "home", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.7", + "rand 0.10.2", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -2124,13 +1975,14 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -2140,7 +1992,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror 2.0.20", "tracing", @@ -2670,12 +2521,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -2751,15 +2596,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.9", -] - [[package]] name = "webpki-roots" version = "1.0.9" @@ -2771,13 +2607,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "windows-core" @@ -2838,22 +2670,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2865,67 +2688,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2938,48 +2728,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/awa-python/Cargo.toml b/awa-python/Cargo.toml index 9ca783b9..790d4a4a 100644 --- a/awa-python/Cargo.toml +++ b/awa-python/Cargo.toml @@ -16,7 +16,7 @@ awa-model = { path = "../awa-model", version = "0.7.0-alpha.1" } awa-worker = { path = "../awa-worker", version = "0.7.0-alpha.1", features = ["__python-bridge"] } pyo3 = { version = "0.29", features = ["macros", "abi3-py310", "chrono"] } pyo3-async-runtimes = { version = "0.29", features = ["tokio-runtime"] } -sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "chrono", "json"] } +sqlx = { version = "0.9", features = ["runtime-tokio", "tls-rustls-ring", "postgres", "chrono", "json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] } diff --git a/awa-python/src/client.rs b/awa-python/src/client.rs index 0874f7e5..308e65f0 100644 --- a/awa-python/src/client.rs +++ b/awa-python/src/client.rs @@ -666,7 +666,7 @@ impl PyClient { .await .map_err(map_awa_error)?; let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(awa_model::audited_sql(drop_sql)) .execute(&pool) .await .map_err(map_sqlx_error)?; @@ -2573,7 +2573,7 @@ impl PyClient { .await .map_err(map_awa_error)?; let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(awa_model::audited_sql(drop_sql)) .execute(&pool) .await .map_err(map_sqlx_error)?; diff --git a/awa-python/src/transaction.rs b/awa-python/src/transaction.rs index 93396cbe..936e9261 100644 --- a/awa-python/src/transaction.rs +++ b/awa-python/src/transaction.rs @@ -40,10 +40,13 @@ impl PyTransaction { pyo3_async_runtimes::tokio::future_into_py(py, async move { let mut guard = tx.lock().await; let tx_ref = tx_ref(&mut guard)?; - let result = bind_json_args(sqlx::query(&query), &json_args) - .execute(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let result = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .execute(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; Ok(result.rows_affected() as i64) }) } @@ -61,10 +64,13 @@ impl PyTransaction { pyo3_async_runtimes::tokio::future_into_py(py, async move { let mut guard = tx.lock().await; let tx_ref = tx_ref(&mut guard)?; - let row = bind_json_args(sqlx::query(&query), &json_args) - .fetch_one(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let row = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_one(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; row_to_py_dict(&row) }) } @@ -82,10 +88,13 @@ impl PyTransaction { pyo3_async_runtimes::tokio::future_into_py(py, async move { let mut guard = tx.lock().await; let tx_ref = tx_ref(&mut guard)?; - let row = bind_json_args(sqlx::query(&query), &json_args) - .fetch_optional(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let row = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_optional(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; match row { Some(row) => row_to_py_dict(&row), None => Ok(Python::attach(|py| py.None())), @@ -106,10 +115,13 @@ impl PyTransaction { pyo3_async_runtimes::tokio::future_into_py(py, async move { let mut guard = tx.lock().await; let tx_ref = tx_ref(&mut guard)?; - let rows = bind_json_args(sqlx::query(&query), &json_args) - .fetch_all(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let rows = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_all(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; Python::attach(|py| { let list = pyo3::types::PyList::empty(py); for row in &rows { @@ -576,10 +588,13 @@ impl PySyncTransaction { pyo3_async_runtimes::tokio::get_runtime().block_on(async { let mut guard = tx.lock().await; let tx_ref = sync_tx_ref(&mut guard)?; - let result = bind_json_args(sqlx::query(&query), &json_args) - .execute(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let result = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .execute(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; Ok(result.rows_affected() as i64) }) }) @@ -598,10 +613,13 @@ impl PySyncTransaction { pyo3_async_runtimes::tokio::get_runtime().block_on(async { let mut guard = tx.lock().await; let tx_ref = sync_tx_ref(&mut guard)?; - let row = bind_json_args(sqlx::query(&query), &json_args) - .fetch_one(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let row = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_one(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; row_to_py_dict(&row) }) }) @@ -620,10 +638,13 @@ impl PySyncTransaction { pyo3_async_runtimes::tokio::get_runtime().block_on(async { let mut guard = tx.lock().await; let tx_ref = sync_tx_ref(&mut guard)?; - let row = bind_json_args(sqlx::query(&query), &json_args) - .fetch_optional(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let row = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_optional(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; match row { Some(row) => row_to_py_dict(&row), None => Ok(Python::attach(|py| py.None())), @@ -645,10 +666,13 @@ impl PySyncTransaction { pyo3_async_runtimes::tokio::get_runtime().block_on(async { let mut guard = tx.lock().await; let tx_ref = sync_tx_ref(&mut guard)?; - let rows = bind_json_args(sqlx::query(&query), &json_args) - .fetch_all(&mut **tx_ref) - .await - .map_err(map_sqlx_error)?; + let rows = bind_json_args( + sqlx::query(awa_model::caller_provided_sql(query)), + &json_args, + ) + .fetch_all(&mut **tx_ref) + .await + .map_err(map_sqlx_error)?; Python::attach(|py| { let list = pyo3::types::PyList::empty(py); for row in &rows { diff --git a/awa-seaorm/tests/seaorm.rs b/awa-seaorm/tests/seaorm.rs index b38cc97f..e26a7219 100644 --- a/awa-seaorm/tests/seaorm.rs +++ b/awa-seaorm/tests/seaorm.rs @@ -1,3 +1,4 @@ +use awa::audited_sql; use awa::{AwaError, InsertOpts, JobArgs, JobResult, QueueConfig, UniqueOpts}; use awa_seaorm::{client_builder, insert, insert_raw, insert_with, migrate, SeaOrmAwaExt}; use sea_orm::{ConnectionTrait, DatabaseConnection, TransactionTrait}; @@ -35,9 +36,9 @@ async fn setup_database() -> (sqlx::PgPool, DatabaseConnection) { async fn create_app_table(pool: &sqlx::PgPool, table_name: &str) { let table_name = quoted_identifier(table_name); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE TABLE IF NOT EXISTS {table_name} (id TEXT PRIMARY KEY, note TEXT NOT NULL)" - )) + ))) .execute(pool) .await .expect("create app table"); @@ -45,7 +46,7 @@ async fn create_app_table(pool: &sqlx::PgPool, table_name: &str) { async fn drop_app_table(pool: &sqlx::PgPool, table_name: &str) { let table_name = quoted_identifier(table_name); - sqlx::query(&format!("DROP TABLE IF EXISTS {table_name}")) + sqlx::query(audited_sql(format!("DROP TABLE IF EXISTS {table_name}"))) .execute(pool) .await .expect("drop app table"); @@ -125,9 +126,9 @@ async fn enqueue_commits_atomically_with_app_writes() { txn.commit().await.expect("commit transaction"); - let app_count: i64 = sqlx::query_scalar(&format!( + let app_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {table_name} WHERE id = 'commit-app-row'" - )) + ))) .fetch_one(&pool) .await .expect("count committed app row"); @@ -170,9 +171,9 @@ async fn enqueue_rolls_back_with_app_writes() { txn.rollback().await.expect("rollback transaction"); - let app_count: i64 = sqlx::query_scalar(&format!( + let app_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {table_name} WHERE id = 'rollback-app-row'" - )) + ))) .fetch_one(&pool) .await .expect("count rolled-back app row"); diff --git a/awa-testing/src/lib.rs b/awa-testing/src/lib.rs index c17f75b2..a463b477 100644 --- a/awa-testing/src/lib.rs +++ b/awa-testing/src/lib.rs @@ -4,6 +4,7 @@ pub mod setup; +use awa_model::audited_sql; use awa_model::{AwaError, JobArgs, JobRow}; use awa_worker::context::ProgressState; use awa_worker::{JobContext, JobError, JobResult, Worker}; @@ -262,9 +263,9 @@ impl TestClient { let target_queue = match queue { Some(queue) => queue.to_string(), None => { - let resolved: Option = sqlx::query_scalar(&format!( + let resolved: Option = sqlx::query_scalar(audited_sql(format!( "SELECT queue FROM {schema}.ready_entries WHERE kind = $1 ORDER BY job_id ASC LIMIT 1" - )) + ))) .bind(worker.kind()) .fetch_optional(&self.pool) .await?; diff --git a/awa-testing/src/setup.rs b/awa-testing/src/setup.rs index c91be4e6..a69625bc 100644 --- a/awa-testing/src/setup.rs +++ b/awa-testing/src/setup.rs @@ -1,5 +1,6 @@ //! Common test setup utilities for Awa integration tests. +use awa_model::audited_sql; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; use std::collections::HashMap; @@ -214,14 +215,14 @@ pub async fn clean_queue(pool: &PgPool, queue: &str) { /// leaves the rest of the lane (and its `job_unique_claims`) behind. async fn clean_queue_substrate(pool: &PgPool, schema: &str, queue: &str) { // Release unique claims first, while the rows that carry the job ids exist. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM awa.job_unique_claims WHERE job_id IN ( SELECT job_id FROM {schema}.ready_entries WHERE queue = $1 UNION SELECT job_id FROM {schema}.deferred_jobs WHERE queue = $1 UNION SELECT job_id FROM {schema}.leases WHERE queue = $1 UNION SELECT job_id FROM {schema}.done_entries WHERE queue = $1 UNION SELECT job_id FROM {schema}.dlq_entries WHERE queue = $1)" - )) + ))) .bind(queue) .execute(pool) .await @@ -234,11 +235,13 @@ async fn clean_queue_substrate(pool: &PgPool, schema: &str, queue: &str) { "done_entries", "dlq_entries", ] { - sqlx::query(&format!("DELETE FROM {schema}.{plane} WHERE queue = $1")) - .bind(queue) - .execute(pool) - .await - .unwrap_or_else(|err| panic!("Failed to clean {plane} for queue {queue}: {err}")); + sqlx::query(audited_sql(format!( + "DELETE FROM {schema}.{plane} WHERE queue = $1" + ))) + .bind(queue) + .execute(pool) + .await + .unwrap_or_else(|err| panic!("Failed to clean {plane} for queue {queue}: {err}")); } } diff --git a/awa-worker/src/client.rs b/awa-worker/src/client.rs index 4c93f5ea..a56896b4 100644 --- a/awa-worker/src/client.rs +++ b/awa-worker/src/client.rs @@ -14,6 +14,7 @@ use awa_model::admin::{ QueueRuntimeConfigSnapshot, QueueRuntimeMode, QueueRuntimeSnapshot, RateLimitSnapshot, RuntimeSnapshotInput, StorageCapability, TransitionRole, }; +use awa_model::audited_sql; use awa_model::{ storage as transition, JobArgs, PartitionedQueue, PeriodicJob, QueueStorageConfig, }; @@ -2167,7 +2168,7 @@ impl Client { let leader = self.leader.load(Ordering::SeqCst); let effective_storage = self.effective_storage.read().await.clone(); let available_rows = if let Some(store) = effective_storage.queue_storage_store() { - sqlx::query_as::<_, (String, i64)>(&format!( + sqlx::query_as::<_, (String, i64)>(audited_sql(format!( r#" SELECT enqueues.queue, @@ -2190,7 +2191,7 @@ impl Client { store.schema(), store.schema(), store.schema() - )) + ))) .fetch_all(&self.pool) .await .unwrap_or_default() @@ -2626,7 +2627,10 @@ mod tests { .await .expect("Failed to connect to admin database for client tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql)) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("23505") => {} @@ -2654,13 +2658,13 @@ mod tests { async fn apply_migrations_through(pool: &PgPool, version: i32) { for (_version, _desc, sql) in migrations::migration_sql_range(0, version) { - sqlx::raw_sql(&sql).execute(pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql)).execute(pool).await.unwrap(); } } async fn drop_queue_storage_schema(pool: &PgPool, schema: &str) { let sql = format!("DROP SCHEMA IF EXISTS {schema} CASCADE"); - sqlx::query(&sql) + sqlx::query(audited_sql(sql)) .execute(pool) .await .expect("Failed to drop queue storage schema"); @@ -2871,7 +2875,7 @@ mod tests { ); let start = Instant::now(); loop { - let done: bool = sqlx::query_scalar(&sql) + let done: bool = sqlx::query_scalar(audited_sql(sql.clone())) .bind(job_id) .fetch_one(pool) .await @@ -3263,7 +3267,7 @@ mod tests { for (_version, _desc, sql) in migrations::migration_sql_range(9, migrations::CURRENT_VERSION) { - sqlx::raw_sql(&sql) + sqlx::raw_sql(audited_sql(sql)) .execute(&pool) .await .expect("raw migration application should succeed mid-drain"); diff --git a/awa-worker/src/completion.rs b/awa-worker/src/completion.rs index 87d3646f..0a654e10 100644 --- a/awa-worker/src/completion.rs +++ b/awa-worker/src/completion.rs @@ -422,7 +422,10 @@ mod tests { .await .expect("Failed to connect to admin database for completion tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(awa_model::audited_sql(create_sql)) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("23505") => {} diff --git a/awa-worker/src/maintenance.rs b/awa-worker/src/maintenance.rs index c3542b66..2566a7d7 100644 --- a/awa-worker/src/maintenance.rs +++ b/awa-worker/src/maintenance.rs @@ -1,6 +1,7 @@ use crate::executor::DlqPolicy; use crate::runtime::InFlightMap; use crate::storage::{QueueStorageRuntime, RuntimeStorage}; +use awa_model::audited_sql; use awa_model::cron::{ atomic_enqueue, list_cron_jobs, upsert_cron_job, CronJobRow, CronMissedFirePolicy, }; @@ -2087,7 +2088,7 @@ impl MaintenanceService { rescue_kind: &'static str, ) -> Result, awa_model::AwaError> { let ids: Vec = { - let query = sqlx::query_scalar(candidates_sql); + let query = sqlx::query_scalar(audited_sql(candidates_sql)); let query = match staleness_ms { Some(ms) => query.bind(ms), None => query, @@ -2101,7 +2102,7 @@ impl MaintenanceService { let mut rescued = Vec::new(); for id in ids { let attempt = { - let query = sqlx::query_as::<_, JobRow>(per_row_sql).bind(id); + let query = sqlx::query_as::<_, JobRow>(audited_sql(per_row_sql)).bind(id); let query = match staleness_ms { Some(ms) => query.bind(ms), None => query, @@ -2589,7 +2590,7 @@ impl MaintenanceService { let mut tx = self.pool.begin().await?; let promote_start = std::time::Instant::now(); let sql = Self::promote_sql(state); - let promoted_rows: Vec<(String,)> = sqlx::query_as(&sql) + let promoted_rows: Vec<(String,)> = sqlx::query_as(audited_sql(sql)) .bind(PROMOTE_BATCH_SIZE) .fetch_all(&mut *tx) .await?; @@ -3039,7 +3040,7 @@ impl MaintenanceService { let retention_secs = i64::try_from(self.dlq_retention.as_secs()).unwrap_or(i64::MAX); let global_result = if override_queues.is_empty() { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE job_id IN ( @@ -3048,13 +3049,13 @@ impl MaintenanceService { LIMIT $2 ) "# - )) + ))) .bind(retention_secs) .bind(self.dlq_cleanup_batch_size) .execute(&self.pool) .await } else { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE job_id IN ( @@ -3064,7 +3065,7 @@ impl MaintenanceService { LIMIT $2 ) "# - )) + ))) .bind(retention_secs) .bind(self.dlq_cleanup_batch_size) .bind(&override_queues) @@ -3087,7 +3088,7 @@ impl MaintenanceService { continue; }; let retention_secs = i64::try_from(retention.as_secs()).unwrap_or(i64::MAX); - match sqlx::query(&format!( + match sqlx::query(audited_sql(format!( r#" DELETE FROM {schema}.dlq_entries WHERE job_id IN ( @@ -3097,7 +3098,7 @@ impl MaintenanceService { LIMIT $2 ) "# - )) + ))) .bind(retention_secs) .bind(self.dlq_cleanup_batch_size) .bind(queue) @@ -3405,7 +3406,7 @@ impl MaintenanceService { // accepted approximation for the cheap gauge. Surfaces that need the // exact running count (which DOES expand lease_claim_batches) go // through QueueStorage::queue_counts_exact() / admin::state_counts(). - let rows: Vec = match sqlx::query_as(&format!( + let rows: Vec = match sqlx::query_as(audited_sql(format!( r#" WITH head_signal AS ( SELECT @@ -3543,7 +3544,7 @@ impl MaintenanceService { ON dlq.queue = queues.queue ORDER BY queues.queue "# - )) + ))) .fetch_all(&self.pool) .await { @@ -3732,7 +3733,10 @@ mod tests { .await .expect("Failed to connect to admin database for maintenance tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql)) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => panic!("Failed to create maintenance test database {database_name}: {err}"), diff --git a/awa/src/lib.rs b/awa/src/lib.rs index 51d6b407..86c3f9af 100644 --- a/awa/src/lib.rs +++ b/awa/src/lib.rs @@ -11,7 +11,7 @@ pub use awa_model; // Re-export core model types (includes JobArgs derive macro via awa-model) pub use awa_model::{ - self as model, adapter, admin, bridge, callback_contract, insert, insert_many, + self as model, adapter, admin, audited_sql, bridge, callback_contract, insert, insert_many, insert_many_copy, insert_many_copy_from_pool, insert_with, map_sqlx_error, migrations, partition_for_ordering_key, partition_hash64, prepare_job_insert, prepare_raw_job_insert, storage, AwaError, CallbackConfig, DefaultAction, DlqMetadata, DlqRow, InsertOpts, diff --git a/awa/tests/benchmark_test.rs b/awa/tests/benchmark_test.rs index 9e30efa9..5cc4c28c 100644 --- a/awa/tests/benchmark_test.rs +++ b/awa/tests/benchmark_test.rs @@ -9,6 +9,7 @@ mod bench_output; +use awa::audited_sql; use awa::model::{ insert_many, insert_many_copy_from_pool, migrations, QueueStorage, QueueStorageConfig, }; @@ -51,7 +52,7 @@ async fn ensure_pgstattuple(pool: &sqlx::PgPool) { async fn recreate_queue_storage_schema(pool: &sqlx::PgPool, store: &QueueStorage) { let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(pool) .await .expect("Failed to drop queue storage benchmark schema"); diff --git a/awa/tests/chaos_suite_test.rs b/awa/tests/chaos_suite_test.rs index 7d5a6a1c..bf785b0f 100644 --- a/awa/tests/chaos_suite_test.rs +++ b/awa/tests/chaos_suite_test.rs @@ -4,6 +4,7 @@ //! nightly/manual chaos lane. use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert_with, migrations, InsertOpts, QueueStorageConfig}; use awa::{Client, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; use chrono::{Duration as ChronoDuration, Utc}; @@ -217,7 +218,7 @@ async fn queue_state_counts(pool: &sqlx::PgPool, queue: &str) -> HashMap = sqlx::query_as(&sql) + let rows: Vec<(String, i64)> = sqlx::query_as(audited_sql(sql.clone())) .bind(queue) .fetch_all(pool) .await @@ -344,7 +345,7 @@ async fn kind_state_count(pool: &sqlx::PgPool, queue: &str, kind: &str, state: & ) AS running_jobs "#, ); - return sqlx::query_scalar(&sql) + return sqlx::query_scalar(audited_sql(sql.clone())) .bind(queue) .bind(kind) .fetch_one(pool) @@ -449,7 +450,7 @@ async fn backdate_compact_claims( ON CONFLICT (job_id, run_lease) DO NOTHING "#, ); - sqlx::query(&shield_sql) + sqlx::query(audited_sql(shield_sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -478,7 +479,7 @@ async fn backdate_compact_claims( updated_at = EXCLUDED.updated_at "#, ); - let member_rows = sqlx::query(&attempt_sql) + let member_rows = sqlx::query(audited_sql(attempt_sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -504,7 +505,7 @@ async fn backdate_compact_claims( )) "#, ); - sqlx::query(&batch_sql) + sqlx::query(audited_sql(batch_sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -558,7 +559,7 @@ async fn backdate_running_kind(pool: &sqlx::PgPool, queue: &str, kind: &str) -> ) "#, ); - let receipt_rows = sqlx::query(&receipt_sql) + let receipt_rows = sqlx::query(audited_sql(receipt_sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -587,7 +588,7 @@ async fn backdate_running_kind(pool: &sqlx::PgPool, queue: &str, kind: &str) -> AND leases.state = 'running' "#, ); - let lease_rows = sqlx::query(&lease_sql) + let lease_rows = sqlx::query(audited_sql(lease_sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -651,7 +652,7 @@ async fn backdate_running_jobs(pool: &sqlx::PgPool, queue: &str) -> u64 { ) "#, ); - let receipt_rows = sqlx::query(&receipt_sql) + let receipt_rows = sqlx::query(audited_sql(receipt_sql.clone())) .bind(queue) .execute(pool) .await @@ -670,7 +671,7 @@ async fn backdate_running_jobs(pool: &sqlx::PgPool, queue: &str) -> u64 { AND state = 'running' "#, ); - let lease_rows = sqlx::query(&lease_sql) + let lease_rows = sqlx::query(audited_sql(lease_sql.clone())) .bind(queue) .execute(pool) .await @@ -707,7 +708,7 @@ async fn backdate_retryable_kind(pool: &sqlx::PgPool, queue: &str, kind: &str) - AND state = 'retryable' "#, ); - return sqlx::query(&sql) + return sqlx::query(audited_sql(sql.clone())) .bind(queue) .bind(kind) .execute(pool) @@ -743,7 +744,7 @@ async fn backdate_callback_timeouts(pool: &sqlx::PgPool, queue: &str) -> u64 { AND state = 'waiting_external' "#, ); - return sqlx::query(&sql) + return sqlx::query(audited_sql(sql.clone())) .bind(queue) .execute(pool) .await diff --git a/awa/tests/lifecycle_hook_test.rs b/awa/tests/lifecycle_hook_test.rs index 3eb72d9f..5298b843 100644 --- a/awa/tests/lifecycle_hook_test.rs +++ b/awa/tests/lifecycle_hook_test.rs @@ -2,6 +2,7 @@ //! //! Set DATABASE_URL=postgres://postgres:test@localhost:15432/awa_test +use awa::audited_sql; use awa::model::queue_storage::{QueueStorage, QueueStorageConfig}; use awa::model::{admin, migrations}; use awa::{ @@ -79,11 +80,11 @@ async fn active_queue_storage_schema(pool: &sqlx::PgPool) -> Option { async fn backdate_running_heartbeat(pool: &sqlx::PgPool, job_id: i64) { if let Some(schema) = active_queue_storage_schema(pool).await { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.leases \ SET heartbeat_at = now() - interval '5 minutes' \ WHERE job_id = $1 AND state = 'running'" - )) + ))) .bind(job_id) .execute(pool) .await diff --git a/awa/tests/migration_test.rs b/awa/tests/migration_test.rs index 3ca01a48..394bb5b9 100644 --- a/awa/tests/migration_test.rs +++ b/awa/tests/migration_test.rs @@ -7,6 +7,7 @@ //! //! Set DATABASE_URL=postgres://postgres:test@localhost:15432/awa_test +use awa::audited_sql; use awa::model::{insert_many, insert_many_copy_from_pool, migrations, storage, QueueStorage}; use awa::{InsertOpts, InsertParams, JobArgs, UniqueOpts}; use serde::{Deserialize, Serialize}; @@ -162,10 +163,12 @@ async fn simulate_non_canonical_compat_routing(pool: &PgPool) { } async fn install_queue_storage_backend(pool: &PgPool, schema: &str) { - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await - .expect("queue storage test schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await + .expect("queue storage test schema should drop cleanly"); let store = QueueStorage::from_existing_schema(schema).expect("queue storage schema should validate"); @@ -176,10 +179,12 @@ async fn install_queue_storage_backend(pool: &PgPool, schema: &str) { } async fn prepare_queue_storage_schema(pool: &PgPool, schema: &str) { - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await - .expect("queue storage test schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await + .expect("queue storage test schema should drop cleanly"); let store = QueueStorage::from_existing_schema(schema).expect("queue storage schema should validate"); @@ -206,13 +211,13 @@ fn assert_safe_generated_role_name(role: &str) { async fn create_login_role(pool: &PgPool, role: &str) { assert_safe_generated_role_name(role); - sqlx::query(&format!("DROP ROLE IF EXISTS {role}")) + sqlx::query(audited_sql(format!("DROP ROLE IF EXISTS {role}"))) .execute(pool) .await .expect("test role should be dropped before create"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "CREATE ROLE {role} LOGIN PASSWORD 'awa_test_password'" - )) + ))) .execute(pool) .await .expect("test role should be created"); @@ -220,7 +225,7 @@ async fn create_login_role(pool: &PgPool, role: &str) { async fn drop_login_role(pool: &PgPool, role: &str) { assert_safe_generated_role_name(role); - let _ = sqlx::query(&format!("DROP ROLE IF EXISTS {role}")) + let _ = sqlx::query(audited_sql(format!("DROP ROLE IF EXISTS {role}"))) .execute(pool) .await; } @@ -232,7 +237,7 @@ async fn grant_runtime_privileges(pool: &PgPool, role: &str, include_truncate: b } else { "SELECT, INSERT, UPDATE, DELETE" }; - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( r#" GRANT CONNECT ON DATABASE awa_migration_test TO {role}; GRANT USAGE ON SCHEMA awa TO {role}; @@ -241,7 +246,7 @@ async fn grant_runtime_privileges(pool: &PgPool, role: &str, include_truncate: b GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA awa TO {role}; REVOKE EXECUTE ON FUNCTION awa.install_queue_storage_substrate(TEXT, INT, INT, INT, BOOLEAN) FROM {role}; "# - )) + ))) .execute(pool) .await .expect("runtime grants should apply"); @@ -479,7 +484,7 @@ async fn test_full_migration_replay_converges_to_identical_schema() { let mut conn = pool.acquire().await.expect("acquire"); let mut tx = conn.begin().await.expect("begin replay"); for (version, _, sql) in migrations::migration_sql() { - sqlx::raw_sql(&sql) + sqlx::raw_sql(audited_sql(sql.clone())) .execute(&mut *tx) .await .unwrap_or_else(|err| panic!("replay of migration v{version} failed: {err}")); @@ -512,11 +517,11 @@ async fn test_every_migration_is_individually_re_runnable() { let mut conn = pool.acquire().await.expect("acquire"); for (version, _, sql) in migrations::migration_sql() { - sqlx::raw_sql(&sql) + sqlx::raw_sql(audited_sql(sql.clone())) .execute(&mut *conn) .await .unwrap_or_else(|err| panic!("first apply of migration v{version} failed: {err}")); - sqlx::raw_sql(&sql) + sqlx::raw_sql(audited_sql(sql.clone())) .execute(&mut *conn) .await .unwrap_or_else(|err| { @@ -564,7 +569,7 @@ async fn test_every_migration_is_individually_re_runnable() { /// Event triggers need superuser, which the migration test database already /// requires (other tests here create and drop login roles). async fn arm_migration_abort(pool: &PgPool, identity_pattern: &str) { - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( "CREATE OR REPLACE FUNCTION public.awa_test_abort_migration() \ RETURNS event_trigger LANGUAGE plpgsql AS $fn$ \ BEGIN \ @@ -576,7 +581,7 @@ async fn arm_migration_abort(pool: &PgPool, identity_pattern: &str) { DROP EVENT TRIGGER IF EXISTS awa_test_abort_migration; \ CREATE EVENT TRIGGER awa_test_abort_migration ON ddl_command_end \ EXECUTE FUNCTION public.awa_test_abort_migration();" - )) + ))) .execute(pool) .await .expect("arming the migration abort event trigger requires superuser"); @@ -951,7 +956,10 @@ async fn test_step_through_upgrade_preserves_data() { let v1_sql = migrations::migration_sql(); let (v1_version, _, v1_up) = &v1_sql[0]; assert_eq!(*v1_version, 1); - sqlx::raw_sql(v1_up).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(v1_up.clone())) + .execute(&pool) + .await + .unwrap(); let version = migrations::current_version(&pool).await.unwrap(); assert_eq!(version, 1); @@ -986,7 +994,10 @@ async fn test_step_through_upgrade_preserves_data() { // Step 3: apply the remaining migrations the way a 0.6-line binary // would (raw migration SQL) and verify the seeded data survives. for (_version, _desc, sql) in migrations::migration_sql_range(1, migrations::CURRENT_VERSION) { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } let version = migrations::current_version(&pool).await.unwrap(); @@ -1098,7 +1109,10 @@ async fn test_migration_sql_matches_run() { reset_schema(&pool).await; for (_version, _desc, sql) in migrations::migration_sql() { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } let tables_from_sql: Vec = sqlx::query_scalar( @@ -1123,7 +1137,10 @@ async fn test_v023_migrates_legacy_default_queue_storage_tables() { reset_schema(&pool).await; for (_version, _desc, sql) in migrations::migration_sql_range(0, 22) { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } sqlx::raw_sql( @@ -1245,7 +1262,10 @@ async fn test_v027_rebuckets_existing_terminal_live_counts() { reset_schema(&pool).await; for (_version, _desc, sql) in migrations::migration_sql_range(0, 26) { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } sqlx::raw_sql( @@ -1319,7 +1339,10 @@ async fn test_legacy_version_upgrade() { reset_schema(&pool).await; let v1_sql = &migrations::migration_sql()[0].2; - sqlx::raw_sql(v1_sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(v1_sql.clone())) + .execute(&pool) + .await + .unwrap(); sqlx::raw_sql( r#" @@ -1333,8 +1356,14 @@ async fn test_legacy_version_upgrade() { let v2_sql = &migrations::migration_sql()[1].2; let v3_sql = &migrations::migration_sql()[2].2; - sqlx::raw_sql(v2_sql).execute(&pool).await.unwrap(); - sqlx::raw_sql(v3_sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(v2_sql.clone())) + .execute(&pool) + .await + .unwrap(); + sqlx::raw_sql(audited_sql(v3_sql.clone())) + .execute(&pool) + .await + .unwrap(); sqlx::raw_sql( r#" @@ -1392,7 +1421,10 @@ async fn test_migration_sql_range_produces_valid_schema() { // Apply only V1+V2 via range, then verify V2 artifacts exist but V3+ don't. for (_version, _desc, sql) in migrations::migration_sql_range(0, 2) { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } let has_runtime: bool = sqlx::query_scalar( @@ -1413,7 +1445,10 @@ async fn test_migration_sql_range_produces_valid_schema() { // Now apply V3+V4 via range and verify. for (_version, _desc, sql) in migrations::migration_sql_range(2, migrations::CURRENT_VERSION) { - sqlx::raw_sql(&sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(sql.clone())) + .execute(&pool) + .await + .unwrap(); } let has_maintenance: bool = sqlx::query_scalar( @@ -1623,10 +1658,12 @@ async fn test_v031_backfills_queue_storage_failed_done_metric_index() { prepare_queue_storage_schema(&pool, schema).await; let index_name = format!("idx_{schema}_done_0_failed_queue"); - sqlx::query(&format!("DROP INDEX IF EXISTS {schema}.{index_name}")) - .execute(&pool) - .await - .expect("failed done_entries test index should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP INDEX IF EXISTS {schema}.{index_name}" + ))) + .execute(&pool) + .await + .expect("failed done_entries test index should drop cleanly"); sqlx::raw_sql( r#" @@ -1661,9 +1698,9 @@ async fn test_v031_backfills_queue_storage_failed_done_metric_index() { .await .expect("v031 should rerun cleanly"); - let has_done_failed_index: bool = sqlx::query_scalar(&format!( + let has_done_failed_index: bool = sqlx::query_scalar(audited_sql(format!( "SELECT to_regclass('{schema}.{index_name}') IS NOT NULL" - )) + ))) .fetch_one(&pool) .await .expect("failed done_entries index probe should succeed"); @@ -1695,14 +1732,14 @@ async fn test_v042_refreshes_compact_deadline_cursors_and_index() { prepare_queue_storage_schema(&pool, schema).await; // Strip the v042 additions to mimic a schema installed before #246. - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( r#" ALTER TABLE {schema}.claim_ring_slots DROP COLUMN IF EXISTS batch_deadline_cursor_deadline_at, DROP COLUMN IF EXISTS batch_deadline_cursor_batch_id; DROP INDEX IF EXISTS {schema}.idx_{schema}_lease_claim_batches_0_deadline_cursor; "# - )) + ))) .execute(&pool) .await .expect("stripping v042 additions should succeed"); @@ -1751,13 +1788,13 @@ async fn test_v042_refreshes_compact_deadline_cursors_and_index() { // just child 0 — enumerate the installed children and assert the // partial sweep index (deadline_at IS NOT NULL, so zero-deadline // traffic never maintains entries) exists on each. - let child_slots: Vec = sqlx::query_scalar(&format!( + let child_slots: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT substring(tablename FROM 'lease_claim_batches_(\\d+)$') FROM pg_tables WHERE schemaname = '{schema}' AND tablename ~ '^lease_claim_batches_\\d+$' ORDER BY 1" - )) + ))) .fetch_all(&pool) .await .expect("enumerate lease_claim_batches children"); @@ -1769,9 +1806,9 @@ async fn test_v042_refreshes_compact_deadline_cursors_and_index() { let index_name = format!("idx_{schema}_lease_claim_batches_{slot}_deadline_cursor"); // Resolve by OID (relnames are truncated to 63 chars) and read the // definition in one probe; NULL means the index is missing. - let index_def: Option = sqlx::query_scalar(&format!( + let index_def: Option = sqlx::query_scalar(audited_sql(format!( "SELECT pg_get_indexdef(to_regclass('{schema}.{index_name}')::oid)" - )) + ))) .fetch_one(&pool) .await .expect("index definition probe should succeed"); @@ -1794,10 +1831,12 @@ async fn test_v042_refreshes_compact_deadline_cursors_and_index() { // ledger loop now running alongside the v042 compact-deadline loop, a // leaked probe schema multiplies the single-tx lock footprint of the // reinstall and can trip max_locks_per_transaction under concurrency. - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("compact-deadline probe schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("compact-deadline probe schema should drop cleanly"); } async fn rollups_failed_column_exists(pool: &PgPool, schema: &str) -> bool { @@ -1830,9 +1869,9 @@ async fn test_v032_backfills_queue_storage_pruned_failed_rollup_column() { prepare_queue_storage_schema(&pool, schema).await; // Simulate a substrate prepared by a pre-v032 binary. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.queue_terminal_rollups DROP COLUMN pruned_failed_count" - )) + ))) .execute(&pool) .await .expect("pruned_failed_count test column should drop cleanly"); @@ -1918,10 +1957,12 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { // v043 discovery loop reinstalls every existing queue-storage schema, // and reinstalling a stale copy here only wastes a large single-tx // lock footprint (and can trip max_locks_per_transaction). - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("leftover ledger probe schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("leftover ledger probe schema should drop cleanly"); migrations::run(&pool).await.unwrap(); @@ -1933,7 +1974,7 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { // cursor columns on each singleton. Seed the queue cursor at a // non-genesis position (current_slot = 3, generation = 19) so the ledger // seed is observable. - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( r#" DROP TABLE {schema}.queue_ring_rotations; DROP TABLE {schema}.lease_ring_rotations; @@ -1962,7 +2003,7 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { UPDATE {schema}.queue_ring_state SET current_slot = 3, generation = 19 WHERE singleton; "# - )) + ))) .execute(&pool) .await .expect("downgrade to pre-v043 substrate shape should succeed"); @@ -1992,9 +2033,9 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { ); // Authority defaults to 'columns' on upgrade. - let authority: String = sqlx::query_scalar(&format!( + let authority: String = sqlx::query_scalar(audited_sql(format!( "SELECT authority FROM {schema}.ring_cursor_authority WHERE singleton" - )) + ))) .fetch_one(&pool) .await .expect("authority row should exist after upgrade"); @@ -2005,10 +2046,10 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { // The queue ledger was seeded from the legacy cursor exactly: the // current row carries (generation = 19, slot = 3). - let (ledger_slot, ledger_gen): (i32, i64) = sqlx::query_as(&format!( + let (ledger_slot, ledger_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.queue_ring_rotations \ ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("queue ledger cursor should be readable after upgrade"); @@ -2019,9 +2060,9 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { ); // The authority resolver returns the compat columns (== ledger here). - let (cursor_slot, cursor_gen): (i32, i64) = sqlx::query_as(&format!( + let (cursor_slot, cursor_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.ring_cursor('queue')" - )) + ))) .fetch_one(&pool) .await .expect("ring_cursor('queue') should resolve after upgrade"); @@ -2040,10 +2081,10 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { ); // A claim after upgrade routes off the resolved cursor without error. - let claimed: i64 = sqlx::query_scalar(&format!( + let claimed: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.claim_ready_runtime(\ 'v043_upgrade_q'::text, 8::bigint, 0::double precision, 0::double precision)" - )) + ))) .fetch_one(&pool) .await .expect("claim_ready_runtime must route off the resolved cursor after upgrade"); @@ -2053,7 +2094,7 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { // Simulate the unreleased shipped-v043 shape: drop the compat columns, // leaving only the ledger. Rerun v043; it must re-ADD the columns seeded // FROM the ledger max (3, 19) and keep authority 'columns'. - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( r#" DROP TRIGGER reject_compat_ring_cursor_update_after_flip ON {schema}.queue_ring_state; DROP TRIGGER reject_compat_ring_cursor_update_after_flip ON {schema}.lease_ring_state; @@ -2062,7 +2103,7 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { ALTER TABLE {schema}.lease_ring_state DROP COLUMN current_slot, DROP COLUMN generation; ALTER TABLE {schema}.claim_ring_state DROP COLUMN current_slot, DROP COLUMN generation; "# - )) + ))) .execute(&pool) .await .expect("dev-shape column drop should succeed"); @@ -2074,9 +2115,9 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { .await .expect("v043 should rerun cleanly against the dev shape"); - let (restored_slot, restored_gen): (i32, i64) = sqlx::query_as(&format!( + let (restored_slot, restored_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT current_slot, generation FROM {schema}.queue_ring_state WHERE singleton" - )) + ))) .fetch_one(&pool) .await .expect("columns should be restored from the ledger"); @@ -2091,10 +2132,12 @@ async fn test_v043_expand_only_restores_compat_columns_and_seeds_ledger() { // Clean up so a later `migrations::run` (this or another test) does not // reinstall this schema through the v043 discovery loop. - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("ledger probe schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("ledger probe schema should drop cleanly"); } /// #371 v043 staged upgrade: a FRESH queue-storage install starts directly @@ -2108,19 +2151,21 @@ async fn test_v043_fresh_install_starts_in_ledger_authority() { reset_schema(&pool).await; let schema = "awa_queue_storage_v043_fresh"; - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("leftover fresh probe schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("leftover fresh probe schema should drop cleanly"); migrations::run(&pool).await.unwrap(); // Fresh install of a custom schema (never existed before). prepare_queue_storage_schema(&pool, schema).await; - let authority: String = sqlx::query_scalar(&format!( + let authority: String = sqlx::query_scalar(audited_sql(format!( "SELECT authority FROM {schema}.ring_cursor_authority WHERE singleton" - )) + ))) .fetch_one(&pool) .await .expect("authority row should exist on a fresh install"); @@ -2130,18 +2175,20 @@ async fn test_v043_fresh_install_starts_in_ledger_authority() { ); // ring_cursor resolves the genesis ledger cursor (0, 0). - let (slot, generation): (i32, i64) = sqlx::query_as(&format!( + let (slot, generation): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.ring_cursor('queue')" - )) + ))) .fetch_one(&pool) .await .expect("ring_cursor('queue') should resolve on a fresh install"); assert_eq!((slot, generation), (0, 0), "fresh genesis cursor is (0, 0)"); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("fresh probe schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("fresh probe schema should drop cleanly"); } #[tokio::test] @@ -2162,10 +2209,12 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() "freshly prepared queue-storage schema should be ready" ); - sqlx::query(&format!("DROP SEQUENCE {schema}.job_id_seq CASCADE")) - .execute(&pool) - .await - .expect("test sequence drop should succeed"); + sqlx::query(audited_sql(format!( + "DROP SEQUENCE {schema}.job_id_seq CASCADE" + ))) + .execute(&pool) + .await + .expect("test sequence drop should succeed"); assert!( !storage::queue_storage_schema_ready(&pool, schema) @@ -2175,9 +2224,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP SEQUENCE {schema}.lease_claim_receipt_id_seq CASCADE" - )) + ))) .execute(&pool) .await .expect("test receipt sequence drop should succeed"); @@ -2190,9 +2239,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP SEQUENCE {schema}.lease_claim_batch_id_seq CASCADE" - )) + ))) .execute(&pool) .await .expect("test compact claim batch sequence drop should succeed"); @@ -2205,10 +2254,12 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!("DROP TABLE {schema}.ready_tombstones CASCADE")) - .execute(&pool) - .await - .expect("test ready_tombstones drop should succeed"); + sqlx::query(audited_sql(format!( + "DROP TABLE {schema}.ready_tombstones CASCADE" + ))) + .execute(&pool) + .await + .expect("test ready_tombstones drop should succeed"); assert!( !storage::queue_storage_schema_ready(&pool, schema) @@ -2218,9 +2269,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE {schema}.receipt_completion_batches CASCADE" - )) + ))) .execute(&pool) .await .expect("test receipt_completion_batches drop should succeed"); @@ -2233,10 +2284,12 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!("DROP TABLE {schema}.lease_claim_batches CASCADE")) - .execute(&pool) - .await - .expect("test lease_claim_batches drop should succeed"); + sqlx::query(audited_sql(format!( + "DROP TABLE {schema}.lease_claim_batches CASCADE" + ))) + .execute(&pool) + .await + .expect("test lease_claim_batches drop should succeed"); assert!( !storage::queue_storage_schema_ready(&pool, schema) @@ -2246,9 +2299,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE {schema}.receipt_completion_tombstones CASCADE" - )) + ))) .execute(&pool) .await .expect("test receipt_completion_tombstones drop should succeed"); @@ -2261,9 +2314,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP TABLE {schema}.queue_terminal_count_deltas CASCADE" - )) + ))) .execute(&pool) .await .expect("test queue_terminal_count_deltas drop should succeed"); @@ -2276,9 +2329,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "ALTER TABLE {schema}.lease_claim_closure_batches DROP COLUMN receipt_ranges" - )) + ))) .execute(&pool) .await .expect("test receipt_ranges drop should succeed"); @@ -2291,9 +2344,9 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP FUNCTION {schema}.claim_ready_runtime(text, bigint, double precision, double precision)" - )) + ))) .execute(&pool) .await .expect("test claim function drop should succeed"); @@ -2306,13 +2359,13 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() ); prepare_queue_storage_schema(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP FUNCTION {schema}.claim_ready_runtime(text, bigint, double precision, double precision)" - )) + ))) .execute(&pool) .await .expect("test claim function drop before stub should succeed"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" CREATE FUNCTION {schema}.claim_ready_runtime( p_queue TEXT, @@ -2326,7 +2379,7 @@ async fn test_queue_storage_schema_ready_requires_sequence_and_claim_function() SELECT NULL::bigint WHERE FALSE $$; "# - )) + ))) .execute(&pool) .await .expect("test stale claim function create should succeed"); @@ -2348,10 +2401,12 @@ async fn test_prepare_schema_preserves_trusted_terminal_counter_marker_on_curren migrations::run(&pool).await.unwrap(); let schema = "awa_queue_storage_trusted_marker"; - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("queue storage test schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("queue storage test schema should drop cleanly"); let store = QueueStorage::from_existing_schema(schema).expect("queue storage schema should validate"); store @@ -2359,7 +2414,7 @@ async fn test_prepare_schema_preserves_trusted_terminal_counter_marker_on_curren .await .expect("queue storage schema preparation should succeed"); - sqlx::raw_sql(&format!( + sqlx::raw_sql(audited_sql(format!( r#" INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, state, @@ -2381,7 +2436,7 @@ async fn test_prepare_schema_preserves_trusted_terminal_counter_marker_on_curren SET terminal_counter_trusted_at = now() WHERE singleton = TRUE; "# - )) + ))) .execute(&pool) .await .expect("seed current-shape terminal counters"); @@ -2391,10 +2446,10 @@ async fn test_prepare_schema_preserves_trusted_terminal_counter_marker_on_curren .await .expect("idempotent prepare_schema should succeed"); - let trusted: bool = sqlx::query_scalar(&format!( + let trusted: bool = sqlx::query_scalar(audited_sql(format!( "SELECT terminal_counter_trusted_at IS NOT NULL \ FROM {schema}.queue_ring_state WHERE singleton = TRUE" - )) + ))) .fetch_one(&pool) .await .expect("trust marker query should succeed"); @@ -2414,10 +2469,12 @@ async fn test_v030_preserves_untrusted_terminal_counter_marker_on_empty_schema() migrations::run(&pool).await.unwrap(); let schema = "awa_queue_storage_untrusted_marker"; - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("queue storage test schema should drop cleanly"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("queue storage test schema should drop cleanly"); let store = QueueStorage::from_existing_schema(schema).expect("queue storage schema should validate"); store @@ -2425,26 +2482,26 @@ async fn test_v030_preserves_untrusted_terminal_counter_marker_on_empty_schema() .await .expect("queue storage schema preparation should succeed"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_ring_state \ SET terminal_counter_trusted_at = NULL \ WHERE singleton = TRUE" - )) + ))) .execute(&pool) .await .expect("clear trust marker"); for (_version, _desc, sql) in migrations::migration_sql_range(29, 30) { - sqlx::raw_sql(&sql) + sqlx::raw_sql(audited_sql(sql.clone())) .execute(&pool) .await .expect("v030 migration should rerun cleanly"); } - let trusted: bool = sqlx::query_scalar(&format!( + let trusted: bool = sqlx::query_scalar(audited_sql(format!( "SELECT terminal_counter_trusted_at IS NOT NULL \ FROM {schema}.queue_ring_state WHERE singleton = TRUE" - )) + ))) .fetch_one(&pool) .await .expect("trust marker query should succeed"); @@ -3258,9 +3315,9 @@ async fn test_insert_job_compat_routes_under_active_queue_storage_engine() { assert_eq!(row.queue, "compat_refusal_queue"); assert_eq!(row.state, awa::JobState::Available); - let lane_seq: i64 = sqlx::query_scalar(&format!( + let lane_seq: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT lane_seq FROM {schema}.ready_entries WHERE job_id = $1" - )) + ))) .bind(row.id) .fetch_one(&pool) .await @@ -3270,9 +3327,9 @@ async fn test_insert_job_compat_routes_under_active_queue_storage_engine() { "insert_job_compat must reserve queue-storage lanes through the sequence allocator" ); - let ready_segments: i64 = sqlx::query_scalar(&format!( + let ready_segments: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_segments WHERE queue = $1" - )) + ))) .bind("compat_refusal_queue") .fetch_one(&pool) .await @@ -3657,7 +3714,10 @@ async fn test_legacy_v3_only_upgrade() { reset_schema(&pool).await; let v1_sql = &migrations::migration_sql()[0].2; - sqlx::raw_sql(v1_sql).execute(&pool).await.unwrap(); + sqlx::raw_sql(audited_sql(v1_sql.clone())) + .execute(&pool) + .await + .unwrap(); sqlx::raw_sql( r#" diff --git a/awa/tests/postgres_failover_smoke_test.rs b/awa/tests/postgres_failover_smoke_test.rs index 751779c5..89e9a930 100644 --- a/awa/tests/postgres_failover_smoke_test.rs +++ b/awa/tests/postgres_failover_smoke_test.rs @@ -4,6 +4,7 @@ //! Docker Compose and boots a primary/replica stack on demand. use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert_with, migrations, InsertOpts}; use awa::{Client, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; use chrono::{Duration as ChronoDuration, Utc}; @@ -397,7 +398,7 @@ async fn queue_state_counts(pool: &sqlx::PgPool, queue: &str) -> HashMap = sqlx::query_as(&sql) + let rows: Vec<(String, i64)> = sqlx::query_as(audited_sql(sql.clone())) .bind(queue) .fetch_all(pool) .await @@ -520,7 +521,7 @@ async fn storage_debug(pool: &sqlx::PgPool, queue: &str) -> String { ) AS rollups \ USING (queue, priority))" ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .bind(queue) .fetch_one(pool) .await @@ -539,11 +540,11 @@ async fn assert_unfolded_terminal_rollup_is_visible(pool: &sqlx::PgPool) { .await .expect("failover smoke should use queue storage"); let queue = format!("failover_rollup_probe_{}", Uuid::new_v4().simple()); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_terminal_rollup_deltas ( \ queue, priority, pruned_completed_delta, pruned_failed_delta \ ) VALUES ($1, 0, 3, 0)" - )) + ))) .bind(&queue) .execute(pool) .await diff --git a/awa/tests/progress_test.rs b/awa/tests/progress_test.rs index 9f4b830b..eb839c3a 100644 --- a/awa/tests/progress_test.rs +++ b/awa/tests/progress_test.rs @@ -2,6 +2,7 @@ //! //! Set DATABASE_URL=postgres://postgres:test@localhost:15432/awa_test +use awa::audited_sql; use awa::model::admin; use awa::{JobArgs, JobContext, JobError, JobResult, JobState, Worker}; use awa_testing::TestClient; @@ -88,19 +89,19 @@ async fn ensure_database_exists(url: &str) { let terminate_sql = format!( "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{database_name}' AND pid <> pg_backend_pid()" ); - sqlx::query(&terminate_sql) + sqlx::query(audited_sql(terminate_sql.clone())) .execute(&admin_pool) .await .expect("Failed to terminate existing progress test connections"); let drop_sql = format!("DROP DATABASE IF EXISTS {database_name}"); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(&admin_pool) .await .expect("Failed to drop progress test database"); let create_sql = format!("CREATE DATABASE {database_name}"); - sqlx::query(&create_sql) + sqlx::query(audited_sql(create_sql.clone())) .execute(&admin_pool) .await .expect("Failed to create progress test database"); diff --git a/awa/tests/queue_storage_benchmark_test.rs b/awa/tests/queue_storage_benchmark_test.rs index 861c0d24..3d529462 100644 --- a/awa/tests/queue_storage_benchmark_test.rs +++ b/awa/tests/queue_storage_benchmark_test.rs @@ -12,6 +12,7 @@ mod bench_output; +use awa::audited_sql; use awa::model::{ migrations, AwaError, InsertOpts, InsertParams, PruneOutcome, QueueStorage, QueueStorageConfig, RotateOutcome, @@ -89,7 +90,10 @@ async fn ensure_database_exists(url: &str) { .await .expect("Failed to connect to admin database for queue_storage benchmarks"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => { @@ -266,7 +270,7 @@ async fn capture_db_profile_delta( async fn recreate_store_schema(pool: &sqlx::PgPool, store: &QueueStorage) { let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(pool) .await .expect("Failed to drop experimental queue storage schema"); @@ -668,7 +672,7 @@ async fn overlap_reader( "history_snapshot" => { let query = format!("SELECT count(*)::bigint FROM {schema}.ready_entries WHERE queue = $1"); - let _: i64 = sqlx::query_scalar(&query) + let _: i64 = sqlx::query_scalar(audited_sql(query.clone())) .bind(&queue) .fetch_one(conn.as_mut()) .await @@ -693,7 +697,7 @@ async fn overlap_reader( SELECT available.current_available + pruned.terminal_rollup \ FROM available CROSS JOIN pruned" ); - let _: i64 = sqlx::query_scalar(&query) + let _: i64 = sqlx::query_scalar(audited_sql(query.clone())) .bind(&queue) .fetch_one(conn.as_mut()) .await @@ -1298,10 +1302,13 @@ async fn test_queue_storage_deep_backlog_drain_benchmark() { let seed_rate = inserted as f64 / seed_elapsed.as_secs_f64().max(0.001); if analyze_ready { - sqlx::query(&format!("ANALYZE {}.ready_entries", store.schema())) - .execute(&pool) - .await - .expect("Failed to analyze ready_entries after deep-backlog seed"); + sqlx::query(audited_sql(format!( + "ANALYZE {}.ready_entries", + store.schema() + ))) + .execute(&pool) + .await + .expect("Failed to analyze ready_entries after deep-backlog seed"); } println!( diff --git a/awa/tests/queue_storage_runtime_test.rs b/awa/tests/queue_storage_runtime_test.rs index 83309dcb..e90109dc 100644 --- a/awa/tests/queue_storage_runtime_test.rs +++ b/awa/tests/queue_storage_runtime_test.rs @@ -3,6 +3,7 @@ //! These tests exercise the full dispatcher/worker/maintenance wiring with the //! queue_storage backend enabled. +use awa::audited_sql; use awa::model::{ admin, batch_operations, insert, migrations, storage, AwaError, BatchOperationFilter, BatchOperationSpec, PruneOutcome, QueueStorage, QueueStorageConfig, RotateOutcome, SkipReason, @@ -167,13 +168,15 @@ async fn ensure_template_database() -> &'static str { .expect("Failed to list leftover queue_storage test databases"); for leftover in leftovers { validate_database_name(&leftover); - sqlx::raw_sql(&format!("DROP DATABASE IF EXISTS {leftover} WITH (FORCE)")) - .execute(&admin_pool) - .await - .expect("Failed to drop leftover queue_storage test database"); + sqlx::raw_sql(audited_sql(format!( + "DROP DATABASE IF EXISTS {leftover} WITH (FORCE)" + ))) + .execute(&admin_pool) + .await + .expect("Failed to drop leftover queue_storage test database"); } - sqlx::raw_sql(&format!("CREATE DATABASE {template_name}")) + sqlx::raw_sql(audited_sql(format!("CREATE DATABASE {template_name}"))) .execute(&admin_pool) .await .expect("Failed to create queue_storage template database"); @@ -219,7 +222,10 @@ async fn setup_pool(max_connections: u32) -> (TestDbGuard, sqlx::PgPool) { let create_sql = format!("CREATE DATABASE {db_name} TEMPLATE {template_name}"); let mut attempts = 0; loop { - match sqlx::raw_sql(&create_sql).execute(&admin_pool).await { + match sqlx::raw_sql(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => break, // 55006: "source database is being accessed by other users" — // another test is mid-copy from the same template. Retry. @@ -302,7 +308,7 @@ async fn test_queue_storage_prepare_schema_concurrent_startups_serialize() { async fn recreate_store_schema(pool: &sqlx::PgPool, store: &QueueStorage) { let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(pool) .await .expect("Failed to drop queue_storage schema"); @@ -505,7 +511,7 @@ async fn attempt_state_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { "SELECT count(*)::bigint FROM {}.attempt_state", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count attempt_state rows") @@ -513,7 +519,7 @@ async fn attempt_state_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { async fn lease_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { let sql = format!("SELECT count(*)::bigint FROM {}.leases", store.schema()); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count leases") @@ -524,7 +530,7 @@ async fn lease_claim_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { "SELECT count(*)::bigint FROM {}.lease_claims", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count lease_claims") @@ -535,7 +541,7 @@ async fn lease_claim_batch_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i "SELECT count(*)::bigint FROM {}.lease_claim_batches", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count lease_claim_batches") @@ -609,7 +615,7 @@ async fn open_receipt_claim_count(pool: &sqlx::PgPool, store: &QueueStorage) -> ) "#, ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count open receipt claims (derived)") @@ -617,7 +623,7 @@ async fn open_receipt_claim_count(pool: &sqlx::PgPool, store: &QueueStorage) -> async fn receipt_claim_slot_for_job(pool: &sqlx::PgPool, store: &QueueStorage, job_id: i64) -> i32 { let schema = store.schema(); - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" SELECT claim_slot FROM ( @@ -635,7 +641,7 @@ async fn receipt_claim_slot_for_job(pool: &sqlx::PgPool, store: &QueueStorage, j ORDER BY run_lease DESC LIMIT 1 "# - )) + ))) .bind(job_id) .fetch_one(pool) .await @@ -650,7 +656,7 @@ async fn receipt_claim_count_in_child( ) -> i64 { let claim_child = format!("{schema}.lease_claims_{claim_slot}"); let claim_batch_child = format!("{schema}.lease_claim_batches_{claim_slot}"); - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( r#" SELECT (SELECT count(*)::bigint FROM {claim_child} WHERE job_id = $1) @@ -662,7 +668,7 @@ async fn receipt_claim_count_in_child( WHERE items.job_id = $1 ), 0) "# - )) + ))) .bind(job_id) .fetch_one(pool) .await @@ -674,7 +680,7 @@ async fn lease_claim_closure_count(pool: &sqlx::PgPool, store: &QueueStorage) -> "SELECT count(*)::bigint FROM {}.lease_claim_closures", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count lease_claim_closures") @@ -685,7 +691,7 @@ async fn lease_claim_closure_batch_count(pool: &sqlx::PgPool, store: &QueueStora "SELECT count(*)::bigint FROM {}.lease_claim_closure_batches", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count lease_claim_closure_batches") @@ -696,7 +702,7 @@ async fn ready_tombstone_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 "SELECT count(*)::bigint FROM {}.ready_tombstones", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count ready_tombstones") @@ -707,7 +713,7 @@ async fn ready_segment_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { "SELECT count(*)::bigint FROM {}.ready_segments", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count ready_segments") @@ -715,7 +721,7 @@ async fn ready_segment_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { async fn tombstone_ready_job(pool: &sqlx::PgPool, store: &QueueStorage, job_id: i64) { let schema = store.schema(); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.ready_tombstones ( ready_slot, ready_generation, queue, priority, enqueue_shard, lane_seq, job_id @@ -725,7 +731,7 @@ async fn tombstone_ready_job(pool: &sqlx::PgPool, store: &QueueStorage, job_id: WHERE job_id = $1 ON CONFLICT DO NOTHING "# - )) + ))) .bind(job_id) .execute(pool) .await @@ -740,11 +746,11 @@ async fn claim_cursor_for( enqueue_shard: i16, ) -> i64 { let schema = store.schema(); - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT {schema}.sequence_next_value(seq_name) FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(priority) .bind(enqueue_shard) @@ -832,7 +838,7 @@ async fn enqueue_job( ) }; - sqlx::query_scalar::<_, i64>(&query) + sqlx::query_scalar::<_, i64>(audited_sql(query.clone())) .bind(&queue_names) .fetch_one(pool) .await @@ -900,7 +906,7 @@ async fn age_receipt_claim( age: Duration, ) { let age_millis = i64::try_from(age.as_millis()).expect("test receipt age fits in i64 millis"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" WITH aged_rows AS ( UPDATE {schema}.lease_claims @@ -921,7 +927,7 @@ async fn age_receipt_claim( SELECT (SELECT count(*) FROM aged_rows) + (SELECT count(*) FROM aged_batches) "#, schema = store.schema() - )) + ))) .bind(age_millis) .bind(job_id) .bind(run_lease) @@ -938,13 +944,13 @@ async fn age_attempt_heartbeat( age: Duration, ) { let age_millis = i64::try_from(age.as_millis()).expect("test heartbeat age fits in i64 millis"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {}.attempt_state SET heartbeat_at = clock_timestamp() - ($1 * interval '1 millisecond'), updated_at = clock_timestamp() WHERE job_id = $2 AND run_lease = $3", store.schema() - )) + ))) .bind(age_millis) .bind(job_id) .bind(run_lease) @@ -981,10 +987,10 @@ async fn wait_for_callback_job( } async fn dlq_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.dlq_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -992,10 +998,10 @@ async fn dlq_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i6 } async fn failed_done_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.done_entries WHERE queue = $1 AND state = 'failed'", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -1053,10 +1059,10 @@ async fn wait_for_failed_done_count( } async fn completed_done_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.done_entries WHERE queue = $1 AND state = 'completed'", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -1064,10 +1070,10 @@ async fn completed_done_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: } async fn completed_terminal_count(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.terminal_jobs WHERE queue = $1 AND state = 'completed'", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -1079,10 +1085,10 @@ async fn receipt_completion_batch_count( store: &QueueStorage, queue: &str, ) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.receipt_completion_batches WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -1090,10 +1096,10 @@ async fn receipt_completion_batch_count( } async fn receipt_completion_tombstone_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {}.receipt_completion_tombstones", store.schema() - )) + ))) .fetch_one(pool) .await .expect("Failed to count receipt completion tombstones") @@ -1110,7 +1116,7 @@ async fn done_body_columns( Option>, Option, ) { - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" SELECT args, max_attempts, run_at, created_at, payload FROM {}.done_entries @@ -1119,7 +1125,7 @@ async fn done_body_columns( LIMIT 1 "#, store.schema() - )) + ))) .bind(job_id) .fetch_one(pool) .await @@ -1137,7 +1143,7 @@ async fn terminal_view_body_columns( DateTime, serde_json::Value, ) { - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" SELECT args, max_attempts, run_at, created_at, payload FROM {}.terminal_jobs @@ -1146,7 +1152,7 @@ async fn terminal_view_body_columns( LIMIT 1 "#, store.schema() - )) + ))) .bind(job_id) .fetch_one(pool) .await @@ -1154,10 +1160,10 @@ async fn terminal_view_body_columns( } async fn dlq_reason(pool: &sqlx::PgPool, store: &QueueStorage, job_id: i64) -> String { - sqlx::query_scalar::<_, String>(&format!( + sqlx::query_scalar::<_, String>(audited_sql(format!( "SELECT dlq_reason FROM {}.dlq_entries WHERE job_id = $1 ORDER BY dlq_at DESC LIMIT 1", store.schema() - )) + ))) .bind(job_id) .fetch_one(pool) .await @@ -1446,7 +1452,7 @@ async fn test_queue_storage_claim_runtime_does_not_write_ready_segment_cache() { ) .await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.queue_claim_heads SET ready_segment_slot = -17, @@ -1454,7 +1460,7 @@ async fn test_queue_storage_claim_runtime_does_not_write_ready_segment_cache() { ready_segment_next_lane_seq = -19 WHERE queue = $1 "# - )) + ))) .bind(queue) .execute(&pool) .await @@ -1467,7 +1473,7 @@ async fn test_queue_storage_claim_runtime_does_not_write_ready_segment_cache() { let claimed = claimed.into_iter().next().expect("missing claimed job"); let (cached_slot, cached_generation, cached_next): (Option, Option, Option) = - sqlx::query_as(&format!( + sqlx::query_as(audited_sql(format!( r#" SELECT ready_segment_slot, ready_segment_generation, ready_segment_next_lane_seq FROM {schema}.queue_claim_heads @@ -1475,7 +1481,7 @@ async fn test_queue_storage_claim_runtime_does_not_write_ready_segment_cache() { AND priority = $2 AND enqueue_shard = $3 "# - )) + ))) .bind(queue) .bind(claimed.claim.priority) .bind(claimed.claim.enqueue_shard) @@ -1595,9 +1601,9 @@ async fn test_queue_storage_cancel_available_tombstones_and_retains_ready_backin 1, "available cancel should tombstone the ready lane" ); - let retained_ready_rows: i64 = sqlx::query_scalar(&format!( + let retained_ready_rows: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_entries WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -1752,9 +1758,9 @@ async fn test_queue_storage_batch_ready_noop_does_not_tombstone() { .expect("set_priority no-op should not fail"); assert!(!moved, "set_priority to the existing priority is a no-op"); - let tombstones: i64 = sqlx::query_scalar(&format!( + let tombstones: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_tombstones WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -1799,10 +1805,10 @@ async fn test_queue_storage_ready_tombstone_head_advances_claim_cursor() { "exact counts must not report tombstoned ready rows as available" ); - let claimed: Vec = sqlx::query_as(&format!( + let claimed: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -1864,10 +1870,10 @@ async fn test_queue_storage_ready_tombstone_non_head_does_not_skip_live_prefix() "only the non-tombstoned prefix row should be available" ); - let claimed: Vec = sqlx::query_as(&format!( + let claimed: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(2_i64) .bind(0.0_f64) @@ -2033,15 +2039,15 @@ async fn test_claim_ring_rotates_and_prunes_empty() { .await; // Seeded state: cursor (slot 0, generation 0), slot_count = 4. - let (initial_slot, initial_gen): (i32, i64) = sqlx::query_as(&format!( + let (initial_slot, initial_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read initial claim ring cursor"); - let initial_count: i32 = sqlx::query_scalar(&format!( + let initial_count: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT slot_count FROM {schema}.claim_ring_state WHERE singleton = TRUE" - )) + ))) .fetch_one(&pool) .await .expect("read initial claim ring slot_count"); @@ -2052,9 +2058,9 @@ async fn test_claim_ring_rotates_and_prunes_empty() { // #371: per-slot generations are derived from the ledger cursor, so the // seeded shape is (a) the slot rows exist and (b) the genesis cursor is // the single ledger row (0, 0). - let slot_rows: Vec = sqlx::query_scalar(&format!( + let slot_rows: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.claim_ring_slots ORDER BY slot" - )) + ))) .fetch_all(&pool) .await .expect("read initial claim ring slot rows"); @@ -2063,9 +2069,9 @@ async fn test_claim_ring_rotates_and_prunes_empty() { vec![0, 1, 2, 3], "seeded slot table should hold one row per ring slot" ); - let ledger_rows: i64 = sqlx::query_scalar(&format!( + let ledger_rows: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.claim_ring_rotations" - )) + ))) .fetch_one(&pool) .await .expect("count initial claim ring ledger rows"); @@ -2090,9 +2096,9 @@ async fn test_claim_ring_rotates_and_prunes_empty() { other => panic!("rotate_claims step {step} unexpected outcome: {other:?}"), } } - let (idle_slot, idle_gen): (i32, i64) = sqlx::query_as(&format!( + let (idle_slot, idle_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read claim ring cursor after idle rotations"); @@ -2119,15 +2125,15 @@ async fn test_claim_ring_rotates_and_prunes_empty() { // reset() re-seeds the ring to the initial shape — the ledger back to a // single genesis cursor (0, 0), claim_ring_slots back to one row per slot. store.reset(&pool).await.expect("reset should succeed"); - let (reset_slot, reset_gen): (i32, i64) = sqlx::query_as(&format!( + let (reset_slot, reset_gen): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read claim ring cursor after reset"); - let reset_count: i32 = sqlx::query_scalar(&format!( + let reset_count: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT slot_count FROM {schema}.claim_ring_state WHERE singleton = TRUE" - )) + ))) .fetch_one(&pool) .await .expect("read claim ring slot_count after reset"); @@ -2135,9 +2141,9 @@ async fn test_claim_ring_rotates_and_prunes_empty() { assert_eq!(reset_gen, 0); assert_eq!(reset_count, 4); - let post_reset_rows: Vec = sqlx::query_scalar(&format!( + let post_reset_rows: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.claim_ring_slots ORDER BY slot" - )) + ))) .fetch_all(&pool) .await .expect("read claim ring slot rows after reset"); @@ -2146,9 +2152,9 @@ async fn test_claim_ring_rotates_and_prunes_empty() { vec![0, 1, 2, 3], "reset should restore the seeded claim-ring slot table" ); - let post_reset_ledger: i64 = sqlx::query_scalar(&format!( + let post_reset_ledger: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.claim_ring_rotations" - )) + ))) .fetch_one(&pool) .await .expect("count claim ring ledger rows after reset"); @@ -2185,23 +2191,23 @@ async fn test_successful_prunes_report_database_phase_timings() { .await; for ring in ["queue", "lease", "claim"] { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.{ring}_ring_rotations (generation, slot) VALUES (1, 1)" - )) + ))) .execute(&pool) .await .unwrap_or_else(|err| panic!("advance {ring} ring: {err}")); } let filenodes = || async { - sqlx::query_as::<_, (i64, i64, i64)>(&format!( + sqlx::query_as::<_, (i64, i64, i64)>(audited_sql(format!( r#" SELECT pg_relation_filenode('{schema}.ready_entries_0'::regclass)::bigint, pg_relation_filenode('{schema}.leases_0'::regclass)::bigint, pg_relation_filenode('{schema}.lease_claims_0'::regclass)::bigint "# - )) + ))) .fetch_one(&pool) .await .expect("read ring child filenodes") @@ -2265,17 +2271,17 @@ async fn test_maintenance_idle_prune_replaces_child_filenode_once() { }; let _store = create_store_with_config(&pool, store_config.clone()).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) VALUES (1, 1)" - )) + ))) .execute(&pool) .await .expect("advance empty queue ring"); let child_filenode = || async { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT pg_relation_filenode('{schema}.ready_entries_0'::regclass)::bigint" - )) + ))) .fetch_one(&pool) .await .expect("read queue child filenode") @@ -2357,7 +2363,7 @@ async fn test_idle_gate_holds_rotation_open_until_sealed_slots_reclaimed() { // (2, 2). The current cursor is the max-generation row, so slot 2 is // open and slots 0/1 are sealed. for (slot, generation) in [(0_i32, 0_i64), (1, 1)] { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, state, priority, attempt, run_lease, lane_seq, enqueue_shard, @@ -2366,7 +2372,7 @@ async fn test_idle_gate_holds_rotation_open_until_sealed_slots_reclaimed() { 'completed'::awa.job_state, 2::smallint, 1::smallint, 1::bigint, $5::bigint, 0::smallint, now(), now(), '{{}}'::jsonb)" - )) + ))) .bind(slot) .bind(generation) .bind(8_000_000_i64 + i64::from(slot)) @@ -2378,10 +2384,10 @@ async fn test_idle_gate_holds_rotation_open_until_sealed_slots_reclaimed() { } // Advance the cursor by appending to the rotation ledger exactly as // two busy rotations would (genesis (0, 0) already exists from install). - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) \ VALUES (1, 1), (2, 2)" - )) + ))) .execute(&pool) .await .expect("advance cursor as two busy rotations would"); @@ -2633,9 +2639,9 @@ async fn test_claim_ring_rotate_and_prune_under_load() { // Sanity: the claim landed in slot 0 and compact claim-local batch // evidence closes it without an explicit per-job closure row. let slot0_claims = receipt_claim_count_in_child(&pool, schema, 0, job_id).await; - let slot0_closures: i64 = sqlx::query_scalar(&format!( + let slot0_closures: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claim_closures_0" - )) + ))) .fetch_one(&pool) .await .expect("count lease_claim_closures_0"); @@ -2749,7 +2755,7 @@ async fn test_claim_ring_rotate_and_prune_under_load() { // Prune the oldest initialized slot. The completed claim has a // compact receipt batch, so PartitionTruncateSafety holds even // without a completed-closure row, and prune TRUNCATEs both children. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.claim_ring_slots SET rescue_cursor_claimed_at = clock_timestamp(), @@ -2757,7 +2763,7 @@ async fn test_claim_ring_rotate_and_prune_under_load() { rescue_cursor_run_lease = 1 WHERE slot = 0 "# - )) + ))) .bind(job_id) .execute(&pool) .await @@ -2773,20 +2779,21 @@ async fn test_claim_ring_rotate_and_prune_under_load() { } // Claim-ring children for slot 0 are now empty. - let post_prune_claims: i64 = - sqlx::query_scalar(&format!("SELECT count(*) FROM {schema}.lease_claims_0")) - .fetch_one(&pool) - .await - .expect("count lease_claims_0 after prune"); - let post_prune_claim_batches: i64 = sqlx::query_scalar(&format!( + let post_prune_claims: i64 = sqlx::query_scalar(audited_sql(format!( + "SELECT count(*) FROM {schema}.lease_claims_0" + ))) + .fetch_one(&pool) + .await + .expect("count lease_claims_0 after prune"); + let post_prune_claim_batches: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claim_batches_0" - )) + ))) .fetch_one(&pool) .await .expect("count lease_claim_batches_0 after prune"); - let post_prune_closures: i64 = sqlx::query_scalar(&format!( + let post_prune_closures: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claim_closures_0" - )) + ))) .fetch_one(&pool) .await .expect("count lease_claim_closures_0 after prune"); @@ -2802,7 +2809,7 @@ async fn test_claim_ring_rotate_and_prune_under_load() { post_prune_closures, 0, "lease_claim_closures_0 must be empty post-prune" ); - let cursor_reset: (bool, i64, i64) = sqlx::query_as(&format!( + let cursor_reset: (bool, i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT rescue_cursor_claimed_at = '-infinity'::timestamptz, @@ -2811,7 +2818,7 @@ async fn test_claim_ring_rotate_and_prune_under_load() { FROM {schema}.claim_ring_slots WHERE slot = 0 "# - )) + ))) .fetch_one(&pool) .await .expect("read claim rescue cursor after claim prune"); @@ -2861,17 +2868,17 @@ async fn test_prune_oldest_leases_does_not_reset_claim_rescue_cursor() { RotateOutcome::SkippedIdle { slot, .. } => assert_eq!(slot, 0), other => panic!("expected SkippedIdle {{ slot: 0 }}, got {other:?}"), } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_ring_rotations (generation, slot) VALUES (1, 1) "# - )) + ))) .execute(&pool) .await .expect("advance lease ring cursor for prune setup"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.claim_ring_slots SET rescue_cursor_claimed_at = clock_timestamp(), @@ -2879,7 +2886,7 @@ async fn test_prune_oldest_leases_does_not_reset_claim_rescue_cursor() { rescue_cursor_run_lease = 7 WHERE slot = 0 "# - )) + ))) .execute(&pool) .await .expect("seed claim rescue cursor before lease prune"); @@ -2893,7 +2900,7 @@ async fn test_prune_oldest_leases_does_not_reset_claim_rescue_cursor() { "lease prune should truncate empty lease slot 0, got {prune:?}" ); - let cursor: (bool, i64, i64) = sqlx::query_as(&format!( + let cursor: (bool, i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT rescue_cursor_claimed_at = '-infinity'::timestamptz, @@ -2902,7 +2909,7 @@ async fn test_prune_oldest_leases_does_not_reset_claim_rescue_cursor() { FROM {schema}.claim_ring_slots WHERE slot = 0 "# - )) + ))) .fetch_one(&pool) .await .expect("read claim rescue cursor after lease prune"); @@ -2936,14 +2943,14 @@ async fn test_prune_oldest_claims_refuses_to_truncate_open_claim() { .await; // Synthesize an open claim in slot 0 without a matching closure. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims ( claim_slot, job_id, run_lease, ready_slot, ready_generation, queue, priority, attempt, max_attempts, lane_seq ) VALUES (0, 999, 1, 0, 0, 'synthetic', 2, 1, 25, 999) "# - )) + ))) .execute(&pool) .await .expect("seed open claim"); @@ -2957,9 +2964,9 @@ async fn test_prune_oldest_claims_refuses_to_truncate_open_claim() { } let mut reader_tx = pool.begin().await.expect("begin claim reader tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {schema}.lease_claims_0, {schema}.lease_claim_closures_0, {schema}.lease_claim_closure_batches_0 IN ACCESS SHARE MODE" - )) + ))) .execute(reader_tx.as_mut()) .await .expect("lock claim children in access share mode"); @@ -2983,9 +2990,9 @@ async fn test_prune_oldest_claims_refuses_to_truncate_open_claim() { reader_tx.rollback().await.expect("release reader lock"); // The claim is still there — not lost. - let survived: i64 = sqlx::query_scalar(&format!( + let survived: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claims_0 WHERE job_id = 999" - )) + ))) .fetch_one(&pool) .await .expect("count survivor"); @@ -3022,22 +3029,22 @@ async fn test_prune_oldest_claims_rechecks_open_claim_after_lock_wait() { // The ring is empty at this point, so `rotate_claims` idle-skips // (#371); append a rotation to the ledger to move the cursor off // slot 0 directly so prune targets it. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.claim_ring_rotations (generation, slot) VALUES (1, 1)" - )) + ))) .execute(&pool) .await .expect("advance claim ring cursor away from slot 0"); let mut writer_tx = pool.begin().await.expect("begin claim writer tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims ( claim_slot, job_id, run_lease, ready_slot, ready_generation, queue, priority, attempt, max_attempts, lane_seq ) VALUES (0, 1001, 1, 0, 0, 'synthetic', 2, 1, 25, 1001) "# - )) + ))) .execute(writer_tx.as_mut()) .await .expect("seed uncommitted open claim"); @@ -3097,9 +3104,9 @@ async fn test_prune_oldest_claims_rechecks_open_claim_after_lock_wait() { "post-lock proof must see the committed open claim, got {outcome:?}" ); - let survived: i64 = sqlx::query_scalar(&format!( + let survived: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claims_0 WHERE job_id = 1001" - )) + ))) .fetch_one(&pool) .await .expect("count post-lock survivor"); @@ -3132,16 +3139,16 @@ async fn test_prune_oldest_rechecks_pending_ready_after_lock_wait() { ) .await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.ready_segments WHERE ready_slot = 0 AND queue = $1" - )) + ))) .bind(queue) .execute(&pool) .await .expect("remove seed ready segment"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.ready_entries WHERE job_id = $1" - )) + ))) .bind(seed_job_id) .execute(&pool) .await @@ -3159,16 +3166,16 @@ async fn test_prune_oldest_rechecks_pending_ready_after_lock_wait() { matches!(idle, RotateOutcome::SkippedIdle { slot: 0, .. }), "empty ready slot 0 should idle-skip: {idle:?}" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) VALUES (1, 1)" - )) + ))) .execute(&pool) .await .expect("advance queue ring cursor off slot 0 for prune setup"); let post_lock_job_id = 1_000_091_i64; let mut writer_tx = pool.begin().await.expect("begin ready writer tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.ready_entries ( ready_slot, ready_generation, job_id, kind, queue, args, priority, @@ -3180,13 +3187,13 @@ async fn test_prune_oldest_rechecks_pending_ready_after_lock_wait() { NULL, clock_timestamp(), NULL, NULL, '{{}}'::jsonb ) "# - )) + ))) .bind(post_lock_job_id) .bind(queue) .execute(writer_tx.as_mut()) .await .expect("seed uncommitted ready row"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.ready_segments ( ready_slot, ready_generation, queue, priority, enqueue_shard, @@ -3195,7 +3202,7 @@ async fn test_prune_oldest_rechecks_pending_ready_after_lock_wait() { ON CONFLICT (ready_slot, ready_generation, queue, priority, enqueue_shard, first_lane_seq) DO NOTHING "# - )) + ))) .bind(queue) .execute(writer_tx.as_mut()) .await @@ -3256,9 +3263,9 @@ async fn test_prune_oldest_rechecks_pending_ready_after_lock_wait() { "post-lock proof must see the committed pending ready row, got {outcome:?}" ); - let survived: i64 = sqlx::query_scalar(&format!( + let survived: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_entries_0 WHERE job_id = $1" - )) + ))) .bind(post_lock_job_id) .fetch_one(&pool) .await @@ -3560,10 +3567,10 @@ async fn test_compact_batch_claims_are_visible_as_open() { // `deadline_at` (the deadline-backed shape the chaos tests exercise) — // no worker runs, so nothing materializes into `leases` or closes the // claim. - let claimed: Vec = sqlx::query_as(&format!( + let claimed: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(claim_count) // p_deadline_secs > 0 → the batch carries a non-NULL deadline_at. @@ -3669,7 +3676,7 @@ async fn test_compact_batch_claims_are_visible_as_open() { // already landed in done_entries and assert it is NOT double-listed // as running. let stale_job_id = 9_600_001_i64; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.lease_claims ( claim_slot, receipt_id, job_id, run_lease, ready_slot, ready_generation, queue, priority, attempt, @@ -3677,13 +3684,13 @@ async fn test_compact_batch_claims_are_visible_as_open() { ) VALUES (0, 96001, $1, 7, 0, 0, $2, 2::smallint, 1::smallint, 25::smallint, 9601::bigint, 0::smallint, now(), now() + interval '5 minutes')" - )) + ))) .bind(stale_job_id) .bind(queue) .execute(&pool) .await .expect("seed legacy row-local claim"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.ready_entries ( ready_slot, ready_generation, job_id, kind, queue, args, priority, attempt, run_lease, max_attempts, lane_seq, enqueue_shard, run_at, @@ -3691,13 +3698,13 @@ async fn test_compact_batch_claims_are_visible_as_open() { ) VALUES (0, 0, $1, 'stale_claim_job', $2, '{{}}'::jsonb, 2, 1, 7, 25, 9601, 0, now(), now(), now(), NULL, NULL, '{{}}'::jsonb)" - )) + ))) .bind(stale_job_id) .bind(queue) .execute(&pool) .await .expect("seed matching ready row for the row-local claim"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, state, priority, attempt, run_lease, lane_seq, enqueue_shard, @@ -3705,7 +3712,7 @@ async fn test_compact_batch_claims_are_visible_as_open() { ) VALUES (0, 0, $1, 'stale_claim_job', $2, 'completed'::awa.job_state, 2::smallint, 1::smallint, 7::bigint, 9601::bigint, 0::smallint, now(), now(), '{{}}'::jsonb)" - )) + ))) .bind(stale_job_id) .bind(queue) .execute(&pool) @@ -3773,15 +3780,15 @@ async fn test_lease_claim_partition_routing() { // idle-skips (#371); append the cursor hops to the ledger directly, // exactly as a rotation of a busy ring would (slot = generation mod // slot_count). - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.claim_ring_rotations (generation, slot) VALUES (1, 1), (2, 2)" - )) + ))) .execute(&pool) .await .expect("advance claim ring cursor for routing setup"); - let current_slot: i32 = sqlx::query_scalar(&format!( + let current_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read current claim slot"); @@ -3839,9 +3846,9 @@ async fn test_lease_claim_partition_routing() { "claim evidence must be in claim slot child" ); - let closure_in_child: i64 = sqlx::query_scalar(&format!( + let closure_in_child: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claim_closures_2 WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -3851,13 +3858,13 @@ async fn test_lease_claim_partition_routing() { "compact successful completion should not write an explicit closure row" ); - let compact_batch_claim_slot: i32 = sqlx::query_scalar(&format!( + let compact_batch_claim_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT claim_slot FROM {schema}.receipt_completion_batches WHERE job_ids @> ARRAY[$1]::bigint[] ORDER BY batch_id DESC LIMIT 1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -3871,14 +3878,14 @@ async fn test_lease_claim_partition_routing() { .claim .receipt_id .expect("receipt claim should carry receipt_id"); - let compact_batch_closes_receipt: bool = sqlx::query_scalar(&format!( + let compact_batch_closes_receipt: bool = sqlx::query_scalar(audited_sql(format!( "SELECT EXISTS ( SELECT 1 FROM {schema}.lease_claim_closure_batches AS batches WHERE batches.claim_slot = $1 AND batches.receipt_ranges @> $2 )" - )) + ))) .bind(current_slot) .bind(receipt_id) .fetch_one(&pool) @@ -3994,10 +4001,10 @@ async fn test_lease_claim_rotation_isolation() { /// ring's append-only rotation ledger (#371). The `{ring}_ring_state` /// singleton no longer carries the cursor columns. async fn ring_cursor(pool: &sqlx::PgPool, schema: &str, ring: &str) -> (i32, i64) { - sqlx::query_as::<_, (i32, i64)>(&format!( + sqlx::query_as::<_, (i32, i64)>(audited_sql(format!( "SELECT slot, generation FROM {schema}.{ring}_ring_rotations \ ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(pool) .await .expect("read ring cursor") @@ -4007,9 +4014,9 @@ async fn ring_cursor(pool: &sqlx::PgPool, schema: &str, ring: &str) -> (i32, i64 /// install (the genesis cursor); grows by one per rotation and is trimmed /// back to one wrap (`slot_count` rows) by the maintenance fold. async fn ring_ledger_row_count(pool: &sqlx::PgPool, schema: &str, ring: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.{ring}_ring_rotations" - )) + ))) .fetch_one(pool) .await .expect("count ring ledger rows") @@ -4020,9 +4027,9 @@ async fn ring_ledger_row_count(pool: &sqlx::PgPool, schema: &str, ring: &str) -> /// deterministic form of "no dead tuple was created", independent of /// pg_stat flush timing. async fn ring_row_identity(pool: &sqlx::PgPool, schema: &str, table: &str) -> (String, String) { - sqlx::query_as::<_, (String, String)>(&format!( + sqlx::query_as::<_, (String, String)>(audited_sql(format!( "SELECT ctid::text, xmin::text FROM {schema}.{table}" - )) + ))) .fetch_one(pool) .await .expect("read ring-state row identity") @@ -4082,7 +4089,7 @@ async fn test_idle_ring_rotation_skips_without_ring_state_churn() { "lease_ring_state", "claim_ring_state", ] { - sqlx::query(&format!("VACUUM {schema}.{table}")) + sqlx::query(audited_sql(format!("VACUUM {schema}.{table}"))) .execute(&pool) .await .expect("vacuum ring-state table"); @@ -4441,10 +4448,10 @@ async fn test_ring_rotation_ledger_append_is_a_generation_cas() { assert_eq!((current_slot, generation), (0, 0), "genesis cursor"); // First appender wins generation 1 at slot 1 (= 1 mod 4). - let winner = sqlx::query(&format!( + let winner = sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) \ VALUES (1, 1) ON CONFLICT (generation) DO NOTHING" - )) + ))) .execute(&pool) .await .expect("winning rotation append"); @@ -4459,10 +4466,10 @@ async fn test_ring_rotation_ledger_append_is_a_generation_cas() { // the generation PK and writes nothing — the lost race rotate maps to // SkippedBusy. Even a different target slot cannot double-advance the // generation. - let loser = sqlx::query(&format!( + let loser = sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) \ VALUES (1, 2) ON CONFLICT (generation) DO NOTHING" - )) + ))) .execute(&pool) .await .expect("losing rotation append"); @@ -4492,11 +4499,11 @@ async fn test_ring_rotation_ledger_append_is_a_generation_cas() { /// Force a schema into compat ('columns') authority, simulating a rolling /// upgrade where the ledgers were seeded but the fleet has not yet flipped. async fn set_ring_authority(pool: &sqlx::PgPool, schema: &str, authority: &str) { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.ring_cursor_authority SET authority = $1, \ flipped_at = CASE WHEN $1 = 'ledger' THEN now() ELSE NULL END \ WHERE singleton" - )) + ))) .bind(authority) .execute(pool) .await @@ -4507,7 +4514,7 @@ async fn set_ring_authority(pool: &sqlx::PgPool, schema: &str, authority: &str) // those columns from the ledger after moving authority back to columns. if authority == "columns" { for ring in ["queue", "lease", "claim"] { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.{ring}_ring_state AS state \ SET current_slot = cursor.slot, generation = cursor.generation \ FROM ( \ @@ -4516,7 +4523,7 @@ async fn set_ring_authority(pool: &sqlx::PgPool, schema: &str, authority: &str) ORDER BY generation DESC LIMIT 1 \ ) AS cursor \ WHERE state.singleton" - )) + ))) .execute(pool) .await .expect("restore compat cursor from ledger"); @@ -4525,9 +4532,9 @@ async fn set_ring_authority(pool: &sqlx::PgPool, schema: &str, authority: &str) } async fn ring_authority(pool: &sqlx::PgPool, schema: &str) -> String { - sqlx::query_scalar::<_, String>(&format!( + sqlx::query_scalar::<_, String>(audited_sql(format!( "SELECT authority FROM {schema}.ring_cursor_authority WHERE singleton" - )) + ))) .fetch_one(pool) .await .expect("read ring authority") @@ -4535,9 +4542,9 @@ async fn ring_authority(pool: &sqlx::PgPool, schema: &str) -> String { /// The compat-authority cursor: the mutable singleton columns. async fn ring_columns_cursor(pool: &sqlx::PgPool, schema: &str, ring: &str) -> (i32, i64) { - sqlx::query_as::<_, (i32, i64)>(&format!( + sqlx::query_as::<_, (i32, i64)>(audited_sql(format!( "SELECT current_slot, generation FROM {schema}.{ring}_ring_state WHERE singleton" - )) + ))) .fetch_one(pool) .await .expect("read ring columns cursor") @@ -4557,7 +4564,7 @@ async fn insert_flip_test_runtime( } else { "now()" }; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO awa.runtime_instances ( \ instance_id, hostname, pid, version, binary_version, \ storage_capability, transition_role, started_at, last_seen_at, \ @@ -4569,7 +4576,7 @@ async fn insert_flip_test_runtime( 'queue_storage', 'queue_storage_target', now() - interval '1 minute', {last_seen}, \ 1000, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL, '[]'::jsonb \ )" - )) + ))) .bind(id) .bind(binary_version) .execute(pool) @@ -4658,9 +4665,9 @@ async fn test_compat_mode_reconciles_externally_casd_columns() { // Simulate a live 0.6 rotator advancing the columns twice WITHOUT // touching the ledger (raw SQL, exactly the pre-#371 write path). - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_ring_state SET current_slot = 2, generation = 2 WHERE singleton" - )) + ))) .execute(&pool) .await .expect("simulate 0.6 external CAS"); @@ -4731,9 +4738,9 @@ async fn test_flip_reconciles_ledgers_and_fences_old_cursor_updates() { ("lease", 1, 3_i64), ("claim", 2, 2_i64), ] { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.{ring}_ring_state SET current_slot = $1, generation = $2 WHERE singleton" - )) + ))) .bind(slot) .bind(generation) .execute(&pool) @@ -4777,9 +4784,9 @@ async fn test_flip_reconciles_ledgers_and_fences_old_cursor_updates() { "{ring} compat cursor poisoned at flip" ); } - let initialized_queue_slots: i64 = sqlx::query_scalar(&format!( + let initialized_queue_slots: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.queue_ring_slots WHERE generation >= 0" - )) + ))) .fetch_one(&pool) .await .expect("count legacy queue prune candidates"); @@ -4791,9 +4798,9 @@ async fn test_flip_reconciles_ledgers_and_fences_old_cursor_updates() { // This is the exact cursor mutation shape used by the pre-ledger // rotator. The -1 sentinel alone would let it compute and persist slot // zero; the trigger must reject that write once authority is ledger. - let old_rotate = sqlx::query(&format!( + let old_rotate = sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_ring_state SET current_slot = 0, generation = 0 WHERE singleton" - )) + ))) .execute(&pool) .await .expect_err("old-style cursor advance must be fenced after the flip"); @@ -4997,10 +5004,10 @@ async fn test_terminal_rollup_delta_fold_defers_under_pinned_horizon() { assert!(!outcome.skipped_mvcc_pinned); assert_eq!(outcome.folded_delta_rows, 1); assert_eq!(pruned_delta_row_count(&pool, schema, queue).await, 0); - let folded: i64 = sqlx::query_scalar::<_, i64>(&format!( + let folded: i64 = sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT COALESCE(SUM(pruned_completed_count), 0)::bigint \ FROM {schema}.queue_terminal_rollups WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -5291,9 +5298,9 @@ async fn test_legacy_zero_deadline_claim_conversion_error_rolls_back() { ) .await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.ready_entries SET payload = '{{\"metadata\":\"bad\"}}'::jsonb WHERE job_id = $1" - )) + ))) .bind(job_id) .execute(&pool) .await @@ -5309,9 +5316,9 @@ async fn test_legacy_zero_deadline_claim_conversion_error_rolls_back() { "failed conversion must not leave an unrescueable legacy zero-deadline lease" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.ready_entries SET payload = '{{}}'::jsonb WHERE job_id = $1" - )) + ))) .bind(job_id) .execute(&pool) .await @@ -5358,9 +5365,9 @@ async fn test_legacy_zero_deadline_claim_without_receipts_succeeds() { "legacy non-receipts claims must not carry receipt identities" ); - let deadline_at: Option> = sqlx::query_scalar(&format!( + let deadline_at: Option> = sqlx::query_scalar(audited_sql(format!( "SELECT deadline_at FROM {schema}.leases WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -5388,18 +5395,20 @@ async fn test_legacy_zero_deadline_claim_without_receipts_succeeds() { async fn test_lease_claim_migration_preserves_rows() { let (_db_guard, pool) = setup_pool(4).await; let schema = "awa_qs_claim_migration"; - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(&pool) - .await - .expect("drop schema"); - sqlx::query(&format!("CREATE SCHEMA {schema}")) + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(&pool) + .await + .expect("drop schema"); + sqlx::query(audited_sql(format!("CREATE SCHEMA {schema}"))) .execute(&pool) .await .expect("create schema"); // Stand up the legacy regular-table shape so the migration path // runs on `prepare_schema`. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" CREATE TABLE {schema}.lease_claims ( job_id BIGINT NOT NULL, @@ -5418,12 +5427,12 @@ async fn test_lease_claim_migration_preserves_rows() { PRIMARY KEY (job_id, run_lease) ) "# - )) + ))) .execute(&pool) .await .expect("legacy lease_claims"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" CREATE TABLE {schema}.lease_claim_closures ( job_id BIGINT NOT NULL, @@ -5433,13 +5442,13 @@ async fn test_lease_claim_migration_preserves_rows() { PRIMARY KEY (job_id, run_lease) ) "# - )) + ))) .execute(&pool) .await .expect("legacy lease_claim_closures"); for job_id in 1..=5_i64 { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims (job_id, run_lease, ready_slot, ready_generation, queue, @@ -5448,20 +5457,20 @@ async fn test_lease_claim_migration_preserves_rows() { VALUES ($1, 1, 0, 0, 'legacy', 2, 1, 25, $1, ($1 % 2)::smallint, now(), NULL, TIMESTAMPTZ '2030-01-01 00:00:00+00') "# - )) + ))) .bind(job_id) .execute(&pool) .await .expect("seed lease_claims row"); } for job_id in [1_i64, 2] { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claim_closures (job_id, run_lease, outcome, closed_at) VALUES ($1, 1, 'completed', now()) "# - )) + ))) .bind(job_id) .execute(&pool) .await @@ -5531,16 +5540,16 @@ async fn test_lease_claim_migration_preserves_rows() { // All pre-existing rows landed in the current claim slot (the // max-generation ledger row, #371). - let current_slot: i32 = sqlx::query_scalar(&format!( + let current_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.claim_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read current slot"); - let claims_count: i64 = sqlx::query_scalar(&format!( + let claims_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claims WHERE claim_slot = $1" - )) + ))) .bind(current_slot) .fetch_one(&pool) .await @@ -5550,7 +5559,7 @@ async fn test_lease_claim_migration_preserves_rows() { "all 5 legacy claim rows must migrate into current_slot" ); - let preserved_claim_shape: (i16, bool) = sqlx::query_as(&format!( + let preserved_claim_shape: (i16, bool) = sqlx::query_as(audited_sql(format!( r#" SELECT enqueue_shard, deadline_at = TIMESTAMPTZ '2030-01-01 00:00:00+00' @@ -5558,15 +5567,15 @@ async fn test_lease_claim_migration_preserves_rows() { WHERE job_id = 3 AND run_lease = 1 "# - )) + ))) .fetch_one(&pool) .await .expect("read migrated claim metadata"); assert_eq!(preserved_claim_shape, (1, true)); - let closures_count: i64 = sqlx::query_scalar(&format!( + let closures_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claim_closures WHERE claim_slot = $1" - )) + ))) .bind(current_slot) .fetch_one(&pool) .await @@ -5583,11 +5592,12 @@ async fn test_lease_claim_migration_preserves_rows() { .await .expect("prepare_schema idempotent after migration"); - let claims_count_after: i64 = - sqlx::query_scalar(&format!("SELECT count(*) FROM {schema}.lease_claims")) - .fetch_one(&pool) - .await - .expect("count claims after idempotent call"); + let claims_count_after: i64 = sqlx::query_scalar(audited_sql(format!( + "SELECT count(*) FROM {schema}.lease_claims" + ))) + .fetch_one(&pool) + .await + .expect("count claims after idempotent call"); assert_eq!( claims_count_after, 5, "idempotent prepare must not duplicate" @@ -6366,9 +6376,9 @@ async fn test_queue_storage_custom_metadata_completion_uses_wide_terminal_row() ); assert_eq!(completed_terminal_count(&pool, &store, queue).await, 1); - let metadata: serde_json::Value = sqlx::query_scalar(&format!( + let metadata: serde_json::Value = sqlx::query_scalar(audited_sql(format!( "SELECT payload->'metadata' FROM {schema}.terminal_jobs WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -6927,8 +6937,8 @@ async fn test_queue_storage_receipt_deadline_rescue_force_closes_expired_claim() // Verify deadline_at landed on the compact claim batch and that no // per-job lease_claims row was written. - let deadline_at: Option> = sqlx::query_scalar(&format!( - "SELECT deadline_at FROM {schema}.lease_claim_batches WHERE batch_id = $1" + let deadline_at: Option> = sqlx::query_scalar(audited_sql( + format!("SELECT deadline_at FROM {schema}.lease_claim_batches WHERE batch_id = $1"), )) .bind(claimed[0].claim.claim_batch_id) .fetch_one(&pool) @@ -6938,9 +6948,9 @@ async fn test_queue_storage_receipt_deadline_rescue_force_closes_expired_claim() deadline_at.is_some(), "deadline_at must be set on the claim batch when deadline_duration > 0" ); - let row_claims: i64 = sqlx::query_scalar(&format!( + let row_claims: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.lease_claims" - )) + ))) .fetch_one(&pool) .await .expect("count lease_claims"); @@ -6949,11 +6959,11 @@ async fn test_queue_storage_receipt_deadline_rescue_force_closes_expired_claim() "deadline-backed claims must not write row-local lease_claims rows" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches \ SET deadline_at = clock_timestamp() - interval '1 millisecond' \ WHERE batch_id = $1" - )) + ))) .bind(claimed[0].claim.claim_batch_id) .execute(&pool) .await @@ -6968,10 +6978,10 @@ async fn test_queue_storage_receipt_deadline_rescue_force_closes_expired_claim() // Closure evidence is compact: a lease_claim_closure_batches row with // outcome='deadline_expired' covering the member's receipt_id. - let outcome: String = sqlx::query_scalar(&format!( + let outcome: String = sqlx::query_scalar(audited_sql(format!( "SELECT outcome FROM {schema}.lease_claim_closure_batches \ WHERE receipt_ranges @> $1::bigint" - )) + ))) .bind(claimed[0].claim.receipt_id) .fetch_one(&pool) .await @@ -6986,9 +6996,9 @@ async fn test_queue_storage_receipt_deadline_rescue_force_closes_expired_claim() .expect("claim after promote should succeed"); let reclaimed = if reclaimed.is_empty() { // The rescued row lands in deferred_jobs with backoff; promote it. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.deferred_jobs SET run_at = clock_timestamp() WHERE job_id = $1" - )) + ))) .bind(job_id) .execute(&pool) .await @@ -7094,7 +7104,7 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina .await .expect("complete first receipt job"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.lease_claim_batches SET deadline_at = CASE @@ -7103,7 +7113,7 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina END WHERE batch_id IN ($1, $2) "# - )) + ))) .bind(first_batch) .bind(second_batch) .execute(&pool) @@ -7119,9 +7129,9 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina "terminal evidence should close the expired first claim and the future second claim must not be rescued" ); - let cursor_batch: i64 = sqlx::query_scalar(&format!( + let cursor_batch: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT batch_deadline_cursor_batch_id FROM {schema}.claim_ring_slots WHERE slot = $1" - )) + ))) .bind(claimed[0].claim.claim_slot) .fetch_one(&pool) .await @@ -7131,11 +7141,11 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina "batch deadline cursor should advance past the completed batch instead of rechecking it forever" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches \ SET deadline_at = clock_timestamp() - interval '1 second' \ WHERE batch_id = $1" - )) + ))) .bind(second_batch) .execute(&pool) .await @@ -7150,9 +7160,9 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina // Compact members close compactly: rescue writes a // lease_claim_closure_batches row, never a row-local closure. - let row_closures: i64 = sqlx::query_scalar(&format!( + let row_closures: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.lease_claim_closures" - )) + ))) .fetch_one(&pool) .await .expect("count receipt closures"); @@ -7160,9 +7170,9 @@ async fn test_queue_storage_receipt_deadline_rescue_cursor_advances_over_termina row_closures, 0, "compact members must not write row-local closures" ); - let expired_closures: i64 = sqlx::query_scalar(&format!( + let expired_closures: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.lease_claim_closure_batches WHERE outcome = 'deadline_expired'" - )) + ))) .fetch_one(&pool) .await .expect("count deadline_expired closure batches"); @@ -7214,10 +7224,10 @@ async fn test_queue_storage_compact_deadline_claim_completes_normally() { .await .expect("complete deadline-backed compact claim"); - let completed: String = sqlx::query_scalar(&format!( + let completed: String = sqlx::query_scalar(audited_sql(format!( "SELECT outcome FROM {schema}.lease_claim_closure_batches \ WHERE receipt_ranges @> $1::bigint" - )) + ))) .bind(claimed[0].claim.receipt_id) .fetch_one(&pool) .await @@ -7226,9 +7236,9 @@ async fn test_queue_storage_compact_deadline_claim_completes_normally() { // Rescue must not touch the completed member even after the deadline // passes: closure evidence wins. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches SET deadline_at = clock_timestamp() - interval '1 second'" - )) + ))) .execute(&pool) .await .expect("expire completed batch deadline"); @@ -7243,7 +7253,7 @@ async fn test_queue_storage_compact_deadline_claim_completes_normally() { // Claim-prune count proof shape: claims (rows + batch members) must // equal closures (explicit + compact closed_count). - let (claims_total, closures_total): (i64, i64) = sqlx::query_as(&format!( + let (claims_total, closures_total): (i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT (SELECT count(*)::bigint FROM {schema}.lease_claims) @@ -7251,7 +7261,7 @@ async fn test_queue_storage_compact_deadline_claim_completes_normally() { (SELECT count(*)::bigint FROM {schema}.lease_claim_closures) + (SELECT COALESCE(sum(closed_count), 0)::bigint FROM {schema}.lease_claim_closure_batches) "# - )) + ))) .fetch_one(&pool) .await .expect("count claim/closure totals"); @@ -7337,9 +7347,9 @@ async fn test_queue_storage_compact_deadline_partial_batch_rescues_only_expired_ .await .expect("complete one member of the deadline batch"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches SET deadline_at = clock_timestamp() - interval '1 second'" - )) + ))) .execute(&pool) .await .expect("expire shared batch deadline"); @@ -7351,17 +7361,17 @@ async fn test_queue_storage_compact_deadline_partial_batch_rescues_only_expired_ assert_eq!(rescued.len(), 1, "only the open member is rescued"); assert_eq!(rescued[0].id, second); - let completed_outcome: String = sqlx::query_scalar(&format!( + let completed_outcome: String = sqlx::query_scalar(audited_sql(format!( "SELECT outcome FROM {schema}.lease_claim_closure_batches WHERE receipt_ranges @> $1::bigint" - )) + ))) .bind(completed_entry.claim.receipt_id) .fetch_one(&pool) .await .expect("completed member closure"); assert_eq!(completed_outcome, "completed"); - let expired_outcome: String = sqlx::query_scalar(&format!( + let expired_outcome: String = sqlx::query_scalar(audited_sql(format!( "SELECT outcome FROM {schema}.lease_claim_closure_batches WHERE receipt_ranges @> $1::bigint" - )) + ))) .bind(open_entry.claim.receipt_id) .fetch_one(&pool) .await @@ -7369,9 +7379,9 @@ async fn test_queue_storage_compact_deadline_partial_batch_rescues_only_expired_ assert_eq!(expired_outcome, "deadline_expired"); // Cursor advances past the batch once every member is closed. - let cursor_batch: i64 = sqlx::query_scalar(&format!( + let cursor_batch: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT batch_deadline_cursor_batch_id FROM {schema}.claim_ring_slots WHERE slot = $1" - )) + ))) .bind(open_entry.claim.claim_slot) .fetch_one(&pool) .await @@ -7382,7 +7392,7 @@ async fn test_queue_storage_compact_deadline_partial_batch_rescues_only_expired_ "batch deadline cursor should advance past the fully-closed batch" ); - let (claims_total, closures_total): (i64, i64) = sqlx::query_as(&format!( + let (claims_total, closures_total): (i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT (SELECT count(*)::bigint FROM {schema}.lease_claims) @@ -7390,7 +7400,7 @@ async fn test_queue_storage_compact_deadline_partial_batch_rescues_only_expired_ (SELECT count(*)::bigint FROM {schema}.lease_claim_closures) + (SELECT COALESCE(sum(closed_count), 0)::bigint FROM {schema}.lease_claim_closure_batches) "# - )) + ))) .fetch_one(&pool) .await .expect("count claim/closure totals"); @@ -7444,14 +7454,14 @@ async fn test_queue_storage_legacy_row_deadline_claim_still_rescued() { // row-local lease_claims row with an (already expired) deadline and no // batch row — exactly what in-flight deadline claims look like when a // 0.6 node wrote them just before the upgrade landed. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.lease_claim_batches WHERE batch_id = $1" - )) + ))) .bind(entry.claim.claim_batch_id) .execute(&pool) .await .expect("remove compact batch row"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims ( claim_slot, receipt_id, job_id, run_lease, ready_slot, @@ -7462,7 +7472,7 @@ async fn test_queue_storage_legacy_row_deadline_claim_still_rescued() { clock_timestamp() - interval '1 second' ) "# - )) + ))) .bind(entry.claim.claim_slot) .bind(entry.claim.receipt_id) .bind(entry.job.id) @@ -7486,9 +7496,9 @@ async fn test_queue_storage_legacy_row_deadline_claim_still_rescued() { assert_eq!(rescued.len(), 1); assert_eq!(rescued[0].id, job_id); - let outcome: String = sqlx::query_scalar(&format!( + let outcome: String = sqlx::query_scalar(audited_sql(format!( "SELECT outcome FROM {schema}.lease_claim_closures WHERE job_id = $1 AND run_lease = $2" - )) + ))) .bind(job_id) .bind(entry.job.run_lease) .fetch_one(&pool) @@ -7542,9 +7552,9 @@ async fn test_queue_storage_expired_deadline_batch_blocks_prune_until_rescued() RotateOutcome::Rotated { slot, .. } => assert_eq!(slot, 1), other => panic!("expected rotate to slot 1, got {other:?}"), } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches SET deadline_at = clock_timestamp() - interval '1 second'" - )) + ))) .execute(&pool) .await .expect("expire sealed-slot batch deadline"); @@ -7584,7 +7594,7 @@ async fn test_queue_storage_expired_deadline_batch_blocks_prune_until_rescued() other => panic!("expected Pruned {{ slot: 0 }}, got {other:?}"), } - let (cursor_deadline_reset, cursor_batch): (bool, i64) = sqlx::query_as(&format!( + let (cursor_deadline_reset, cursor_batch): (bool, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT batch_deadline_cursor_deadline_at = '-infinity'::timestamptz, @@ -7592,7 +7602,7 @@ async fn test_queue_storage_expired_deadline_batch_blocks_prune_until_rescued() FROM {schema}.claim_ring_slots WHERE slot = 0 "# - )) + ))) .fetch_one(&pool) .await .expect("read batch deadline cursor after prune"); @@ -7702,12 +7712,12 @@ async fn test_queue_storage_receipt_rescue_cursor_sweeps_past_fresh_claims_and_w ) .await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.attempt_state (job_id, run_lease, heartbeat_at, updated_at) VALUES ($1, $2, clock_timestamp(), clock_timestamp()) "# - )) + ))) .bind(job_b) .bind(claim_b.job.run_lease) .execute(&pool) @@ -7730,13 +7740,13 @@ async fn test_queue_storage_receipt_rescue_cursor_sweeps_past_fresh_claims_and_w "fresh heartbeat on job B must not prevent rescuing stale job C" ); - let cursor: (DateTime, i64, i64) = sqlx::query_as(&format!( + let cursor: (DateTime, i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT rescue_cursor_claimed_at, rescue_cursor_job_id, rescue_cursor_run_lease FROM {schema}.claim_ring_slots WHERE slot = $1 "# - )) + ))) .bind(claim_a.claim.claim_slot) .fetch_one(&pool) .await @@ -7747,14 +7757,14 @@ async fn test_queue_storage_receipt_rescue_cursor_sweeps_past_fresh_claims_and_w "fresh job B must not pin the sweep cursor ahead of stale job C" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" UPDATE {schema}.attempt_state SET heartbeat_at = clock_timestamp() - interval '300 seconds', updated_at = clock_timestamp() WHERE job_id = $1 AND run_lease = $2 "# - )) + ))) .bind(job_b) .bind(claim_b.job.run_lease) .execute(&pool) @@ -7771,13 +7781,13 @@ async fn test_queue_storage_receipt_rescue_cursor_sweeps_past_fresh_claims_and_w "second rescue should wrap and close the formerly fresh claim" ); - let cursor: (DateTime, i64, i64) = sqlx::query_as(&format!( + let cursor: (DateTime, i64, i64) = sqlx::query_as(audited_sql(format!( r#" SELECT rescue_cursor_claimed_at, rescue_cursor_job_id, rescue_cursor_run_lease FROM {schema}.claim_ring_slots WHERE slot = $1 "# - )) + ))) .bind(claim_a.claim.claim_slot) .fetch_one(&pool) .await @@ -8165,7 +8175,7 @@ async fn test_queue_storage_completes_materialized_receipt_after_lease_ring_rota ), other => panic!("expected idle lease ring before materialization, got {other:?}"), } - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_ring_rotations (generation, slot) SELECT cursor.generation + 1, @@ -8179,13 +8189,13 @@ async fn test_queue_storage_completes_materialized_receipt_after_lease_ring_rota ) AS cursor WHERE state.singleton = TRUE "# - )) + ))) .execute(&pool) .await .expect("advance lease ring cursor for materialization regression setup"); - let rotated_lease_slot: i32 = sqlx::query_scalar(&format!( + let rotated_lease_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT slot FROM {schema}.lease_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(&pool) .await .expect("read advanced lease ring cursor"); @@ -8203,9 +8213,9 @@ async fn test_queue_storage_completes_materialized_receipt_after_lease_ring_rota ) .await .expect("register callback and materialize receipt claim"); - let materialized_lease_slot: i32 = sqlx::query_scalar(&format!( + let materialized_lease_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT lease_slot FROM {schema}.leases WHERE job_id = $1 AND run_lease = $2" - )) + ))) .bind(claimed[0].job.id) .bind(claimed[0].job.run_lease) .fetch_one(&pool) @@ -8573,11 +8583,11 @@ async fn test_queue_storage_claim_gap_does_not_skip_uncommitted_enqueue_sequence assert_eq!(claimed[0].claim.lane_seq, 1); let claim_cursor = || async { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT {schema}.sequence_next_value(seq_name) FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8588,9 +8598,9 @@ async fn test_queue_storage_claim_gap_does_not_skip_uncommitted_enqueue_sequence assert_eq!(claim_cursor().await, 2); let mut tx = pool.begin().await.expect("begin enqueue reservation"); - let reserved: i64 = sqlx::query_scalar(&format!( + let reserved: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT {schema}.reserve_enqueue_seq($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8654,11 +8664,11 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { assert_eq!(claimed[0].claim.lane_seq, 1); let claim_cursor = || async { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT {schema}.sequence_next_value(seq_name) FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8669,9 +8679,9 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { assert_eq!(claim_cursor().await, 2); let mut reservation_tx = pool.begin().await.expect("begin enqueue reservation"); - let reserved: i64 = sqlx::query_scalar(&format!( + let reserved: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT {schema}.reserve_enqueue_seq($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8689,9 +8699,9 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { let later = tokio::spawn(async move { let mut tx = later_pool.begin().await.expect("begin later enqueue"); later_started_task.notify_one(); - let lane_seq: i64 = sqlx::query_scalar(&format!( + let lane_seq: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT {later_schema}.reserve_enqueue_seq($1, $2, $3, $4)" - )) + ))) .bind(&later_queue) .bind(2_i16) .bind(0_i16) @@ -8700,23 +8710,23 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { .await .expect("reserve later enqueue sequence"); - let job_id: i64 = sqlx::query_scalar(&format!( + let job_id: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT nextval('{later_schema}.job_id_seq'::regclass)::bigint" - )) + ))) .fetch_one(tx.as_mut()) .await .expect("allocate later job id"); - let (ready_slot, ready_generation): (i32, i64) = sqlx::query_as(&format!( + let (ready_slot, ready_generation): (i32, i64) = sqlx::query_as(audited_sql(format!( "SELECT slot, generation FROM {later_schema}.queue_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(tx.as_mut()) .await .expect("current queue ring"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {later_schema}.ready_entries ( ready_slot, ready_generation, job_id, kind, queue, args, @@ -8731,7 +8741,7 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { NULL, NULL, '{{}}'::jsonb ) "# - )) + ))) .bind(ready_slot) .bind(ready_generation) .bind(job_id) @@ -8741,7 +8751,7 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { .await .expect("insert later ready row"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {later_schema}.ready_segments ( ready_slot, ready_generation, queue, priority, enqueue_shard, @@ -8749,7 +8759,7 @@ async fn test_queue_storage_enqueue_reservation_orders_ready_visibility() { ) VALUES ($1, $2, $3, 2, 0, $4, $4 + 1, clock_timestamp()) "# - )) + ))) .bind(ready_slot) .bind(ready_generation) .bind(&later_queue) @@ -8834,11 +8844,11 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await; let claim_cursor = || async { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT {schema}.sequence_next_value(seq_name) FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8846,11 +8856,11 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("claim cursor") }; - let claim_seq_name: String = sqlx::query_scalar(&format!( + let claim_seq_name: String = sqlx::query_scalar(audited_sql(format!( "SELECT seq_name FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -8858,10 +8868,10 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("claim sequence name"); - let first: Vec = sqlx::query_as(&format!( + let first: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -8889,7 +8899,7 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc "raw claim intentionally leaves the post-commit claim cursor advance unsent" ); - let claim_attempt_batches: i64 = sqlx::query_scalar(&format!( + let claim_attempt_batches: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT COALESCE(sum(claimed_count), 0)::bigint FROM {schema}.ready_claim_attempt_batches WHERE ready_slot = $1 @@ -8901,7 +8911,7 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc AND first_lane_seq <= $7 AND next_lane_seq > $7 AND lane_ranges @> int8range($7, $7 + 1, '[)')" - )) + ))) .bind(first[0].ready_slot) .bind(first[0].ready_generation) .bind(queue) @@ -8920,18 +8930,18 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc let mut locked_claim_child = pool.begin().await.expect("begin claim child lock"); let claim_child = format!("{schema}.lease_claims_{}", first[0].claim_slot); let claim_batch_child = format!("{schema}.lease_claim_batches_{}", first[0].claim_slot); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {claim_child}, {claim_batch_child} IN ACCESS EXCLUSIVE MODE" - )) + ))) .execute(locked_claim_child.as_mut()) .await .expect("lock old claim child"); let recovered_while_claim_child_locked = tokio::time::timeout(Duration::from_secs(2), async { - sqlx::query_as::<_, (i64, i64, i64, i32)>(&format!( + sqlx::query_as::<_, (i64, i64, i64, i32)>(audited_sql(format!( "SELECT job_id, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -8969,10 +8979,10 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc other => panic!("expected claim ring to rotate to slot 1, got {other:?}"), } - let second: Vec<(i64, i64, i64, i32)> = sqlx::query_as(&format!( + let second: Vec<(i64, i64, i64, i32)> = sqlx::query_as(audited_sql(format!( "SELECT job_id, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -8991,7 +9001,7 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc "spent receipt evidence should advance the stale claim cursor over the emitted attempt" ); - let receipt_rows: i64 = sqlx::query_scalar(&format!( + let receipt_rows: i64 = sqlx::query_scalar(audited_sql(format!( r#" WITH claim_items AS ( SELECT job_id, run_lease @@ -9008,7 +9018,7 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc ) SELECT count(*)::bigint FROM claim_items "# - )) + ))) .bind(job_id) .bind(1_i64) .fetch_one(&pool) @@ -9034,10 +9044,10 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("reset claim cursor for closed-receipt phase"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.lease_claim_closures (claim_slot, job_id, run_lease, outcome) VALUES ($1, $2, $3, 'completed')" - )) + ))) .bind(0_i32) .bind(job_id) .bind(1_i64) @@ -9045,10 +9055,10 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("close first receipt"); - let after_closure: Vec<(i64, i64, i64, i32)> = sqlx::query_as(&format!( + let after_closure: Vec<(i64, i64, i64, i32)> = sqlx::query_as(audited_sql(format!( "SELECT job_id, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -9095,11 +9105,11 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("complete through compact receipt batch"); - let compact_terminal_batches: i64 = sqlx::query_scalar(&format!( + let compact_terminal_batches: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.receipt_completion_batches WHERE job_ids @> ARRAY[$1]::bigint[]" - )) + ))) .bind(compact_job_id) .fetch_one(&pool) .await @@ -9118,31 +9128,31 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc .await .expect("reset claim cursor for compact terminal-evidence phase"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.lease_claim_closure_batches WHERE claim_slot = $1" - )) + ))) .bind(compact_claim_slot) .execute(&pool) .await .expect("remove compact claim-ring closure evidence"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.lease_claim_closures WHERE job_id = $1 AND run_lease = $2" - )) + ))) .bind(compact_job_id) .bind(compact_run_lease) .execute(&pool) .await .expect("remove explicit closure evidence"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DELETE FROM {schema}.lease_claims WHERE job_id = $1 AND run_lease = $2" - )) + ))) .bind(compact_job_id) .bind(compact_run_lease) .execute(&pool) .await .expect("remove claim evidence"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" DELETE FROM {schema}.lease_claim_batches AS claim_batches WHERE EXISTS ( @@ -9151,14 +9161,14 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc WHERE items.job_id = $1 AND items.run_lease = $2 ) "# - )) + ))) .bind(compact_job_id) .bind(compact_run_lease) .execute(&pool) .await .expect("remove compact claim evidence"); - let remaining_attempt_batches: i64 = sqlx::query_scalar(&format!( + let remaining_attempt_batches: i64 = sqlx::query_scalar(audited_sql(format!( r#" SELECT count(*)::bigint FROM {schema}.ready_claim_attempt_batches @@ -9172,7 +9182,7 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc AND next_lane_seq > $7 AND lane_ranges @> int8range($7, $7 + 1, '[)') "# - )) + ))) .bind(compact_claimed[0].claim.ready_slot) .bind(compact_claimed[0].claim.ready_generation) .bind(queue) @@ -9188,10 +9198,10 @@ async fn test_queue_storage_receipt_claim_dedupes_when_post_commit_cursor_advanc "ready-claim-attempt evidence must remain until the ready slot is pruned with ready_entries" ); - let after_receipt_prune: Vec<(i64, i64, i64, i32)> = sqlx::query_as(&format!( + let after_receipt_prune: Vec<(i64, i64, i64, i32)> = sqlx::query_as(audited_sql(format!( "SELECT job_id, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -9250,11 +9260,11 @@ async fn test_queue_storage_receipt_multi_row_claim_dedupes_when_cursor_advance_ } let claim_cursor = || async { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT {schema}.sequence_next_value(seq_name) FROM {schema}.queue_claim_heads WHERE queue = $1 AND priority = $2 AND enqueue_shard = $3" - )) + ))) .bind(queue) .bind(2_i16) .bind(0_i16) @@ -9266,10 +9276,10 @@ async fn test_queue_storage_receipt_multi_row_claim_dedupes_when_cursor_advance_ // Raw claim of the whole lane in one batch; calling the SQL function // directly leaves the post-commit claim cursor advance unsent, exactly // as a worker crash between commit and advance would. - let first: Vec = sqlx::query_as(&format!( + let first: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(batch_len) .bind(0.0_f64) @@ -9297,7 +9307,7 @@ async fn test_queue_storage_receipt_multi_row_claim_dedupes_when_cursor_advance_ // The whole claimed range is durably recorded as attempt-ledger evidence, // so recovery can dedup every lane without the per-row probe. - let attempt_batch_total: i64 = sqlx::query_scalar(&format!( + let attempt_batch_total: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT COALESCE(sum(claimed_count), 0)::bigint FROM {schema}.ready_claim_attempt_batches WHERE ready_slot = $1 @@ -9306,7 +9316,7 @@ async fn test_queue_storage_receipt_multi_row_claim_dedupes_when_cursor_advance_ AND priority = $4 AND enqueue_shard = $5 AND lane_ranges @> int8range(1, $6 + 1, '[)')" - )) + ))) .bind(first[0].ready_slot) .bind(first[0].ready_generation) .bind(queue) @@ -9323,10 +9333,10 @@ async fn test_queue_storage_receipt_multi_row_claim_dedupes_when_cursor_advance_ // Re-claim with the stale cursor: every lane in the batch must be deduped, // and the cursor must advance past the whole spent range. - let second: Vec = sqlx::query_as(&format!( + let second: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(batch_len) .bind(0.0_f64) @@ -9916,9 +9926,9 @@ async fn test_queue_storage_prune_waits_until_ready_tombstone_cursor_spent() { ); let mut reader_tx = pool.begin().await.expect("begin ready reader tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {schema}.ready_entries_0, {schema}.done_entries_0, {schema}.ready_tombstones_0, {schema}.receipt_completion_batches_0, {schema}.receipt_completion_tombstones_0, {schema}.queue_terminal_count_deltas_0 IN ACCESS SHARE MODE" - )) + ))) .execute(reader_tx.as_mut()) .await .expect("lock queue prune children in access share mode"); @@ -9944,10 +9954,10 @@ async fn test_queue_storage_prune_waits_until_ready_tombstone_cursor_spent() { .await .expect("release ready reader lock"); - let claimed: Vec = sqlx::query_as(&format!( + let claimed: Vec = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, job_id, priority, attempt, run_lease, lane_seq, claim_slot FROM {schema}.claim_ready_runtime($1, $2, $3, $4)" - )) + ))) .bind(queue) .bind(1_i64) .bind(0.0_f64) @@ -9977,9 +9987,9 @@ async fn test_queue_storage_prune_waits_until_ready_tombstone_cursor_spent() { 0, "queue prune should truncate tombstones with the matching ready slot" ); - let ready_rows: i64 = sqlx::query_scalar(&format!( + let ready_rows: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -10027,9 +10037,9 @@ async fn test_queue_storage_prune_pending_ready_match_is_scoped_by_enqueue_shard ) .await; - let ready_heads: Vec<(i16, i64)> = sqlx::query_as(&format!( + let ready_heads: Vec<(i16, i64)> = sqlx::query_as(audited_sql(format!( "SELECT enqueue_shard, lane_seq FROM {schema}.ready_entries WHERE queue = $1 ORDER BY enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -10096,7 +10106,7 @@ async fn test_queue_storage_queue_counts_reads_legacy_lane_rollups_and_backfills let schema = "awa_qs_legacy_pruned_rollup"; let store = create_store(&pool, schema).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_lanes ( queue, @@ -10109,7 +10119,7 @@ async fn test_queue_storage_queue_counts_reads_legacy_lane_rollups_and_backfills ON CONFLICT (queue, priority) DO UPDATE SET pruned_completed_count = EXCLUDED.pruned_completed_count "# - )) + ))) .bind(queue) .execute(&pool) .await @@ -10126,18 +10136,18 @@ async fn test_queue_storage_queue_counts_reads_legacy_lane_rollups_and_backfills .await .expect("Failed to rerun queue storage schema preparation"); - let legacy_lane_rollup: i64 = sqlx::query_scalar(&format!( + let legacy_lane_rollup: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT pruned_completed_count FROM {schema}.queue_lanes WHERE queue = $1 AND priority = 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await .expect("Failed to read legacy lane rollup after backfill"); assert_eq!(legacy_lane_rollup, 0); - let cold_rollup: i64 = sqlx::query_scalar(&format!( + let cold_rollup: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT pruned_completed_count FROM {schema}.queue_terminal_rollups WHERE queue = $1 AND priority = 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -10225,7 +10235,7 @@ async fn test_available_count_matches_ready_entries_scan() { ) { let schema = store.schema(); // Ground-truth scan — same available-row predicate as the exact API. - let scan: i64 = sqlx::query_scalar(&format!( + let scan: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_entries AS ready JOIN {schema}.queue_claim_heads AS claims @@ -10244,7 +10254,7 @@ async fn test_available_count_matches_ready_entries_scan() { AND tomb.enqueue_shard = ready.enqueue_shard AND tomb.lane_seq = ready.lane_seq )" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -10266,7 +10276,7 @@ async fn test_available_count_matches_ready_entries_scan() { // asserts a never-undercount invariant on the hot-path // approximation, which is allowed to drift up by the number // of mid-ring deletes since the last claim on that lane. - let derived_approx: i64 = sqlx::query_scalar(&format!( + let derived_approx: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT COALESCE( sum(GREATEST( {schema}.sequence_next_value(qe.seq_name) @@ -10281,7 +10291,7 @@ async fn test_available_count_matches_ready_entries_scan() { AND qc.priority = qe.priority AND qc.enqueue_shard = qe.enqueue_shard WHERE qe.queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -10340,7 +10350,7 @@ async fn test_available_count_matches_ready_entries_scan() { // ── checkpoint 5: cancel an available row ──────────────────────── // Pick a still-available job at priority 2 and cancel it. - let candidate: i64 = sqlx::query_scalar(&format!( + let candidate: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT job_id FROM {schema}.ready_entries AS ready JOIN {schema}.queue_claim_heads AS claims @@ -10352,7 +10362,7 @@ async fn test_available_count_matches_ready_entries_scan() { AND ready.lane_seq >= {schema}.sequence_next_value(claims.seq_name) ORDER BY ready.lane_seq ASC LIMIT 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -10411,13 +10421,13 @@ async fn test_available_count_matches_ready_entries_scan() { // Same compat route in reverse. The SQL function cannot safely move the // non-transactional claim sequence before its caller's transaction commits, // so this pins the exact API count and never-undercount hot-path contract. - let compat_id: i64 = sqlx::query_scalar(&format!( + let compat_id: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT job_id FROM {schema}.ready_entries WHERE queue = $1 AND kind = 'compat_kind' ORDER BY lane_seq DESC LIMIT 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -10453,13 +10463,13 @@ async fn test_queue_storage_queue_counts_and_claims_aggregate_across_stripes() { .await .expect("Failed to enqueue striped jobs"); - let physical_queues: Vec = sqlx::query_scalar(&format!( + let physical_queues: Vec = sqlx::query_scalar(audited_sql(format!( r#" SELECT DISTINCT queue FROM {schema}.ready_entries ORDER BY queue "# - )) + ))) .fetch_all(&pool) .await .expect("Failed to read physical stripe queues"); @@ -10885,11 +10895,11 @@ async fn test_queue_storage_compact_receipt_completion_is_idempotent_without_clo 1, "idempotent compact success should keep one claim-closure batch" ); - let closure_batch_segment: (i32, i64, i32) = sqlx::query_as(&format!( + let closure_batch_segment: (i32, i64, i32) = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, ready_generation, closed_count FROM {schema}.lease_claim_closure_batches WHERE claim_slot = $1" - )) + ))) .bind(claimed[0].claim.claim_slot) .fetch_one(&pool) .await @@ -10903,11 +10913,11 @@ async fn test_queue_storage_compact_receipt_completion_is_idempotent_without_clo ), "compact closure batches must carry ready segment metadata for prune count proofs" ); - let closure_receipt_ids: Vec = sqlx::query_scalar(&format!( + let closure_receipt_ids: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT receipt_ids FROM {schema}.lease_claim_closure_batches WHERE claim_slot = $1" - )) + ))) .bind(claimed[0].claim.claim_slot) .fetch_one(&pool) .await @@ -11086,10 +11096,10 @@ async fn test_queue_terminal_live_counts_prune_folds_into_rollups() { 0, "fold must drain the unfolded rollup deltas" ); - let folded: i64 = sqlx::query_scalar::<_, i64>(&format!( + let folded: i64 = sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT COALESCE(SUM(pruned_completed_count), 0)::bigint \ FROM {schema}.queue_terminal_rollups WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -11144,10 +11154,10 @@ async fn test_queue_storage_prune_carries_failed_rows_inside_retention_floor() { } let carried_job_id = job_ids[1]; let expired_job_id = job_ids[2]; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.done_entries SET finalized_at = now() - interval '2 hours' \ WHERE job_id = $1" - )) + ))) .bind(expired_job_id) .execute(&pool) .await @@ -11180,9 +11190,9 @@ async fn test_queue_storage_prune_carries_failed_rows_inside_retention_floor() { // The fresh failed row was carried to the live slot as a wide, // self-contained row; the completed and expired rows are gone. assert_eq!(done_entries_count(&pool, schema, queue).await, 1); - let (carried_slot, carried_state): (i32, String) = sqlx::query_as(&format!( + let (carried_slot, carried_state): (i32, String) = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, state::text FROM {schema}.done_entries WHERE job_id = $1" - )) + ))) .bind(carried_job_id) .fetch_one(&pool) .await @@ -11270,10 +11280,10 @@ async fn test_queue_storage_retry_failed_outcome_surfaces_pruned_rows() { } let carried_job_id = job_ids[0]; let expired_job_id = job_ids[1]; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.done_entries SET finalized_at = now() - interval '2 hours' \ WHERE job_id = $1" - )) + ))) .bind(expired_job_id) .execute(&pool) .await @@ -11459,9 +11469,9 @@ async fn test_queue_storage_prune_carry_forward_survives_retry_and_rebuild() { // self-contained wide rows in the live slot with intact bodies. assert_eq!(done_entries_count(&pool, schema, queue).await, 2); for &failed_id in &failed_ids { - let (carried_slot, carried_state): (i32, String) = sqlx::query_as(&format!( + let (carried_slot, carried_state): (i32, String) = sqlx::query_as(audited_sql(format!( "SELECT ready_slot, state::text FROM {schema}.done_entries WHERE job_id = $1" - )) + ))) .bind(failed_id) .fetch_one(&pool) .await @@ -11741,9 +11751,9 @@ async fn test_queue_storage_prune_re_carries_failed_row_exactly_once() { 1, "exactly one done row after first carry" ); - let first_slot: i32 = sqlx::query_scalar(&format!( + let first_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT ready_slot FROM {schema}.done_entries WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -11760,12 +11770,12 @@ async fn test_queue_storage_prune_re_carries_failed_row_exactly_once() { // the ring busy. After the three hops the cursor is (slot 0, // generation 4), which makes `oldest_initialized_ring_slot` target // slot 1 for the second prune. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_ring_rotations (generation, slot) VALUES (2, 2), (3, 3), (4, 0) "# - )) + ))) .execute(&pool) .await .expect("lap queue ring cursor around to slot 0 for re-carry setup"); @@ -11792,9 +11802,9 @@ async fn test_queue_storage_prune_re_carries_failed_row_exactly_once() { 1, "re-carry must not duplicate the done row" ); - let second_slot: i32 = sqlx::query_scalar(&format!( + let second_slot: i32 = sqlx::query_scalar(audited_sql(format!( "SELECT ready_slot FROM {schema}.done_entries WHERE job_id = $1" - )) + ))) .bind(job_id) .fetch_one(&pool) .await @@ -11987,10 +11997,10 @@ async fn test_queue_terminal_count_delta_rollup_skips_empty_old_slots() { // are derived from the ledger. A loaded ring laps the same way; this // test only needs the sealed-slot bookkeeping, not the traffic that // would normally drive it. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.queue_ring_rotations (generation, slot) \ SELECT g, g::int FROM generate_series(1, 21) AS g" - )) + ))) .execute(&pool) .await .expect("advance queue ring cursor to slot 21"); @@ -11998,17 +12008,17 @@ async fn test_queue_terminal_count_delta_rollup_skips_empty_old_slots() { let target_slot = 20_i32; // Sealed-slot generation is derived from the ledger (#371): the // largest generation whose slot equals target_slot. - let target_generation: i64 = sqlx::query_scalar(&format!( + let target_generation: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT generation FROM {schema}.queue_ring_rotations \ WHERE slot = $1 ORDER BY generation DESC LIMIT 1" - )) + ))) .bind(target_slot) .fetch_one(&pool) .await .expect("target slot generation"); assert!(target_generation >= 0); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_terminal_count_deltas ( ready_slot, ready_generation, queue, priority, enqueue_shard, @@ -12016,7 +12026,7 @@ async fn test_queue_terminal_count_delta_rollup_skips_empty_old_slots() { ) VALUES ($1, $2, $3, 1, 0, 0, 7) "# - )) + ))) .bind(target_slot) .bind(target_generation) .bind(queue) @@ -12059,10 +12069,10 @@ async fn seed_terminal_rows_with_kind( // Insert N done_entries rows at (ready_slot=0, priority=2, // enqueue_shard=0). lane_seq stays unique per call by reading the // current max and adding rownums. - let next_lane_seq: i64 = sqlx::query_scalar::<_, i64>(&format!( + let next_lane_seq: i64 = sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT COALESCE(max(lane_seq), 0)::bigint + 1 \ FROM {schema}.done_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12073,7 +12083,7 @@ async fn seed_terminal_rows_with_kind( .fetch_one(pool) .await .unwrap_or(1_000_000); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, state, @@ -12097,7 +12107,7 @@ async fn seed_terminal_rows_with_kind( '{{}}'::jsonb FROM generate_series(1, $6::int) AS g "# - )) + ))) .bind(next_job_id) .bind(kind) .bind(queue) @@ -12108,7 +12118,7 @@ async fn seed_terminal_rows_with_kind( .await .expect("seed done_entries"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.queue_terminal_live_counts AS counts ( ready_slot, queue, priority, enqueue_shard, counter_bucket, live_terminal_count @@ -12128,7 +12138,7 @@ async fn seed_terminal_rows_with_kind( ON CONFLICT (ready_slot, queue, priority, enqueue_shard, counter_bucket) DO UPDATE SET live_terminal_count = counts.live_terminal_count + EXCLUDED.live_terminal_count "# - )) + ))) .bind(queue) .bind(next_job_id) .bind(n as i32) @@ -12138,10 +12148,10 @@ async fn seed_terminal_rows_with_kind( } async fn first_failed_job_id(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT job_id FROM {schema}.done_entries \ WHERE queue = $1 AND state = 'failed' LIMIT 1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12149,10 +12159,10 @@ async fn first_failed_job_id(pool: &sqlx::PgPool, schema: &str, queue: &str) -> } async fn first_cancelled_job_id(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT job_id FROM {schema}.done_entries \ WHERE queue = $1 AND state = 'cancelled' LIMIT 1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12188,9 +12198,9 @@ async fn test_queue_terminal_live_counts_decrement_on_sql_compat_delete() { assert_eq!(done_entries_count(&pool, schema, queue).await, 5); assert_eq!(live_count_sum(&pool, schema, queue).await, 5); - let target_id: i64 = sqlx::query_scalar::<_, i64>(&format!( + let target_id: i64 = sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT job_id FROM {schema}.done_entries WHERE queue = $1 LIMIT 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -12222,9 +12232,9 @@ async fn test_queue_terminal_live_counts_rebuild_restores_invariant() { // Seed 7 terminal rows with matching counter entries, then manually // poison the counter to simulate rollover drift. seed_terminal_rows(&pool, schema, queue, "completed", 7).await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_terminal_live_counts SET live_terminal_count = 999 WHERE queue = $1" - )) + ))) .bind(queue) .execute(&pool) .await @@ -12284,7 +12294,7 @@ async fn test_queue_terminal_counter_trust_marker_gates_read_path() { // counter increment. // 2. Manually clearing the trust marker (operator hasn't yet run // `awa storage rebuild-terminal-counters`). - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.done_entries ( ready_slot, ready_generation, job_id, kind, queue, state, priority, attempt, run_lease, lane_seq, enqueue_shard, @@ -12292,15 +12302,15 @@ async fn test_queue_terminal_counter_trust_marker_gates_read_path() { ) VALUES (0, 1, 7000000, 'chaos_job', $1, 'completed'::awa.job_state, 2::smallint, 1::smallint, 1::bigint, 9999::bigint, 0::smallint, now(), now(), '{{}}'::jsonb)" - )) + ))) .bind(queue) .execute(&pool) .await .expect("seed orphan done row"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_ring_state \ SET terminal_counter_trusted_at = NULL WHERE singleton = TRUE" - )) + ))) .execute(&pool) .await .expect("clear trust marker"); @@ -12399,10 +12409,10 @@ async fn test_queue_terminal_counts_include_compact_receipt_batches() { "trusted exact counts include retained compact receipt batches directly" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_ring_state \ SET terminal_counter_trusted_at = NULL WHERE singleton = TRUE" - )) + ))) .execute(&pool) .await .expect("clear trust marker"); @@ -12417,9 +12427,9 @@ async fn test_queue_terminal_counts_include_compact_receipt_batches() { "untrusted path must count compact receipt completions via terminal_jobs" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "TRUNCATE TABLE {schema}.queue_terminal_live_counts, {schema}.queue_terminal_count_deltas" - )) + ))) .execute(&pool) .await .expect("clear counters before rebuild"); @@ -12446,10 +12456,10 @@ async fn test_queue_terminal_counts_include_compact_receipt_batches() { } async fn live_count_sum(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT COALESCE(SUM(live_terminal_count), 0)::bigint \ FROM {schema}.queue_terminal_live_counts WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12457,10 +12467,10 @@ async fn live_count_sum(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { } async fn terminal_delta_sum(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT COALESCE(SUM(terminal_delta), 0)::bigint \ FROM {schema}.queue_terminal_count_deltas WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12468,10 +12478,10 @@ async fn terminal_delta_sum(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i } async fn terminal_delta_row_count(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint \ FROM {schema}.queue_terminal_count_deltas WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12489,7 +12499,7 @@ async fn terminal_counter_sum(pool: &sqlx::PgPool, schema: &str, queue: &str) -> /// same folded-plus-pending sum the production count readers do — the sum /// is stable across fold timing while still pinning the exact totals. async fn pruned_rollup_sums(pool: &sqlx::PgPool, schema: &str, queue: &str) -> (i64, i64) { - sqlx::query_as::<_, (i64, i64)>(&format!( + sqlx::query_as::<_, (i64, i64)>(audited_sql(format!( "SELECT COALESCE(( SELECT SUM(pruned_completed_count) @@ -12507,7 +12517,7 @@ async fn pruned_rollup_sums(pool: &sqlx::PgPool, schema: &str, queue: &str) -> ( SELECT SUM(pruned_failed_delta) FROM {schema}.queue_terminal_rollup_deltas WHERE queue = $1 ), 0)::bigint" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12517,10 +12527,10 @@ async fn pruned_rollup_sums(pool: &sqlx::PgPool, schema: &str, queue: &str) -> ( /// Number of unfolded `queue_terminal_rollup_deltas` rows for a queue /// (#371). Zero after a fold clears the horizon-gated deltas. async fn pruned_delta_row_count(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint \ FROM {schema}.queue_terminal_rollup_deltas WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12528,9 +12538,9 @@ async fn pruned_delta_row_count(pool: &sqlx::PgPool, schema: &str, queue: &str) } async fn done_entries_count(pool: &sqlx::PgPool, schema: &str, queue: &str) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.done_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(pool) .await @@ -12681,9 +12691,9 @@ async fn backdate_one_lane_ready_job_run_at( job_id: i64, run_at: DateTime, ) { - let updated_ready = sqlx::query(&format!( + let updated_ready = sqlx::query(audited_sql(format!( "UPDATE {schema}.ready_entries SET run_at = $1 WHERE job_id = $2" - )) + ))) .bind(run_at) .bind(job_id) .execute(pool) @@ -12695,7 +12705,7 @@ async fn backdate_one_lane_ready_job_run_at( "expected exactly one ready row for manual aging backdate" ); - let updated_segment = sqlx::query(&format!( + let updated_segment = sqlx::query(audited_sql(format!( r#" WITH target AS ( SELECT @@ -12719,7 +12729,7 @@ async fn backdate_one_lane_ready_job_run_at( AND segment.first_lane_seq = target.lane_seq AND segment.next_lane_seq = target.lane_seq + 1 "# - )) + ))) .bind(run_at) .bind(job_id) .execute(pool) @@ -12888,9 +12898,9 @@ async fn test_queue_storage_aged_completion_stays_compact_and_keeps_lane_priorit "both successful completions should use compact receipt batches" ); - let stored_priority: i16 = sqlx::query_scalar(&format!( + let stored_priority: i16 = sqlx::query_scalar(audited_sql(format!( "SELECT priority FROM {schema}.terminal_jobs WHERE job_id = $1" - )) + ))) .bind(low_id) .fetch_one(&pool) .await @@ -12943,9 +12953,9 @@ async fn test_queue_storage_bounded_claimers_can_steal_idle_slot() { .expect("instance A should acquire claimer") .expect("instance A should get a claimer slot"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_claimer_leases SET last_claimed_at = $1 WHERE queue = $2 AND claimer_slot = $3" - )) + ))) .bind(Utc::now() - chrono::Duration::milliseconds(1_000)) .bind(queue) .bind(lease_a.claimer_slot) @@ -12982,9 +12992,9 @@ async fn test_queue_storage_claimer_heartbeat_skips_fresh_lease() { .expect("instance should acquire claimer") .expect("instance should get a claimer slot"); - let before: DateTime = sqlx::query_scalar(&format!( + let before: DateTime = sqlx::query_scalar(audited_sql(format!( "SELECT last_claimed_at FROM {schema}.queue_claimer_leases WHERE queue = $1 AND claimer_slot = $2" - )) + ))) .bind(queue) .bind(lease.claimer_slot) .fetch_one(&pool) @@ -13011,9 +13021,9 @@ async fn test_queue_storage_claimer_heartbeat_skips_fresh_lease() { .expect("fresh lease claim should succeed"); assert_eq!(claimed.len(), 1); - let after_fresh: DateTime = sqlx::query_scalar(&format!( + let after_fresh: DateTime = sqlx::query_scalar(audited_sql(format!( "SELECT last_claimed_at FROM {schema}.queue_claimer_leases WHERE queue = $1 AND claimer_slot = $2" - )) + ))) .bind(queue) .bind(lease.claimer_slot) .fetch_one(&pool) @@ -13024,9 +13034,9 @@ async fn test_queue_storage_claimer_heartbeat_skips_fresh_lease() { "fresh heartbeat should not rewrite queue_claimer_leases" ); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.queue_claimer_leases SET last_claimed_at = $1 WHERE queue = $2 AND claimer_slot = $3" - )) + ))) .bind( Utc::now() - chrono::Duration::from_std(idle_threshold + Duration::from_secs(1)) @@ -13058,9 +13068,9 @@ async fn test_queue_storage_claimer_heartbeat_skips_fresh_lease() { .expect("stale lease claim should succeed"); assert_eq!(claimed.len(), 1); - let after_stale: DateTime = sqlx::query_scalar(&format!( + let after_stale: DateTime = sqlx::query_scalar(audited_sql(format!( "SELECT last_claimed_at FROM {schema}.queue_claimer_leases WHERE queue = $1 AND claimer_slot = $2" - )) + ))) .bind(queue) .bind(lease.claimer_slot) .fetch_one(&pool) @@ -13104,9 +13114,9 @@ async fn test_queue_storage_prune_oldest_blocks_on_reader_lock() { ); let mut reader_tx = pool.begin().await.expect("Failed to begin reader lock tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {schema}.ready_entries_0, {schema}.done_entries_0 IN ACCESS SHARE MODE" - )) + ))) .execute(reader_tx.as_mut()) .await .expect("Failed to lock ready/done reader tables"); @@ -14022,20 +14032,20 @@ async fn test_queue_storage_jobs_view_insert_select_delete_compat() { assert_eq!(jobs[1].id, scheduled_id); assert_eq!(jobs[1].state, JobState::Scheduled); - let ready_count: i64 = sqlx::query_scalar(&format!( + let ready_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await .expect("Failed to count ready entries"); assert_eq!(ready_count, 1); - let deferred_count: i64 = sqlx::query_scalar(&format!( + let deferred_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.deferred_jobs WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14055,26 +14065,26 @@ async fn test_queue_storage_jobs_view_insert_select_delete_compat() { .fetch_one(&pool) .await .expect("Failed to count remaining awa.jobs rows"); - let retained_ready_after_delete: i64 = sqlx::query_scalar(&format!( + let retained_ready_after_delete: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_entries WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await .expect("Failed to recount ready entries"); - let tombstones_after_delete: i64 = sqlx::query_scalar(&format!( + let tombstones_after_delete: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.ready_tombstones WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await .expect("Failed to count ready tombstones"); - let deferred_after_delete: i64 = sqlx::query_scalar(&format!( + let deferred_after_delete: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.deferred_jobs WHERE queue = $1", store.schema() - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14127,9 +14137,9 @@ async fn test_priority_aging_lifts_effective_priority_and_records_original() { // Backdate past two aging windows so floor(elapsed / interval) = 2, // i.e. a priority-4 row's effective priority becomes 2. let aging_interval = Duration::from_millis(100); - let job_id = sqlx::query_scalar::<_, i64>(&format!( + let job_id = sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT job_id FROM {schema}.ready_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14279,7 +14289,7 @@ async fn test_queue_storage_ensure_lane_cache_recovers_after_rollback() { format!("DELETE FROM {schema}.queue_lanes WHERE queue = $1 AND priority = $2"), format!("DELETE FROM {schema}.ready_entries WHERE queue = $1 AND priority = $2"), ] { - sqlx::query(&stmt) + sqlx::query(audited_sql(stmt.clone())) .bind(queue) .bind(4_i16) .execute(&pool) @@ -14295,8 +14305,9 @@ async fn test_queue_storage_ensure_lane_cache_recovers_after_rollback() { .await .expect("post-rollback enqueue should self-heal via cache invalidation"); - let (next_seq, ready_count, max_lane_seq): (i64, i64, Option) = sqlx::query_as(&format!( - "SELECT + let (next_seq, ready_count, max_lane_seq): (i64, i64, Option) = + sqlx::query_as(audited_sql(format!( + "SELECT {schema}.sequence_next_value(heads.seq_name), count(ready.*)::bigint, max(ready.lane_seq) @@ -14307,12 +14318,12 @@ async fn test_queue_storage_ensure_lane_cache_recovers_after_rollback() { AND ready.enqueue_shard = heads.enqueue_shard WHERE heads.queue = $1 AND heads.priority = $2 GROUP BY heads.seq_name" - )) - .bind(queue) - .bind(4_i16) - .fetch_one(&pool) - .await - .expect("queue_enqueue_heads row should exist after recovery"); + ))) + .bind(queue) + .bind(4_i16) + .fetch_one(&pool) + .await + .expect("queue_enqueue_heads row should exist after recovery"); assert_eq!( ready_count, 3, @@ -14404,14 +14415,14 @@ async fn test_queue_storage_multi_shard_round_trip_through_completion() { } // Every shard should surface at least one public terminal row. - let shard_counts: Vec<(i16, i64)> = sqlx::query_as(&format!( + let shard_counts: Vec<(i16, i64)> = sqlx::query_as(audited_sql(format!( "SELECT enqueue_shard, count(*)::bigint FROM {schema}.terminal_jobs WHERE queue = $1 AND state = 'completed' GROUP BY enqueue_shard ORDER BY enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -14434,7 +14445,7 @@ async fn test_queue_storage_multi_shard_round_trip_through_completion() { // lane_seq)` tuple. Each shard's `lane_seq` starts independently at // 1, so at S=4 with 4 jobs per shard there must be at least one // tuple that repeats. - let max_dupes: i64 = sqlx::query_scalar(&format!( + let max_dupes: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT COALESCE(max(c), 0)::bigint FROM ( SELECT count(*) AS c FROM {schema}.terminal_jobs @@ -14442,7 +14453,7 @@ async fn test_queue_storage_multi_shard_round_trip_through_completion() { AND state = 'completed' GROUP BY ready_slot, queue, priority, lane_seq ) AS grouped" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14496,9 +14507,9 @@ async fn test_queue_storage_multi_shard_public_available_counts_are_exact() { .await; } - let direct_ready_count: i64 = sqlx::query_scalar(&format!( + let direct_ready_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.ready_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14615,9 +14626,9 @@ async fn test_queue_storage_ordering_key_routes_to_stable_shard() { } } - let rows: Vec<(i64, i16)> = sqlx::query_as(&format!( + let rows: Vec<(i64, i16)> = sqlx::query_as(audited_sql(format!( "SELECT job_id, enqueue_shard FROM {schema}.ready_entries WHERE queue = $1" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -14704,13 +14715,13 @@ async fn test_queue_storage_multi_shard_claim_path_does_not_starve_shards() { } } - let pre_counts: Vec<(i16, i64)> = sqlx::query_as(&format!( + let pre_counts: Vec<(i16, i64)> = sqlx::query_as(audited_sql(format!( "SELECT enqueue_shard, count(*)::bigint FROM {schema}.ready_entries WHERE queue = $1 GROUP BY enqueue_shard ORDER BY enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -14726,12 +14737,12 @@ async fn test_queue_storage_multi_shard_claim_path_does_not_starve_shards() { let deadline = Instant::now() + Duration::from_secs(30); loop { - let done_count: i64 = sqlx::query_scalar(&format!( + let done_count: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.terminal_jobs WHERE queue = $1 AND state = 'completed'" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -14747,7 +14758,7 @@ async fn test_queue_storage_multi_shard_claim_path_does_not_starve_shards() { tokio::time::sleep(Duration::from_millis(25)).await; } - let heads: Vec<(i16, i64, i64)> = sqlx::query_as(&format!( + let heads: Vec<(i16, i64, i64)> = sqlx::query_as(audited_sql(format!( "SELECT claims.enqueue_shard, {schema}.sequence_next_value(claims.seq_name) AS claim_seq, {schema}.sequence_next_value(enqueues.seq_name) AS next_seq @@ -14758,7 +14769,7 @@ async fn test_queue_storage_multi_shard_claim_path_does_not_starve_shards() { AND enqueues.enqueue_shard = claims.enqueue_shard WHERE claims.queue = $1 ORDER BY claims.enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -14845,12 +14856,12 @@ async fn test_queue_storage_lowering_enqueue_shards_drains_existing_rows() { } } - let pre_shards: Vec = sqlx::query_scalar(&format!( + let pre_shards: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT DISTINCT enqueue_shard FROM {schema}.ready_entries WHERE queue = $1 ORDER BY enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await @@ -14889,13 +14900,13 @@ async fn test_queue_storage_lowering_enqueue_shards_drains_existing_rows() { .await; } - let done_shards: Vec = sqlx::query_scalar(&format!( + let done_shards: Vec = sqlx::query_scalar(audited_sql(format!( "SELECT DISTINCT enqueue_shard FROM {schema}.terminal_jobs WHERE queue = $1 AND state = 'completed' ORDER BY enqueue_shard" - )) + ))) .bind(queue) .fetch_all(&pool) .await diff --git a/awa/tests/queue_storage_soak_test.rs b/awa/tests/queue_storage_soak_test.rs index ef2f54e5..4689b25b 100644 --- a/awa/tests/queue_storage_soak_test.rs +++ b/awa/tests/queue_storage_soak_test.rs @@ -5,6 +5,7 @@ //! confined to the small hot tables. use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert, migrations, storage, QueueStorage, QueueStorageConfig}; use awa::{Client, InsertOpts, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; use serde::{Deserialize, Serialize}; @@ -72,7 +73,10 @@ async fn ensure_database_exists(url: &str) { .await .expect("Failed to connect to admin database for queue_storage soak tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => { @@ -115,7 +119,7 @@ async fn ensure_pgstattuple(pool: &sqlx::PgPool) { async fn recreate_store_schema(pool: &sqlx::PgPool, store: &QueueStorage) { let drop_sql = format!("DROP SCHEMA IF EXISTS {} CASCADE", store.schema()); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(pool) .await .expect("Failed to drop queue_storage soak schema"); @@ -253,7 +257,7 @@ async fn attempt_state_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { "SELECT count(*)::bigint FROM {}.attempt_state", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .fetch_one(pool) .await .expect("Failed to count attempt_state rows") @@ -264,7 +268,7 @@ async fn dlq_depth(pool: &sqlx::PgPool, store: &QueueStorage, queue: &str) -> i6 "SELECT count(*)::bigint FROM {}.dlq_entries WHERE queue = $1", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .bind(queue) .fetch_one(pool) .await @@ -276,7 +280,7 @@ async fn queue_storage_done_count(pool: &sqlx::PgPool, store: &QueueStorage, que "SELECT count(*)::bigint FROM {}.done_entries WHERE queue = $1 AND state = 'completed'", store.schema() ); - sqlx::query_scalar::<_, i64>(&sql) + sqlx::query_scalar::<_, i64>(audited_sql(sql.clone())) .bind(queue) .fetch_one(pool) .await diff --git a/awa/tests/receipt_plane_chaos_test.rs b/awa/tests/receipt_plane_chaos_test.rs index 8981f410..5d1e6811 100644 --- a/awa/tests/receipt_plane_chaos_test.rs +++ b/awa/tests/receipt_plane_chaos_test.rs @@ -7,6 +7,7 @@ //! barrier between TRUNCATE and concurrent inserts, and the orphan- //! lease race between admin cancel and materialize. +use awa::audited_sql; use awa::model::{ admin, insert, migrations, storage, PruneOutcome, QueueStorage, QueueStorageConfig, RotateOutcome, @@ -106,10 +107,12 @@ async fn create_store(pool: &sqlx::PgPool, schema: &str, claim_slot_count: usize ..Default::default() }) .expect("queue storage"); - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await - .expect("drop store schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await + .expect("drop store schema"); sqlx::raw_sql( r#" TRUNCATE @@ -139,50 +142,50 @@ async fn create_store(pool: &sqlx::PgPool, schema: &str, claim_slot_count: usize } async fn lease_claim_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.lease_claims", store.schema() - )) + ))) .fetch_one(pool) .await .expect("count lease_claims") } async fn lease_claim_closure_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.lease_claim_closures", store.schema() - )) + ))) .fetch_one(pool) .await .expect("count lease_claim_closures") } async fn lease_claim_batch_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.lease_claim_batches", store.schema() - )) + ))) .fetch_one(pool) .await .expect("count lease_claim_batches") } async fn lease_claim_closure_batch_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.lease_claim_closure_batches", store.schema() - )) + ))) .fetch_one(pool) .await .expect("count lease_claim_closure_batches") } async fn leases_count(pool: &sqlx::PgPool, store: &QueueStorage) -> i64 { - sqlx::query_scalar(&format!( + sqlx::query_scalar(audited_sql(format!( "SELECT count(*)::bigint FROM {}.leases", store.schema() - )) + ))) .fetch_one(pool) .await .expect("count leases") @@ -197,7 +200,7 @@ async fn insert_synthetic_open_claim( queue: &str, claimed_at: chrono::DateTime, ) { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.lease_claims ( claim_slot, job_id, run_lease, ready_slot, ready_generation, @@ -205,7 +208,7 @@ async fn insert_synthetic_open_claim( claimed_at, materialized_at ) VALUES ($1, $2, $3, 0, 0, $4, 2, 1, 25, $2, $5, NULL) "# - )) + ))) .bind(claim_slot) .bind(job_id) .bind(run_lease) @@ -230,7 +233,7 @@ async fn insert_synthetic_claim_with_ready_row( queue: &str, claimed_at: chrono::DateTime, ) { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.ready_entries ( ready_slot, ready_generation, job_id, kind, queue, @@ -242,7 +245,7 @@ async fn insert_synthetic_claim_with_ready_row( clock_timestamp(), clock_timestamp(), clock_timestamp(), '{{}}'::jsonb ) "# - )) + ))) .bind(job_id) .bind(queue) .bind(run_lease) @@ -402,9 +405,9 @@ async fn test_prune_skips_active_under_concurrent_traffic() { "expected at least one SkippedActive, got {skipped_count}" ); - let survived: i64 = sqlx::query_scalar(&format!( + let survived: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT count(*) FROM {schema}.lease_claims_0 WHERE job_id = 99999" - )) + ))) .fetch_one(&pool) .await .expect("count survivor"); @@ -445,10 +448,10 @@ async fn test_prune_claims_blocked_by_concurrent_reader() { Utc::now(), ) .await; - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "INSERT INTO {schema}.lease_claim_closures (claim_slot, job_id, run_lease, outcome) \ VALUES (0, 77777, 77777, 'completed')" - )) + ))) .execute(&pool) .await .expect("seed closure"); @@ -456,9 +459,9 @@ async fn test_prune_claims_blocked_by_concurrent_reader() { store.rotate_claims(&pool).await.expect("rotate off 0"); let mut reader_tx = pool.begin().await.expect("begin reader tx"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "LOCK TABLE {schema}.lease_claims_0, {schema}.lease_claim_closures_0, {schema}.lease_claim_closure_batches_0 IN ACCESS SHARE MODE" - )) + ))) .execute(reader_tx.as_mut()) .await .expect("LOCK TABLE ACCESS SHARE"); @@ -483,10 +486,12 @@ async fn test_prune_claims_blocked_by_concurrent_reader() { "prune must succeed once reader releases, got {pruned:?}" ); - let post: i64 = sqlx::query_scalar(&format!("SELECT count(*) FROM {schema}.lease_claims_0")) - .fetch_one(&pool) - .await - .expect("count post-prune"); + let post: i64 = sqlx::query_scalar(audited_sql(format!( + "SELECT count(*) FROM {schema}.lease_claims_0" + ))) + .fetch_one(&pool) + .await + .expect("count post-prune"); assert_eq!(post, 0, "TRUNCATE must clear lease_claims_0"); } @@ -547,9 +552,9 @@ async fn test_admin_cancel_during_materialize_no_orphan_lease() { .await .expect("enqueue chaos cancel job"); - let job_id: i64 = sqlx::query_scalar(&format!( + let job_id: i64 = sqlx::query_scalar(audited_sql(format!( "SELECT job_id FROM {schema}.ready_entries WHERE queue = $1 ORDER BY job_id DESC LIMIT 1" - )) + ))) .bind(queue) .fetch_one(&pool) .await @@ -566,7 +571,7 @@ async fn test_admin_cancel_during_materialize_no_orphan_lease() { // Inject a synthetic leases row simulating a concurrent // materialize that committed in the cancel race window. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( r#" INSERT INTO {schema}.leases ( lease_slot, lease_generation, ready_slot, ready_generation, @@ -578,7 +583,7 @@ async fn test_admin_cancel_during_materialize_no_orphan_lease() { 25, $1, NULL, NULL, clock_timestamp() ) "# - )) + ))) .bind(job_id) .bind(queue) .bind(run_lease) diff --git a/awa/tests/receipt_plane_regression_gate.rs b/awa/tests/receipt_plane_regression_gate.rs index e8a9073c..e8ac6ceb 100644 --- a/awa/tests/receipt_plane_regression_gate.rs +++ b/awa/tests/receipt_plane_regression_gate.rs @@ -51,6 +51,7 @@ //! ``` use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert, migrations, QueueStorage, QueueStorageConfig}; use awa::{Client, InsertOpts, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; use serde::{Deserialize, Serialize}; @@ -113,10 +114,12 @@ async fn pool() -> sqlx::PgPool { } async fn drop_schema(pool: &sqlx::PgPool, schema: &str) { - sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await - .expect("Failed to drop schema"); + sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await + .expect("Failed to drop schema"); } /// Sample peak `n_dead_tup` across every partition matching the LIKE @@ -145,9 +148,9 @@ async fn sample_per_partition_dead_tup( /// Current ring cursor: the max-generation row of the ring's rotation /// ledger (#371). async fn ring_state(pool: &sqlx::PgPool, schema: &str, ring: &str) -> (i32, i64) { - sqlx::query_as::<_, (i32, i64)>(&format!( + sqlx::query_as::<_, (i32, i64)>(audited_sql(format!( "SELECT slot, generation FROM {schema}.{ring}_ring_rotations ORDER BY generation DESC LIMIT 1" - )) + ))) .fetch_one(pool) .await .expect("ring cursor read failed") diff --git a/awa/tests/retention_test.rs b/awa/tests/retention_test.rs index 2495c22d..7c1a8792 100644 --- a/awa/tests/retention_test.rs +++ b/awa/tests/retention_test.rs @@ -6,6 +6,7 @@ //! //! Set DATABASE_URL=postgres://postgres:test@localhost:15432/awa_test +use awa::audited_sql; use awa::model::{insert_with, InsertOpts}; use awa::{JobArgs, RetentionPolicy}; use awa_testing::TestClient; @@ -77,19 +78,19 @@ async fn ensure_database_exists(url: &str) { let terminate_sql = format!( "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{database_name}' AND pid <> pg_backend_pid()" ); - sqlx::query(&terminate_sql) + sqlx::query(audited_sql(terminate_sql.clone())) .execute(&admin_pool) .await .expect("Failed to terminate existing retention test connections"); let drop_sql = format!("DROP DATABASE IF EXISTS {database_name}"); - sqlx::query(&drop_sql) + sqlx::query(audited_sql(drop_sql.clone())) .execute(&admin_pool) .await .expect("Failed to drop retention test database"); let create_sql = format!("CREATE DATABASE {database_name}"); - sqlx::query(&create_sql) + sqlx::query(audited_sql(create_sql.clone())) .execute(&admin_pool) .await .expect("Failed to create retention test database"); @@ -148,9 +149,9 @@ async fn insert_terminal_job(pool: &sqlx::PgPool, queue: &str, state: &str, age_ .expect("Failed to insert job"); // Move to terminal state with backdated finalized_at - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE awa.jobs SET state = '{state}'::awa.job_state, finalized_at = now() - interval '{age_secs} seconds' WHERE id = $1" - )) + ))) .bind(job.id) .execute(pool) .await diff --git a/awa/tests/rolling_transition_rehearsal_test.rs b/awa/tests/rolling_transition_rehearsal_test.rs index 7f4a4ff3..3e3a5d99 100644 --- a/awa/tests/rolling_transition_rehearsal_test.rs +++ b/awa/tests/rolling_transition_rehearsal_test.rs @@ -57,6 +57,7 @@ //! ``` use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert::insert_with, migrations, storage, QueueStorage, QueueStorageConfig}; use awa::worker::TransitionWorkerRole; use awa::{Client, InsertOpts, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; @@ -267,9 +268,9 @@ async fn reset_schema(pool: &sqlx::PgPool, queue_storage_schema: &str) { .execute(pool) .await .expect("drop awa schema"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "DROP SCHEMA IF EXISTS {queue_storage_schema} CASCADE" - )) + ))) .execute(pool) .await .expect("drop queue-storage schema"); @@ -287,9 +288,11 @@ async fn reset_schema(pool: &sqlx::PgPool, queue_storage_schema: &str) { .await .unwrap_or_default(); for schema in leftover_schemas { - let _ = sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) - .execute(pool) - .await; + let _ = sqlx::query(audited_sql(format!( + "DROP SCHEMA IF EXISTS {schema} CASCADE" + ))) + .execute(pool) + .await; } } @@ -347,10 +350,10 @@ async fn queue_storage_non_completed_terminal_count( schema: &str, queue: &str, ) -> i64 { - sqlx::query_scalar::<_, i64>(&format!( + sqlx::query_scalar::<_, i64>(audited_sql(format!( "SELECT count(*)::bigint FROM {schema}.terminal_jobs \ WHERE queue = $1 AND state IN ('failed', 'cancelled')" - )) + ))) .bind(queue) .fetch_one(pool) .await diff --git a/awa/tests/rolling_upgrade_rehearsal_test.rs b/awa/tests/rolling_upgrade_rehearsal_test.rs index 82958ded..2972665d 100644 --- a/awa/tests/rolling_upgrade_rehearsal_test.rs +++ b/awa/tests/rolling_upgrade_rehearsal_test.rs @@ -5,6 +5,7 @@ //! worker is a PyPI wheel installed by the workflow, not a source checkout. use async_trait::async_trait; +use awa::audited_sql; use awa::model::{ admin, cron::{pause_cron_job, trigger_cron_job, upsert_cron_job, PeriodicJob}, @@ -705,7 +706,7 @@ async fn schema_version(pool: &sqlx::PgPool) -> i32 { async fn ring_cursor_pair(pool: &sqlx::PgPool, ring: &str) -> ((i32, i64), (i32, i64)) { let (column_slot, column_generation, ledger_slot, ledger_generation) = - sqlx::query_as::<_, (i32, i64, i32, i64)>(&format!( + sqlx::query_as::<_, (i32, i64, i32, i64)>(audited_sql(format!( "SELECT state.current_slot, state.generation, ledger.slot, ledger.generation \ FROM awa.{ring}_ring_state AS state \ CROSS JOIN LATERAL ( \ @@ -713,7 +714,7 @@ async fn ring_cursor_pair(pool: &sqlx::PgPool, ring: &str) -> ((i32, i64), (i32, ORDER BY generation DESC LIMIT 1 \ ) AS ledger \ WHERE state.singleton" - )) + ))) .fetch_one(pool) .await .expect("read ring cursors"); diff --git a/awa/tests/scale_test.rs b/awa/tests/scale_test.rs index d53960fc..1f5da723 100644 --- a/awa/tests/scale_test.rs +++ b/awa/tests/scale_test.rs @@ -5,6 +5,7 @@ //! //! Each test uses a unique queue name to avoid interference when running in parallel. +use awa::audited_sql; use awa_macros::JobArgs; use awa_model::{insert_many, insert_with, migrations, InsertOpts, JobRow, JobState, UniqueOpts}; use serde::{Deserialize, Serialize}; @@ -75,7 +76,10 @@ async fn ensure_database_exists(url: &str) { .await .expect("Failed to connect to admin database for scale tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => panic!("Failed to create scale test database {database_name}: {err}"), diff --git a/awa/tests/telemetry_test.rs b/awa/tests/telemetry_test.rs index 63411ae8..2e0dd360 100644 --- a/awa/tests/telemetry_test.rs +++ b/awa/tests/telemetry_test.rs @@ -11,6 +11,7 @@ //! See docs/test-plan.md for local setup instructions. use async_trait::async_trait; +use awa::audited_sql; use awa::model::{insert_with, migrations, InsertOpts}; use awa::{Client, JobArgs, JobContext, JobError, JobResult, QueueConfig, Worker}; use opentelemetry::global; @@ -79,7 +80,10 @@ async fn ensure_database_exists(url: &str) { .map(|(_, name)| name) .expect("database URL should include a database name"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => panic!("Failed to create telemetry test database {database_name}: {err}"), @@ -154,14 +158,16 @@ async fn clean_queue_storage_claims(pool: &sqlx::PgPool, queue: &str) { return; } for table in ["lease_claim_batches", "lease_claims", "leases"] { - sqlx::query(&format!("DELETE FROM {schema}.{table} WHERE queue = $1")) - .bind(queue) - .execute(pool) - .await - .unwrap_or_else(|err| panic!("Failed to clean {schema}.{table}: {err}")); + sqlx::query(audited_sql(format!( + "DELETE FROM {schema}.{table} WHERE queue = $1" + ))) + .bind(queue) + .execute(pool) + .await + .unwrap_or_else(|err| panic!("Failed to clean {schema}.{table}: {err}")); } for table in ["lease_claim_closures", "lease_claim_closure_batches"] { - sqlx::query(&format!("DELETE FROM {schema}.{table}")) + sqlx::query(audited_sql(format!("DELETE FROM {schema}.{table}"))) .execute(pool) .await .unwrap_or_else(|err| panic!("Failed to clean {schema}.{table}: {err}")); @@ -303,7 +309,7 @@ async fn queue_job_count(pool: &sqlx::PgPool, queue: &str, state: &str) -> i64 { "SELECT COUNT(*)::bigint FROM ({sources}) AS jobs \ WHERE state = $2::awa.job_state" ); - return sqlx::query_scalar(&sql) + return sqlx::query_scalar(audited_sql(sql.clone())) .bind(queue) .bind(state) .fetch_one(pool) @@ -331,7 +337,7 @@ async fn queue_state_breakdown(pool: &sqlx::PgPool, queue: &str) -> Vec<(String, "SELECT state::text, COUNT(*)::bigint FROM ({sources}) AS jobs \ GROUP BY state ORDER BY state" ); - return sqlx::query_as(&sql) + return sqlx::query_as(audited_sql(sql.clone())) .bind(queue) .fetch_all(pool) .await @@ -350,11 +356,11 @@ async fn queue_state_breakdown(pool: &sqlx::PgPool, queue: &str) -> Vec<(String, async fn backdate_callback_timeouts_for_queue(pool: &sqlx::PgPool, queue: &str) { if let Some(schema) = active_queue_storage_schema(pool).await { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.leases \ SET callback_timeout_at = now() - interval '1 second' \ WHERE queue = $1 AND state = 'waiting_external'" - )) + ))) .bind(queue) .execute(pool) .await @@ -374,11 +380,11 @@ async fn backdate_callback_timeouts_for_queue(pool: &sqlx::PgPool, queue: &str) async fn backdate_callback_timeouts_by_ids(pool: &sqlx::PgPool, job_ids: &[i64]) { if let Some(schema) = active_queue_storage_schema(pool).await { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.leases \ SET callback_timeout_at = now() - interval '1 second' \ WHERE job_id = ANY($1) AND state = 'waiting_external'" - )) + ))) .bind(job_ids) .execute(pool) .await @@ -397,11 +403,11 @@ async fn backdate_callback_timeouts_by_ids(pool: &sqlx::PgPool, job_ids: &[i64]) async fn backdate_retryable_run_at_for_queue(pool: &sqlx::PgPool, queue: &str) { if let Some(schema) = active_queue_storage_schema(pool).await { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.deferred_jobs \ SET run_at = now() - interval '1 second' \ WHERE queue = $1 AND state = 'retryable'" - )) + ))) .bind(queue) .execute(pool) .await @@ -421,11 +427,11 @@ async fn backdate_retryable_run_at_for_queue(pool: &sqlx::PgPool, queue: &str) { async fn backdate_scheduled_run_at_by_ids(pool: &sqlx::PgPool, job_ids: &[i64]) { if let Some(schema) = active_queue_storage_schema(pool).await { - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.deferred_jobs \ SET run_at = now() - interval '1 second' \ WHERE job_id = ANY($1) AND state = 'scheduled'" - )) + ))) .bind(job_ids) .execute(pool) .await @@ -449,29 +455,29 @@ async fn backdate_running_deadline(pool: &sqlx::PgPool, job_id: i64) { // claims), or a legacy row-local `lease_claims` row for claims // written before the upgrade. Backdate the deadline on all three so // the rescue setup works regardless of which plane holds the attempt. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.leases \ SET deadline_at = now() - interval '1 second' \ WHERE job_id = $1 AND state = 'running'" - )) + ))) .bind(job_id) .execute(pool) .await .expect("Failed to backdate queue-storage lease deadline rescue job"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches \ SET deadline_at = now() - interval '1 second' \ WHERE $1 = ANY(job_ids)" - )) + ))) .bind(job_id) .execute(pool) .await .expect("Failed to backdate queue-storage compact batch deadline rescue job"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claims \ SET deadline_at = now() - interval '1 second' \ WHERE job_id = $1" - )) + ))) .bind(job_id) .execute(pool) .await @@ -495,29 +501,29 @@ async fn backdate_running_heartbeat(pool: &sqlx::PgPool, job_id: i64) { // deadline_at on both the compact `lease_claim_batches` plane (the // #246 default) and legacy row-local `lease_claims` so // `rescue_expired_receipt_deadlines_tx` closes it on the next tick. - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.leases \ SET heartbeat_at = now() - interval '5 minutes' \ WHERE job_id = $1 AND state = 'running'" - )) + ))) .bind(job_id) .execute(pool) .await .expect("Failed to backdate queue-storage lease heartbeat rescue job"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claim_batches \ SET deadline_at = now() - interval '1 second' \ WHERE $1 = ANY(job_ids)" - )) + ))) .bind(job_id) .execute(pool) .await .expect("Failed to backdate queue-storage compact batch rescue job"); - sqlx::query(&format!( + sqlx::query(audited_sql(format!( "UPDATE {schema}.lease_claims \ SET deadline_at = now() - interval '1 second' \ WHERE job_id = $1" - )) + ))) .bind(job_id) .execute(pool) .await diff --git a/awa/tests/validation_test.rs b/awa/tests/validation_test.rs index d11245a0..50f42f2f 100644 --- a/awa/tests/validation_test.rs +++ b/awa/tests/validation_test.rs @@ -4,6 +4,7 @@ //! and uses unique queue names to avoid interference when running in parallel. //! All tests target real Postgres. +use awa::audited_sql; use awa_macros::JobArgs; use awa_model::{insert_many, insert_with, migrations, InsertOpts, JobRow, JobState, UniqueOpts}; use serde::{Deserialize, Serialize}; @@ -69,7 +70,10 @@ async fn ensure_database_exists(url: &str) { .await .expect("Failed to connect to admin database for validation tests"); let create_sql = format!("CREATE DATABASE {database_name}"); - match sqlx::query(&create_sql).execute(&admin_pool).await { + match sqlx::query(audited_sql(create_sql.clone())) + .execute(&admin_pool) + .await + { Ok(_) => {} Err(sqlx::Error::Database(db_err)) if db_err.code().as_deref() == Some("42P04") => {} Err(err) => panic!("Failed to create validation test database {database_name}: {err}"), diff --git a/examples/rust-app-demo/Cargo.lock b/examples/rust-app-demo/Cargo.lock index 96525024..b27d2834 100644 --- a/examples/rust-app-demo/Cargo.lock +++ b/examples/rust-app-demo/Cargo.lock @@ -78,7 +78,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "awa" -version = "0.6.0" +version = "0.7.0-alpha.1" dependencies = [ "awa-macros", "awa-model", @@ -87,7 +87,7 @@ dependencies = [ [[package]] name = "awa-macros" -version = "0.6.0" +version = "0.7.0-alpha.1" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -95,9 +95,17 @@ dependencies = [ "syn", ] +[[package]] +name = "awa-metrics" +version = "0.7.0-alpha.1" +dependencies = [ + "awa-model", + "opentelemetry", +] + [[package]] name = "awa-model" -version = "0.6.0" +version = "0.7.0-alpha.1" dependencies = [ "awa-macros", "blake3", @@ -105,12 +113,15 @@ dependencies = [ "chrono-tz", "croner", "hex", + "opentelemetry", + "semver", "serde", "serde_json", "sqlx", "thiserror", "tokio", "tracing", + "tracing-opentelemetry", "uuid", ] @@ -134,10 +145,11 @@ dependencies = [ [[package]] name = "awa-worker" -version = "0.6.0" +version = "0.7.0-alpha.1" dependencies = [ "async-trait", "awa-macros", + "awa-metrics", "awa-model", "chrono", "chrono-tz", @@ -150,6 +162,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tracing-opentelemetry", "uuid", ] @@ -211,12 +224,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bitflags" version = "2.11.0" @@ -237,7 +244,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -249,6 +256,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -283,6 +299,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core", +] + [[package]] name = "chrono" version = "0.4.44" @@ -307,6 +334,12 @@ dependencies = [ "phf", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -316,12 +349,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "constant_time_eq" version = "0.4.2" @@ -343,6 +370,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -393,14 +429,21 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", ] [[package]] @@ -409,10 +452,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -459,13 +511,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -487,9 +538,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -502,6 +553,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -612,6 +669,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core", "wasip2", "wasip3", ] @@ -622,9 +680,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -632,14 +688,19 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -656,29 +717,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", + "digest 0.11.3", ] [[package]] @@ -726,6 +778,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -927,9 +988,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "leb128fmt" @@ -943,24 +1001,6 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" -[[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.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.7.3", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1009,12 +1049,12 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] @@ -1049,42 +1089,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1092,7 +1096,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1103,9 +1106,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -1139,20 +1142,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1189,39 +1183,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "potential_utf" version = "0.1.4" @@ -1231,15 +1198,6 @@ 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 = "prettyplease" version = "0.2.37" @@ -1285,33 +1243,20 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "ppv-lite86", + "chacha20", + "getrandom 0.4.2", "rand_core", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "redox_syscall" @@ -1322,15 +1267,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags", -] - [[package]] name = "regex-automata" version = "0.4.14" @@ -1362,26 +1298,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.37" @@ -1508,13 +1424,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1524,8 +1440,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1553,16 +1480,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - [[package]] name = "siphasher" version = "1.0.2" @@ -1603,21 +1520,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1628,12 +1535,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -1643,17 +1551,16 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror", "tokio", @@ -1661,14 +1568,14 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -1679,78 +1586,63 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn", + "thiserror", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand", - "rsa", "serde", "sha1", - "sha2", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror", "tracing", "uuid", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -1766,16 +1658,14 @@ dependencies = [ "hex", "hkdf", "hmac", - "home", "itoa", "log", "md-5", "memchr", - "once_cell", "rand", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -1787,13 +1677,14 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -1803,7 +1694,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror", "tracing", @@ -2070,6 +1960,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -2090,9 +1996,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-bidi" @@ -2205,12 +2111,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.114" @@ -2291,12 +2191,13 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.26.11" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "webpki-roots 1.0.6", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2310,13 +2211,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "windows-core" @@ -2377,22 +2274,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2404,67 +2292,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2477,48 +2332,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2651,26 +2482,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" version = "0.1.6" diff --git a/examples/rust-app-demo/Cargo.toml b/examples/rust-app-demo/Cargo.toml index 1599ca64..f8784333 100644 --- a/examples/rust-app-demo/Cargo.toml +++ b/examples/rust-app-demo/Cargo.toml @@ -14,7 +14,7 @@ axum = "0.8" chrono = { version = "0.4", features = ["serde"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] } +sqlx = { version = "0.9", features = ["runtime-tokio", "tls-rustls-ring", "postgres", "chrono", "uuid"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/rust-app-demo/src/bin/seed_demo.rs b/examples/rust-app-demo/src/bin/seed_demo.rs index 9fb30785..9ee0e97d 100644 --- a/examples/rust-app-demo/src/bin/seed_demo.rs +++ b/examples/rust-app-demo/src/bin/seed_demo.rs @@ -11,9 +11,9 @@ mod shared; use shared::{ build_demo_client, clear_demo_data, create_checkout, create_pool, hero_scheduled_time, prepare_schema, seed_available_cache_jobs, seed_failed_syncs, seed_pending_payments, - seed_scale, seed_scheduled_reports, wait_for_cron_sync, wait_for_many, CACHE_QUEUE, CRON_NAME, - EMAIL_QUEUE, GenerateRevenueReport, OPS_QUEUE, PAYMENTS_QUEUE, REPORTS_QUEUE, - WarmProductCache, + seed_scale, seed_scheduled_reports, wait_for_cron_sync, wait_for_many, GenerateRevenueReport, + WarmProductCache, CACHE_QUEUE, CRON_NAME, EMAIL_QUEUE, OPS_QUEUE, PAYMENTS_QUEUE, + REPORTS_QUEUE, }; fn parse_scale() -> &'static str { diff --git a/examples/rust-app-demo/src/shared.rs b/examples/rust-app-demo/src/shared.rs index e2f880fc..63cd128d 100644 --- a/examples/rust-app-demo/src/shared.rs +++ b/examples/rust-app-demo/src/shared.rs @@ -1,14 +1,14 @@ #![allow(dead_code)] use awa::{ - admin, insert_with, migrations, Client, InsertOpts, JobArgs, JobContext, JobError, - JobResult, JobState, PeriodicJob, QueueConfig, Worker, + admin, insert_with, migrations, Client, InsertOpts, JobArgs, JobContext, JobError, JobResult, + JobState, PeriodicJob, QueueConfig, Worker, }; use axum::Json; use chrono::{TimeZone, Utc}; use serde::{Deserialize, Serialize}; use serde_json::json; -use sqlx::{postgres::PgPoolOptions, PgPool, Row}; +use sqlx::{postgres::PgPoolOptions, AssertSqlSafe, PgPool, Row}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use uuid::Uuid; @@ -132,8 +132,7 @@ struct CacheWarmWorker; struct ReportWorker; fn decode_args Deserialize<'de>>(ctx: &JobContext) -> Result { - serde_json::from_value(ctx.job.args.clone()) - .map_err(|err| JobError::terminal(err.to_string())) + serde_json::from_value(ctx.job.args.clone()).map_err(|err| JobError::terminal(err.to_string())) } // ── Email: completes in ~5s with progress ───────────────────────── @@ -195,7 +194,10 @@ impl Worker for InventorySyncWorker { let pct = ((i + 1) as f64 / total_items as f64 * 100.0) as u8; let sku = format!("SKU-{:04}", i + 1); - ctx.set_progress(pct.min(100), &format!("Validating {sku} ({}/{total_items})", i + 1)); + ctx.set_progress( + pct.min(100), + &format!("Validating {sku} ({}/{total_items})", i + 1), + ); ctx.update_metadata(json!({ "supplier": args.supplier, "last_sku": sku, @@ -357,7 +359,7 @@ pub async fn prepare_schema(pool: &PgPool) -> Result<(), Box Result<(), sqlx::Error> { - sqlx::query(&format!( + sqlx::query(AssertSqlSafe(format!( r#" CREATE TABLE IF NOT EXISTS {ORDERS_TABLE} ( order_id TEXT PRIMARY KEY, @@ -367,14 +369,14 @@ pub async fn ensure_app_schema(pool: &PgPool) -> Result<(), sqlx::Error> { created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) "# - )) + ))) .execute(pool) .await?; Ok(()) } pub async fn clear_demo_data(pool: &PgPool) -> Result<(), sqlx::Error> { - sqlx::query(&format!("DELETE FROM {ORDERS_TABLE}")) + sqlx::query(AssertSqlSafe(format!("DELETE FROM {ORDERS_TABLE}"))) .execute(pool) .await?; sqlx::query("DELETE FROM awa.jobs WHERE queue LIKE 'rust_store_%'") @@ -461,14 +463,14 @@ pub async fn create_checkout( let resolved_order_id = order_id.unwrap_or_else(|| format!("ord_{}", Uuid::new_v4().simple())); let mut tx = pool.begin().await?; - let inserted = sqlx::query(&format!( + let inserted = sqlx::query(AssertSqlSafe(format!( r#" INSERT INTO {ORDERS_TABLE} (order_id, customer_email, total_cents, status) VALUES ($1, $2, $3, 'submitted') ON CONFLICT (order_id) DO NOTHING RETURNING order_id "# - )) + ))) .bind(&resolved_order_id) .bind(customer_email) .bind(total_cents) @@ -509,14 +511,14 @@ pub async fn create_checkout( } pub async fn list_recent_orders(pool: &PgPool) -> Result>, sqlx::Error> { - let rows = sqlx::query(&format!( + let rows = sqlx::query(AssertSqlSafe(format!( r#" SELECT order_id, customer_email, total_cents, status, created_at FROM {ORDERS_TABLE} ORDER BY created_at DESC LIMIT 20 "# - )) + ))) .fetch_all(pool) .await?;