From 1e8843aeb1c9d302fe11f08a9d8aebf042d3d92a Mon Sep 17 00:00:00 2001 From: Adrien Langou Date: Mon, 9 Nov 2026 15:20:33 +0100 Subject: [PATCH 1/3] fix(deps): update E2E h2 for RUSTSEC-2026-0258 Update h2 from 0.4.15 to the first patched release, 0.4.16, in the standalone e2e/rust lockfile. This addresses GHSA-q83h-524g-xf6h, which permits unbounded buffering of empty HTTP/2 DATA frames. Cargo also resolves the existing broad Windows API requirements of errno, rustix, and tempfile to the already locked windows-sys 0.52.0. No other package versions change, and no source changes are required. Signed-off-by: Adrien Langou --- e2e/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index 20c13456fc..e664206f5c 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -710,7 +710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1002,9 +1002,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", @@ -2641,7 +2641,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3301,7 +3301,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] From 471027afd7564c3b1c552fbc1ff3c4cf786370d1 Mon Sep 17 00:00:00 2001 From: Adrien Langou Date: Mon, 9 Nov 2026 15:50:42 +0100 Subject: [PATCH 2/3] fix(deps): backport SQLx SQLite support for RUSTSEC-2026-0459 Backport SQLx's upstream libsqlite3-sys upper-bound change into the published sqlx-sqlite 0.9.0 driver. Require a patched SQLite binding and select libsqlite3-sys 0.39.1 (SQLite 3.54.1) in all ten affected lockfiles. Keep the upstream Rust sources and SQLx API unchanged. Include the full upstream licenses, verified archive provenance, and removal instructions for the temporary path patches. Preserve upstream rustdoc line breaks and exclude third-party sources from NVIDIA header rewriting. No security-policy exception is added. Cargo also resolves tempfile's broad getrandom requirement to the existing 0.3.4 entry in E2E and the V8 example; no other package versions change in this commit. Signed-off-by: Adrien Langou --- .gitattributes | 4 + Cargo.lock | 6 +- Cargo.toml | 5 + e2e/rust/Cargo.lock | 8 +- e2e/rust/Cargo.toml | 4 + examples/governance-interceptor/Cargo.lock | 6 +- examples/governance-interceptor/Cargo.toml | 4 + examples/isolation-backend-v8/Cargo.lock | 8 +- examples/isolation-backend-v8/Cargo.toml | 4 + .../Cargo.lock | 6 +- .../Cargo.toml | 4 + scripts/update_license_headers.py | 2 + tests/suites/conformance/Cargo.lock | 6 +- tests/suites/conformance/Cargo.toml | 4 + tests/suites/drivers/podman/Cargo.lock | 6 +- tests/suites/drivers/podman/Cargo.toml | 4 + tests/suites/features/Cargo.lock | 6 +- tests/suites/features/Cargo.toml | 4 + tests/support/Cargo.lock | 6 +- tests/support/Cargo.toml | 4 + tests/testkit/Cargo.lock | 6 +- tests/testkit/Cargo.toml | 4 + vendor/sqlx-sqlite/Cargo.toml | 198 ++ vendor/sqlx-sqlite/LICENSE-APACHE | 202 ++ vendor/sqlx-sqlite/LICENSE-MIT | 26 + vendor/sqlx-sqlite/README.openshell.md | 30 + vendor/sqlx-sqlite/src/any.rs | 242 +++ vendor/sqlx-sqlite/src/arguments.rs | 148 ++ vendor/sqlx-sqlite/src/column.rs | 35 + .../sqlx-sqlite/src/connection/collation.rs | 158 ++ vendor/sqlx-sqlite/src/connection/describe.rs | 101 + .../sqlx-sqlite/src/connection/deserialize.rs | 299 +++ .../sqlx-sqlite/src/connection/establish.rs | 271 +++ vendor/sqlx-sqlite/src/connection/execute.rs | 132 ++ vendor/sqlx-sqlite/src/connection/executor.rs | 101 + vendor/sqlx-sqlite/src/connection/explain.rs | 1844 +++++++++++++++++ vendor/sqlx-sqlite/src/connection/handle.rs | 151 ++ vendor/sqlx-sqlite/src/connection/intmap.rs | 175 ++ vendor/sqlx-sqlite/src/connection/mod.rs | 599 ++++++ .../src/connection/preupdate_hook.rs | 157 ++ vendor/sqlx-sqlite/src/connection/worker.rs | 573 +++++ vendor/sqlx-sqlite/src/database.rs | 39 + vendor/sqlx-sqlite/src/error.rs | 139 ++ vendor/sqlx-sqlite/src/lib.rs | 183 ++ vendor/sqlx-sqlite/src/logger.rs | 443 ++++ vendor/sqlx-sqlite/src/migrate.rs | 297 +++ vendor/sqlx-sqlite/src/options/auto_vacuum.rs | 38 + vendor/sqlx-sqlite/src/options/connect.rs | 83 + .../sqlx-sqlite/src/options/journal_mode.rs | 50 + .../sqlx-sqlite/src/options/locking_mode.rs | 38 + vendor/sqlx-sqlite/src/options/mod.rs | 675 ++++++ vendor/sqlx-sqlite/src/options/parse.rs | 231 +++ vendor/sqlx-sqlite/src/options/synchronous.rs | 44 + vendor/sqlx-sqlite/src/query_result.rs | 40 + vendor/sqlx-sqlite/src/regexp.rs | 281 +++ vendor/sqlx-sqlite/src/row.rs | 95 + vendor/sqlx-sqlite/src/statement/handle.rs | 466 +++++ vendor/sqlx-sqlite/src/statement/mod.rs | 59 + .../src/statement/unlock_notify.rs | 65 + vendor/sqlx-sqlite/src/statement/virtual.rs | 204 ++ vendor/sqlx-sqlite/src/testing/mod.rs | 83 + vendor/sqlx-sqlite/src/transaction.rs | 35 + vendor/sqlx-sqlite/src/type_checking.rs | 56 + vendor/sqlx-sqlite/src/type_info.rs | 161 ++ vendor/sqlx-sqlite/src/types/bool.rs | 31 + vendor/sqlx-sqlite/src/types/bytes.rs | 105 + vendor/sqlx-sqlite/src/types/chrono.rs | 220 ++ vendor/sqlx-sqlite/src/types/float.rs | 49 + vendor/sqlx-sqlite/src/types/int.rs | 107 + vendor/sqlx-sqlite/src/types/json.rs | 37 + vendor/sqlx-sqlite/src/types/mod.rs | 240 +++ vendor/sqlx-sqlite/src/types/str.rs | 106 + vendor/sqlx-sqlite/src/types/text.rs | 37 + vendor/sqlx-sqlite/src/types/time.rs | 319 +++ vendor/sqlx-sqlite/src/types/uint.rs | 99 + vendor/sqlx-sqlite/src/types/uuid.rs | 85 + vendor/sqlx-sqlite/src/value.rs | 416 ++++ 77 files changed, 11167 insertions(+), 42 deletions(-) create mode 100644 vendor/sqlx-sqlite/Cargo.toml create mode 100644 vendor/sqlx-sqlite/LICENSE-APACHE create mode 100644 vendor/sqlx-sqlite/LICENSE-MIT create mode 100644 vendor/sqlx-sqlite/README.openshell.md create mode 100644 vendor/sqlx-sqlite/src/any.rs create mode 100644 vendor/sqlx-sqlite/src/arguments.rs create mode 100644 vendor/sqlx-sqlite/src/column.rs create mode 100644 vendor/sqlx-sqlite/src/connection/collation.rs create mode 100644 vendor/sqlx-sqlite/src/connection/describe.rs create mode 100644 vendor/sqlx-sqlite/src/connection/deserialize.rs create mode 100644 vendor/sqlx-sqlite/src/connection/establish.rs create mode 100644 vendor/sqlx-sqlite/src/connection/execute.rs create mode 100644 vendor/sqlx-sqlite/src/connection/executor.rs create mode 100644 vendor/sqlx-sqlite/src/connection/explain.rs create mode 100644 vendor/sqlx-sqlite/src/connection/handle.rs create mode 100644 vendor/sqlx-sqlite/src/connection/intmap.rs create mode 100644 vendor/sqlx-sqlite/src/connection/mod.rs create mode 100644 vendor/sqlx-sqlite/src/connection/preupdate_hook.rs create mode 100644 vendor/sqlx-sqlite/src/connection/worker.rs create mode 100644 vendor/sqlx-sqlite/src/database.rs create mode 100644 vendor/sqlx-sqlite/src/error.rs create mode 100644 vendor/sqlx-sqlite/src/lib.rs create mode 100644 vendor/sqlx-sqlite/src/logger.rs create mode 100644 vendor/sqlx-sqlite/src/migrate.rs create mode 100644 vendor/sqlx-sqlite/src/options/auto_vacuum.rs create mode 100644 vendor/sqlx-sqlite/src/options/connect.rs create mode 100644 vendor/sqlx-sqlite/src/options/journal_mode.rs create mode 100644 vendor/sqlx-sqlite/src/options/locking_mode.rs create mode 100644 vendor/sqlx-sqlite/src/options/mod.rs create mode 100644 vendor/sqlx-sqlite/src/options/parse.rs create mode 100644 vendor/sqlx-sqlite/src/options/synchronous.rs create mode 100644 vendor/sqlx-sqlite/src/query_result.rs create mode 100644 vendor/sqlx-sqlite/src/regexp.rs create mode 100644 vendor/sqlx-sqlite/src/row.rs create mode 100644 vendor/sqlx-sqlite/src/statement/handle.rs create mode 100644 vendor/sqlx-sqlite/src/statement/mod.rs create mode 100644 vendor/sqlx-sqlite/src/statement/unlock_notify.rs create mode 100644 vendor/sqlx-sqlite/src/statement/virtual.rs create mode 100644 vendor/sqlx-sqlite/src/testing/mod.rs create mode 100644 vendor/sqlx-sqlite/src/transaction.rs create mode 100644 vendor/sqlx-sqlite/src/type_checking.rs create mode 100644 vendor/sqlx-sqlite/src/type_info.rs create mode 100644 vendor/sqlx-sqlite/src/types/bool.rs create mode 100644 vendor/sqlx-sqlite/src/types/bytes.rs create mode 100644 vendor/sqlx-sqlite/src/types/chrono.rs create mode 100644 vendor/sqlx-sqlite/src/types/float.rs create mode 100644 vendor/sqlx-sqlite/src/types/int.rs create mode 100644 vendor/sqlx-sqlite/src/types/json.rs create mode 100644 vendor/sqlx-sqlite/src/types/mod.rs create mode 100644 vendor/sqlx-sqlite/src/types/str.rs create mode 100644 vendor/sqlx-sqlite/src/types/text.rs create mode 100644 vendor/sqlx-sqlite/src/types/time.rs create mode 100644 vendor/sqlx-sqlite/src/types/uint.rs create mode 100644 vendor/sqlx-sqlite/src/types/uuid.rs create mode 100644 vendor/sqlx-sqlite/src/value.rs diff --git a/.gitattributes b/.gitattributes index 11a48c1820..d9c2a6099d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,7 @@ crates/openshell-core/src/proto/openshell.*.rs linguist-generated # Vendored OCSF schemas fetched from schema.ocsf.io crates/openshell-ocsf-validate/schemas/v*/** linguist-generated + +# Keep the SQLx import separate in reviews and preserve its rustdoc line breaks. +vendor/sqlx-sqlite/** linguist-vendored +vendor/sqlx-sqlite/src/connection/deserialize.rs whitespace=-blank-at-eol diff --git a/Cargo.lock b/Cargo.lock index d178930a4a..8ebb3730e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3672,9 +3672,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -7856,8 +7856,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/Cargo.toml b/Cargo.toml index 5254c11284..49ed89aca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,11 @@ [workspace] resolver = "2" members = ["crates/*"] +exclude = ["vendor/sqlx-sqlite"] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "vendor/sqlx-sqlite" } [workspace.package] version = "0.0.0" diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index e664206f5c..e2a50ca690 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -1739,9 +1739,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3181,8 +3181,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", @@ -3298,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.52.0", diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index b0938d9625..0bad43abfd 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -319,3 +319,7 @@ missing_panics_doc = "allow" name = "live_internet_traffic_perf" path = "tests/live_internet_traffic_perf.rs" required-features = ["e2e-host-gateway"] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/governance-interceptor/Cargo.lock b/examples/governance-interceptor/Cargo.lock index fa08aa3aba..1c2e61d24e 100644 --- a/examples/governance-interceptor/Cargo.lock +++ b/examples/governance-interceptor/Cargo.lock @@ -1627,9 +1627,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -2997,8 +2997,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/examples/governance-interceptor/Cargo.toml b/examples/governance-interceptor/Cargo.toml index a2ab52ef99..4b245278fb 100644 --- a/examples/governance-interceptor/Cargo.toml +++ b/examples/governance-interceptor/Cargo.toml @@ -32,3 +32,7 @@ path = "src/main.rs" [[bin]] name = "governance-smoke-client" path = "src/smoke_client.rs" + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/isolation-backend-v8/Cargo.lock b/examples/isolation-backend-v8/Cargo.lock index 54445e64e4..5d2e73a167 100644 --- a/examples/isolation-backend-v8/Cargo.lock +++ b/examples/isolation-backend-v8/Cargo.lock @@ -1886,9 +1886,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3250,8 +3250,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", @@ -3400,7 +3398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.5", "windows-sys 0.61.2", diff --git a/examples/isolation-backend-v8/Cargo.toml b/examples/isolation-backend-v8/Cargo.toml index 4be42380a4..fe6c8fd381 100644 --- a/examples/isolation-backend-v8/Cargo.toml +++ b/examples/isolation-backend-v8/Cargo.toml @@ -41,3 +41,7 @@ tonic-prost-build = "0.14" [[bin]] name = "js-sbx" path = "src/main.rs" + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/supervisor-middleware-content-guard/Cargo.lock b/examples/supervisor-middleware-content-guard/Cargo.lock index f453c06919..f4f46338d7 100644 --- a/examples/supervisor-middleware-content-guard/Cargo.lock +++ b/examples/supervisor-middleware-content-guard/Cargo.lock @@ -1775,9 +1775,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3119,8 +3119,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/examples/supervisor-middleware-content-guard/Cargo.toml b/examples/supervisor-middleware-content-guard/Cargo.toml index 89e7b7a85e..9fe5cf4230 100644 --- a/examples/supervisor-middleware-content-guard/Cargo.toml +++ b/examples/supervisor-middleware-content-guard/Cargo.toml @@ -26,3 +26,7 @@ openshell-policy = { path = "../../crates/openshell-policy" } [[bin]] name = "supervisor-middleware-content-guard" path = "src/main.rs" + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/scripts/update_license_headers.py b/scripts/update_license_headers.py index bb00feaa4f..9abaa410d5 100755 --- a/scripts/update_license_headers.py +++ b/scripts/update_license_headers.py @@ -84,6 +84,8 @@ ".cache", "python/openshell/_proto", "sdk/typescript/src/gen", + # Preserve upstream SQLx copyrights and its MIT/Apache-2.0 licenses. + "vendor/sqlx-sqlite", } # Individual filenames to skip. diff --git a/tests/suites/conformance/Cargo.lock b/tests/suites/conformance/Cargo.lock index ea04cd150d..75b892dbf8 100644 --- a/tests/suites/conformance/Cargo.lock +++ b/tests/suites/conformance/Cargo.lock @@ -1740,9 +1740,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3280,8 +3280,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/conformance/Cargo.toml b/tests/suites/conformance/Cargo.toml index 99274d8dfa..66bc498f2b 100644 --- a/tests/suites/conformance/Cargo.toml +++ b/tests/suites/conformance/Cargo.toml @@ -27,3 +27,7 @@ tempfile = "3" tokio = { version = "1", features = ["time"] } [workspace] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../../vendor/sqlx-sqlite" } diff --git a/tests/suites/drivers/podman/Cargo.lock b/tests/suites/drivers/podman/Cargo.lock index 6633a3e703..33b57b8d23 100644 --- a/tests/suites/drivers/podman/Cargo.lock +++ b/tests/suites/drivers/podman/Cargo.lock @@ -1739,9 +1739,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3296,8 +3296,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/drivers/podman/Cargo.toml b/tests/suites/drivers/podman/Cargo.toml index 02c0304a1c..cfc6ba6c44 100644 --- a/tests/suites/drivers/podman/Cargo.toml +++ b/tests/suites/drivers/podman/Cargo.toml @@ -18,3 +18,7 @@ openshell-testkit = { path = "../../../testkit/testkit" } podman-support = { path = "../../../support/podman" } [workspace] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../../../vendor/sqlx-sqlite" } diff --git a/tests/suites/features/Cargo.lock b/tests/suites/features/Cargo.lock index bb1a1427ee..fb6739ed33 100644 --- a/tests/suites/features/Cargo.lock +++ b/tests/suites/features/Cargo.lock @@ -1766,9 +1766,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3328,8 +3328,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/features/Cargo.toml b/tests/suites/features/Cargo.toml index af907b4b15..ff429cb27e 100644 --- a/tests/suites/features/Cargo.toml +++ b/tests/suites/features/Cargo.toml @@ -4,3 +4,7 @@ [workspace] resolver = "2" members = ["host-gateway", "keycloak-provider-refresh", "kubernetes-updates", "oci-image"] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../../vendor/sqlx-sqlite" } diff --git a/tests/support/Cargo.lock b/tests/support/Cargo.lock index f8b591438f..30552f5496 100644 --- a/tests/support/Cargo.lock +++ b/tests/support/Cargo.lock @@ -1748,9 +1748,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3296,8 +3296,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/support/Cargo.toml b/tests/support/Cargo.toml index f56ee275f5..e6f202617b 100644 --- a/tests/support/Cargo.toml +++ b/tests/support/Cargo.toml @@ -6,3 +6,7 @@ [workspace] resolver = "2" members = ["container-engine", "keycloak", "podman"] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/tests/testkit/Cargo.lock b/tests/testkit/Cargo.lock index db2e79b6ca..3adacc41c1 100644 --- a/tests/testkit/Cargo.lock +++ b/tests/testkit/Cargo.lock @@ -1730,9 +1730,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "pkg-config", @@ -3272,8 +3272,6 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/testkit/Cargo.toml b/tests/testkit/Cargo.toml index 17a5e5b052..df14368d94 100644 --- a/tests/testkit/Cargo.toml +++ b/tests/testkit/Cargo.toml @@ -6,3 +6,7 @@ [workspace] resolver = "2" members = ["testkit", "testkit-macros"] + +[patch.crates-io] +# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. +sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/vendor/sqlx-sqlite/Cargo.toml b/vendor/sqlx-sqlite/Cargo.toml new file mode 100644 index 0000000000..9188ea900f --- /dev/null +++ b/vendor/sqlx-sqlite/Cargo.toml @@ -0,0 +1,198 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.94.0" +name = "sqlx-sqlite" +version = "0.9.0" +authors = [ + "Ryan Leckey ", + "Austin Bonander ", + "Chloe Ross ", + "Daniel Akhterov ", +] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "SQLite driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." +documentation = "https://docs.rs/sqlx" +readme = false +license = "MIT OR Apache-2.0" +repository = "https://github.com/launchbadge/sqlx" + +[package.metadata.docs.rs] +features = ["__unstable_docs"] + +[features] +_unstable-all-sqlite-features = [ + "deserialize", + "load-extension", + "preupdate-hook", + "unlock-notify", +] +_unstable-all-types = [ + "json", + "chrono", + "time", + "uuid", +] +_unstable-docs = [ + "bundled", + "any", + "_unstable-all-types", + "_unstable-all-sqlite-features", +] +any = ["sqlx-core/any"] +bigdecimal = [] +bundled = ["libsqlite3-sys/bundled"] +chrono = [ + "dep:chrono", + "sqlx-core/chrono", +] +deserialize = [] +json = [ + "sqlx-core/json", + "serde", +] +load-extension = [] +migrate = ["sqlx-core/migrate"] +offline = [ + "sqlx-core/offline", + "serde", +] +preupdate-hook = ["libsqlite3-sys/preupdate_hook"] +regexp = ["dep:regex"] +rust_decimal = [] +sqlx-toml = ["sqlx-core/sqlx-toml"] +time = [ + "dep:time", + "sqlx-core/time", +] +unbundled = ["libsqlite3-sys/buildtime_bindgen"] +unlock-notify = ["libsqlite3-sys/unlock_notify"] +uuid = [ + "dep:uuid", + "sqlx-core/uuid", +] + +[lib] +name = "sqlx_sqlite" +path = "src/lib.rs" + +[dependencies.atoi] +version = "2.0" + +[dependencies.chrono] +version = "0.4.34" +features = [ + "std", + "clock", +] +optional = true +default-features = false + +[dependencies.flume] +version = "0.12.0" +features = ["async"] +default-features = false + +[dependencies.form_urlencoded] +version = "1.2.2" + +[dependencies.futures-channel] +version = "0.3.32" +features = [ + "sink", + "alloc", + "std", +] +default-features = false + +[dependencies.futures-core] +version = "0.3.32" +default-features = false + +[dependencies.futures-executor] +version = "0.3.32" + +[dependencies.futures-intrusive] +version = "0.5.0" + +[dependencies.futures-util] +version = "0.3.32" +features = [ + "alloc", + "sink", +] +default-features = false + +[dependencies.libsqlite3-sys] +version = ">=0.39.0, <0.40.0" +features = [ + "pkg-config", + "vcpkg", +] +default-features = false + +[dependencies.log] +version = "0.4.18" + +[dependencies.percent-encoding] +version = "2.3.0" + +[dependencies.regex] +version = "1.6.0" +optional = true + +[dependencies.serde] +version = "1.0.219" +features = ["derive"] +optional = true + +[dependencies.sqlx-core] +version = "=0.9.0" + +[dependencies.thiserror] +version = "2.0.18" +features = ["std"] +default-features = false + +[dependencies.time] +version = "0.3.47" +features = [ + "formatting", + "parsing", + "macros", +] +optional = true + +[dependencies.tracing] +version = "0.1.37" +features = ["log"] + +[dependencies.url] +version = "2.2.2" + +[dependencies.uuid] +version = "1.12.1" +optional = true + +[dev-dependencies] + +[lints.clippy] +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_sign_loss = "deny" +disallowed_methods = "deny" diff --git a/vendor/sqlx-sqlite/LICENSE-APACHE b/vendor/sqlx-sqlite/LICENSE-APACHE new file mode 100644 index 0000000000..e14699fb14 --- /dev/null +++ b/vendor/sqlx-sqlite/LICENSE-APACHE @@ -0,0 +1,202 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright (C) SQLx Contributors +Portions of this work Copyright (C) LaunchBadge, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/sqlx-sqlite/LICENSE-MIT b/vendor/sqlx-sqlite/LICENSE-MIT new file mode 100644 index 0000000000..6fe828e8ea --- /dev/null +++ b/vendor/sqlx-sqlite/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (C) SQLx Contributors +Portions of this work Copyright (C) LaunchBadge, LLC + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/sqlx-sqlite/README.openshell.md b/vendor/sqlx-sqlite/README.openshell.md new file mode 100644 index 0000000000..2b3534aec0 --- /dev/null +++ b/vendor/sqlx-sqlite/README.openshell.md @@ -0,0 +1,30 @@ +# Temporary SQLx SQLite security backport + +This is the published `sqlx-sqlite` 0.9.0 crate with its SQLite dependency +constraint changed to `>=0.39.0, <0.40.0`. The minimum excludes versions affected +by [RUSTSEC-2026-0459](https://rustsec.org/advisories/RUSTSEC-2026-0459.html). +The upper bound follows the upstream changes +[94aafe3](https://github.com/transact-rs/sqlx/commit/94aafe3a68884d923b0798a767c8d7f6cfda89d2) +and [10462fa](https://github.com/transact-rs/sqlx/commit/10462fa4414c6c7f1ed1d39bb31678e3b726878c), +which have not yet been published in a SQLx release. + +The Rust sources are unchanged. The archive's license-path placeholders are +replaced with the complete license texts from the published source commit. +Its unused lockfile, workspace manifest template, and Cargo cache metadata are +omitted. A targeted Git whitespace attribute preserves upstream rustdoc hard +line breaks in `src/connection/deserialize.rs`. + +- Source: +- Published source commit: `003b698e99e024f3621b8043a2426fde5b741171` +- Archive SHA-256: `488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c` +- Licenses: [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) + +OpenShell's Cargo workspaces patch this crate by path so the build continues to +use registry dependencies without a Git-source exception. Their lockfiles select +`libsqlite3-sys` 0.39.1, which bundles SQLite 3.54.1. SQLite features and the +SQLx 0.9 API remain unchanged. + +Remove this directory, the workspace patches, the root workspace exclusion, +the Git attributes, and the license-header exclusion once a published SQLx +release accepts a patched `libsqlite3-sys`. Update the affected lockfiles and +rerun the persistence and dependency-policy checks when removing the backport. diff --git a/vendor/sqlx-sqlite/src/any.rs b/vendor/sqlx-sqlite/src/any.rs new file mode 100644 index 0000000000..b3a5af5543 --- /dev/null +++ b/vendor/sqlx-sqlite/src/any.rs @@ -0,0 +1,242 @@ +use crate::{ + Either, Sqlite, SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnectOptions, + SqliteConnection, SqliteQueryResult, SqliteRow, SqliteTransactionManager, SqliteTypeInfo, +}; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt}; + +use sqlx_core::any::{ + AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, + AnyStatement, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, +}; +use sqlx_core::sql_str::SqlStr; + +use crate::arguments::SqliteArgumentsBuffer; +use crate::type_info::DataType; +use sqlx_core::connection::{ConnectOptions, Connection}; +use sqlx_core::database::Database; +use sqlx_core::executor::Executor; +use sqlx_core::transaction::TransactionManager; +use std::pin::pin; +use std::sync::Arc; + +sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Sqlite); + +impl AnyConnectionBackend for SqliteConnection { + fn name(&self) -> &str { + ::NAME + } + + fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close(*self).boxed() + } + + fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close_hard(*self).boxed() + } + + fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::ping(self).boxed() + } + + fn begin(&mut self, statement: Option) -> BoxFuture<'_, sqlx_core::Result<()>> { + SqliteTransactionManager::begin(self, statement).boxed() + } + + fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + SqliteTransactionManager::commit(self).boxed() + } + + fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + SqliteTransactionManager::rollback(self).boxed() + } + + fn start_rollback(&mut self) { + SqliteTransactionManager::start_rollback(self) + } + + fn get_transaction_depth(&self) -> usize { + SqliteTransactionManager::get_transaction_depth(self) + } + + fn shrink_buffers(&mut self) { + // NO-OP. + } + + fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::flush(self).boxed() + } + + fn should_flush(&self) -> bool { + Connection::should_flush(self) + } + + #[cfg(feature = "migrate")] + fn as_migrate( + &mut self, + ) -> sqlx_core::Result<&mut (dyn sqlx_core::migrate::Migrate + Send + 'static)> { + Ok(self) + } + + fn fetch_many( + &mut self, + query: SqlStr, + persistent: bool, + arguments: Option, + ) -> BoxStream<'_, sqlx_core::Result>> { + let persistent = persistent && arguments.is_some(); + let args = arguments.map(map_arguments); + + Box::pin( + self.worker + .execute(query, args, self.row_channel_size, persistent, None) + .map_ok(flume::Receiver::into_stream) + .try_flatten_stream() + .map( + move |res: sqlx_core::Result>| match res? { + Either::Left(result) => Ok(Either::Left(map_result(result))), + Either::Right(row) => Ok(Either::Right(AnyRow::try_from(&row)?)), + }, + ), + ) + } + + fn fetch_optional( + &mut self, + query: SqlStr, + persistent: bool, + arguments: Option, + ) -> BoxFuture<'_, sqlx_core::Result>> { + let persistent = persistent && arguments.is_some(); + let args = arguments.map(map_arguments); + + Box::pin(async move { + let mut stream = pin!( + self.worker + .execute(query, args, self.row_channel_size, persistent, Some(1)) + .map_ok(flume::Receiver::into_stream) + .await? + ); + + if let Some(Either::Right(row)) = stream.try_next().await? { + return Ok(Some(AnyRow::try_from(&row)?)); + } + + Ok(None) + }) + } + + fn prepare_with<'c, 'q: 'c>( + &'c mut self, + sql: SqlStr, + _parameters: &[AnyTypeInfo], + ) -> BoxFuture<'c, sqlx_core::Result> { + Box::pin(async move { + let statement = Executor::prepare_with(self, sql, &[]).await?; + let column_names = statement.column_names.clone(); + AnyStatement::try_from_statement(statement, column_names) + }) + } + + #[cfg(feature = "offline")] + fn describe( + &mut self, + sql: SqlStr, + ) -> BoxFuture<'_, sqlx_core::Result>> { + Box::pin(async move { Executor::describe(self, sql).await?.try_into_any() }) + } +} + +impl<'a> TryFrom<&'a SqliteTypeInfo> for AnyTypeInfo { + type Error = sqlx_core::Error; + + fn try_from(sqlite_type: &'a SqliteTypeInfo) -> Result { + Ok(AnyTypeInfo { + kind: match &sqlite_type.0 { + DataType::Null => AnyTypeInfoKind::Null, + DataType::Int4 => AnyTypeInfoKind::Integer, + DataType::Integer => AnyTypeInfoKind::BigInt, + DataType::Float => AnyTypeInfoKind::Double, + DataType::Blob => AnyTypeInfoKind::Blob, + DataType::Text => AnyTypeInfoKind::Text, + _ => { + return Err(sqlx_core::Error::AnyDriverError( + format!("Any driver does not support the SQLite type {sqlite_type:?}") + .into(), + )) + } + }, + }) + } +} + +impl<'a> TryFrom<&'a SqliteColumn> for AnyColumn { + type Error = sqlx_core::Error; + + fn try_from(col: &'a SqliteColumn) -> Result { + let type_info = + AnyTypeInfo::try_from(&col.type_info).map_err(|e| sqlx_core::Error::ColumnDecode { + index: col.name.to_string(), + source: e.into(), + })?; + + Ok(AnyColumn { + ordinal: col.ordinal, + name: col.name.clone(), + type_info, + }) + } +} + +impl<'a> TryFrom<&'a SqliteRow> for AnyRow { + type Error = sqlx_core::Error; + + fn try_from(row: &'a SqliteRow) -> Result { + AnyRow::map_from(row, row.column_names.clone()) + } +} + +impl<'a> TryFrom<&'a AnyConnectOptions> for SqliteConnectOptions { + type Error = sqlx_core::Error; + + fn try_from(opts: &'a AnyConnectOptions) -> Result { + let mut opts_out = SqliteConnectOptions::from_url(&opts.database_url)?; + opts_out.log_settings = opts.log_settings.clone(); + + Ok(opts_out) + } +} + +// Infallible alternative to AnyArguments::convert_into() +fn map_arguments(args: AnyArguments) -> SqliteArguments { + let values = args + .values + .0 + .into_iter() + .map(|val| match val { + AnyValueKind::Null(_) => SqliteArgumentValue::Null, + AnyValueKind::Bool(b) => SqliteArgumentValue::Int(b as i32), + AnyValueKind::SmallInt(i) => SqliteArgumentValue::Int(i as i32), + AnyValueKind::Integer(i) => SqliteArgumentValue::Int(i), + AnyValueKind::BigInt(i) => SqliteArgumentValue::Int64(i), + AnyValueKind::Real(r) => SqliteArgumentValue::Double(r as f64), + AnyValueKind::Double(d) => SqliteArgumentValue::Double(d), + AnyValueKind::Text(t) => SqliteArgumentValue::Text(Arc::new(t.to_string())), + AnyValueKind::Blob(b) => SqliteArgumentValue::Blob(Arc::new(b.to_vec())), + // AnyValueKind is `#[non_exhaustive]` but we should have covered everything + _ => unreachable!("BUG: missing mapping for {val:?}"), + }) + .collect(); + + SqliteArguments { + values: SqliteArgumentsBuffer::new(values), + } +} + +fn map_result(res: SqliteQueryResult) -> AnyQueryResult { + AnyQueryResult { + rows_affected: res.rows_affected(), + last_insert_id: None, + } +} diff --git a/vendor/sqlx-sqlite/src/arguments.rs b/vendor/sqlx-sqlite/src/arguments.rs new file mode 100644 index 0000000000..4fd7da5067 --- /dev/null +++ b/vendor/sqlx-sqlite/src/arguments.rs @@ -0,0 +1,148 @@ +use crate::encode::{Encode, IsNull}; +use crate::error::Error; +use crate::statement::StatementHandle; +use crate::Sqlite; +use atoi::atoi; +use libsqlite3_sys::SQLITE_OK; +use std::sync::Arc; + +pub(crate) use sqlx_core::arguments::*; +use sqlx_core::error::BoxDynError; + +#[derive(Debug, Clone)] +pub enum SqliteArgumentValue { + Null, + Text(Arc), + TextSlice(Arc), + Blob(Arc>), + Double(f64), + Int(i32), + Int64(i64), +} + +#[derive(Default, Debug, Clone)] +pub struct SqliteArguments { + pub(crate) values: SqliteArgumentsBuffer, +} + +#[derive(Default, Debug, Clone)] +pub struct SqliteArgumentsBuffer(Vec); + +impl SqliteArguments { + pub(crate) fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> + where + T: Encode<'t, Sqlite>, + { + let value_length_before_encoding = self.values.0.len(); + + match value.encode(&mut self.values) { + Ok(IsNull::Yes) => self.values.0.push(SqliteArgumentValue::Null), + Ok(IsNull::No) => {} + Err(error) => { + // reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind + self.values.0.truncate(value_length_before_encoding); + return Err(error); + } + }; + + Ok(()) + } +} + +impl Arguments for SqliteArguments { + type Database = Sqlite; + + fn reserve(&mut self, len: usize, _size_hint: usize) { + self.values.0.reserve(len); + } + + fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> + where + T: Encode<'t, Self::Database>, + { + self.add(value) + } + + fn len(&self) -> usize { + self.values.0.len() + } +} + +impl SqliteArguments { + pub(super) fn bind(&self, handle: &mut StatementHandle, offset: usize) -> Result { + let mut arg_i = offset; + // for handle in &statement.handles { + + let cnt = handle.bind_parameter_count(); + + for param_i in 1..=cnt { + // figure out the index of this bind parameter into our argument tuple + let n: usize = if let Some(name) = handle.bind_parameter_name(param_i) { + if let Some(name) = name.strip_prefix('?') { + // parameter should have the form ?NNN + atoi(name.as_bytes()).expect("parameter of the form ?NNN") + } else if let Some(name) = name.strip_prefix('$') { + // parameter should have the form $NNN + atoi(name.as_bytes()).ok_or_else(|| { + err_protocol!( + "parameters with non-integer names are not currently supported: {}", + name + ) + })? + } else { + return Err(err_protocol!("unsupported SQL parameter format: {}", name)); + } + } else { + arg_i += 1; + arg_i + }; + + if n > self.values.0.len() { + // SQLite treats unbound variables as NULL + // we reproduce this here + // If you are reading this and think this should be an error, open an issue and we can + // discuss configuring this somehow + // Note that the query macros have a different way of enforcing + // argument arity + break; + } + + self.values.0[n - 1].bind(handle, param_i)?; + } + + Ok(arg_i - offset) + } +} + +impl SqliteArgumentsBuffer { + #[allow(dead_code)] // clippy incorrectly reports this as unused + pub(crate) fn new(values: Vec) -> SqliteArgumentsBuffer { + Self(values) + } + + pub(crate) fn push(&mut self, value: SqliteArgumentValue) { + self.0.push(value); + } +} + +impl SqliteArgumentValue { + fn bind(&self, handle: &mut StatementHandle, i: usize) -> Result<(), Error> { + use SqliteArgumentValue::*; + + let status = match self { + Text(v) => handle.bind_text(i, v), + TextSlice(v) => handle.bind_text(i, v), + Blob(v) => handle.bind_blob(i, v), + Int(v) => handle.bind_int(i, *v), + Int64(v) => handle.bind_int64(i, *v), + Double(v) => handle.bind_double(i, *v), + Null => handle.bind_null(i), + }; + + if status != SQLITE_OK { + return Err(handle.last_error().into()); + } + + Ok(()) + } +} diff --git a/vendor/sqlx-sqlite/src/column.rs b/vendor/sqlx-sqlite/src/column.rs new file mode 100644 index 0000000000..d319bd46a8 --- /dev/null +++ b/vendor/sqlx-sqlite/src/column.rs @@ -0,0 +1,35 @@ +use crate::ext::ustr::UStr; +use crate::{Sqlite, SqliteTypeInfo}; + +pub(crate) use sqlx_core::column::*; + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] +pub struct SqliteColumn { + pub(crate) name: UStr, + pub(crate) ordinal: usize, + pub(crate) type_info: SqliteTypeInfo, + + #[cfg_attr(feature = "offline", serde(default))] + pub(crate) origin: ColumnOrigin, +} + +impl Column for SqliteColumn { + type Database = Sqlite; + + fn ordinal(&self) -> usize { + self.ordinal + } + + fn name(&self) -> &str { + &self.name + } + + fn type_info(&self) -> &SqliteTypeInfo { + &self.type_info + } + + fn origin(&self) -> ColumnOrigin { + self.origin.clone() + } +} diff --git a/vendor/sqlx-sqlite/src/connection/collation.rs b/vendor/sqlx-sqlite/src/connection/collation.rs new file mode 100644 index 0000000000..229e780adf --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/collation.rs @@ -0,0 +1,158 @@ +use std::cmp::Ordering; +use std::ffi::CString; +use std::fmt::{self, Debug, Formatter}; +use std::os::raw::{c_int, c_void}; +use std::slice; +use std::sync::Arc; + +use libsqlite3_sys::{sqlite3_create_collation_v2, SQLITE_OK, SQLITE_UTF8}; + +use crate::connection::handle::ConnectionHandle; +use crate::error::Error; + +#[derive(Clone)] +pub struct Collation { + name: Arc, + #[allow(clippy::type_complexity)] + collate: Arc Ordering + Send + Sync + 'static>, + // SAFETY: these must match the concrete type of `collate` + call: unsafe extern "C" fn( + arg1: *mut c_void, + arg2: c_int, + arg3: *const c_void, + arg4: c_int, + arg5: *const c_void, + ) -> c_int, + free: unsafe extern "C" fn(*mut c_void), +} + +impl Collation { + pub fn new(name: N, collate: F) -> Self + where + N: Into>, + F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, + { + unsafe extern "C" fn drop_arc_value(p: *mut c_void) { + drop(Arc::from_raw(p as *mut T)); + } + + Collation { + name: name.into(), + collate: Arc::new(collate), + call: call_boxed_closure::, + free: drop_arc_value::, + } + } + + pub(crate) fn create(&self, handle: &mut ConnectionHandle) -> Result<(), Error> { + let raw_f = Arc::into_raw(Arc::clone(&self.collate)); + let c_name = CString::new(&*self.name) + .map_err(|_| err_protocol!("invalid collation name: {:?}", self.name))?; + let flags = SQLITE_UTF8; + let r = unsafe { + sqlite3_create_collation_v2( + handle.as_ptr(), + c_name.as_ptr(), + flags, + raw_f as *mut c_void, + Some(self.call), + Some(self.free), + ) + }; + + if r == SQLITE_OK { + Ok(()) + } else { + // The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails. + drop(unsafe { Arc::from_raw(raw_f) }); + Err(handle.expect_error().into()) + } + } +} + +impl Debug for Collation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Collation") + .field("name", &self.name) + .finish_non_exhaustive() + } +} + +pub(crate) fn create_collation( + handle: &mut ConnectionHandle, + name: &str, + compare: F, +) -> Result<(), Error> +where + F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, +{ + unsafe extern "C" fn free_boxed_value(p: *mut c_void) { + drop(Box::from_raw(p as *mut T)); + } + + let boxed_f: *mut F = Box::into_raw(Box::new(compare)); + let c_name = + CString::new(name).map_err(|_| err_protocol!("invalid collation name: {}", name))?; + let flags = SQLITE_UTF8; + let r = unsafe { + sqlite3_create_collation_v2( + handle.as_ptr(), + c_name.as_ptr(), + flags, + boxed_f as *mut c_void, + Some(call_boxed_closure::), + Some(free_boxed_value::), + ) + }; + + if r == SQLITE_OK { + Ok(()) + } else { + // The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails. + drop(unsafe { Box::from_raw(boxed_f) }); + Err(handle.expect_error().into()) + } +} + +unsafe extern "C" fn call_boxed_closure( + data: *mut c_void, + left_len: c_int, + left_ptr: *const c_void, + right_len: c_int, + right_ptr: *const c_void, +) -> c_int +where + C: Fn(&str, &str) -> Ordering, +{ + let boxed_f: *mut C = data as *mut C; + + // Note: unwinding is now caught at the FFI boundary: + // https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding + assert!(!boxed_f.is_null()); + + let left_len = + usize::try_from(left_len).unwrap_or_else(|_| panic!("left_len out of range: {left_len}")); + + let right_len = usize::try_from(right_len) + .unwrap_or_else(|_| panic!("right_len out of range: {right_len}")); + + // SQLite explicitly documents that invalid UTF-8 may be passed into + // application-defined collating sequences. The safe `Fn(&str, &str)` + // signature exposed to users must never observe invalid UTF-8, so + // lossily coerce the raw bytes here. + let s1 = { + let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len); + String::from_utf8_lossy(c_slice) + }; + let s2 = { + let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len); + String::from_utf8_lossy(c_slice) + }; + let t = (*boxed_f)(&s1, &s2); + + match t { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + } +} diff --git a/vendor/sqlx-sqlite/src/connection/describe.rs b/vendor/sqlx-sqlite/src/connection/describe.rs new file mode 100644 index 0000000000..400c671d96 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/describe.rs @@ -0,0 +1,101 @@ +use crate::connection::explain::explain; +use crate::connection::ConnectionState; +use crate::describe::Describe; +use crate::error::Error; +use crate::statement::VirtualStatement; +use crate::type_info::DataType; +use crate::{Sqlite, SqliteColumn}; +use sqlx_core::sql_str::SqlStr; +use sqlx_core::Either; +use std::convert::identity; + +pub(crate) fn describe( + conn: &mut ConnectionState, + query: SqlStr, +) -> Result, Error> { + // describing a statement from SQLite can be involved + // each SQLx statement is comprised of multiple SQL statements + + let mut statement = VirtualStatement::new(query.as_str(), false)?; + + let mut columns = Vec::new(); + let mut nullable = Vec::new(); + let mut num_params = 0; + + // we start by finding the first statement that *can* return results + while let Some(stmt) = statement.prepare_next(&mut conn.handle)? { + num_params += stmt.handle.bind_parameter_count(); + + let mut stepped = false; + + let num = stmt.handle.column_count(); + if num == 0 { + // no columns in this statement; skip + continue; + } + + // next we try to use [column_decltype] to inspect the type of each column + columns.reserve(num); + + // as a last resort, we explain the original query and attempt to + // infer what would the expression types be as a fallback + // to [column_decltype] + + // if explain.. fails, ignore the failure and we'll have no fallback + let (fallback, fallback_nullable) = match explain(conn, stmt.handle.sql()) { + Ok(v) => v, + Err(error) => { + tracing::debug!(%error, "describe: explain introspection failed"); + + (vec![], vec![]) + } + }; + + for col in 0..num { + let name = stmt.handle.column_name(col).to_owned(); + + let origin = stmt.handle.column_origin(col); + + let type_info = if let Some(ty) = stmt.handle.column_decltype(col) { + ty + } else { + // if that fails, we back up and attempt to step the statement + // once *if* its read-only and then use [column_type] as a + // fallback to [column_decltype] + if !stepped && stmt.handle.read_only() { + stepped = true; + let _ = stmt.handle.step(); + } + + let mut ty = stmt.handle.column_type_info(col); + + if ty.0 == DataType::Null { + if let Some(fallback) = fallback.get(col).cloned() { + ty = fallback; + } + } + + ty + }; + + // check explain + let col_nullable = stmt.handle.column_nullable(col)?; + let exp_nullable = fallback_nullable.get(col).copied().and_then(identity); + + nullable.push(exp_nullable.or(col_nullable)); + + columns.push(SqliteColumn { + name: name.into(), + type_info, + ordinal: col, + origin, + }); + } + } + + Ok(Describe { + columns, + parameters: Some(Either::Right(num_params)), + nullable, + }) +} diff --git a/vendor/sqlx-sqlite/src/connection/deserialize.rs b/vendor/sqlx-sqlite/src/connection/deserialize.rs new file mode 100644 index 0000000000..5056eb6784 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/deserialize.rs @@ -0,0 +1,299 @@ +use super::ConnectionState; +use crate::{error::Error, SqliteConnection, SqliteError}; +use libsqlite3_sys::{ + sqlite3_deserialize, sqlite3_free, sqlite3_malloc64, sqlite3_serialize, + SQLITE_DESERIALIZE_FREEONCLOSE, SQLITE_DESERIALIZE_READONLY, SQLITE_DESERIALIZE_RESIZEABLE, + SQLITE_NOMEM, SQLITE_OK, +}; +use std::ffi::c_char; +use std::fmt::Debug; +use std::{ + ops::{Deref, DerefMut}, + ptr, + ptr::NonNull, +}; + +impl SqliteConnection { + /// Serialize the given SQLite database schema using [`sqlite3_serialize()`]. + /// + /// The returned buffer is a SQLite managed allocation containing the equivalent data + /// as writing the database to disk. It is freed on-drop. + /// + /// To serialize the primary, unqualified schema (`main`), pass `None` for the schema name. + /// + /// # Errors + /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`). + /// * [`Error::Database`] if the schema does not exist or another error occurs. + /// + /// [`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))] + pub async fn serialize(&mut self, schema: Option<&str>) -> Result { + let schema = schema.map(SchemaName::try_from).transpose()?; + + self.worker.serialize(schema).await + } + + /// Deserialize a SQLite database from a buffer into the specified schema using [`sqlite3_deserialize()`]. + /// + /// The given schema will be disconnected and re-connected as an in-memory database + /// backed by `data`, which should be the serialized form of a database previously returned + /// by a call to [`Self::serialize()`], documented as being equivalent to + /// the contents of the database file on disk. + /// + /// An error will be returned if a schema with the given name is not already attached. + /// You can use `ATTACH ':memory' as ""` to create an empty schema first. + /// + /// Pass `None` to deserialize to the primary, unqualified schema (`main`). + /// + /// The SQLite connection will take ownership of `data` and will free it when the connection + /// is closed or the schema is detached ([`SQLITE_DESERIALIZE_FREEONCLOSE`][deserialize-flags]). + /// + /// If `read_only` is `true`, the schema is opened as read-only ([`SQLITE_DESERIALIZE_READONLY`][deserialize-flags]). + /// If `false`, the schema is marked as resizable ([`SQLITE_DESERIALIZE_RESIZABLE`][deserialize-flags]). + /// + /// If the database is in WAL mode, an error is returned. + /// See [`sqlite3_deserialize()`] for details. + /// + /// # Errors + /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`). + /// * [`Error::Database`] if an error occurs during deserialization. + /// + /// [`sqlite3_deserialize()`]: https://sqlite.org/c3ref/deserialize.html + /// [deserialize-flags]: https://sqlite.org/c3ref/c_deserialize_freeonclose.html + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))] + pub async fn deserialize( + &mut self, + schema: Option<&str>, + data: SqliteOwnedBuf, + read_only: bool, + ) -> Result<(), Error> { + let schema = schema.map(SchemaName::try_from).transpose()?; + + self.worker.deserialize(schema, data, read_only).await + } +} + +pub(crate) fn serialize( + conn: &mut ConnectionState, + schema: Option, +) -> Result { + let mut size = 0; + + let buf = unsafe { + let ptr = sqlite3_serialize( + conn.handle.as_ptr(), + schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr), + &mut size, + 0, + ); + + // looking at the source, `sqlite3_serialize` actually sets `size = -1` on error: + // https://github.com/sqlite/sqlite/blob/da5f81387843f92652128087a8f8ecef0b79461d/src/memdb.c#L776 + usize::try_from(size) + .ok() + .and_then(|size| SqliteOwnedBuf::from_raw(ptr, size)) + }; + + if let Some(buf) = buf { + return Ok(buf); + } + + if let Some(error) = conn.handle.last_error() { + return Err(error.into()); + } + + if size > 0 { + // If `size` is positive but `sqlite3_serialize` still returned NULL, + // the most likely culprit is an out-of-memory condition. + return Err(SqliteError::from_code(SQLITE_NOMEM).into()); + } + + // Otherwise, the schema was probably not found. + // We return the equivalent error as when you try to execute `PRAGMA .page_count` + // against a non-existent schema. + Err(SqliteError::generic(format!( + "database {} does not exist", + schema.as_ref().map_or("main", SchemaName::as_str) + )) + .into()) +} + +pub(crate) fn deserialize( + conn: &mut ConnectionState, + schema: Option, + data: SqliteOwnedBuf, + read_only: bool, +) -> Result<(), Error> { + // SQLITE_DESERIALIZE_FREEONCLOSE causes SQLite to take ownership of the buffer + let mut flags = SQLITE_DESERIALIZE_FREEONCLOSE; + if read_only { + flags |= SQLITE_DESERIALIZE_READONLY; + } else { + flags |= SQLITE_DESERIALIZE_RESIZEABLE; + } + + let (buf, size) = data.into_raw(); + + let rc = unsafe { + sqlite3_deserialize( + conn.handle.as_ptr(), + schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr), + buf, + i64::try_from(size).unwrap(), + i64::try_from(size).unwrap(), + flags, + ) + }; + + match rc { + SQLITE_OK => Ok(()), + SQLITE_NOMEM => Err(SqliteError::from_code(SQLITE_NOMEM).into()), + // SQLite unfortunately doesn't set any specific message for deserialization errors. + _ => Err(SqliteError::generic("an error occurred during deserialization").into()), + } +} + +/// Memory buffer owned and allocated by SQLite. Freed on drop. +/// +/// Intended primarily for use with [`SqliteConnection::serialize()`] and [`SqliteConnection::deserialize()`]. +/// +/// Can be created from `&[u8]` using the `TryFrom` impl. The slice must not be empty. +#[derive(Debug)] +pub struct SqliteOwnedBuf { + ptr: NonNull, + size: usize, +} + +unsafe impl Send for SqliteOwnedBuf {} +unsafe impl Sync for SqliteOwnedBuf {} + +impl Drop for SqliteOwnedBuf { + fn drop(&mut self) { + unsafe { + sqlite3_free(self.ptr.as_ptr().cast()); + } + } +} + +impl SqliteOwnedBuf { + /// Uses `sqlite3_malloc` to allocate a buffer and returns a pointer to it. + /// + /// # Safety + /// The allocated buffer is uninitialized. + unsafe fn with_capacity(size: usize) -> Option { + let ptr = sqlite3_malloc64(u64::try_from(size).unwrap()).cast::(); + Self::from_raw(ptr, size) + } + + /// Creates a new mem buffer from a pointer that has been created with sqlite_malloc + /// + /// # Safety: + /// * The pointer must point to a valid allocation created by `sqlite3_malloc()`, or `NULL`. + unsafe fn from_raw(ptr: *mut u8, size: usize) -> Option { + Some(Self { + ptr: NonNull::new(ptr)?, + size, + }) + } + + fn into_raw(self) -> (*mut u8, usize) { + let raw = (self.ptr.as_ptr(), self.size); + // this is used in sqlite_deserialize and + // underlying buffer must not be freed + std::mem::forget(self); + raw + } +} + +/// # Errors +/// Returns [`Error::InvalidArgument`] if the slice is empty. +impl TryFrom<&[u8]> for SqliteOwnedBuf { + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + unsafe { + // SAFETY: `buf` is not initialized until `ptr::copy_nonoverlapping` completes. + let mut buf = Self::with_capacity(bytes.len()).ok_or_else(|| { + Error::InvalidArgument("SQLite owned buffer cannot be empty".to_string()) + })?; + ptr::copy_nonoverlapping(bytes.as_ptr(), buf.ptr.as_mut(), buf.size); + Ok(buf) + } + } +} + +impl Deref for SqliteOwnedBuf { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) } + } +} + +impl DerefMut for SqliteOwnedBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.size) } + } +} + +impl AsRef<[u8]> for SqliteOwnedBuf { + fn as_ref(&self) -> &[u8] { + self.deref() + } +} + +impl AsMut<[u8]> for SqliteOwnedBuf { + fn as_mut(&mut self) -> &mut [u8] { + self.deref_mut() + } +} + +/// Checked schema name to pass to SQLite. +/// +/// # Safety: +/// * Valid UTF-8 (not guaranteed by `CString`) +/// * No internal zero bytes (`\0`) (not guaranteed by `String`) +/// * Terminated with a zero byte (`\0`) (not guaranteed by `String`) +#[derive(Debug)] +pub(crate) struct SchemaName(Box); + +impl SchemaName { + /// Get the schema name as a string without the zero byte terminator. + pub fn as_str(&self) -> &str { + &self.0[..self.0.len() - 1] + } + + /// Get a pointer to the string data, suitable for passing as C's `*const char`. + /// + /// # Safety + /// The string data is guaranteed to be terminated with a zero byte. + pub fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() as *const c_char + } +} + +impl<'a> TryFrom<&'a str> for SchemaName { + type Error = Error; + + fn try_from(name: &'a str) -> Result { + // SAFETY: we must ensure that the string does not contain an internal NULL byte + if let Some(pos) = name.as_bytes().iter().position(|&b| b == 0) { + return Err(Error::InvalidArgument(format!( + "schema name {name:?} contains a zero byte at index {pos}" + ))); + } + + let capacity = name.len().checked_add(1).unwrap(); + + let mut s = String::new(); + // `String::with_capacity()` does not guarantee that it will not overallocate, + // which might mean an unnecessary reallocation to make `capacity == len` + // in the conversion to `Box`. + s.reserve_exact(capacity); + + s.push_str(name); + s.push('\0'); + + Ok(SchemaName(s.into())) + } +} diff --git a/vendor/sqlx-sqlite/src/connection/establish.rs b/vendor/sqlx-sqlite/src/connection/establish.rs new file mode 100644 index 0000000000..cedf752e6a --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/establish.rs @@ -0,0 +1,271 @@ +use crate::connection::handle::ConnectionHandle; +use crate::connection::LogSettings; +use crate::connection::{ConnectionState, Statements}; +use crate::error::Error; +use crate::SqliteConnectOptions; +use libsqlite3_sys::{ + sqlite3_busy_timeout, SQLITE_OPEN_CREATE, SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_MEMORY, + SQLITE_OPEN_NOMUTEX, SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, + SQLITE_OPEN_SHAREDCACHE, SQLITE_OPEN_URI, +}; +use percent_encoding::NON_ALPHANUMERIC; +use std::collections::BTreeMap; +use std::ffi::CString; +use std::io; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +#[cfg(feature = "load-extension")] +use sqlx_core::IndexMap; + +// This was originally `AtomicU64` but that's not supported on MIPS (or PowerPC): +// https://github.com/launchbadge/sqlx/issues/2859 +// https://doc.rust-lang.org/stable/std/sync/atomic/index.html#portability +static THREAD_ID: AtomicUsize = AtomicUsize::new(0); + +pub struct EstablishParams { + filename: CString, + open_flags: i32, + busy_timeout: Duration, + statement_cache_capacity: usize, + log_settings: LogSettings, + #[cfg(feature = "load-extension")] + extensions: IndexMap>, + pub(crate) thread_name: String, + pub(crate) command_channel_size: usize, + #[cfg(feature = "regexp")] + register_regexp_function: bool, +} + +impl EstablishParams { + pub fn from_options(options: &SqliteConnectOptions) -> Result { + let mut filename = options + .filename + .to_str() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + "filename passed to SQLite must be valid UTF-8", + ) + })? + .to_owned(); + + // Set common flags we expect to have in sqlite + let mut flags = SQLITE_OPEN_URI; + + // By default, we connect to an in-memory database. + // [SQLITE_OPEN_NOMUTEX] will instruct [sqlite3_open_v2] to return an error if it + // cannot satisfy our wish for a thread-safe, lock-free connection object + + flags |= if options.serialized { + SQLITE_OPEN_FULLMUTEX + } else { + SQLITE_OPEN_NOMUTEX + }; + + flags |= if options.read_only { + SQLITE_OPEN_READONLY + } else if options.create_if_missing { + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + } else { + SQLITE_OPEN_READWRITE + }; + + if options.in_memory { + flags |= SQLITE_OPEN_MEMORY; + } + + flags |= if options.shared_cache { + SQLITE_OPEN_SHAREDCACHE + } else { + SQLITE_OPEN_PRIVATECACHE + }; + + let mut query_params = BTreeMap::new(); + + if options.immutable { + query_params.insert("immutable", "true"); + } + + if let Some(vfs) = options.vfs.as_deref() { + query_params.insert("vfs", vfs); + } + + if !query_params.is_empty() { + filename = format!( + "file:{}?", + percent_encoding::percent_encode(filename.as_bytes(), NON_ALPHANUMERIC), + ); + + // Suffix serializer automatically handles `&` separators for us. + let filename_len = filename.len(); + filename = form_urlencoded::Serializer::for_suffix(filename, filename_len) + .extend_pairs(query_params) + .finish(); + } + + let filename = CString::new(filename).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "filename passed to SQLite must not contain nul bytes", + ) + })?; + + #[cfg(feature = "load-extension")] + let extensions = options + .extensions + .iter() + .map(|(name, entry)| { + let entry = entry + .as_ref() + .map(|e| { + CString::new(e.as_bytes()).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "extension entrypoint names passed to SQLite must not contain nul bytes" + ) + }) + }) + .transpose()?; + Ok(( + CString::new(name.as_bytes()).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "extension names passed to SQLite must not contain nul bytes", + ) + })?, + entry, + )) + }) + .collect::>, io::Error>>()?; + + let thread_id = THREAD_ID.fetch_add(1, Ordering::AcqRel); + + Ok(Self { + filename, + open_flags: flags, + busy_timeout: options.busy_timeout, + statement_cache_capacity: options.statement_cache_capacity, + log_settings: options.log_settings.clone(), + #[cfg(feature = "load-extension")] + extensions, + thread_name: (options.thread_name)(thread_id as u64), + command_channel_size: options.command_channel_size, + #[cfg(feature = "regexp")] + register_regexp_function: options.register_regexp_function, + }) + } + + pub(crate) fn establish(&self) -> Result { + let mut handle = ConnectionHandle::open(&self.filename, self.open_flags)?; + + #[cfg(feature = "load-extension")] + unsafe { + self.apply_extensions(&mut handle)?; + } + + #[cfg(feature = "regexp")] + if self.register_regexp_function { + // configure a `regexp` function for sqlite, it does not come with one by default + let status = crate::regexp::register(handle.as_ptr()); + if status != libsqlite3_sys::SQLITE_OK { + return Err(Error::Database(Box::new(handle.expect_error()))); + } + } + + // Configure a busy timeout + // This causes SQLite to automatically sleep in increasing intervals until the time + // when there is something locked during [sqlite3_step]. + // + // We also need to convert the u128 value to i32, checking we're not overflowing. + let ms = i32::try_from(self.busy_timeout.as_millis()) + .expect("Given busy timeout value is too big."); + + handle.call_with_result(|db| unsafe { sqlite3_busy_timeout(db, ms) })?; + + Ok(ConnectionState { + handle, + statements: Statements::new(self.statement_cache_capacity), + log_settings: self.log_settings.clone(), + progress_handler_callback: None, + update_hook_callback: None, + #[cfg(feature = "preupdate-hook")] + preupdate_hook_callback: None, + commit_hook_callback: None, + rollback_hook_callback: None, + }) + } + + #[cfg(feature = "load-extension")] + unsafe fn apply_extensions(&self, handle: &mut ConnectionHandle) -> Result<(), Error> { + use libsqlite3_sys::{sqlite3_free, sqlite3_load_extension}; + use std::ffi::{c_int, CStr}; + use std::ptr; + + /// `true` enables *just* `sqlite3_load_extension`, false disables *all* extension loading. + fn enable_load_extension( + handle: &mut ConnectionHandle, + enabled: bool, + ) -> Result<(), Error> { + use libsqlite3_sys::{sqlite3_db_config, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION}; + + // SAFETY: we have exclusive access and this matches the expected signature + // + handle.call_with_result(|db| unsafe { + // https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.bool-char-as-int + sqlite3_db_config( + db, + SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, + enabled as c_int, + ptr::null_mut::(), + ) + })?; + + Ok(()) + } + + if self.extensions.is_empty() { + return Ok(()); + } + + // We enable extension loading only so long as *we're* doing it. + enable_load_extension(handle, true)?; + + for (name, entrypoint) in &self.extensions { + let name_ptr = name.as_ptr(); + let entrypoint_ptr = entrypoint.as_ref().map_or_else(ptr::null, |s| s.as_ptr()); + let mut err_msg_ptr = ptr::null_mut(); + + // SAFETY: + // * we have exclusive access + // * all pointers are initialized + // * we warn the user about loading extensions in documentation + handle + .call_with_result(|db| unsafe { + sqlite3_load_extension(db, name_ptr, entrypoint_ptr, &mut err_msg_ptr) + }) + .map_err(|e| { + if !err_msg_ptr.is_null() { + // SAFETY: pointer is not-null, + // and we copy the error message to an allocation we own. + let err_msg = unsafe { CStr::from_ptr(err_msg_ptr) } + // In practice, the string *should* be UTF-8. + .to_string_lossy() + .into_owned(); + + // SAFETY: we're expected to free the error message afterward. + unsafe { + sqlite3_free(err_msg_ptr.cast()); + } + + e.with_message(err_msg) + } else { + e + } + })?; + } + + // We then disable extension loading immediately afterward. + enable_load_extension(handle, false) + } +} diff --git a/vendor/sqlx-sqlite/src/connection/execute.rs b/vendor/sqlx-sqlite/src/connection/execute.rs new file mode 100644 index 0000000000..733a1abbe6 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/execute.rs @@ -0,0 +1,132 @@ +use crate::connection::{ConnectionHandle, ConnectionState}; +use crate::error::Error; +use crate::logger::QueryLogger; +use crate::statement::{StatementHandle, VirtualStatement}; +use crate::{SqliteArguments, SqliteQueryResult, SqliteRow}; +use sqlx_core::sql_str::SqlSafeStr; +use sqlx_core::Either; + +pub struct ExecuteIter<'a> { + handle: &'a mut ConnectionHandle, + statement: &'a mut VirtualStatement, + logger: QueryLogger, + args: Option, + + /// since a `VirtualStatement` can encompass multiple actual statements, + /// this keeps track of the number of arguments so far + args_used: usize, + + goto_next: bool, +} + +pub(crate) fn iter( + conn: &mut ConnectionState, + query: impl SqlSafeStr, + args: Option, + persistent: bool, +) -> Result, Error> { + let query = query.into_sql_str(); + // fetch the cached statement or allocate a new one + let statement = conn.statements.get(query.as_str(), persistent)?; + + let logger = QueryLogger::new(query, conn.log_settings.clone()); + + Ok(ExecuteIter { + handle: &mut conn.handle, + statement, + logger, + args, + args_used: 0, + goto_next: true, + }) +} + +fn bind( + statement: &mut StatementHandle, + arguments: &Option, + offset: usize, +) -> Result { + let mut n = 0; + + if let Some(arguments) = arguments { + n = arguments.bind(statement, offset)?; + } + + Ok(n) +} + +impl ExecuteIter<'_> { + pub fn finish(self) -> Result<(), Error> { + for res in self { + let _ = res?; + } + + Ok(()) + } +} + +impl Iterator for ExecuteIter<'_> { + type Item = Result, Error>; + + fn next(&mut self) -> Option { + let statement = if self.goto_next { + let statement = match self.statement.prepare_next(self.handle) { + Ok(Some(statement)) => statement, + Ok(None) => return None, + Err(e) => return Some(Err(e)), + }; + + self.goto_next = false; + + // sanity check: ensure the VM is reset and the bindings are cleared + if let Err(e) = statement.handle.reset() { + return Some(Err(e.into())); + } + + statement.handle.clear_bindings(); + + match bind(statement.handle, &self.args, self.args_used) { + Ok(args_used) => self.args_used += args_used, + Err(e) => return Some(Err(e)), + } + + statement + } else { + self.statement.current()? + }; + + match statement.handle.step() { + Ok(true) => { + self.logger.increment_rows_returned(); + + Some(Ok(Either::Right(SqliteRow::current( + statement.handle, + statement.columns, + statement.column_names, + )))) + } + Ok(false) => { + let last_insert_rowid = self.handle.last_insert_rowid(); + + let changes = statement.handle.changes(); + self.logger.increase_rows_affected(changes); + + let done = SqliteQueryResult { + changes, + last_insert_rowid, + }; + + self.goto_next = true; + + Some(Ok(Either::Left(done))) + } + Err(e) => Some(Err(e.into())), + } + } +} + +impl Drop for ExecuteIter<'_> { + fn drop(&mut self) { + self.statement.reset().ok(); + } +} diff --git a/vendor/sqlx-sqlite/src/connection/executor.rs b/vendor/sqlx-sqlite/src/connection/executor.rs new file mode 100644 index 0000000000..a62d3349a5 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/executor.rs @@ -0,0 +1,101 @@ +use crate::{ + Sqlite, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, SqliteTypeInfo, +}; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; +use sqlx_core::error::Error; +use sqlx_core::executor::{Execute, Executor}; +use sqlx_core::sql_str::SqlStr; +use sqlx_core::Either; +use std::{future, pin::pin}; + +impl<'c> Executor<'c> for &'c mut SqliteConnection { + type Database = Sqlite; + + fn fetch_many<'e, 'q, E>( + self, + mut query: E, + ) -> BoxStream<'e, Result, Error>> + where + 'c: 'e, + E: Execute<'q, Self::Database>, + 'q: 'e, + E: 'q, + { + let arguments = match query.take_arguments().map_err(Error::Encode) { + Ok(arguments) => arguments, + Err(error) => return stream::once(future::ready(Err(error))).boxed(), + }; + let persistent = query.persistent() && arguments.is_some(); + let sql = query.sql(); + + Box::pin( + self.worker + .execute(sql, arguments, self.row_channel_size, persistent, None) + .map_ok(flume::Receiver::into_stream) + .try_flatten_stream(), + ) + } + + fn fetch_optional<'e, 'q, E>( + self, + mut query: E, + ) -> BoxFuture<'e, Result, Error>> + where + 'c: 'e, + E: Execute<'q, Self::Database>, + 'q: 'e, + E: 'q, + { + let arguments = match query.take_arguments().map_err(Error::Encode) { + Ok(arguments) => arguments, + Err(error) => return future::ready(Err(error)).boxed(), + }; + let persistent = query.persistent() && arguments.is_some(); + + Box::pin(async move { + let sql = query.sql(); + let mut stream = pin!(self + .worker + .execute(sql, arguments, self.row_channel_size, persistent, Some(1)) + .map_ok(flume::Receiver::into_stream) + .try_flatten_stream()); + + while let Some(res) = stream.try_next().await? { + if let Either::Right(row) = res { + return Ok(Some(row)); + } + } + + Ok(None) + }) + } + + fn prepare_with<'e>( + self, + sql: SqlStr, + _parameters: &[SqliteTypeInfo], + ) -> BoxFuture<'e, Result> + where + 'c: 'e, + { + Box::pin(async move { + let statement = self.worker.prepare(sql).await?; + + Ok(statement) + }) + } + + #[doc(hidden)] + #[cfg(feature = "offline")] + fn describe<'e>( + self, + sql: SqlStr, + ) -> BoxFuture<'e, Result, Error>> + where + 'c: 'e, + { + Box::pin(async move { self.worker.describe(sql).await }) + } +} diff --git a/vendor/sqlx-sqlite/src/connection/explain.rs b/vendor/sqlx-sqlite/src/connection/explain.rs new file mode 100644 index 0000000000..550f21557e --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/explain.rs @@ -0,0 +1,1844 @@ +// Bad casts in this module SHOULD NOT result in a SQL injection +// https://github.com/launchbadge/sqlx/issues/3440 +#![allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss +)] +use crate::connection::intmap::IntMap; +use crate::connection::{execute, ConnectionState}; +use crate::error::Error; +use crate::from_row::FromRow; +use crate::logger::{BranchParent, BranchResult, DebugDiff}; +use crate::type_info::DataType; +use crate::SqliteTypeInfo; +use sqlx_core::sql_str::AssertSqlSafe; +use sqlx_core::{hash_map, HashMap}; +use std::fmt::Debug; +use std::str::from_utf8; + +// affinity +const SQLITE_AFF_NONE: u8 = 0x40; /* '@' */ +const SQLITE_AFF_BLOB: u8 = 0x41; /* 'A' */ +const SQLITE_AFF_TEXT: u8 = 0x42; /* 'B' */ +const SQLITE_AFF_NUMERIC: u8 = 0x43; /* 'C' */ +const SQLITE_AFF_INTEGER: u8 = 0x44; /* 'D' */ +const SQLITE_AFF_REAL: u8 = 0x45; /* 'E' */ + +// opcodes +const OP_INIT: &str = "Init"; +const OP_GOTO: &str = "Goto"; +const OP_DECR_JUMP_ZERO: &str = "DecrJumpZero"; +const OP_DELETE: &str = "Delete"; +const OP_ELSE_EQ: &str = "ElseEq"; +const OP_EQ: &str = "Eq"; +const OP_END_COROUTINE: &str = "EndCoroutine"; +const OP_FILTER: &str = "Filter"; +const OP_FK_IF_ZERO: &str = "FkIfZero"; +const OP_FOUND: &str = "Found"; +const OP_GE: &str = "Ge"; +const OP_GO_SUB: &str = "Gosub"; +const OP_GT: &str = "Gt"; +const OP_IDX_GE: &str = "IdxGE"; +const OP_IDX_GT: &str = "IdxGT"; +const OP_IDX_LE: &str = "IdxLE"; +const OP_IDX_LT: &str = "IdxLT"; +const OP_IDX_ROWID: &str = "IdxRowid"; +const OP_IF: &str = "If"; +const OP_IF_NO_HOPE: &str = "IfNoHope"; +const OP_IF_NOT: &str = "IfNot"; +const OP_IF_NOT_OPEN: &str = "IfNotOpen"; +const OP_IF_NOT_ZERO: &str = "IfNotZero"; +const OP_IF_NULL_ROW: &str = "IfNullRow"; +const OP_IF_POS: &str = "IfPos"; +const OP_IF_SMALLER: &str = "IfSmaller"; +const OP_INCR_VACUUM: &str = "IncrVacuum"; +const OP_INIT_COROUTINE: &str = "InitCoroutine"; +const OP_IS_NULL: &str = "IsNull"; +const OP_IS_NULL_OR_TYPE: &str = "IsNullOrType"; +const OP_LAST: &str = "Last"; +const OP_LE: &str = "Le"; +const OP_LT: &str = "Lt"; +const OP_MUST_BE_INT: &str = "MustBeInt"; +const OP_NE: &str = "Ne"; +const OP_NEXT: &str = "Next"; +const OP_NO_CONFLICT: &str = "NoConflict"; +const OP_NOT_EXISTS: &str = "NotExists"; +const OP_NOT_NULL: &str = "NotNull"; +const OP_ONCE: &str = "Once"; +const OP_PREV: &str = "Prev"; +const OP_PROGRAM: &str = "Program"; +const OP_RETURN: &str = "Return"; +const OP_REWIND: &str = "Rewind"; +const OP_ROW_DATA: &str = "RowData"; +const OP_ROW_SET_READ: &str = "RowSetRead"; +const OP_ROW_SET_TEST: &str = "RowSetTest"; +const OP_SEEK_GE: &str = "SeekGE"; +const OP_SEEK_GT: &str = "SeekGT"; +const OP_SEEK_LE: &str = "SeekLE"; +const OP_SEEK_LT: &str = "SeekLT"; +const OP_SEEK_ROW_ID: &str = "SeekRowid"; +const OP_SEEK_SCAN: &str = "SeekScan"; +const OP_SEQUENCE: &str = "Sequence"; +const OP_SEQUENCE_TEST: &str = "SequenceTest"; +const OP_SORT: &str = "Sort"; +const OP_SORTER_DATA: &str = "SorterData"; +const OP_SORTER_INSERT: &str = "SorterInsert"; +const OP_SORTER_NEXT: &str = "SorterNext"; +const OP_SORTER_OPEN: &str = "SorterOpen"; +const OP_SORTER_SORT: &str = "SorterSort"; +const OP_V_FILTER: &str = "VFilter"; +const OP_V_NEXT: &str = "VNext"; +const OP_YIELD: &str = "Yield"; +const OP_JUMP: &str = "Jump"; +const OP_COLUMN: &str = "Column"; +const OP_MAKE_RECORD: &str = "MakeRecord"; +const OP_INSERT: &str = "Insert"; +const OP_IDX_INSERT: &str = "IdxInsert"; +const OP_OPEN_DUP: &str = "OpenDup"; +const OP_OPEN_PSEUDO: &str = "OpenPseudo"; +const OP_OPEN_READ: &str = "OpenRead"; +const OP_OPEN_WRITE: &str = "OpenWrite"; +const OP_OPEN_EPHEMERAL: &str = "OpenEphemeral"; +const OP_OPEN_AUTOINDEX: &str = "OpenAutoindex"; +const OP_AGG_FINAL: &str = "AggFinal"; +const OP_AGG_VALUE: &str = "AggValue"; +const OP_AGG_STEP: &str = "AggStep"; +const OP_FUNCTION: &str = "Function"; +const OP_MOVE: &str = "Move"; +const OP_COPY: &str = "Copy"; +const OP_SCOPY: &str = "SCopy"; +const OP_NULL: &str = "Null"; +const OP_NULL_ROW: &str = "NullRow"; +const OP_INT_COPY: &str = "IntCopy"; +const OP_CAST: &str = "Cast"; +const OP_STRING8: &str = "String8"; +const OP_INT64: &str = "Int64"; +const OP_INTEGER: &str = "Integer"; +const OP_REAL: &str = "Real"; +const OP_NOT: &str = "Not"; +const OP_BLOB: &str = "Blob"; +const OP_VARIABLE: &str = "Variable"; +const OP_COUNT: &str = "Count"; +const OP_ROWID: &str = "Rowid"; +const OP_NEWROWID: &str = "NewRowid"; +const OP_OR: &str = "Or"; +const OP_AND: &str = "And"; +const OP_BIT_AND: &str = "BitAnd"; +const OP_BIT_OR: &str = "BitOr"; +const OP_SHIFT_LEFT: &str = "ShiftLeft"; +const OP_SHIFT_RIGHT: &str = "ShiftRight"; +const OP_ADD: &str = "Add"; +const OP_SUBTRACT: &str = "Subtract"; +const OP_MULTIPLY: &str = "Multiply"; +const OP_DIVIDE: &str = "Divide"; +const OP_REMAINDER: &str = "Remainder"; +const OP_CONCAT: &str = "Concat"; +const OP_OFFSET_LIMIT: &str = "OffsetLimit"; +const OP_RESULT_ROW: &str = "ResultRow"; +const OP_HALT: &str = "Halt"; +const OP_HALT_IF_NULL: &str = "HaltIfNull"; + +const MAX_LOOP_COUNT: u8 = 2; +const MAX_TOTAL_INSTRUCTION_COUNT: u32 = 100_000; + +#[derive(Clone, Eq, PartialEq, Hash)] +enum ColumnType { + Single { + datatype: DataType, + nullable: Option, + }, + Record(IntMap), +} + +impl Default for ColumnType { + fn default() -> Self { + Self::Single { + datatype: DataType::Null, + nullable: None, + } + } +} + +impl ColumnType { + fn null() -> Self { + Self::Single { + datatype: DataType::Null, + nullable: Some(true), + } + } + fn map_to_datatype(&self) -> DataType { + match self { + Self::Single { datatype, .. } => *datatype, + Self::Record(_) => DataType::Null, //If we're trying to coerce to a regular Datatype, we can assume a Record is invalid for the context + } + } + fn map_to_nullable(&self) -> Option { + match self { + Self::Single { nullable, .. } => *nullable, + Self::Record(_) => None, //If we're trying to coerce to a regular Datatype, we can assume a Record is invalid for the context + } + } +} + +impl core::fmt::Debug for ColumnType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Single { datatype, nullable } => { + let nullable_str = match nullable { + Some(true) => "NULL", + Some(false) => "NOT NULL", + None => "NULL?", + }; + write!(f, "{:?} {}", datatype, nullable_str) + } + Self::Record(columns) => { + f.write_str("Record(")?; + let mut column_iter = columns.iter(); + if let Some(item) = column_iter.next() { + write!(f, "{:?}", item)?; + for item in column_iter { + write!(f, ", {:?}", item)?; + } + } + f.write_str(")") + } + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +enum RegDataType { + Single(ColumnType), + Int(i64), +} + +impl RegDataType { + fn map_to_datatype(&self) -> DataType { + match self { + RegDataType::Single(d) => d.map_to_datatype(), + RegDataType::Int(_) => DataType::Integer, + } + } + fn map_to_nullable(&self) -> Option { + match self { + RegDataType::Single(d) => d.map_to_nullable(), + RegDataType::Int(_) => Some(false), + } + } + fn map_to_columntype(&self) -> ColumnType { + match self { + RegDataType::Single(d) => d.clone(), + RegDataType::Int(_) => ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }, + } + } +} + +impl Default for RegDataType { + fn default() -> Self { + Self::Single(ColumnType::default()) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +struct TableDataType { + cols: IntMap, + is_empty: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +enum CursorDataType { + Normal(i64), + Pseudo(i64), +} + +impl CursorDataType { + fn columns( + &self, + tables: &IntMap, + registers: &IntMap, + ) -> IntMap { + match self { + Self::Normal(i) => match tables.get(i) { + Some(tab) => tab.cols.clone(), + None => IntMap::new(), + }, + Self::Pseudo(i) => match registers.get(i) { + Some(RegDataType::Single(ColumnType::Record(r))) => r.clone(), + _ => IntMap::new(), + }, + } + } + + fn columns_ref<'s, 'r, 'o>( + &'s self, + tables: &'r IntMap, + registers: &'r IntMap, + ) -> Option<&'o IntMap> + where + 's: 'o, + 'r: 'o, + { + match self { + Self::Normal(i) => match tables.get(i) { + Some(tab) => Some(&tab.cols), + None => None, + }, + Self::Pseudo(i) => match registers.get(i) { + Some(RegDataType::Single(ColumnType::Record(r))) => Some(r), + _ => None, + }, + } + } + + fn columns_mut<'s, 'r, 'o>( + &'s self, + tables: &'r mut IntMap, + registers: &'r mut IntMap, + ) -> Option<&'o mut IntMap> + where + 's: 'o, + 'r: 'o, + { + match self { + Self::Normal(i) => match tables.get_mut(i) { + Some(tab) => Some(&mut tab.cols), + None => None, + }, + Self::Pseudo(i) => match registers.get_mut(i) { + Some(RegDataType::Single(ColumnType::Record(r))) => Some(r), + _ => None, + }, + } + } + + fn table_mut<'s, 'r, 'o>( + &'s self, + tables: &'r mut IntMap, + ) -> Option<&'o mut TableDataType> + where + 's: 'o, + 'r: 'o, + { + match self { + Self::Normal(i) => match tables.get_mut(i) { + Some(tab) => Some(tab), + None => None, + }, + _ => None, + } + } + + fn is_empty(&self, tables: &IntMap) -> Option { + match self { + Self::Normal(i) => match tables.get(i) { + Some(tab) => tab.is_empty, + None => Some(true), + }, + Self::Pseudo(_) => Some(false), //pseudo cursors have exactly one row + } + } +} + +#[allow(clippy::wildcard_in_or_patterns)] +fn affinity_to_type(affinity: u8) -> DataType { + match affinity { + SQLITE_AFF_BLOB => DataType::Blob, + SQLITE_AFF_INTEGER => DataType::Integer, + SQLITE_AFF_NUMERIC => DataType::Numeric, + SQLITE_AFF_REAL => DataType::Float, + SQLITE_AFF_TEXT => DataType::Text, + + SQLITE_AFF_NONE | _ => DataType::Null, + } +} + +#[allow(clippy::wildcard_in_or_patterns)] +fn opcode_to_type(op: &str) -> DataType { + match op { + OP_REAL => DataType::Float, + OP_BLOB => DataType::Blob, + OP_AND | OP_OR => DataType::Bool, + OP_NEWROWID | OP_IDX_ROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => { + DataType::Integer + } + OP_STRING8 => DataType::Text, + OP_COLUMN | _ => DataType::Null, + } +} + +fn root_block_columns( + conn: &mut ConnectionState, +) -> Result>, Error> { + let table_block_columns: Vec<(i64, i64, i64, String, bool)> = execute::iter( + conn, + "SELECT s.dbnum, s.rootpage, col.cid as colnum, col.type, col.\"notnull\" + FROM ( + select 1 dbnum, tss.* from temp.sqlite_schema tss + UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss + ) s + JOIN pragma_table_info(s.name) AS col + WHERE s.type = 'table' + UNION ALL + SELECT s.dbnum, s.rootpage, idx.seqno as colnum, col.type, col.\"notnull\" + FROM ( + select 1 dbnum, tss.* from temp.sqlite_schema tss + UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss + ) s + JOIN pragma_index_info(s.name) AS idx + LEFT JOIN pragma_table_info(s.tbl_name) as col + ON col.cid = idx.cid + WHERE s.type = 'index'", + None, + false, + )? + .filter_map(|res| res.map(|either| either.right()).transpose()) + .map(|row| FromRow::from_row(&row?)) + .collect::, Error>>()?; + + let mut row_info: HashMap<(i64, i64), IntMap> = HashMap::new(); + for (dbnum, block, colnum, datatype, notnull) in table_block_columns { + let row_info = row_info.entry((dbnum, block)).or_default(); + row_info.insert( + colnum, + ColumnType::Single { + datatype: datatype.parse().unwrap_or(DataType::Null), + nullable: Some(!notnull), + }, + ); + } + + Ok(row_info) +} + +struct Sequence(i64); + +impl Sequence { + pub fn new() -> Self { + Self(0) + } + pub fn next(&mut self) -> i64 { + let curr = self.0; + self.0 += 1; + curr + } +} + +#[derive(Debug)] +struct QueryState { + // The number of times each instruction has been visited + pub visited: Vec, + // A unique identifier of the query branch + pub branch_id: i64, + // How many instructions have been executed on this branch (NOT the same as program_i, which is the currently executing instruction of the program) + pub instruction_counter: i64, + // Parent branch this branch was forked from (if any) + pub branch_parent: Option, + // State of the virtual machine + pub mem: MemoryState, + // Results published by the execution + pub result: Option, Option)>>, +} + +impl From<&QueryState> for MemoryState { + fn from(val: &QueryState) -> Self { + val.mem.clone() + } +} + +impl From for MemoryState { + fn from(val: QueryState) -> Self { + val.mem + } +} + +impl From<&QueryState> for BranchParent { + fn from(val: &QueryState) -> Self { + Self { + id: val.branch_id, + idx: val.instruction_counter, + } + } +} + +impl QueryState { + fn get_reference(&self) -> BranchParent { + BranchParent { + id: self.branch_id, + idx: self.instruction_counter, + } + } + fn new_branch(&self, branch_seq: &mut Sequence) -> Self { + Self { + visited: self.visited.clone(), + branch_id: branch_seq.next(), + instruction_counter: 0, + branch_parent: Some(BranchParent { + id: self.branch_id, + idx: self.instruction_counter - 1, //instruction counter is incremented at the start of processing an instruction, so need to subtract 1 to get the 'current' instruction + }), + mem: self.mem.clone(), + result: self.result.clone(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct MemoryState { + // Next instruction to execute + pub program_i: usize, + // Registers + pub r: IntMap, + // Rows that pointers point to + pub p: IntMap, + // Table definitions pointed to by pointers + pub t: IntMap, +} + +impl DebugDiff for MemoryState { + fn diff(&self, prev: &Self) -> String { + let r_diff = self.r.diff(&prev.r); + let p_diff = self.p.diff(&prev.p); + let t_diff = self.t.diff(&prev.t); + + let mut differences = String::new(); + for (i, v) in r_diff { + if !differences.is_empty() { + differences.push('\n'); + } + differences.push_str(&format!("r[{}]={:?}", i, v)) + } + for (i, v) in p_diff { + if !differences.is_empty() { + differences.push('\n'); + } + differences.push_str(&format!("p[{}]={:?}", i, v)) + } + for (i, v) in t_diff { + if !differences.is_empty() { + differences.push('\n'); + } + differences.push_str(&format!("t[{}]={:?}", i, v)) + } + differences + } +} + +struct BranchList { + states: Vec, + visited_branch_state: HashMap, +} + +impl BranchList { + pub fn new(state: QueryState) -> Self { + Self { + states: vec![state], + visited_branch_state: HashMap::new(), + } + } + pub fn push( + &mut self, + mut state: QueryState, + logger: &mut crate::logger::QueryPlanLogger<'_, R, MemoryState, P>, + ) { + logger.add_branch(&state, &state.branch_parent.unwrap()); + match self.visited_branch_state.entry(state.mem) { + hash_map::Entry::Vacant(entry) => { + //this state is not identical to another state, so it will need to be processed + state.mem = entry.key().clone(); //replace state.mem since .entry() moved it + entry.insert(state.get_reference()); + self.states.push(state); + } + hash_map::Entry::Occupied(entry) => { + //already saw a state identical to this one, so no point in processing it + state.mem = entry.key().clone(); //replace state.mem since .entry() moved it + logger.add_result(state, BranchResult::Dedup(*entry.get())); + } + } + } + pub fn pop(&mut self) -> Option { + self.states.pop() + } +} + +// Opcode Reference: https://sqlite.org/opcode.html +pub(super) fn explain( + conn: &mut ConnectionState, + query: &str, +) -> Result<(Vec, Vec>), Error> { + let root_block_cols = root_block_columns(conn)?; + let program: Vec<(i64, String, i64, i64, i64, Vec)> = + execute::iter(conn, AssertSqlSafe(format!("EXPLAIN {query}")), None, false)? + .filter_map(|res| res.map(|either| either.right()).transpose()) + .map(|row| FromRow::from_row(&row?)) + .collect::, Error>>()?; + let program_size = program.len(); + + let mut logger = crate::logger::QueryPlanLogger::new(query, &program); + let mut branch_seq = Sequence::new(); + let mut states = BranchList::new(QueryState { + visited: vec![0; program_size], + branch_id: branch_seq.next(), + branch_parent: None, + instruction_counter: 0, + result: None, + mem: MemoryState { + program_i: 0, + r: IntMap::new(), + t: IntMap::new(), + p: IntMap::new(), + }, + }); + + let mut gas = MAX_TOTAL_INSTRUCTION_COUNT; + let mut result_states = Vec::new(); + + while let Some(mut state) = states.pop() { + while state.mem.program_i < program_size { + let (_, ref opcode, p1, p2, p3, ref p4) = program[state.mem.program_i]; + + logger.add_operation(state.mem.program_i, &state); + state.instruction_counter += 1; + + //limit the number of 'instructions' that can be evaluated + if gas > 0 { + gas -= 1; + } else { + logger.add_result(state, BranchResult::GasLimit); + break; + } + + if state.visited[state.mem.program_i] > MAX_LOOP_COUNT { + logger.add_result(state, BranchResult::LoopLimit); + //avoid (infinite) loops by breaking if we ever hit the same instruction twice + break; + } + + state.visited[state.mem.program_i] += 1; + + match &**opcode { + OP_INIT => { + // start at + state.mem.program_i = p2 as usize; + continue; + } + + OP_GOTO => { + // goto + + state.mem.program_i = p2 as usize; + continue; + } + + OP_GO_SUB => { + // store current instruction in r[p1], goto + state + .mem + .r + .insert(p1, RegDataType::Int(state.mem.program_i as i64)); + state.mem.program_i = p2 as usize; + continue; + } + + OP_FK_IF_ZERO => { + // goto if no constraints are unsatisfied (assumed to be true) + + state.mem.program_i = p2 as usize; + continue; + } + + OP_DECR_JUMP_ZERO | OP_ELSE_EQ | OP_EQ | OP_FILTER | OP_FOUND | OP_GE | OP_GT + | OP_IDX_GE | OP_IDX_GT | OP_IDX_LE | OP_IDX_LT | OP_IF_NO_HOPE | OP_IF_NOT + | OP_IF_NOT_OPEN | OP_IF_NOT_ZERO | OP_IF_NULL_ROW | OP_IF_SMALLER + | OP_INCR_VACUUM | OP_IS_NULL_OR_TYPE | OP_LE | OP_LT | OP_NE | OP_NEXT + | OP_NO_CONFLICT | OP_NOT_EXISTS | OP_ONCE | OP_PREV | OP_PROGRAM + | OP_ROW_SET_READ | OP_ROW_SET_TEST | OP_SEEK_GE | OP_SEEK_GT | OP_SEEK_LE + | OP_SEEK_LT | OP_SEEK_ROW_ID | OP_SEEK_SCAN | OP_SEQUENCE_TEST + | OP_SORTER_NEXT | OP_V_FILTER | OP_V_NEXT => { + // goto or next instruction (depending on actual values) + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + states.push(branch_state, &mut logger); + + state.mem.program_i += 1; + continue; + } + + OP_IS_NULL => { + // goto if p1 is null + + //branch if maybe null + let might_branch = match state.mem.r.get(&p1) { + Some(r_p1) => !matches!(r_p1.map_to_nullable(), Some(false)), + _ => false, + }; + + //nobranch if maybe not null + let might_not_branch = match state.mem.r.get(&p1) { + Some(r_p1) => r_p1.map_to_nullable() != Some(false), + _ => false, + }; + + if might_branch { + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + branch_state + .mem + .r + .insert(p1, RegDataType::Single(ColumnType::default())); + + states.push(branch_state, &mut logger); + } + + if might_not_branch { + state.mem.program_i += 1; + if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = + state.mem.r.get_mut(&p1) + { + *nullable = Some(false); + } + continue; + } else { + logger.add_result(state, BranchResult::Branched); + break; + } + } + + OP_NOT_NULL => { + // goto if p1 is not null + + let might_branch = match state.mem.r.get(&p1) { + Some(r_p1) => !matches!(r_p1.map_to_datatype(), DataType::Null), + _ => false, + }; + + let might_not_branch = match state.mem.r.get(&p1) { + Some(r_p1) => !matches!(r_p1.map_to_nullable(), Some(false)), + _ => false, + }; + + if might_branch { + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = + branch_state.mem.r.get_mut(&p1) + { + *nullable = Some(false); + } + + states.push(branch_state, &mut logger); + } + + if might_not_branch { + state.mem.program_i += 1; + state + .mem + .r + .insert(p1, RegDataType::Single(ColumnType::default())); + continue; + } else { + logger.add_result(state, BranchResult::Branched); + break; + } + } + + OP_MUST_BE_INT => { + // if p1 can be coerced to int, continue + // if p1 cannot be coerced to int, error if p2 == 0, else jump to p2 + + //don't bother checking actual types, just don't branch to instruction 0 + if p2 != 0 { + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + states.push(branch_state, &mut logger); + } + + state.mem.program_i += 1; + continue; + } + + OP_IF => { + // goto if r[p1] is true (1) or r[p1] is null and p3 is nonzero + + let might_branch = match state.mem.r.get(&p1) { + Some(RegDataType::Int(r_p1)) => *r_p1 != 0, + _ => true, + }; + + let might_not_branch = match state.mem.r.get(&p1) { + Some(RegDataType::Int(r_p1)) => *r_p1 == 0, + _ => true, + }; + + if might_branch { + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + if p3 == 0 { + branch_state.mem.r.insert(p1, RegDataType::Int(1)); + } + + states.push(branch_state, &mut logger); + } + + if might_not_branch { + state.mem.program_i += 1; + if p3 == 0 { + state.mem.r.insert(p1, RegDataType::Int(0)); + } + continue; + } else { + logger.add_result(state, BranchResult::Branched); + break; + } + } + + OP_IF_POS => { + // goto if r[p1] is true (1) or r[p1] is null and p3 is nonzero + + // as a workaround for large offset clauses, both branches will be attempted after 1 loop + + let might_branch = match state.mem.r.get(&p1) { + Some(RegDataType::Int(r_p1)) => *r_p1 >= 1, + _ => true, + }; + + let might_not_branch = match state.mem.r.get(&p1) { + Some(RegDataType::Int(r_p1)) => *r_p1 < 1, + _ => true, + }; + + let loop_detected = state.visited[state.mem.program_i] > 1; + if might_branch || loop_detected { + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + if let Some(RegDataType::Int(r_p1)) = branch_state.mem.r.get_mut(&p1) { + *r_p1 -= 1; + } + states.push(branch_state, &mut logger); + } + + if might_not_branch { + state.mem.program_i += 1; + continue; + } else if loop_detected { + state.mem.program_i += 1; + if matches!(state.mem.r.get_mut(&p1), Some(RegDataType::Int(..))) { + //forget the exact value, in case some later cares + state.mem.r.insert( + p1, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } + continue; + } else { + logger.add_result(state, BranchResult::Branched); + break; + } + } + + OP_REWIND | OP_LAST | OP_SORT | OP_SORTER_SORT => { + // goto if cursor p1 is empty and p2 != 0, else next instruction + + if p2 == 0 { + state.mem.program_i += 1; + continue; + } + + if let Some(cursor) = state.mem.p.get(&p1) { + if matches!(cursor.is_empty(&state.mem.t), None | Some(true)) { + //only take this branch if the cursor is empty + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + + if let Some(cur) = branch_state.mem.p.get(&p1) { + if let Some(tab) = cur.table_mut(&mut branch_state.mem.t) { + tab.is_empty = Some(true); + } + } + states.push(branch_state, &mut logger); + } + + if matches!(cursor.is_empty(&state.mem.t), None | Some(false)) { + //only take this branch if the cursor is non-empty + state.mem.program_i += 1; + continue; + } else { + logger.add_result(state, BranchResult::Branched); + break; + } + } + + logger.add_result(state, BranchResult::Branched); + break; + } + + OP_INIT_COROUTINE => { + // goto or next instruction (depending on actual values) + + state.mem.r.insert(p1, RegDataType::Int(p3)); + + if p2 != 0 { + state.mem.program_i = p2 as usize; + } else { + state.mem.program_i += 1; + } + continue; + } + + OP_END_COROUTINE => { + // jump to p2 of the yield instruction pointed at by register p1 + + if let Some(RegDataType::Int(yield_i)) = state.mem.r.get(&p1) { + if let Some((_, yield_op, _, yield_p2, _, _)) = + program.get(*yield_i as usize) + { + if OP_YIELD == yield_op.as_str() { + state.mem.program_i = (*yield_p2) as usize; + state.mem.r.remove(&p1); + continue; + } else { + logger.add_result(state, BranchResult::Error); + break; + } + } else { + logger.add_result(state, BranchResult::Error); + break; + } + } else { + logger.add_result(state, BranchResult::Error); + break; + } + } + + OP_RETURN => { + // jump to the instruction after the instruction pointed at by register p1 + + if let Some(RegDataType::Int(return_i)) = state.mem.r.get(&p1) { + state.mem.program_i = (*return_i + 1) as usize; + state.mem.r.remove(&p1); + continue; + } else if p3 == 1 { + state.mem.program_i += 1; + continue; + } else { + logger.add_result(state, BranchResult::Error); + break; + } + } + + OP_YIELD => { + // jump to p2 of the yield instruction pointed at by register p1, store prior instruction in p1 + + if let Some(RegDataType::Int(yield_i)) = state.mem.r.get_mut(&p1) { + let program_i: usize = state.mem.program_i; + + //if yielding to a yield operation, go to the NEXT instruction after that instruction + if program + .get(*yield_i as usize) + .map(|(_, yield_op, _, _, _, _)| yield_op.as_str()) + == Some(OP_YIELD) + { + state.mem.program_i = (*yield_i + 1) as usize; + *yield_i = program_i as i64; + continue; + } else { + state.mem.program_i = *yield_i as usize; + *yield_i = program_i as i64; + continue; + } + } else { + logger.add_result(state, BranchResult::Error); + break; + } + } + + OP_JUMP => { + // goto one of , , or based on the result of a prior compare + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p1 as usize; + states.push(branch_state, &mut logger); + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p2 as usize; + states.push(branch_state, &mut logger); + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i = p3 as usize; + states.push(branch_state, &mut logger); + } + + OP_COLUMN => { + //Get the row stored at p1, or NULL; get the column stored at p2, or NULL + let value: ColumnType = state + .mem + .p + .get(&p1) + .and_then(|c| c.columns_ref(&state.mem.t, &state.mem.r)) + .and_then(|cc| cc.get(&p2)) + .cloned() + .unwrap_or_default(); + + // insert into p3 the datatype of the col + state.mem.r.insert(p3, RegDataType::Single(value)); + } + + OP_SEQUENCE => { + //Copy sequence number from cursor p1 to register p2, increment cursor p1 sequence number + + //Cursor emulation doesn't sequence value, but it is an int + state.mem.r.insert( + p2, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } + + OP_ROW_DATA | OP_SORTER_DATA => { + //Get entire row from cursor p1, store it into register p2 + if let Some(record) = state + .mem + .p + .get(&p1) + .map(|c| c.columns(&state.mem.t, &state.mem.r)) + { + state + .mem + .r + .insert(p2, RegDataType::Single(ColumnType::Record(record))); + } else { + state + .mem + .r + .insert(p2, RegDataType::Single(ColumnType::Record(IntMap::new()))); + } + } + + OP_MAKE_RECORD => { + // p3 = Record([p1 .. p1 + p2]) + let mut record = Vec::with_capacity(p2 as usize); + for reg in p1..p1 + p2 { + record.push( + state + .mem + .r + .get(®) + .map(|d| d.map_to_columntype()) + .unwrap_or(ColumnType::default()), + ); + } + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Record(IntMap::from_dense_record(&record))), + ); + } + + OP_INSERT | OP_IDX_INSERT | OP_SORTER_INSERT => { + if let Some(RegDataType::Single(columntype)) = state.mem.r.get(&p2) { + match columntype { + ColumnType::Record(record) => { + if let Some(TableDataType { cols, is_empty }) = state + .mem + .p + .get(&p1) + .and_then(|cur| cur.table_mut(&mut state.mem.t)) + { + // Insert the record into wherever pointer p1 is + *cols = record.clone(); + *is_empty = Some(false); + } + } + ColumnType::Single { + datatype: DataType::Null, + nullable: _, + } => { + if let Some(TableDataType { is_empty, .. }) = state + .mem + .p + .get(&p1) + .and_then(|cur| cur.table_mut(&mut state.mem.t)) + { + // Insert a null record into wherever pointer p1 is + *is_empty = Some(false); + } + } + _ => {} + } + } + //Noop if the register p2 isn't a record, or if pointer p1 does not exist + } + + OP_DELETE => { + // delete a record from cursor p1 + if let Some(TableDataType { is_empty, .. }) = state + .mem + .p + .get(&p1) + .and_then(|cur| cur.table_mut(&mut state.mem.t)) + { + if *is_empty == Some(false) { + *is_empty = None; //the cursor might be empty now + } + } + } + + OP_OPEN_PSEUDO => { + // Create a cursor p1 aliasing the record from register p2 + state.mem.p.insert(p1, CursorDataType::Pseudo(p2)); + } + + OP_OPEN_DUP => { + if let Some(cur) = state.mem.p.get(&p2) { + state.mem.p.insert(p1, cur.clone()); + } + } + + OP_OPEN_READ | OP_OPEN_WRITE => { + //Create a new pointer which is referenced by p1, take column metadata from db schema if found + let table_info = if p3 == 0 || p3 == 1 { + if let Some(columns) = root_block_cols.get(&(p3, p2)) { + TableDataType { + cols: columns.clone(), + is_empty: None, + } + } else { + TableDataType { + cols: IntMap::new(), + is_empty: None, + } + } + } else { + TableDataType { + cols: IntMap::new(), + is_empty: None, + } + }; + + state.mem.t.insert(state.mem.program_i as i64, table_info); + state + .mem + .p + .insert(p1, CursorDataType::Normal(state.mem.program_i as i64)); + } + + OP_OPEN_EPHEMERAL | OP_OPEN_AUTOINDEX | OP_SORTER_OPEN => { + //Create a new pointer which is referenced by p1 + let table_info = TableDataType { + cols: IntMap::from_elem(ColumnType::null(), p2 as usize), + is_empty: Some(true), + }; + + state.mem.t.insert(state.mem.program_i as i64, table_info); + state + .mem + .p + .insert(p1, CursorDataType::Normal(state.mem.program_i as i64)); + } + + OP_VARIABLE => { + // r[p2] = + state + .mem + .r + .insert(p2, RegDataType::Single(ColumnType::null())); + } + + // if there is a value in p3, and the query passes, then + // we know that it is not nullable + OP_HALT_IF_NULL => { + if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = + state.mem.r.get_mut(&p3) + { + *nullable = Some(false); + } + } + + OP_FUNCTION => { + // r[p3] = func( _ ), registered function name is in p4 + match from_utf8(p4).map_err(Error::protocol)? { + "last_insert_rowid(0)" => { + // last_insert_rowid() -> INTEGER + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } + "date(-1)" | "time(-1)" | "datetime(-1)" | "strftime(-1)" => { + // date|time|datetime|strftime(...) -> TEXT + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Text, + nullable: Some(p2 != 0), //never a null result if no argument provided + }), + ); + } + "julianday(-1)" => { + // julianday(...) -> REAL + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Float, + nullable: Some(p2 != 0), //never a null result if no argument provided + }), + ); + } + "unixepoch(-1)" => { + // unixepoch(p2...) -> INTEGER + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(p2 != 0), //never a null result if no argument provided + }), + ); + } + + _ => logger.add_unknown_operation(state.mem.program_i), + } + } + + OP_NULL_ROW => { + // all columns in cursor X are potentially nullable + if let Some(cols) = state + .mem + .p + .get_mut(&p1) + .and_then(|c| c.columns_mut(&mut state.mem.t, &mut state.mem.r)) + { + for col in cols.values_mut() { + if let ColumnType::Single { + ref mut nullable, .. + } = col + { + *nullable = Some(true); + } + } + } + //else we don't know about the cursor + } + + OP_AGG_STEP | OP_AGG_VALUE => { + //assume that AGG_FINAL will be called + let p4 = from_utf8(p4).map_err(Error::protocol)?; + + if p4.starts_with("count(") + || p4.starts_with("row_number(") + || p4.starts_with("rank(") + || p4.starts_with("dense_rank(") + || p4.starts_with("ntile(") + { + // count(_) -> INTEGER + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } else if p4.starts_with("percent_rank(") || p4.starts_with("cume_dist") { + // percent_rank(_) -> REAL + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Float, + nullable: Some(false), + }), + ); + } else if p4.starts_with("sum(") { + if let Some(r_p2) = state.mem.r.get(&p2) { + let datatype = match r_p2.map_to_datatype() { + // The result of a `SUM()` can be arbitrarily large + DataType::Integer | DataType::Int4 | DataType::Bool => { + DataType::Integer + } + _ => DataType::Float, + }; + let nullable = r_p2.map_to_nullable(); + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { datatype, nullable }), + ); + } + } else if p4.starts_with("lead(") || p4.starts_with("lag(") { + if let Some(r_p2) = state.mem.r.get(&p2) { + let datatype = r_p2.map_to_datatype(); + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype, + nullable: Some(true), + }), + ); + } + } else if let Some(v) = state.mem.r.get(&p2).cloned() { + // r[p3] = AGG ( r[p2] ) + state.mem.r.insert(p3, v); + } + } + + OP_AGG_FINAL => { + let p4 = from_utf8(p4).map_err(Error::protocol)?; + + if p4.starts_with("count(") + || p4.starts_with("row_number(") + || p4.starts_with("rank(") + || p4.starts_with("dense_rank(") + || p4.starts_with("ntile(") + { + // count(_) -> INTEGER + state.mem.r.insert( + p1, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } else if p4.starts_with("percent_rank(") || p4.starts_with("cume_dist") { + // percent_rank(_) -> REAL + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Float, + nullable: Some(false), + }), + ); + } else if p4.starts_with("lead(") || p4.starts_with("lag(") { + if let Some(r_p2) = state.mem.r.get(&p2) { + let datatype = r_p2.map_to_datatype(); + state.mem.r.insert( + p3, + RegDataType::Single(ColumnType::Single { + datatype, + nullable: Some(true), + }), + ); + } + } + } + + OP_CAST => { + // affinity(r[p1]) + if let Some(v) = state.mem.r.get_mut(&p1) { + *v = RegDataType::Single(ColumnType::Single { + datatype: affinity_to_type(p2 as u8), + nullable: v.map_to_nullable(), + }); + } + } + + OP_SCOPY | OP_INT_COPY => { + // r[p2] = r[p1] + if let Some(v) = state.mem.r.get(&p1).cloned() { + state.mem.r.insert(p2, v); + } + } + + OP_COPY => { + // r[p2..=p2+p3] = r[p1..=p1+p3] + if p3 >= 0 { + for i in 0..=p3 { + let src = p1 + i; + let dst = p2 + i; + if let Some(v) = state.mem.r.get(&src).cloned() { + state.mem.r.insert(dst, v); + } + } + } + } + + OP_MOVE => { + // r[p2..p2+p3] = r[p1..p1+p3]; r[p1..p1+p3] = null + if p3 >= 1 { + for i in 0..p3 { + let src = p1 + i; + let dst = p2 + i; + if let Some(v) = state.mem.r.get(&src).cloned() { + state.mem.r.insert(dst, v); + state + .mem + .r + .insert(src, RegDataType::Single(ColumnType::null())); + } + } + } + } + + OP_INTEGER => { + // r[p2] = p1 + state.mem.r.insert(p2, RegDataType::Int(p1)); + } + + OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_IDX_ROWID + | OP_NEWROWID => { + // r[p2] = + state.mem.r.insert( + p2, + RegDataType::Single(ColumnType::Single { + datatype: opcode_to_type(opcode), + nullable: Some(false), + }), + ); + } + + OP_NOT => { + // r[p2] = NOT r[p1] + if let Some(a) = state.mem.r.get(&p1).cloned() { + state.mem.r.insert(p2, a); + } + } + + OP_NULL => { + // r[p2..p3] = null + let idx_range = if p2 < p3 { p2..=p3 } else { p2..=p2 }; + + for idx in idx_range { + state + .mem + .r + .insert(idx, RegDataType::Single(ColumnType::null())); + } + } + + OP_OR | OP_AND | OP_BIT_AND | OP_BIT_OR | OP_SHIFT_LEFT | OP_SHIFT_RIGHT + | OP_ADD | OP_SUBTRACT | OP_MULTIPLY | OP_DIVIDE | OP_REMAINDER | OP_CONCAT => { + // r[p3] = r[p1] + r[p2] + let value = match (state.mem.r.get(&p1), state.mem.r.get(&p2)) { + (Some(a), Some(b)) => RegDataType::Single(ColumnType::Single { + datatype: if matches!(a.map_to_datatype(), DataType::Null) { + b.map_to_datatype() + } else { + a.map_to_datatype() + }, + nullable: match (a.map_to_nullable(), b.map_to_nullable()) { + (Some(a_n), Some(b_n)) => Some(a_n | b_n), + (Some(a_n), None) => Some(a_n), + (None, Some(b_n)) => Some(b_n), + (None, None) => None, + }, + }), + (Some(v), None) => RegDataType::Single(ColumnType::Single { + datatype: v.map_to_datatype(), + nullable: None, + }), + (None, Some(v)) => RegDataType::Single(ColumnType::Single { + datatype: v.map_to_datatype(), + nullable: None, + }), + _ => RegDataType::default(), + }; + + state.mem.r.insert(p3, value); + } + + OP_OFFSET_LIMIT => { + // r[p2] = if r[p2] < 0 { r[p1] } else if r[p1]<0 { -1 } else { r[p1] + r[p3] } + state.mem.r.insert( + p2, + RegDataType::Single(ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false), + }), + ); + } + + OP_RESULT_ROW => { + // output = r[p1 .. p1 + p2] + let result: Vec<_> = (p1..p1 + p2) + .map(|i| { + state + .mem + .r + .get(&i) + .map(RegDataType::map_to_columntype) + .unwrap_or_default() + }) + .collect(); + + let mut branch_state = state.new_branch(&mut branch_seq); + branch_state.mem.program_i += 1; + states.push(branch_state, &mut logger); + + logger.add_result( + state, + BranchResult::Result(IntMap::from_dense_record(&result)), + ); + + result_states.push(result); + break; + } + + OP_HALT => { + logger.add_result(state, BranchResult::Halt); + break; + } + + _ => { + // ignore unsupported operations + // if we fail to find an r later, we just give up + logger.add_unknown_operation(state.mem.program_i); + } + } + + state.mem.program_i += 1; + } + } + + let mut output: Vec> = Vec::new(); + let mut nullable: Vec> = Vec::new(); + + while let Some(result) = result_states.pop() { + // find the datatype info from each ResultRow execution + for (idx, this_col) in result.into_iter().enumerate() { + let this_type = this_col.map_to_datatype(); + let this_nullable = this_col.map_to_nullable(); + if output.len() == idx { + output.push(Some(SqliteTypeInfo(this_type))); + } else if output[idx].is_none() + || matches!(output[idx], Some(SqliteTypeInfo(DataType::Null))) + && !matches!(this_type, DataType::Null) + { + output[idx] = Some(SqliteTypeInfo(this_type)); + } + + if nullable.len() == idx { + nullable.push(this_nullable); + } else if let Some(ref mut null) = nullable[idx] { + //if any ResultRow's column is nullable, the final result is nullable + if let Some(this_null) = this_nullable { + *null |= this_null; + } + } else { + nullable[idx] = this_nullable; + } + } + } + + let output = output + .into_iter() + .map(|o| o.unwrap_or(SqliteTypeInfo(DataType::Null))) + .collect(); + + Ok((output, nullable)) +} + +#[test] +fn test_root_block_columns_has_types() { + use crate::SqliteConnectOptions; + use std::str::FromStr; + let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); + let mut conn = super::EstablishParams::from_options(&conn_options) + .unwrap() + .establish() + .unwrap(); + + assert!(execute::iter( + &mut conn, + r"CREATE TABLE t(a INTEGER PRIMARY KEY, b_null TEXT NULL, b TEXT NOT NULL);", + None, + false + ) + .unwrap() + .next() + .is_some()); + assert!( + execute::iter(&mut conn, r"CREATE INDEX i1 on t (a,b_null);", None, false) + .unwrap() + .next() + .is_some() + ); + assert!(execute::iter( + &mut conn, + r"CREATE UNIQUE INDEX i2 on t (a,b_null);", + None, + false + ) + .unwrap() + .next() + .is_some()); + assert!(execute::iter( + &mut conn, + r"CREATE TABLE t2(a INTEGER NOT NULL, b_null NUMERIC NULL, b NUMERIC NOT NULL);", + None, + false + ) + .unwrap() + .next() + .is_some()); + assert!(execute::iter( + &mut conn, + r"CREATE INDEX t2i1 on t2 (a,b_null);", + None, + false + ) + .unwrap() + .next() + .is_some()); + assert!(execute::iter( + &mut conn, + r"CREATE UNIQUE INDEX t2i2 on t2 (a,b);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + assert!(execute::iter( + &mut conn, + r"CREATE TEMPORARY TABLE t3(a TEXT PRIMARY KEY, b REAL NOT NULL, b_null REAL NULL);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + let table_block_nums: HashMap = execute::iter( + &mut conn, + r"select name, 0 db_seq, rootpage from main.sqlite_schema UNION ALL select name, 1 db_seq, rootpage from temp.sqlite_schema", + None, + false, + ) + .unwrap() + .filter_map(|res| res.map(|either| either.right()).transpose()) + .map(|row| FromRow::from_row(row.as_ref().unwrap())) + .map(|row| row.map(|(name,seq,block)|(name,(seq,block)))) + .collect::, Error>>() + .unwrap(); + + let root_block_cols = root_block_columns(&mut conn).unwrap(); + + // there should be 7 tables/indexes created explicitly, plus 1 autoindex for t3 + assert_eq!(8, root_block_cols.len()); + + //prove that we have some information for each table & index + for (name, db_seq_block) in dbg!(&table_block_nums) { + assert!( + root_block_cols.contains_key(db_seq_block), + "{:?}", + (name, db_seq_block) + ); + } + + //prove that each block has the correct information + { + let table_db_block = table_block_nums["t"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(true) //sqlite primary key columns are nullable unless declared not null + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Text, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&1) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Text, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&2) + ); + } + + { + let table_db_block = table_block_nums["i1"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(true) //sqlite primary key columns are nullable unless declared not null + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Text, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&1) + ); + } + + { + let table_db_block = table_block_nums["i2"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(true) //sqlite primary key columns are nullable unless declared not null + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Text, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&1) + ); + } + + { + let table_db_block = table_block_nums["t2"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Null, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&1) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Null, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&2) + ); + } + + { + let table_db_block = table_block_nums["t2i1"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Null, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&1) + ); + } + + { + let table_db_block = table_block_nums["t2i2"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Integer, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Null, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&1) + ); + } + + { + let table_db_block = table_block_nums["t3"]; + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Text, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&0) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Float, + nullable: Some(false) + }), + root_block_cols[&table_db_block].get(&1) + ); + assert_eq!( + Some(&ColumnType::Single { + datatype: DataType::Float, + nullable: Some(true) + }), + root_block_cols[&table_db_block].get(&2) + ); + } +} + +#[test] +fn test_explain() { + use crate::SqliteConnectOptions; + use std::str::FromStr; + let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); + let mut conn = super::EstablishParams::from_options(&conn_options) + .unwrap() + .establish() + .unwrap(); + + assert!(execute::iter( + &mut conn, + r"CREATE TABLE an_alias(a INTEGER PRIMARY KEY);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + assert!(execute::iter( + &mut conn, + r"CREATE TABLE not_an_alias(a INT PRIMARY KEY);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM not_an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(true)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM not_an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); +} diff --git a/vendor/sqlx-sqlite/src/connection/handle.rs b/vendor/sqlx-sqlite/src/connection/handle.rs new file mode 100644 index 0000000000..e89179b30e --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/handle.rs @@ -0,0 +1,151 @@ +use std::ffi::{c_int, CStr, CString}; +use std::ptr::NonNull; +use std::{io, ptr}; + +use crate::error::Error; +use libsqlite3_sys::{ + sqlite3, sqlite3_close, sqlite3_exec, sqlite3_extended_result_codes, sqlite3_get_autocommit, + sqlite3_last_insert_rowid, sqlite3_open_v2, SQLITE_OK, +}; + +use crate::SqliteError; + +/// Managed SQLite3 database handle. +/// The database handle will be closed when this is dropped. +#[derive(Debug)] +pub(crate) struct ConnectionHandle(NonNull); + +// A SQLite3 handle is safe to send between threads, provided not more than +// one is accessing it at the same time. This is upheld as long as [SQLITE_CONFIG_MULTITHREAD] is +// enabled and [SQLITE_THREADSAFE] was enabled when sqlite was compiled. We refuse to work +// if these conditions are not upheld. +// +// +// + +unsafe impl Send for ConnectionHandle {} + +impl ConnectionHandle { + pub(crate) fn open(filename: &CStr, flags: c_int) -> Result { + let mut handle = ptr::null_mut(); + + // + let status = unsafe { sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, ptr::null()) }; + + // SAFETY: the database is still initialized as long as the pointer is not `NULL`. + // We need to close it even if there's an error. + let mut handle = Self(NonNull::new(handle).ok_or_else(|| { + Error::Io(io::Error::new( + io::ErrorKind::OutOfMemory, + "SQLite is unable to allocate memory to hold the sqlite3 object", + )) + })?); + + if status != SQLITE_OK { + return Err(Error::Database(Box::new(handle.expect_error()))); + } + + // Enable extended result codes + // https://www.sqlite.org/c3ref/extended_result_codes.html + unsafe { + // This only returns a non-OK code if SQLite is built with `SQLITE_ENABLE_API_ARMOR` + // and the database pointer is `NULL` or already closed. + // + // The invariants of this type guarantee that neither is true. + sqlite3_extended_result_codes(handle.as_ptr(), 1); + } + + Ok(handle) + } + + #[inline] + pub(crate) fn as_ptr(&self) -> *mut sqlite3 { + self.0.as_ptr() + } + + pub(crate) fn as_non_null_ptr(&self) -> NonNull { + self.0 + } + + pub(crate) fn call_with_result( + &mut self, + call: impl FnOnce(*mut sqlite3) -> c_int, + ) -> Result<(), SqliteError> { + let res = call(self.as_ptr()); + + if res == SQLITE_OK { + Ok(()) + } else { + Err(self + .last_error() + .unwrap_or_else(|| SqliteError::from_code(res))) + } + } + + pub(crate) fn in_transaction(&mut self) -> bool { + // SAFETY: we have exclusive access to the database handle + let ret = unsafe { sqlite3_get_autocommit(self.as_ptr()) }; + ret == 0 + } + + pub(crate) fn last_insert_rowid(&mut self) -> i64 { + // SAFETY: we have exclusive access to the database handle + unsafe { sqlite3_last_insert_rowid(self.as_ptr()) } + } + + pub(crate) fn last_error(&mut self) -> Option { + // SAFETY: we have exclusive access to the database handle + unsafe { SqliteError::try_new(self.as_ptr()) } + } + + #[track_caller] + pub(crate) fn expect_error(&mut self) -> SqliteError { + self.last_error() + .expect("expected error code to be set in current context") + } + + pub(crate) fn exec(&mut self, query: impl Into) -> Result<(), Error> { + let query = query.into(); + let query = CString::new(query).map_err(|_| err_protocol!("query contains nul bytes"))?; + + // SAFETY: we have exclusive access to the database handle + unsafe { + #[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))] + loop { + let status = sqlite3_exec( + self.as_ptr(), + query.as_ptr(), + // callback if we wanted result rows + None, + // callback data + ptr::null_mut(), + // out-pointer for the error message, we just use `SqliteError::new()` + ptr::null_mut(), + ); + + match status { + SQLITE_OK => return Ok(()), + #[cfg(feature = "unlock-notify")] + libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => { + crate::statement::unlock_notify::wait(self.as_ptr())? + } + _ => return Err(SqliteError::new(self.as_ptr()).into()), + } + } + } + } +} + +impl Drop for ConnectionHandle { + fn drop(&mut self) { + unsafe { + // https://sqlite.org/c3ref/close.html + let status = sqlite3_close(self.0.as_ptr()); + if status != SQLITE_OK { + // this should *only* happen due to an internal bug in SQLite where we left + // SQLite handles open + panic!("{}", SqliteError::new(self.0.as_ptr())); + } + } + } +} diff --git a/vendor/sqlx-sqlite/src/connection/intmap.rs b/vendor/sqlx-sqlite/src/connection/intmap.rs new file mode 100644 index 0000000000..f8308067cc --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/intmap.rs @@ -0,0 +1,175 @@ +// Bad casts in this module SHOULD NOT result in a SQL injection +// https://github.com/launchbadge/sqlx/issues/3440 +#![allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss +)] +use std::cmp::Ordering; +use std::{fmt::Debug, hash::Hash}; + +/// Simplistic map implementation built on a Vec of Options (index = key) +#[derive(Debug, Clone, Eq)] +pub(crate) struct IntMap(Vec>); + +impl Default for IntMap { + fn default() -> Self { + IntMap(Vec::new()) + } +} + +impl IntMap { + pub(crate) fn new() -> Self { + Self(Vec::new()) + } + + pub(crate) fn expand(&mut self, size: i64) -> usize { + let idx = usize::try_from(size).expect("negative column index unsupported"); + if idx >= self.0.len() { + let new_len = idx.checked_add(1).expect("idx + 1 overflowed"); + + self.0.resize_with(new_len, || None); + } + idx + } + + pub(crate) fn values_mut(&mut self) -> impl Iterator { + self.0.iter_mut().filter_map(Option::as_mut) + } + + pub(crate) fn values(&self) -> impl Iterator { + self.0.iter().filter_map(Option::as_ref) + } + + pub(crate) fn get(&self, idx: &i64) -> Option<&V> { + let idx: usize = (*idx) + .try_into() + .expect("negative column index unsupported"); + + match self.0.get(idx) { + Some(Some(v)) => Some(v), + _ => None, + } + } + + pub(crate) fn get_mut(&mut self, idx: &i64) -> Option<&mut V> { + let idx: usize = (*idx) + .try_into() + .expect("negative column index unsupported"); + match self.0.get_mut(idx) { + Some(Some(v)) => Some(v), + _ => None, + } + } + + pub(crate) fn insert(&mut self, idx: i64, value: V) -> Option { + let idx: usize = self.expand(idx); + + self.0[idx].replace(value) + } + + pub(crate) fn remove(&mut self, idx: &i64) -> Option { + let idx: usize = (*idx) + .try_into() + .expect("negative column index unsupported"); + + let item = self.0.get_mut(idx); + match item { + Some(content) => content.take(), + None => None, + } + } + + pub(crate) fn iter(&self) -> impl Iterator> { + self.0.iter().map(Option::as_ref) + } + + pub(crate) fn iter_entries(&self) -> impl Iterator { + self.0 + .iter() + .enumerate() + .filter_map(|(i, v)| v.as_ref().map(|v: &V| (i as i64, v))) + } + + pub(crate) fn last_index(&self) -> Option { + self.0.iter().rposition(|v| v.is_some()).map(|i| i as i64) + } +} + +impl IntMap { + pub(crate) fn get_mut_or_default(&mut self, idx: &i64) -> &mut V { + let idx: usize = self.expand(*idx); + self.0[idx].get_or_insert_default() + } +} + +impl IntMap { + pub(crate) fn from_elem(elem: V, len: usize) -> Self { + Self(vec![Some(elem); len]) + } + pub(crate) fn from_dense_record(record: &[V]) -> Self { + Self(record.iter().cloned().map(Some).collect()) + } +} + +impl IntMap { + /// get the additions to this intmap compared to the prev intmap + pub(crate) fn diff<'a, 'b, 'c>( + &'a self, + prev: &'b Self, + ) -> impl Iterator)> + where + 'a: 'c, + 'b: 'c, + { + let self_pad = if prev.0.len() > self.0.len() { + prev.0.len() - self.0.len() + } else { + 0 + }; + self.iter() + .chain(std::iter::repeat_n(None, self_pad)) + .zip(prev.iter().chain(std::iter::repeat(None))) + .enumerate() + .filter(|(_i, (n, p))| n != p) + .map(|(i, (n, _p))| (i, n)) + } +} + +impl Hash for IntMap { + fn hash(&self, state: &mut H) { + for value in self.values() { + value.hash(state); + } + } +} + +impl PartialEq for IntMap { + fn eq(&self, other: &Self) -> bool { + match self.0.len().cmp(&other.0.len()) { + Ordering::Greater => { + self.0[..other.0.len()] == other.0 + && self.0[other.0.len()..].iter().all(Option::is_none) + } + Ordering::Less => { + other.0[..self.0.len()] == self.0 + && other.0[self.0.len()..].iter().all(Option::is_none) + } + Ordering::Equal => self.0 == other.0, + } + } +} + +impl FromIterator<(i64, V)> for IntMap { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut result = Self(Vec::new()); + for (idx, val) in iter { + let idx = result.expand(idx); + result.0[idx] = Some(val); + } + result + } +} diff --git a/vendor/sqlx-sqlite/src/connection/mod.rs b/vendor/sqlx-sqlite/src/connection/mod.rs new file mode 100644 index 0000000000..218c747143 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/mod.rs @@ -0,0 +1,599 @@ +use std::cmp::Ordering; +use std::ffi::CStr; +use std::fmt::Write; +use std::fmt::{self, Debug, Formatter}; +use std::future::Future; +use std::os::raw::{c_char, c_int, c_void}; +use std::panic::catch_unwind; +use std::ptr; +use std::ptr::NonNull; + +use futures_intrusive::sync::MutexGuard; +use libsqlite3_sys::{ + sqlite3, sqlite3_commit_hook, sqlite3_progress_handler, sqlite3_rollback_hook, + sqlite3_update_hook, SQLITE_DELETE, SQLITE_INSERT, SQLITE_UPDATE, +}; +#[cfg(feature = "preupdate-hook")] +pub use preupdate_hook::*; + +pub(crate) use handle::ConnectionHandle; +use sqlx_core::common::StatementCache; +pub(crate) use sqlx_core::connection::*; +use sqlx_core::error::Error; +use sqlx_core::executor::Executor; +use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr}; +use sqlx_core::transaction::Transaction; + +use crate::connection::establish::EstablishParams; +use crate::connection::worker::ConnectionWorker; +use crate::options::OptimizeOnClose; +use crate::statement::VirtualStatement; +use crate::{Sqlite, SqliteConnectOptions, SqliteError}; + +pub(crate) mod collation; +pub(crate) mod describe; +pub(crate) mod establish; +pub(crate) mod execute; +mod executor; +mod explain; +mod handle; +pub(crate) mod intmap; +#[cfg(feature = "preupdate-hook")] +mod preupdate_hook; + +#[cfg(feature = "deserialize")] +pub(crate) mod deserialize; + +mod worker; + +/// A connection to an open [Sqlite] database. +/// +/// Because SQLite is an in-process database accessed by blocking API calls, SQLx uses a background +/// thread and communicates with it via channels to allow non-blocking access to the database. +/// +/// Dropping this struct will signal the worker thread to quit and close the database, though +/// if an error occurs there is no way to pass it back to the user this way. +/// +/// You can explicitly call [`.close()`][Self::close] to ensure the database is closed successfully +/// or get an error otherwise. +pub struct SqliteConnection { + optimize_on_close: OptimizeOnClose, + pub(crate) worker: ConnectionWorker, + pub(crate) row_channel_size: usize, +} + +pub struct LockedSqliteHandle<'a> { + pub(crate) guard: MutexGuard<'a, ConnectionState>, +} + +/// Represents a callback handler that will be shared with the underlying sqlite3 connection. +pub(crate) struct Handler(NonNull bool + Send + 'static>); +unsafe impl Send for Handler {} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SqliteOperation { + Insert, + Update, + Delete, + Unknown(i32), +} + +impl From for SqliteOperation { + fn from(value: i32) -> Self { + match value { + SQLITE_INSERT => SqliteOperation::Insert, + SQLITE_UPDATE => SqliteOperation::Update, + SQLITE_DELETE => SqliteOperation::Delete, + code => SqliteOperation::Unknown(code), + } + } +} + +pub struct UpdateHookResult<'a> { + pub operation: SqliteOperation, + pub database: &'a str, + pub table: &'a str, + pub rowid: i64, +} + +pub(crate) struct UpdateHookHandler(NonNull); +unsafe impl Send for UpdateHookHandler {} + +pub(crate) struct CommitHookHandler(NonNull bool + Send + 'static>); +unsafe impl Send for CommitHookHandler {} + +pub(crate) struct RollbackHookHandler(NonNull); +unsafe impl Send for RollbackHookHandler {} + +pub(crate) struct ConnectionState { + pub(crate) handle: ConnectionHandle, + + pub(crate) statements: Statements, + + log_settings: LogSettings, + + /// Stores the progress handler set on the current connection. If the handler returns `false`, + /// the query is interrupted. + progress_handler_callback: Option, + + update_hook_callback: Option, + #[cfg(feature = "preupdate-hook")] + preupdate_hook_callback: Option, + + commit_hook_callback: Option, + + rollback_hook_callback: Option, +} + +impl ConnectionState { + /// Drops the `progress_handler_callback` if it exists. + pub(crate) fn remove_progress_handler(&mut self) { + if let Some(mut handler) = self.progress_handler_callback.take() { + unsafe { + sqlite3_progress_handler(self.handle.as_ptr(), 0, None, ptr::null_mut()); + let _ = { Box::from_raw(handler.0.as_mut()) }; + } + } + } + + pub(crate) fn remove_update_hook(&mut self) { + if let Some(mut handler) = self.update_hook_callback.take() { + unsafe { + sqlite3_update_hook(self.handle.as_ptr(), None, ptr::null_mut()); + let _ = { Box::from_raw(handler.0.as_mut()) }; + } + } + } + + #[cfg(feature = "preupdate-hook")] + pub(crate) fn remove_preupdate_hook(&mut self) { + if let Some(mut handler) = self.preupdate_hook_callback.take() { + unsafe { + libsqlite3_sys::sqlite3_preupdate_hook(self.handle.as_ptr(), None, ptr::null_mut()); + let _ = { Box::from_raw(handler.0.as_mut()) }; + } + } + } + + pub(crate) fn remove_commit_hook(&mut self) { + if let Some(mut handler) = self.commit_hook_callback.take() { + unsafe { + sqlite3_commit_hook(self.handle.as_ptr(), None, ptr::null_mut()); + let _ = { Box::from_raw(handler.0.as_mut()) }; + } + } + } + + pub(crate) fn remove_rollback_hook(&mut self) { + if let Some(mut handler) = self.rollback_hook_callback.take() { + unsafe { + sqlite3_rollback_hook(self.handle.as_ptr(), None, ptr::null_mut()); + let _ = { Box::from_raw(handler.0.as_mut()) }; + } + } + } +} + +pub(crate) struct Statements { + // cache of semi-persistent statements + cached: StatementCache, + // most recent non-persistent statement + temp: Option, +} + +impl SqliteConnection { + pub(crate) async fn establish(options: &SqliteConnectOptions) -> Result { + let params = EstablishParams::from_options(options)?; + let worker = ConnectionWorker::establish(params).await?; + Ok(Self { + optimize_on_close: options.optimize_on_close.clone(), + worker, + row_channel_size: options.row_channel_size, + }) + } + + /// Lock the SQLite database handle out from the worker thread so direct SQLite API calls can + /// be made safely. + /// + /// Returns an error if the worker thread crashed. + pub async fn lock_handle(&mut self) -> Result, Error> { + let guard = self.worker.unlock_db().await?; + + Ok(LockedSqliteHandle { guard }) + } +} + +impl Debug for SqliteConnection { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("SqliteConnection") + .field("row_channel_size", &self.row_channel_size) + .field("cached_statements_size", &self.cached_statements_size()) + .finish() + } +} + +impl Connection for SqliteConnection { + type Database = Sqlite; + + type Options = SqliteConnectOptions; + + async fn close(mut self) -> Result<(), Error> { + if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close { + let mut pragma_string = String::new(); + if let Some(limit) = analysis_limit { + write!(pragma_string, "PRAGMA analysis_limit = {limit}; ").ok(); + } + pragma_string.push_str("PRAGMA optimize;"); + self.execute(AssertSqlSafe(pragma_string)).await?; + } + let shutdown = self.worker.shutdown(); + // Drop the statement worker, which should + // cover all references to the connection handle outside of the worker thread + drop(self); + // Ensure the worker thread has terminated + shutdown.await + } + + async fn close_hard(self) -> Result<(), Error> { + drop(self); + Ok(()) + } + + /// Ensure the background worker thread is alive and accepting commands. + fn ping(&mut self) -> impl Future> + Send + '_ { + self.worker.ping() + } + + fn begin( + &mut self, + ) -> impl Future, Error>> + Send + '_ { + Transaction::begin(self, None) + } + + fn begin_with( + &mut self, + statement: impl SqlSafeStr, + ) -> impl Future, Error>> + Send + '_ + where + Self: Sized, + { + Transaction::begin(self, Some(statement.into_sql_str())) + } + + fn cached_statements_size(&self) -> usize { + self.worker.shared.get_cached_statements_size() + } + + fn clear_cached_statements(&mut self) -> impl Future> + Send + '_ { + self.worker.clear_cache() + } + + #[inline] + fn shrink_buffers(&mut self) { + // No-op. + } + + #[doc(hidden)] + async fn flush(&mut self) -> Result<(), Error> { + // For SQLite, FLUSH does effectively nothing... + // Well, we could use this to ensure that the command channel has been cleared, + // but it would only develop a backlog if a lot of queries are executed and then cancelled + // partway through, and then this would only make that situation worse. + Ok(()) + } + + #[doc(hidden)] + fn should_flush(&self) -> bool { + false + } +} + +/// Implements a C binding to a progress callback. The function returns `0` if the +/// user-provided callback returns `true`, and `1` otherwise to signal an interrupt. +extern "C" fn progress_callback(callback: *mut c_void) -> c_int +where + F: FnMut() -> bool, +{ + unsafe { + let r = catch_unwind(|| { + let callback: *mut F = callback.cast::(); + (*callback)() + }); + c_int::from(!r.unwrap_or_default()) + } +} + +extern "C" fn update_hook( + callback: *mut c_void, + op_code: c_int, + database: *const c_char, + table: *const c_char, + rowid: i64, +) where + F: FnMut(UpdateHookResult), +{ + unsafe { + let _ = catch_unwind(|| { + let callback: *mut F = callback.cast::(); + let operation: SqliteOperation = op_code.into(); + let database = CStr::from_ptr(database).to_str().unwrap_or_default(); + let table = CStr::from_ptr(table).to_str().unwrap_or_default(); + (*callback)(UpdateHookResult { + operation, + database, + table, + rowid, + }) + }); + } +} + +extern "C" fn commit_hook(callback: *mut c_void) -> c_int +where + F: FnMut() -> bool, +{ + unsafe { + let r = catch_unwind(|| { + let callback: *mut F = callback.cast::(); + (*callback)() + }); + c_int::from(!r.unwrap_or_default()) + } +} + +extern "C" fn rollback_hook(callback: *mut c_void) +where + F: FnMut(), +{ + unsafe { + let _ = catch_unwind(|| { + let callback: *mut F = callback.cast::(); + (*callback)() + }); + } +} + +impl LockedSqliteHandle<'_> { + /// Returns the underlying sqlite3* connection handle. + /// + /// As long as this `LockedSqliteHandle` exists, it is guaranteed that the background thread + /// is not making FFI calls on this database handle or any of its statements. + /// + /// ### Note: The `sqlite3` type is semver-exempt. + /// This API exposes the `sqlite3` type from `libsqlite3-sys` crate for type safety. + /// However, we reserve the right to upgrade `libsqlite3-sys` as necessary. + /// + /// Thus, if you are making direct calls via `libsqlite3-sys` you should pin the version + /// of SQLx that you're using, and upgrade it and `libsqlite3-sys` manually as new + /// versions are released. + /// + /// See [the driver root docs][crate] for details. + pub fn as_raw_handle(&mut self) -> NonNull { + self.guard.handle.as_non_null_ptr() + } + + /// Apply a collation to the open database. + /// + /// See [`SqliteConnectOptions::collation()`] for details. + pub fn create_collation( + &mut self, + name: &str, + compare: impl Fn(&str, &str) -> Ordering + Send + Sync + 'static, + ) -> Result<(), Error> { + collation::create_collation(&mut self.guard.handle, name, compare) + } + + /// Sets a progress handler that is invoked periodically during long running calls. If the progress callback + /// returns `false`, then the operation is interrupted. + /// + /// `num_ops` is the approximate number of [virtual machine instructions](https://www.sqlite.org/opcode.html) + /// that are evaluated between successive invocations of the callback. If `num_ops` is less than one then the + /// progress handler is disabled. + /// + /// Only a single progress handler may be defined at one time per database connection; setting a new progress + /// handler cancels the old one. + /// + /// The progress handler callback must not do anything that will modify the database connection that invoked + /// the progress handler. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections + /// in this context. + pub fn set_progress_handler(&mut self, num_ops: i32, callback: F) + where + F: FnMut() -> bool + Send + 'static, + { + unsafe { + let callback_boxed = Box::new(callback); + // SAFETY: `Box::into_raw()` always returns a non-null pointer. + let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); + let handler = callback.as_ptr() as *mut _; + self.guard.remove_progress_handler(); + self.guard.progress_handler_callback = Some(Handler(callback)); + + sqlite3_progress_handler( + self.as_raw_handle().as_mut(), + num_ops, + Some(progress_callback::), + handler, + ); + } + } + + pub fn set_update_hook(&mut self, callback: F) + where + F: FnMut(UpdateHookResult) + Send + 'static, + { + unsafe { + let callback_boxed = Box::new(callback); + // SAFETY: `Box::into_raw()` always returns a non-null pointer. + let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); + let handler = callback.as_ptr() as *mut _; + self.guard.remove_update_hook(); + self.guard.update_hook_callback = Some(UpdateHookHandler(callback)); + + sqlite3_update_hook( + self.as_raw_handle().as_mut(), + Some(update_hook::), + handler, + ); + } + } + + /// Registers a hook that is invoked prior to each `INSERT`, `UPDATE`, and `DELETE` operation on a database table. + /// At most one preupdate hook may be registered at a time on a single database connection. + /// + /// The preupdate hook only fires for changes to real database tables; + /// it is not invoked for changes to virtual tables or to system tables like sqlite_sequence or sqlite_stat1. + /// + /// See https://sqlite.org/c3ref/preupdate_count.html + #[cfg(feature = "preupdate-hook")] + pub fn set_preupdate_hook(&mut self, callback: F) + where + F: FnMut(PreupdateHookResult) + Send + 'static, + { + unsafe { + let callback_boxed = Box::new(callback); + // SAFETY: `Box::into_raw()` always returns a non-null pointer. + let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); + let handler = callback.as_ptr() as *mut _; + self.guard.remove_preupdate_hook(); + self.guard.preupdate_hook_callback = Some(PreupdateHookHandler(callback)); + + libsqlite3_sys::sqlite3_preupdate_hook( + self.as_raw_handle().as_mut(), + Some(preupdate_hook::), + handler, + ); + } + } + + /// Sets a commit hook that is invoked whenever a transaction is committed. If the commit hook callback + /// returns `false`, then the operation is turned into a ROLLBACK. + /// + /// Only a single commit hook may be defined at one time per database connection; setting a new commit hook + /// overrides the old one. + /// + /// The commit hook callback must not do anything that will modify the database connection that invoked + /// the commit hook. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections + /// in this context. + /// + /// See https://www.sqlite.org/c3ref/commit_hook.html + pub fn set_commit_hook(&mut self, callback: F) + where + F: FnMut() -> bool + Send + 'static, + { + unsafe { + let callback_boxed = Box::new(callback); + // SAFETY: `Box::into_raw()` always returns a non-null pointer. + let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); + let handler = callback.as_ptr() as *mut _; + self.guard.remove_commit_hook(); + self.guard.commit_hook_callback = Some(CommitHookHandler(callback)); + + sqlite3_commit_hook( + self.as_raw_handle().as_mut(), + Some(commit_hook::), + handler, + ); + } + } + + /// Sets a rollback hook that is invoked whenever a transaction rollback occurs. The rollback callback is not + /// invoked if a transaction is automatically rolled back because the database connection is closed. + /// + /// See https://www.sqlite.org/c3ref/commit_hook.html + pub fn set_rollback_hook(&mut self, callback: F) + where + F: FnMut() + Send + 'static, + { + unsafe { + let callback_boxed = Box::new(callback); + // SAFETY: `Box::into_raw()` always returns a non-null pointer. + let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); + let handler = callback.as_ptr() as *mut _; + self.guard.remove_rollback_hook(); + self.guard.rollback_hook_callback = Some(RollbackHookHandler(callback)); + + sqlite3_rollback_hook( + self.as_raw_handle().as_mut(), + Some(rollback_hook::), + handler, + ); + } + } + + /// Removes the progress handler on a database connection. The method does nothing if no handler was set. + pub fn remove_progress_handler(&mut self) { + self.guard.remove_progress_handler(); + } + + pub fn remove_update_hook(&mut self) { + self.guard.remove_update_hook(); + } + + #[cfg(feature = "preupdate-hook")] + pub fn remove_preupdate_hook(&mut self) { + self.guard.remove_preupdate_hook(); + } + + pub fn remove_commit_hook(&mut self) { + self.guard.remove_commit_hook(); + } + + pub fn remove_rollback_hook(&mut self) { + self.guard.remove_rollback_hook(); + } + + pub fn last_error(&mut self) -> Option { + self.guard.handle.last_error() + } +} + +impl Drop for ConnectionState { + fn drop(&mut self) { + // explicitly drop statements before the connection handle is dropped + self.statements.clear(); + self.remove_progress_handler(); + self.remove_update_hook(); + self.remove_commit_hook(); + self.remove_rollback_hook(); + } +} + +impl Statements { + fn new(capacity: usize) -> Self { + Statements { + cached: StatementCache::new(capacity), + temp: None, + } + } + + fn get(&mut self, query: &str, persistent: bool) -> Result<&mut VirtualStatement, Error> { + if !persistent || !self.cached.is_enabled() { + return Ok(self.temp.insert(VirtualStatement::new(query, false)?)); + } + + let exists = self.cached.contains_key(query); + + if !exists { + let statement = VirtualStatement::new(query, true)?; + self.cached.insert(query, statement); + } + + let statement = self.cached.get_mut(query).unwrap(); + + if exists { + // as this statement has been executed before, we reset before continuing + statement.reset()?; + } + + Ok(statement) + } + + fn len(&self) -> usize { + self.cached.len() + } + + fn clear(&mut self) { + self.cached.clear(); + self.temp = None; + } +} diff --git a/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs b/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs new file mode 100644 index 0000000000..2759588873 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs @@ -0,0 +1,157 @@ +use super::SqliteOperation; +use crate::{SqliteError, SqliteValueRef}; + +use libsqlite3_sys::{ + sqlite3, sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_new, + sqlite3_preupdate_old, sqlite3_value, SQLITE_OK, +}; +use std::ffi::CStr; +use std::marker::PhantomData; +use std::os::raw::{c_char, c_int, c_void}; +use std::panic::catch_unwind; +use std::ptr; +use std::ptr::NonNull; + +#[derive(Debug, thiserror::Error)] +pub enum PreupdateError { + /// Error returned from the database. + #[error("error returned from database: {0}")] + Database(#[source] SqliteError), + /// Index is not within the valid column range + #[error("{0} is not within the valid column range")] + ColumnIndexOutOfBounds(i32), + /// Column value accessor was invoked from an invalid operation + #[error("column value accessor was invoked from an invalid operation")] + InvalidOperation, +} + +pub(crate) struct PreupdateHookHandler( + pub(super) NonNull, +); +unsafe impl Send for PreupdateHookHandler {} + +#[derive(Debug)] +pub struct PreupdateHookResult<'a> { + pub operation: SqliteOperation, + pub database: &'a str, + pub table: &'a str, + db: *mut sqlite3, + // The database pointer should not be usable after the preupdate hook. + // The lifetime on this struct needs to ensure it cannot outlive the callback. + _db_lifetime: PhantomData<&'a ()>, + old_row_id: i64, + new_row_id: i64, +} + +impl<'a> PreupdateHookResult<'a> { + /// Gets the amount of columns in the row being inserted, deleted, or updated. + pub fn get_column_count(&self) -> i32 { + unsafe { sqlite3_preupdate_count(self.db) } + } + + /// Gets the depth of the query that triggered the preupdate hook. + /// Returns 0 if the preupdate callback was invoked as a result of + /// a direct insert, update, or delete operation; + /// 1 for inserts, updates, or deletes invoked by top-level triggers; + /// 2 for changes resulting from triggers called by top-level triggers; and so forth. + pub fn get_query_depth(&self) -> i32 { + unsafe { sqlite3_preupdate_depth(self.db) } + } + + /// Gets the row id of the row being updated/deleted. + /// Returns an error if called from an insert operation. + pub fn get_old_row_id(&self) -> Result { + if self.operation == SqliteOperation::Insert { + return Err(PreupdateError::InvalidOperation); + } + Ok(self.old_row_id) + } + + /// Gets the row id of the row being inserted/updated. + /// Returns an error if called from a delete operation. + pub fn get_new_row_id(&self) -> Result { + if self.operation == SqliteOperation::Delete { + return Err(PreupdateError::InvalidOperation); + } + Ok(self.new_row_id) + } + + /// Gets the value of the row being updated/deleted at the specified index. + /// Returns an error if called from an insert operation or the index is out of bounds. + pub fn get_old_column_value(&self, i: i32) -> Result, PreupdateError> { + if self.operation == SqliteOperation::Insert { + return Err(PreupdateError::InvalidOperation); + } + self.validate_column_index(i)?; + + let mut p_value: *mut sqlite3_value = ptr::null_mut(); + unsafe { + let ret = sqlite3_preupdate_old(self.db, i, &mut p_value); + self.get_value(ret, p_value) + } + } + + /// Gets the value of the row being inserted/updated at the specified index. + /// Returns an error if called from a delete operation or the index is out of bounds. + pub fn get_new_column_value(&self, i: i32) -> Result, PreupdateError> { + if self.operation == SqliteOperation::Delete { + return Err(PreupdateError::InvalidOperation); + } + self.validate_column_index(i)?; + + let mut p_value: *mut sqlite3_value = ptr::null_mut(); + unsafe { + let ret = sqlite3_preupdate_new(self.db, i, &mut p_value); + self.get_value(ret, p_value) + } + } + + fn validate_column_index(&self, i: i32) -> Result<(), PreupdateError> { + if i < 0 || i >= self.get_column_count() { + return Err(PreupdateError::ColumnIndexOutOfBounds(i)); + } + Ok(()) + } + + unsafe fn get_value( + &self, + ret: i32, + p_value: *mut sqlite3_value, + ) -> Result, PreupdateError> { + if ret != SQLITE_OK { + return Err(PreupdateError::Database(SqliteError::new(self.db))); + } + Ok(SqliteValueRef::borrowed(p_value)) + } +} + +pub(super) extern "C" fn preupdate_hook( + callback: *mut c_void, + db: *mut sqlite3, + op_code: c_int, + database: *const c_char, + table: *const c_char, + old_row_id: i64, + new_row_id: i64, +) where + F: FnMut(PreupdateHookResult) + Send + 'static, +{ + unsafe { + let _ = catch_unwind(|| { + let callback: *mut F = callback.cast::(); + let operation: SqliteOperation = op_code.into(); + let database = CStr::from_ptr(database).to_str().unwrap_or_default(); + let table = CStr::from_ptr(table).to_str().unwrap_or_default(); + + (*callback)(PreupdateHookResult { + operation, + database, + table, + old_row_id, + new_row_id, + db, + _db_lifetime: PhantomData, + }) + }); + } +} diff --git a/vendor/sqlx-sqlite/src/connection/worker.rs b/vendor/sqlx-sqlite/src/connection/worker.rs new file mode 100644 index 0000000000..2585dc1312 --- /dev/null +++ b/vendor/sqlx-sqlite/src/connection/worker.rs @@ -0,0 +1,573 @@ +use std::future::Future; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; + +use futures_channel::oneshot; +use futures_intrusive::sync::{Mutex, MutexGuard}; +use sqlx_core::sql_str::SqlStr; +use tracing::span::Span; + +use sqlx_core::error::Error; +use sqlx_core::transaction::{ + begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql, +}; +use sqlx_core::Either; + +use crate::connection::establish::EstablishParams; +use crate::connection::execute; +use crate::connection::ConnectionState; +use crate::{SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStatement}; + +#[cfg(feature = "deserialize")] +use crate::connection::deserialize::{deserialize, serialize, SchemaName, SqliteOwnedBuf}; + +// Each SQLite connection has a dedicated thread. + +// TODO: Tweak this so that we can use a thread pool per pool of SQLite3 connections to reduce +// OS resource usage. Low priority because a high concurrent load for SQLite3 is very +// unlikely. + +pub(crate) struct ConnectionWorker { + command_tx: flume::Sender<(Command, tracing::Span)>, + /// Mutex for locking access to the database. + pub(crate) shared: Arc, +} + +pub(crate) struct WorkerSharedState { + transaction_depth: AtomicUsize, + cached_statements_size: AtomicUsize, + pub(crate) conn: Mutex, +} + +impl WorkerSharedState { + pub(crate) fn get_transaction_depth(&self) -> usize { + self.transaction_depth.load(Ordering::Acquire) + } + + pub(crate) fn get_cached_statements_size(&self) -> usize { + self.cached_statements_size.load(Ordering::Acquire) + } +} + +enum Command { + Prepare { + query: SqlStr, + tx: oneshot::Sender>, + }, + #[cfg(feature = "offline")] + Describe { + query: SqlStr, + tx: oneshot::Sender, Error>>, + }, + Execute { + query: SqlStr, + arguments: Option, + persistent: bool, + tx: flume::Sender, Error>>, + limit: Option, + }, + #[cfg(feature = "deserialize")] + Serialize { + schema: Option, + tx: oneshot::Sender>, + }, + #[cfg(feature = "deserialize")] + Deserialize { + schema: Option, + data: SqliteOwnedBuf, + read_only: bool, + tx: oneshot::Sender>, + }, + Begin { + tx: rendezvous_oneshot::Sender>, + statement: Option, + }, + Commit { + tx: rendezvous_oneshot::Sender>, + }, + Rollback { + tx: Option>>, + }, + UnlockDb, + ClearCache { + tx: oneshot::Sender<()>, + }, + Ping { + tx: oneshot::Sender<()>, + }, + Shutdown { + tx: oneshot::Sender<()>, + }, +} + +impl ConnectionWorker { + pub(crate) async fn establish(params: EstablishParams) -> Result { + let (establish_tx, establish_rx) = oneshot::channel(); + + thread::Builder::new() + .name(params.thread_name.clone()) + .spawn(move || { + let (command_tx, command_rx) = flume::bounded(params.command_channel_size); + + let conn = match params.establish() { + Ok(conn) => conn, + Err(e) => { + establish_tx.send(Err(e)).ok(); + return; + } + }; + + let shared = Arc::new(WorkerSharedState { + transaction_depth: AtomicUsize::new(0), + cached_statements_size: AtomicUsize::new(0), + // note: must be fair because in `Command::UnlockDb` we unlock the mutex + // and then immediately try to relock it; an unfair mutex would immediately + // grant us the lock even if another task is waiting. + conn: Mutex::new(conn, true), + }); + let mut conn = shared.conn.try_lock().unwrap(); + + if establish_tx + .send(Ok(Self { + command_tx, + shared: Arc::clone(&shared), + })) + .is_err() + { + return; + } + + // If COMMIT or ROLLBACK is processed but not acknowledged, there would be another + // ROLLBACK sent when the `Transaction` drops. We need to ignore it otherwise we + // would rollback an already completed transaction. + let mut ignore_next_start_rollback = false; + + for (cmd, span) in command_rx { + let _guard = span.enter(); + match cmd { + Command::Prepare { query, tx } => { + tx.send(prepare(&mut conn, query)).ok(); + + // This may issue an unnecessary write on failure, + // but it doesn't matter in the grand scheme of things. + update_cached_statements_size( + &conn, + &shared.cached_statements_size, + ); + } + #[cfg(feature = "offline")] + Command::Describe { query, tx } => { + tx.send(crate::connection::describe::describe(&mut conn, query)).ok(); + } + Command::Execute { + query, + arguments, + persistent, + tx, + limit + } => { + let iter = match execute::iter(&mut conn, query, arguments, persistent) + { + Ok(iter) => iter, + Err(e) => { + tx.send(Err(e)).ok(); + continue; + } + }; + + match limit { + None => { + for res in iter { + let has_error = res.is_err(); + if tx.send(res).is_err() || has_error { + break; + } + } + }, + Some(limit) => { + let mut iter = iter; + let mut rows_returned = 0; + + while let Some(res) = iter.next() { + if let Ok(ok) = &res { + if ok.is_right() { + rows_returned += 1; + if rows_returned >= limit { + drop(iter); + let _ = tx.send(res); + break; + } + } + } + let has_error = res.is_err(); + if tx.send(res).is_err() || has_error { + break; + } + } + }, + } + + update_cached_statements_size(&conn, &shared.cached_statements_size); + } + Command::Begin { tx, statement } => { + let depth = shared.transaction_depth.load(Ordering::Acquire); + + let is_custom_statement = statement.is_some(); + let statement = match statement { + // custom `BEGIN` statements are not allowed if + // we're already in a transaction (we need to + // issue a `SAVEPOINT` instead) + Some(_) if depth > 0 => { + if tx.blocking_send(Err(Error::InvalidSavePointStatement)).is_err() { + break; + } + continue; + }, + Some(statement) => statement, + None => begin_ansi_transaction_sql(depth), + }; + let res = + conn.handle + .exec(statement.as_str()) + .and_then(|res| { + if is_custom_statement && !conn.handle.in_transaction() { + return Err(Error::BeginFailed) + } + + shared.transaction_depth.fetch_add(1, Ordering::Release); + + Ok(res) + }); + let res_ok = res.is_ok(); + + if tx.blocking_send(res).is_err() && res_ok { + // The BEGIN was processed but not acknowledged. This means no + // `Transaction` was created and so there is no way to commit / + // rollback this transaction. We need to roll it back + // immediately otherwise it would remain started forever. + if let Err(error) = conn + .handle + .exec(rollback_ansi_transaction_sql(depth + 1).as_str()) + .map(|_| { + shared.transaction_depth.fetch_sub(1, Ordering::Release); + }) + { + // The rollback failed. To prevent leaving the connection + // in an inconsistent state we shutdown this worker which + // causes any subsequent operation on the connection to fail. + tracing::error!(%error, "failed to rollback cancelled transaction"); + break; + } + } + } + Command::Commit { tx } => { + let depth = shared.transaction_depth.load(Ordering::Acquire); + + let res = if depth > 0 { + conn.handle + .exec(commit_ansi_transaction_sql(depth).as_str()) + .map(|_| { + shared.transaction_depth.fetch_sub(1, Ordering::Release); + }) + } else { + Ok(()) + }; + let res_ok = res.is_ok(); + + if tx.blocking_send(res).is_err() && res_ok { + // The COMMIT was processed but not acknowledged. This means that + // the `Transaction` doesn't know it was committed and will try to + // rollback on drop. We need to ignore that rollback. + ignore_next_start_rollback = true; + } + } + Command::Rollback { tx } => { + if ignore_next_start_rollback && tx.is_none() { + ignore_next_start_rollback = false; + continue; + } + + let depth = shared.transaction_depth.load(Ordering::Acquire); + + let res = if depth > 0 { + conn.handle + .exec(rollback_ansi_transaction_sql(depth).as_str()) + .map(|_| { + shared.transaction_depth.fetch_sub(1, Ordering::Release); + }) + } else { + Ok(()) + }; + + let res_ok = res.is_ok(); + + if let Some(tx) = tx { + if tx.blocking_send(res).is_err() && res_ok { + // The ROLLBACK was processed but not acknowledged. This means + // that the `Transaction` doesn't know it was rolled back and + // will try to rollback again on drop. We need to ignore that + // rollback. + ignore_next_start_rollback = true; + } + } + } + #[cfg(feature = "deserialize")] + Command::Serialize { schema, tx } => { + tx.send(serialize(&mut conn, schema)).ok(); + } + #[cfg(feature = "deserialize")] + Command::Deserialize { schema, data, read_only, tx } => { + tx.send(deserialize(&mut conn, schema, data, read_only)).ok(); + } + Command::ClearCache { tx } => { + conn.statements.clear(); + update_cached_statements_size(&conn, &shared.cached_statements_size); + tx.send(()).ok(); + } + Command::UnlockDb => { + drop(conn); + conn = futures_executor::block_on(shared.conn.lock()); + } + Command::Ping { tx } => { + tx.send(()).ok(); + } + Command::Shutdown { tx } => { + // drop the connection references before sending confirmation + // and ending the command loop + drop(conn); + drop(shared); + let _ = tx.send(()); + return; + } + } + } + })?; + + establish_rx.await.map_err(|_| Error::WorkerCrashed)? + } + + pub(crate) async fn prepare(&mut self, query: SqlStr) -> Result { + self.oneshot_cmd(|tx| Command::Prepare { query, tx }) + .await? + } + + #[cfg(feature = "offline")] + pub(crate) async fn describe( + &mut self, + query: SqlStr, + ) -> Result, Error> { + self.oneshot_cmd(|tx| Command::Describe { query, tx }) + .await? + } + + pub(crate) async fn execute( + &mut self, + query: SqlStr, + args: Option, + chan_size: usize, + persistent: bool, + limit: Option, + ) -> Result, Error>>, Error> { + let (tx, rx) = flume::bounded(chan_size); + + self.command_tx + .send_async(( + Command::Execute { + query, + arguments: args, + persistent, + tx, + limit, + }, + Span::current(), + )) + .await + .map_err(|_| Error::WorkerCrashed)?; + + Ok(rx) + } + + pub(crate) async fn begin(&mut self, statement: Option) -> Result<(), Error> { + self.oneshot_cmd_with_ack(|tx| Command::Begin { tx, statement }) + .await? + } + + pub(crate) async fn commit(&mut self) -> Result<(), Error> { + self.oneshot_cmd_with_ack(|tx| Command::Commit { tx }) + .await? + } + + pub(crate) async fn rollback(&mut self) -> Result<(), Error> { + self.oneshot_cmd_with_ack(|tx| Command::Rollback { tx: Some(tx) }) + .await? + } + + pub(crate) fn start_rollback(&mut self) -> Result<(), Error> { + self.command_tx + .send((Command::Rollback { tx: None }, Span::current())) + .map_err(|_| Error::WorkerCrashed) + } + + pub(crate) async fn ping(&mut self) -> Result<(), Error> { + self.oneshot_cmd(|tx| Command::Ping { tx }).await + } + + #[cfg(feature = "deserialize")] + pub(crate) async fn deserialize( + &mut self, + schema: Option, + data: SqliteOwnedBuf, + read_only: bool, + ) -> Result<(), Error> { + self.oneshot_cmd(|tx| Command::Deserialize { + schema, + data, + read_only, + tx, + }) + .await? + } + + #[cfg(feature = "deserialize")] + pub(crate) async fn serialize( + &mut self, + schema: Option, + ) -> Result { + self.oneshot_cmd(|tx| Command::Serialize { schema, tx }) + .await? + } + + async fn oneshot_cmd(&mut self, command: F) -> Result + where + F: FnOnce(oneshot::Sender) -> Command, + { + let (tx, rx) = oneshot::channel(); + + self.command_tx + .send_async((command(tx), Span::current())) + .await + .map_err(|_| Error::WorkerCrashed)?; + + rx.await.map_err(|_| Error::WorkerCrashed) + } + + async fn oneshot_cmd_with_ack(&mut self, command: F) -> Result + where + F: FnOnce(rendezvous_oneshot::Sender) -> Command, + { + let (tx, rx) = rendezvous_oneshot::channel(); + + self.command_tx + .send_async((command(tx), Span::current())) + .await + .map_err(|_| Error::WorkerCrashed)?; + + rx.recv().await.map_err(|_| Error::WorkerCrashed) + } + + pub(crate) async fn clear_cache(&mut self) -> Result<(), Error> { + self.oneshot_cmd(|tx| Command::ClearCache { tx }).await + } + + pub(crate) async fn unlock_db(&mut self) -> Result, Error> { + let (guard, res) = futures_util::future::join( + // we need to join the wait queue for the lock before we send the message + self.shared.conn.lock(), + self.command_tx + .send_async((Command::UnlockDb, Span::current())), + ) + .await; + + res.map_err(|_| Error::WorkerCrashed)?; + + Ok(guard) + } + + /// Send a command to the worker to shut down the processing thread. + /// + /// A `WorkerCrashed` error may be returned if the thread has already stopped. + pub(crate) fn shutdown(&mut self) -> impl Future> { + let (tx, rx) = oneshot::channel(); + + let send_res = self + .command_tx + .send((Command::Shutdown { tx }, Span::current())) + .map_err(|_| Error::WorkerCrashed); + + async move { + send_res?; + + // wait for the response + rx.await.map_err(|_| Error::WorkerCrashed) + } + } +} + +fn prepare(conn: &mut ConnectionState, query: SqlStr) -> Result { + // prepare statement object (or checkout from cache) + let statement = conn.statements.get(query.as_str(), true)?; + + let mut parameters = 0; + let mut columns = None; + let mut column_names = None; + + while let Some(statement) = statement.prepare_next(&mut conn.handle)? { + parameters += statement.handle.bind_parameter_count(); + + // the first non-empty statement is chosen as the statement we pull columns from + if !statement.columns.is_empty() && columns.is_none() { + columns = Some(Arc::clone(statement.columns)); + column_names = Some(Arc::clone(statement.column_names)); + } + } + + Ok(SqliteStatement { + sql: query, + columns: columns.unwrap_or_default(), + column_names: column_names.unwrap_or_default(), + parameters, + }) +} + +fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) { + size.store(conn.statements.len(), Ordering::Release); +} + +// A oneshot channel where send completes only after the receiver receives the value. +mod rendezvous_oneshot { + use super::oneshot::{self, Canceled}; + + pub fn channel() -> (Sender, Receiver) { + let (inner_tx, inner_rx) = oneshot::channel(); + (Sender { inner: inner_tx }, Receiver { inner: inner_rx }) + } + + pub struct Sender { + inner: oneshot::Sender<(T, oneshot::Sender<()>)>, + } + + impl Sender { + pub async fn send(self, value: T) -> Result<(), Canceled> { + let (ack_tx, ack_rx) = oneshot::channel(); + self.inner.send((value, ack_tx)).map_err(|_| Canceled)?; + ack_rx.await + } + + pub fn blocking_send(self, value: T) -> Result<(), Canceled> { + futures_executor::block_on(self.send(value)) + } + } + + pub struct Receiver { + inner: oneshot::Receiver<(T, oneshot::Sender<()>)>, + } + + impl Receiver { + pub async fn recv(self) -> Result { + let (value, ack_tx) = self.inner.await?; + ack_tx.send(()).map_err(|_| Canceled)?; + Ok(value) + } + } +} diff --git a/vendor/sqlx-sqlite/src/database.rs b/vendor/sqlx-sqlite/src/database.rs new file mode 100644 index 0000000000..ca52d1cf6b --- /dev/null +++ b/vendor/sqlx-sqlite/src/database.rs @@ -0,0 +1,39 @@ +pub(crate) use sqlx_core::database::{Database, HasStatementCache}; + +use crate::arguments::SqliteArgumentsBuffer; +use crate::{ + SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, + SqliteTransactionManager, SqliteTypeInfo, SqliteValue, SqliteValueRef, +}; + +/// Sqlite database driver. +#[derive(Debug)] +pub struct Sqlite; + +impl Database for Sqlite { + type Connection = SqliteConnection; + + type TransactionManager = SqliteTransactionManager; + + type Row = SqliteRow; + + type QueryResult = SqliteQueryResult; + + type Column = SqliteColumn; + + type TypeInfo = SqliteTypeInfo; + + type Value = SqliteValue; + type ValueRef<'r> = SqliteValueRef<'r>; + + type Arguments = SqliteArguments; + type ArgumentBuffer = SqliteArgumentsBuffer; + + type Statement = SqliteStatement; + + const NAME: &'static str = "SQLite"; + + const URL_SCHEMES: &'static [&'static str] = &["sqlite"]; +} + +impl HasStatementCache for Sqlite {} diff --git a/vendor/sqlx-sqlite/src/error.rs b/vendor/sqlx-sqlite/src/error.rs new file mode 100644 index 0000000000..5510e25d97 --- /dev/null +++ b/vendor/sqlx-sqlite/src/error.rs @@ -0,0 +1,139 @@ +use std::error::Error as StdError; +use std::ffi::CStr; +use std::fmt::{self, Display, Formatter}; +use std::os::raw::c_int; +use std::{borrow::Cow, str}; + +use libsqlite3_sys::{ + sqlite3, sqlite3_errmsg, sqlite3_errstr, sqlite3_extended_errcode, SQLITE_CONSTRAINT_CHECK, + SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY, + SQLITE_CONSTRAINT_UNIQUE, SQLITE_ERROR, SQLITE_NOMEM, +}; + +pub(crate) use sqlx_core::error::*; + +// Error Codes And Messages +// https://www.sqlite.org/c3ref/errcode.html + +#[derive(Debug)] +pub struct SqliteError { + code: c_int, + message: Cow<'static, str>, +} + +impl SqliteError { + pub(crate) unsafe fn new(handle: *mut sqlite3) -> Self { + Self::try_new(handle).expect("There should be an error") + } + + pub(crate) unsafe fn try_new(handle: *mut sqlite3) -> Option { + // returns the extended result code even when extended result codes are disabled + let code: c_int = unsafe { sqlite3_extended_errcode(handle) }; + + if code == 0 { + return None; + } + + // return English-language text that describes the error + let message = unsafe { + let msg = sqlite3_errmsg(handle); + debug_assert!(!msg.is_null()); + + String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()).into_owned() + }; + + Some(Self { + code, + message: message.into(), + }) + } + + /// For errors during extension load, the error message is supplied via a separate pointer + #[allow(dead_code)] + pub(crate) fn with_message(mut self, error_msg: String) -> Self { + self.message = error_msg.into(); + self + } + + #[allow(dead_code)] + pub(crate) fn from_code(code: c_int) -> Self { + let message = unsafe { + let errstr = sqlite3_errstr(code); + + if !errstr.is_null() { + // SAFETY: `errstr` is guaranteed to be UTF-8 + // The lifetime of the string is "internally managed"; + // the implementation just selects from an array of static strings. + // We copy to an owned buffer in case `libsqlite3` is dynamically loaded somehow. + Cow::Owned(str::from_utf8_unchecked(CStr::from_ptr(errstr).to_bytes()).into()) + } else { + Cow::Borrowed("") + } + }; + + SqliteError { code, message } + } + + #[allow(dead_code)] + pub(crate) fn generic(message: impl Into>) -> Self { + Self { + code: SQLITE_ERROR, + message: message.into(), + } + } + + /// Return `SQLITE_NOMEM`. + pub(crate) fn nomem() -> Self { + Self::from_code(SQLITE_NOMEM) + } +} + +impl Display for SqliteError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // We include the code as some produce ambiguous messages: + // SQLITE_BUSY: "database is locked" + // SQLITE_LOCKED: "database table is locked" + // Sadly there's no function to get the string label back from an error code. + write!(f, "(code: {}) {}", self.code, self.message) + } +} + +impl StdError for SqliteError {} + +impl DatabaseError for SqliteError { + #[inline] + fn message(&self) -> &str { + &self.message + } + + /// The extended result code. + #[inline] + fn code(&self) -> Option> { + Some(format!("{}", self.code).into()) + } + + #[doc(hidden)] + fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { + self + } + + #[doc(hidden)] + fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { + self + } + + #[doc(hidden)] + fn into_error(self: Box) -> Box { + self + } + + fn kind(&self) -> ErrorKind { + match self.code { + SQLITE_CONSTRAINT_UNIQUE | SQLITE_CONSTRAINT_PRIMARYKEY => ErrorKind::UniqueViolation, + SQLITE_CONSTRAINT_FOREIGNKEY => ErrorKind::ForeignKeyViolation, + SQLITE_CONSTRAINT_NOTNULL => ErrorKind::NotNullViolation, + SQLITE_CONSTRAINT_CHECK => ErrorKind::CheckViolation, + _ => ErrorKind::Other, + } + } +} diff --git a/vendor/sqlx-sqlite/src/lib.rs b/vendor/sqlx-sqlite/src/lib.rs new file mode 100644 index 0000000000..17b4bed1ca --- /dev/null +++ b/vendor/sqlx-sqlite/src/lib.rs @@ -0,0 +1,183 @@ +//! **SQLite** database driver. +//! +//! ### Note: `libsqlite3-sys` Version +//! This driver uses the `libsqlite3-sys` crate which links the native library for SQLite 3. +//! Only one version of `libsqlite3-sys` may appear in the dependency tree of your project. +//! +//! As of SQLx 0.9.0, the version of `libsqlite3-sys` is now a range instead of any specific version. +//! See the `Cargo.toml` of the `sqlx-sqlite` crate for the current version range. +//! +//! If you are using `rusqlite` or any other crate that indirectly depends on `libsqlite3-sys`, +//! this should allow Cargo to select a compatible version. +//! +//! If Cargo **fails to select a compatible version**, this means the other crate is using +//! a `libsqlite3-sys` version outside of this range. +//! +//! We may increase the *maximum* version of the range at our discretion, +//! in patch (SemVer-compatible) releases, to allow users to upgrade to newer versions as desired. +//! +//! The *minimum* version of the range may be increased over time to drop very old or +//! insecure versions of SQLite, but this will only occur in major (SemVer-incompatible) releases. +//! +//! Note that this means a `cargo update` may increase the `libsqlite3-sys` version, +//! which could, in rare cases, break your build. +//! +//! To prevent this, you can pin the `libsqlite3-sys` version in your own dependencies: +//! +//! ```toml +//! [dependencies] +//! # for example, if 0.35.0 breaks the build +//! libsqlite3-sys = "0.34" +//! ``` +//! +//! ### Static Linking (Default) +//! The `sqlite` feature enables the `bundled` feature of `libsqlite3-sys`, +//! which builds SQLite 3 from included source code and statically links it into the final binary. +//! +//! This requires some C build tools to be installed on the system; see +//! [the `rusqlite` README][rusqlite-readme-building] for details. +//! +//! This version of SQLite is generally much newer than system-installed versions of SQLite +//! (especially for LTS Linux distributions), and can be updated with a `cargo update`, +//! so this is the recommended option for ease of use and keeping up-to-date. +//! +//! ### Dynamic linking +//! To dynamically link to an existing SQLite library, the `sqlite-unbundled` feature can be used +//! instead. +//! +//! This allows updating SQLite independently of SQLx or using forked versions, but you must have +//! SQLite installed on the system or provide a path to the library at build time (see +//! [the `rusqlite` README][rusqlite-readme-building] for details). +//! +//! Note that this _may_ result in link errors if the SQLite version is too old, +//! or has [certain features disabled at compile-time](https://www.sqlite.org/compile.html). +//! +//! SQLite version `3.20.0` (released August 2018) or newer is recommended. +//! +//! **Please check your SQLite version and the flags it was built with before opening +//! a GitHub issue because of errors in `libsqlite3-sys`.** Thank you. +//! +//! [rusqlite-readme-building]: https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys +//! +//! ### Optional Features +//! +//! The following features +//! + +// SQLite is a C library. All interactions require FFI which is unsafe. +// All unsafe blocks should have comments pointing to SQLite docs and ensuring that we maintain +// invariants. +#![allow(unsafe_code)] + +#[macro_use] +extern crate sqlx_core; + +use std::sync::atomic::AtomicBool; + +pub use arguments::{SqliteArgumentValue, SqliteArguments, SqliteArgumentsBuffer}; +pub use column::SqliteColumn; +#[cfg(feature = "deserialize")] +#[cfg_attr(docsrs, doc(cfg(feature = "deserialize")))] +pub use connection::deserialize::SqliteOwnedBuf; +#[cfg(feature = "preupdate-hook")] +#[cfg_attr(docsrs, doc(cfg(feature = "preupdate-hook")))] +pub use connection::PreupdateHookResult; +pub use connection::{LockedSqliteHandle, SqliteConnection, SqliteOperation, UpdateHookResult}; +pub use database::Sqlite; +pub use error::SqliteError; +pub use options::{ + SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqliteLockingMode, SqliteSynchronous, +}; +pub use query_result::SqliteQueryResult; +pub use row::SqliteRow; +pub use statement::SqliteStatement; +pub use transaction::SqliteTransactionManager; +pub use type_info::SqliteTypeInfo; +pub use value::{SqliteValue, SqliteValueRef}; + +use crate::connection::establish::EstablishParams; + +pub(crate) use sqlx_core::driver_prelude::*; + +use sqlx_core::config; +use sqlx_core::describe::Describe; +use sqlx_core::error::Error; +use sqlx_core::executor::Executor; +use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr}; + +mod arguments; +mod column; +mod connection; +mod database; +mod error; +mod logger; +mod options; +mod query_result; +mod row; +mod statement; +mod transaction; +mod type_checking; +mod type_info; +pub mod types; +mod value; + +#[cfg(feature = "any")] +pub mod any; + +#[cfg(feature = "regexp")] +mod regexp; + +#[cfg(feature = "migrate")] +mod migrate; + +#[cfg(feature = "migrate")] +mod testing; + +/// An alias for [`Pool`][crate::pool::Pool], specialized for SQLite. +pub type SqlitePool = crate::pool::Pool; + +/// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for SQLite. +pub type SqlitePoolOptions = crate::pool::PoolOptions; + +/// An alias for [`Executor<'_, Database = Sqlite>`][Executor]. +pub trait SqliteExecutor<'c>: Executor<'c, Database = Sqlite> {} +impl<'c, T: Executor<'c, Database = Sqlite>> SqliteExecutor<'c> for T {} + +/// An alias for [`Transaction`][sqlx_core::transaction::Transaction], specialized for SQLite. +pub type SqliteTransaction<'c> = sqlx_core::transaction::Transaction<'c, Sqlite>; + +// NOTE: required due to the lack of lazy normalization +impl_into_arguments_for_arguments!(SqliteArguments); +impl_column_index_for_row!(SqliteRow); +impl_column_index_for_statement!(SqliteStatement); +impl_acquire!(Sqlite, SqliteConnection); + +// required because some databases have a different handling of NULL +impl_encode_for_option!(Sqlite); + +/// UNSTABLE: for use by `sqlx-cli` only. +#[doc(hidden)] +pub static CREATE_DB_WAL: AtomicBool = AtomicBool::new(true); + +/// UNSTABLE: for use by `sqlite-macros-core` only. +#[doc(hidden)] +pub fn describe_blocking( + query: &str, + database_url: &str, + driver_config: &config::drivers::Config, +) -> Result, Error> { + let mut opts: SqliteConnectOptions = database_url.parse()?; + + opts = opts.apply_driver_config(&driver_config.sqlite)?; + + let params = EstablishParams::from_options(&opts)?; + let mut conn = params.establish()?; + + // Execute any ancillary `PRAGMA`s + connection::execute::iter(&mut conn, AssertSqlSafe(opts.pragma_string()), None, false)? + .finish()?; + + connection::describe::describe(&mut conn, AssertSqlSafe(query.to_string()).into_sql_str()) + + // SQLite database is closed immediately when `conn` is dropped +} diff --git a/vendor/sqlx-sqlite/src/logger.rs b/vendor/sqlx-sqlite/src/logger.rs new file mode 100644 index 0000000000..1464a730c7 --- /dev/null +++ b/vendor/sqlx-sqlite/src/logger.rs @@ -0,0 +1,443 @@ +// Bad casts in this module SHOULD NOT result in a SQL injection +// https://github.com/launchbadge/sqlx/issues/3440 +#![allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss +)] + +use crate::connection::intmap::IntMap; +use std::collections::HashSet; +use std::fmt::Debug; +use std::hash::Hash; + +pub(crate) use sqlx_core::logger::*; + +#[derive(Debug)] +pub(crate) enum BranchResult { + Result(R), + Dedup(BranchParent), + Halt, + Error, + GasLimit, + LoopLimit, + Branched, +} + +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, Ord, PartialOrd)] +pub(crate) struct BranchParent { + pub id: i64, + pub idx: i64, +} + +#[derive(Debug)] +pub(crate) struct InstructionHistory { + pub program_i: usize, + pub state: S, +} + +pub(crate) trait DebugDiff { + fn diff(&self, prev: &Self) -> String; +} + +pub struct QueryPlanLogger<'q, R: Debug + 'static, S: Debug + DebugDiff + 'static, P: Debug> { + sql: &'q str, + unknown_operations: HashSet, + branch_origins: IntMap, + branch_results: IntMap>, + branch_operations: IntMap>>, + program: &'q [P], +} + +/// convert a string into dot format +fn dot_escape_string(value: impl AsRef) -> String { + value + .as_ref() + .replace('\\', r#"\\"#) + .replace('"', "'") + .replace('\n', r#"\n"#) + .to_string() +} + +impl core::fmt::Display for QueryPlanLogger<'_, R, S, P> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + //writes query plan history in dot format + f.write_str("digraph {\n")?; + + f.write_str("subgraph operations {\n")?; + f.write_str("style=\"rounded\";\nnode [shape=\"point\"];\n")?; + + let all_states: std::collections::HashMap> = self + .branch_operations + .iter_entries() + .flat_map( + |(branch_id, instructions): (i64, &IntMap>)| { + instructions.iter_entries().map( + move |(idx, ih): (i64, &InstructionHistory)| { + (BranchParent { id: branch_id, idx }, ih) + }, + ) + }, + ) + .collect(); + + let mut instruction_uses: IntMap> = Default::default(); + for (k, state) in all_states.iter() { + let entry = instruction_uses.get_mut_or_default(&(state.program_i as i64)); + entry.push(*k); + } + + let mut branch_children: std::collections::HashMap> = + Default::default(); + + let mut branched_with_state: std::collections::HashSet = Default::default(); + + for (branch_id, branch_parent) in self.branch_origins.iter_entries() { + let entry = branch_children.entry(*branch_parent).or_default(); + entry.push(BranchParent { + id: branch_id, + idx: 0, + }); + } + + for (idx, instruction) in self.program.iter().enumerate() { + let escaped_instruction = dot_escape_string(format!("{:?}", instruction)); + write!( + f, + "subgraph cluster_{} {{ label=\"{}\"", + idx, escaped_instruction + )?; + + if self.unknown_operations.contains(&idx) { + f.write_str(" style=dashed")?; + } + + f.write_str(";\n")?; + + let mut state_list: std::collections::BTreeMap< + String, + Vec<(BranchParent, Option)>, + > = Default::default(); + + write!(f, "i{}[style=invis];", idx)?; + + if let Some(this_instruction_uses) = instruction_uses.get(&(idx as i64)) { + for curr_ref in this_instruction_uses.iter() { + if let Some(curr_state) = all_states.get(curr_ref) { + let next_ref = BranchParent { + id: curr_ref.id, + idx: curr_ref.idx + 1, + }; + + if let Some(next_state) = all_states.get(&next_ref) { + let state_diff = next_state.state.diff(&curr_state.state); + + state_list + .entry(state_diff) + .or_default() + .push((*curr_ref, Some(next_ref))); + } else { + state_list + .entry(Default::default()) + .or_default() + .push((*curr_ref, None)); + }; + + if let Some(children) = branch_children.get(curr_ref) { + for next_ref in children { + if let Some(next_state) = all_states.get(next_ref) { + let state_diff = next_state.state.diff(&curr_state.state); + + if !state_diff.is_empty() { + branched_with_state.insert(*next_ref); + } + + state_list + .entry(state_diff) + .or_default() + .push((*curr_ref, Some(*next_ref))); + } + } + }; + } + } + + for curr_ref in this_instruction_uses { + if branch_children.contains_key(curr_ref) { + write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?; + } + } + } else { + write!(f, "i{}->i{}[style=invis];", idx - 1, idx)?; + } + + for (state_num, (state_diff, ref_list)) in state_list.iter().enumerate() { + if !state_diff.is_empty() { + let escaped_state = dot_escape_string(state_diff); + write!( + f, + "subgraph \"cluster_i{}s{}\" {{\nlabel=\"{}\"\n", + idx, state_num, escaped_state + )?; + } + + for (curr_ref, next_ref) in ref_list { + if let Some(next_ref) = next_ref { + let next_program_i = all_states + .get(next_ref) + .map(|s| s.program_i.to_string()) + .unwrap_or_default(); + + if branched_with_state.contains(next_ref) { + write!( + f, + "\"b{}p{}_b{}p{}\"[tooltip=\"next:{}\"];", + curr_ref.id, + curr_ref.idx, + next_ref.id, + next_ref.idx, + next_program_i + )?; + continue; + } else { + write!( + f, + "\"b{}p{}\"[tooltip=\"next:{}\"];", + curr_ref.id, curr_ref.idx, next_program_i + )?; + } + } else { + write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?; + } + } + + if !state_diff.is_empty() { + f.write_str("}\n")?; + } + } + + f.write_str("}\n")?; + } + + f.write_str("};\n")?; //subgraph operations + + let max_branch_id: i64 = [ + self.branch_operations.last_index().unwrap_or(0), + self.branch_results.last_index().unwrap_or(0), + self.branch_results.last_index().unwrap_or(0), + ] + .into_iter() + .max() + .unwrap_or(0); + + f.write_str("subgraph branches {\n")?; + for branch_id in 0..=max_branch_id { + write!(f, "subgraph b{}{{", branch_id)?; + + let branch_num = branch_id as usize; + let color_names = [ + "blue", + "red", + "cyan", + "yellow", + "green", + "magenta", + "orange", + "purple", + "orangered", + "sienna", + "olivedrab", + "pink", + ]; + let color_name_root = color_names[branch_num % color_names.len()]; + let color_name_suffix = match (branch_num / color_names.len()) % 4 { + 0 => "1", + 1 => "4", + 2 => "3", + 3 => "2", + _ => "", + }; //colors are easily confused after color_names.len() * 2, and outright reused after color_names.len() * 4 + write!( + f, + "edge [colorscheme=x11 color={}{}];", + color_name_root, color_name_suffix + )?; + + let mut instruction_list: Vec<(BranchParent, &InstructionHistory)> = Vec::new(); + if let Some(parent) = self.branch_origins.get(&branch_id) { + if let Some(parent_state) = all_states.get(parent) { + instruction_list.push((*parent, parent_state)); + } + } + if let Some(instructions) = self.branch_operations.get(&branch_id) { + for instruction in instructions.iter_entries() { + instruction_list.push(( + BranchParent { + id: branch_id, + idx: instruction.0, + }, + instruction.1, + )) + } + } + + let mut instructions_iter = instruction_list.into_iter(); + + if let Some((cur_ref, _)) = instructions_iter.next() { + let mut prev_ref = cur_ref; + + for (cur_ref, _) in instructions_iter { + if branched_with_state.contains(&cur_ref) { + writeln!( + f, + "\"b{}p{}\" -> \"b{}p{}_b{}p{}\" -> \"b{}p{}\"", + prev_ref.id, + prev_ref.idx, + prev_ref.id, + prev_ref.idx, + cur_ref.id, + cur_ref.idx, + cur_ref.id, + cur_ref.idx + )?; + } else { + write!( + f, + "\"b{}p{}\" -> \"b{}p{}\";", + prev_ref.id, prev_ref.idx, cur_ref.id, cur_ref.idx + )?; + } + prev_ref = cur_ref; + } + + //draw edge to the result of this branch + if let Some(result) = self.branch_results.get(&branch_id) { + if let BranchResult::Dedup(dedup_ref) = result { + write!( + f, + "\"b{}p{}\"->\"b{}p{}\" [style=dotted]", + prev_ref.id, prev_ref.idx, dedup_ref.id, dedup_ref.idx + )?; + } else { + let escaped_result = dot_escape_string(format!("{:?}", result)); + write!( + f, + "\"b{}p{}\" ->\"{}\"; \"{}\" [shape=box];", + prev_ref.id, prev_ref.idx, escaped_result, escaped_result + )?; + } + } else { + write!( + f, + "\"b{}p{}\" ->\"NoResult\"; \"NoResult\" [shape=box];", + prev_ref.id, prev_ref.idx + )?; + } + } + f.write_str("};\n")?; + } + f.write_str("};\n")?; //branches + + f.write_str("}\n")?; + Ok(()) + } +} + +impl<'q, R: Debug, S: Debug + DebugDiff, P: Debug> QueryPlanLogger<'q, R, S, P> { + pub fn new(sql: &'q str, program: &'q [P]) -> Self { + Self { + sql, + unknown_operations: HashSet::new(), + branch_origins: IntMap::new(), + branch_results: IntMap::new(), + branch_operations: IntMap::new(), + program, + } + } + + pub fn log_enabled(&self) -> bool { + log::log_enabled!(target: "sqlx::explain", log::Level::Trace) + || private_tracing_dynamic_enabled!(target: "sqlx::explain", tracing::Level::TRACE) + } + + pub fn add_branch(&mut self, state: I, parent: &BranchParent) + where + BranchParent: From, + { + if !self.log_enabled() { + return; + } + let branch: BranchParent = BranchParent::from(state); + self.branch_origins.insert(branch.id, *parent); + } + + pub fn add_operation(&mut self, program_i: usize, state: I) + where + BranchParent: From, + S: From, + { + if !self.log_enabled() { + return; + } + let branch: BranchParent = BranchParent::from(state); + let state: S = S::from(state); + self.branch_operations + .get_mut_or_default(&branch.id) + .insert(branch.idx, InstructionHistory { program_i, state }); + } + + pub fn add_result(&mut self, state: I, result: BranchResult) + where + BranchParent: for<'a> From<&'a I>, + S: From, + { + if !self.log_enabled() { + return; + } + let branch: BranchParent = BranchParent::from(&state); + self.branch_results.insert(branch.id, result); + } + + pub fn add_unknown_operation(&mut self, operation: usize) { + if !self.log_enabled() { + return; + } + self.unknown_operations.insert(operation); + } + + pub fn finish(&self) { + if !self.log_enabled() { + return; + } + + let mut summary = parse_query_summary(self.sql); + + let sql = if summary != self.sql { + summary.push_str(" …"); + format!( + "\n\n{}\n", + self.sql /* + sqlformat::format( + self.sql, + &sqlformat::QueryParams::None, + sqlformat::FormatOptions::default() + ) + */ + ) + } else { + String::new() + }; + + sqlx_core::private_tracing_dynamic_event!( + target: "sqlx::explain", + tracing::Level::TRACE, + "{}; program:\n{}\n\n{:?}", summary, self, sql + ); + } +} + +impl Drop for QueryPlanLogger<'_, R, S, P> { + fn drop(&mut self) { + self.finish(); + } +} diff --git a/vendor/sqlx-sqlite/src/migrate.rs b/vendor/sqlx-sqlite/src/migrate.rs new file mode 100644 index 0000000000..5864023891 --- /dev/null +++ b/vendor/sqlx-sqlite/src/migrate.rs @@ -0,0 +1,297 @@ +use crate::connection::{ConnectOptions, Connection}; +use crate::error::Error; +use crate::executor::Executor; +use crate::fs; +use crate::migrate::MigrateError; +use crate::migrate::{AppliedMigration, Migration}; +use crate::migrate::{Migrate, MigrateDatabase}; +use crate::query::query; +use crate::query_as::query_as; +use crate::{Sqlite, SqliteConnectOptions, SqliteConnection, SqliteJournalMode}; +use futures_core::future::BoxFuture; +use sqlx_core::sql_str::AssertSqlSafe; +use std::str::FromStr; +use std::sync::atomic::Ordering; +use std::time::Duration; +use std::time::Instant; + +pub(crate) use sqlx_core::migrate::*; +use sqlx_core::query_scalar::query_scalar; + +impl MigrateDatabase for Sqlite { + async fn create_database(url: &str) -> Result<(), Error> { + let mut opts = SqliteConnectOptions::from_str(url)?.create_if_missing(true); + + // Since it doesn't make sense to include this flag in the connection URL, + // we just use an `AtomicBool` to pass it. + if super::CREATE_DB_WAL.load(Ordering::Acquire) { + opts = opts.journal_mode(SqliteJournalMode::Wal); + } + + // Opening a connection to sqlite creates the database + opts.connect() + .await? + // Ensure WAL mode tempfiles are cleaned up + .close() + .await?; + + Ok(()) + } + + async fn database_exists(url: &str) -> Result { + let options = SqliteConnectOptions::from_str(url)?; + + if options.in_memory { + Ok(true) + } else { + Ok(options.filename.exists()) + } + } + + async fn drop_database(url: &str) -> Result<(), Error> { + let options = SqliteConnectOptions::from_str(url)?; + + if !options.in_memory { + fs::remove_file(&*options.filename).await?; + } + + Ok(()) + } +} + +impl Migrate for SqliteConnection { + fn create_schema_if_not_exists<'e>( + &'e mut self, + schema_name: &'e str, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // Check if the schema already exists; if so, don't error. + let schema_version: Option = query_scalar(AssertSqlSafe(format!( + "PRAGMA {schema_name}.schema_version" + ))) + .fetch_optional(&mut *self) + .await?; + + if schema_version.is_some() { + return Ok(()); + } + + Err(MigrateError::CreateSchemasNotSupported( + format!("cannot create new schema {schema_name}; creation of additional schemas in SQLite requires attaching extra database files"), + )) + }) + } + + fn ensure_migrations_table<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // language=SQLite + self.execute(AssertSqlSafe(format!( + r#" +CREATE TABLE IF NOT EXISTS {table_name} ( + version BIGINT PRIMARY KEY, + description TEXT NOT NULL, + installed_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + success BOOLEAN NOT NULL, + checksum BLOB NOT NULL, + execution_time BIGINT NOT NULL +); + "# + ))) + .await?; + + Ok(()) + }) + } + + fn dirty_version<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result, MigrateError>> { + Box::pin(async move { + // language=SQLite + let row: Option<(i64,)> = query_as(AssertSqlSafe(format!( + "SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1" + ))) + .fetch_optional(self) + .await?; + + Ok(row.map(|r| r.0)) + }) + } + + fn list_applied_migrations<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result, MigrateError>> { + Box::pin(async move { + // language=SQLite + let rows: Vec<(i64, Vec)> = query_as(AssertSqlSafe(format!( + "SELECT version, checksum FROM {table_name} ORDER BY version" + ))) + .fetch_all(self) + .await?; + + let migrations = rows + .into_iter() + .map(|(version, checksum)| AppliedMigration { + version, + checksum: checksum.into(), + }) + .collect(); + + Ok(migrations) + }) + } + + fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { + Box::pin(async move { Ok(()) }) + } + + fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { + Box::pin(async move { Ok(()) }) + } + + fn apply<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result> { + Box::pin(async move { + let start = Instant::now(); + + if migration.no_tx { + execute_migration(self, table_name, migration).await?; + } else { + // Use a single transaction for the actual migration script and the essential bookkeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for + // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 + // and update it once the actual transaction completed. + let mut tx = self.begin().await?; + execute_migration(&mut tx, table_name, migration).await?; + tx.commit().await?; + } + + // Update `elapsed_time`. + // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept + // this small risk since this value is not super important. + let elapsed = start.elapsed(); + + // language=SQLite + #[allow(clippy::cast_possible_truncation)] + let _ = query(AssertSqlSafe(format!( + r#" + UPDATE {table_name} + SET execution_time = ?1 + WHERE version = ?2 + "# + ))) + .bind(elapsed.as_nanos() as i64) + .bind(migration.version) + .execute(self) + .await?; + + Ok(elapsed) + }) + } + + fn revert<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result> { + Box::pin(async move { + let start = Instant::now(); + + if migration.no_tx { + revert_migration(self, table_name, migration).await?; + } else { + // Use a single transaction for the actual migration script and the essential bookkeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + let mut tx = self.begin().await?; + revert_migration(&mut tx, table_name, migration).await?; + tx.commit().await?; + } + + let elapsed = start.elapsed(); + + Ok(elapsed) + }) + } + + fn skip<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // language=SQLite + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) + VALUES ( ?1, ?2, TRUE, ?3, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(self) + .await?; + + Ok(()) + }) + } +} + +async fn execute_migration( + conn: &mut SqliteConnection, + table_name: &str, + migration: &Migration, +) -> Result<(), MigrateError> { + let _ = conn + .execute(migration.sql.clone()) + .await + .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; + + // language=SQLite + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) + VALUES ( ?1, ?2, TRUE, ?3, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(conn) + .await?; + + Ok(()) +} + +async fn revert_migration( + conn: &mut SqliteConnection, + table_name: &str, + migration: &Migration, +) -> Result<(), MigrateError> { + let _ = conn + .execute(migration.sql.clone()) + .await + .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; + + // language=SQLite + let _ = query(AssertSqlSafe(format!( + r#" + DELETE FROM {table_name} + WHERE version = ?1 + "# + ))) + .bind(migration.version) + .execute(conn) + .await?; + + Ok(()) +} diff --git a/vendor/sqlx-sqlite/src/options/auto_vacuum.rs b/vendor/sqlx-sqlite/src/options/auto_vacuum.rs new file mode 100644 index 0000000000..582631c5bd --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/auto_vacuum.rs @@ -0,0 +1,38 @@ +use crate::error::Error; +use std::str::FromStr; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SqliteAutoVacuum { + #[default] + None, + Full, + Incremental, +} + +impl SqliteAutoVacuum { + pub(crate) fn as_str(&self) -> &'static str { + match self { + SqliteAutoVacuum::None => "NONE", + SqliteAutoVacuum::Full => "FULL", + SqliteAutoVacuum::Incremental => "INCREMENTAL", + } + } +} + +impl FromStr for SqliteAutoVacuum { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match &*s.to_ascii_lowercase() { + "none" => SqliteAutoVacuum::None, + "full" => SqliteAutoVacuum::Full, + "incremental" => SqliteAutoVacuum::Incremental, + + _ => { + return Err(Error::Configuration( + format!("unknown value {s:?} for `auto_vacuum`").into(), + )); + } + }) + } +} diff --git a/vendor/sqlx-sqlite/src/options/connect.rs b/vendor/sqlx-sqlite/src/options/connect.rs new file mode 100644 index 0000000000..cfa25872b2 --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/connect.rs @@ -0,0 +1,83 @@ +use crate::{SqliteConnectOptions, SqliteConnection}; +use log::LevelFilter; +use sqlx_core::config; +use sqlx_core::connection::ConnectOptions; +use sqlx_core::error::Error; +use sqlx_core::executor::Executor; +use sqlx_core::sql_str::AssertSqlSafe; +use std::fmt::Write; +use std::str::FromStr; +use std::time::Duration; +use url::Url; + +impl ConnectOptions for SqliteConnectOptions { + type Connection = SqliteConnection; + + fn from_url(url: &Url) -> Result { + // SQLite URL parsing is handled specially; + // we want to treat the following URLs as equivalent: + // + // * sqlite:foo.db + // * sqlite://foo.db + // + // If we used `Url::path()`, the latter would return an empty string + // because `foo.db` gets parsed as the hostname. + Self::from_str(url.as_str()) + } + + fn to_url_lossy(&self) -> Url { + self.build_url() + } + + async fn connect(&self) -> Result + where + Self::Connection: Sized, + { + let mut conn = SqliteConnection::establish(self).await?; + + // Execute PRAGMAs + conn.execute(AssertSqlSafe(self.pragma_string())).await?; + + if !self.collations.is_empty() { + let mut locked = conn.lock_handle().await?; + + for collation in &self.collations { + collation.create(&mut locked.guard.handle)?; + } + } + + Ok(conn) + } + + fn log_statements(mut self, level: LevelFilter) -> Self { + self.log_settings.log_statements(level); + self + } + + fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { + self.log_settings.log_slow_statements(level, duration); + self + } + + fn __unstable_apply_driver_config( + self, + config: &config::drivers::Config, + ) -> crate::Result { + self.apply_driver_config(&config.sqlite) + } +} + +impl SqliteConnectOptions { + /// Collect all `PRAMGA` commands into a single string + pub(crate) fn pragma_string(&self) -> String { + let mut string = String::new(); + + for (key, opt_value) in &self.pragmas { + if let Some(value) = opt_value { + write!(string, "PRAGMA {key} = {value}; ").ok(); + } + } + + string + } +} diff --git a/vendor/sqlx-sqlite/src/options/journal_mode.rs b/vendor/sqlx-sqlite/src/options/journal_mode.rs new file mode 100644 index 0000000000..5750ce97ec --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/journal_mode.rs @@ -0,0 +1,50 @@ +use crate::error::Error; +use std::str::FromStr; + +/// Refer to [SQLite documentation] for the meaning of the database journaling mode. +/// +/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_journal_mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SqliteJournalMode { + Delete, + Truncate, + Persist, + Memory, + #[default] + Wal, + Off, +} + +impl SqliteJournalMode { + pub(crate) fn as_str(&self) -> &'static str { + match self { + SqliteJournalMode::Delete => "DELETE", + SqliteJournalMode::Truncate => "TRUNCATE", + SqliteJournalMode::Persist => "PERSIST", + SqliteJournalMode::Memory => "MEMORY", + SqliteJournalMode::Wal => "WAL", + SqliteJournalMode::Off => "OFF", + } + } +} + +impl FromStr for SqliteJournalMode { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match &*s.to_ascii_lowercase() { + "delete" => SqliteJournalMode::Delete, + "truncate" => SqliteJournalMode::Truncate, + "persist" => SqliteJournalMode::Persist, + "memory" => SqliteJournalMode::Memory, + "wal" => SqliteJournalMode::Wal, + "off" => SqliteJournalMode::Off, + + _ => { + return Err(Error::Configuration( + format!("unknown value {s:?} for `journal_mode`").into(), + )); + } + }) + } +} diff --git a/vendor/sqlx-sqlite/src/options/locking_mode.rs b/vendor/sqlx-sqlite/src/options/locking_mode.rs new file mode 100644 index 0000000000..99afd43eef --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/locking_mode.rs @@ -0,0 +1,38 @@ +use crate::error::Error; +use std::str::FromStr; + +/// Refer to [SQLite documentation] for the meaning of the connection locking mode. +/// +/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_locking_mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SqliteLockingMode { + #[default] + Normal, + Exclusive, +} + +impl SqliteLockingMode { + pub(crate) fn as_str(&self) -> &'static str { + match self { + SqliteLockingMode::Normal => "NORMAL", + SqliteLockingMode::Exclusive => "EXCLUSIVE", + } + } +} + +impl FromStr for SqliteLockingMode { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match &*s.to_ascii_lowercase() { + "normal" => SqliteLockingMode::Normal, + "exclusive" => SqliteLockingMode::Exclusive, + + _ => { + return Err(Error::Configuration( + format!("unknown value {s:?} for `locking_mode`").into(), + )); + } + }) + } +} diff --git a/vendor/sqlx-sqlite/src/options/mod.rs b/vendor/sqlx-sqlite/src/options/mod.rs new file mode 100644 index 0000000000..0cafc4d08c --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/mod.rs @@ -0,0 +1,675 @@ +use std::path::Path; + +mod auto_vacuum; +mod connect; +mod journal_mode; +mod locking_mode; +mod parse; +mod synchronous; + +use crate::connection::LogSettings; +pub use auto_vacuum::SqliteAutoVacuum; +pub use journal_mode::SqliteJournalMode; +pub use locking_mode::SqliteLockingMode; +use std::cmp::Ordering; +use std::sync::Arc; +use std::{borrow::Cow, time::Duration}; +pub use synchronous::SqliteSynchronous; + +use crate::common::DebugFn; +use crate::connection::collation::Collation; +use sqlx_core::{config, IndexMap}; + +/// Options and flags which can be used to configure a SQLite connection. +/// +/// A value of `SqliteConnectOptions` can be parsed from a connection URL, +/// as described by [SQLite](https://www.sqlite.org/uri.html). +/// +/// This type also implements [`FromStr`][std::str::FromStr] so you can parse it from a string +/// containing a connection URL and then further adjust options if necessary (see example below). +/// +/// | URL | Description | +/// | -- | -- | +/// `sqlite::memory:` | Open an in-memory database. | +/// `sqlite:data.db` | Open the file `data.db` in the current directory. | +/// `sqlite://data.db` | Open the file `data.db` in the current directory. | +/// `sqlite:///data.db` | Open the file `data.db` from the root (`/`) directory. | +/// `sqlite://data.db?mode=ro` | Open the file `data.db` for read-only access. | +/// +/// # Example +/// +/// ```rust,no_run +/// # async fn example() -> sqlx::Result<()> { +/// use sqlx::ConnectOptions; +/// use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool}; +/// use std::str::FromStr; +/// +/// let opts = SqliteConnectOptions::from_str("sqlite://data.db")? +/// .journal_mode(SqliteJournalMode::Wal) +/// .read_only(true); +/// +/// // use in a pool +/// let pool = SqlitePool::connect_with(opts).await?; +/// +/// // or connect directly +/// # let opts = SqliteConnectOptions::from_str("sqlite://data.db")?; +/// let conn = opts.connect().await?; +/// # +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct SqliteConnectOptions { + pub(crate) filename: Cow<'static, Path>, + pub(crate) in_memory: bool, + pub(crate) read_only: bool, + pub(crate) create_if_missing: bool, + pub(crate) shared_cache: bool, + pub(crate) statement_cache_capacity: usize, + pub(crate) busy_timeout: Duration, + pub(crate) log_settings: LogSettings, + pub(crate) immutable: bool, + pub(crate) vfs: Option>, + + pub(crate) pragmas: IndexMap, Option>>, + + /// Extensions are specified as a pair of \, the majority + /// of SQLite extensions will use the default entry points specified in the docs, these should + /// be added to the map with a `None` value. + /// + #[cfg(feature = "load-extension")] + pub(crate) extensions: IndexMap, Option>>, + + pub(crate) command_channel_size: usize, + pub(crate) row_channel_size: usize, + + pub(crate) collations: Vec, + + pub(crate) serialized: bool, + pub(crate) thread_name: Arc String + Send + Sync + 'static>>, + + pub(crate) optimize_on_close: OptimizeOnClose, + + #[cfg(feature = "regexp")] + pub(crate) register_regexp_function: bool, +} + +#[derive(Clone, Debug)] +pub enum OptimizeOnClose { + Enabled { analysis_limit: Option }, + Disabled, +} + +impl Default for SqliteConnectOptions { + fn default() -> Self { + Self::new() + } +} + +impl SqliteConnectOptions { + /// Construct `Self` with default options. + /// + /// See the source of this method for the current defaults. + pub fn new() -> Self { + let mut pragmas: IndexMap, Option>> = IndexMap::new(); + + // Standard pragmas + // + // Most of these don't actually need to be sent because they would be set to their + // default values anyway. See the SQLite documentation for default values of these PRAGMAs: + // https://www.sqlite.org/pragma.html + // + // However, by inserting into the map here, we can ensure that they're set in the proper + // order, even if they're overwritten later by their respective setters or + // directly by `pragma()` + + // SQLCipher special case: if the `key` pragma is set, it must be executed first. + pragmas.insert("key".into(), None); + + // Other SQLCipher pragmas that has to be after the key, but before any other operation on the database. + // https://www.zetetic.net/sqlcipher/sqlcipher-api/ + + // Bytes of the database file that is not encrypted + // Default for SQLCipher v4 is 0 + // If greater than zero 'cipher_salt' pragma must be also defined + pragmas.insert("cipher_plaintext_header_size".into(), None); + + // Allows to provide salt manually + // By default SQLCipher sets salt automatically, use only in conjunction with + // 'cipher_plaintext_header_size' pragma + pragmas.insert("cipher_salt".into(), None); + + // Number of iterations used in PBKDF2 key derivation. + // Default for SQLCipher v4 is 256000 + pragmas.insert("kdf_iter".into(), None); + + // Define KDF algorithm to be used. + // Default for SQLCipher v4 is PBKDF2_HMAC_SHA512. + pragmas.insert("cipher_kdf_algorithm".into(), None); + + // Enable or disable HMAC functionality. + // Default for SQLCipher v4 is 1. + pragmas.insert("cipher_use_hmac".into(), None); + + // Set default encryption settings depending on the version 1,2,3, or 4. + pragmas.insert("cipher_compatibility".into(), None); + + // Page size of encrypted database. + // Default for SQLCipher v4 is 4096. + pragmas.insert("cipher_page_size".into(), None); + + // Choose algorithm used for HMAC. + // Default for SQLCipher v4 is HMAC_SHA512. + pragmas.insert("cipher_hmac_algorithm".into(), None); + + // Normally, page_size must be set before any other action on the database. + // Defaults to 4096 for new databases. + pragmas.insert("page_size".into(), None); + + // locking_mode should be set before journal_mode: + // https://www.sqlite.org/wal.html#use_of_wal_without_shared_memory + pragmas.insert("locking_mode".into(), None); + + // `auto_vacuum` needs to be executed before `journal_mode`, if set. + // + // Otherwise, a change in the `journal_mode` setting appears to mark even an empty database as dirty, + // requiring a `vacuum` command to be executed to actually apply the new `auto_vacuum` setting. + pragmas.insert("auto_vacuum".into(), None); + + // Don't set `journal_mode` unless the user requested it. + // WAL mode is a permanent setting for created databases and changing into or out of it + // requires an exclusive lock that can't be waited on with `sqlite3_busy_timeout()`. + // https://github.com/launchbadge/sqlx/pull/1930#issuecomment-1168165414 + pragmas.insert("journal_mode".into(), None); + + // We choose to enable foreign key enforcement by default, though SQLite normally + // leaves it off for backward compatibility: https://www.sqlite.org/foreignkeys.html#fk_enable + pragmas.insert("foreign_keys".into(), Some("ON".into())); + + // The `synchronous` pragma defaults to FULL + // https://www.sqlite.org/compile.html#default_synchronous. + pragmas.insert("synchronous".into(), None); + + // Soft limit on the number of rows that `ANALYZE` touches per index. + pragmas.insert("analysis_limit".into(), None); + + Self { + filename: Cow::Borrowed(Path::new(":memory:")), + in_memory: false, + read_only: false, + create_if_missing: false, + shared_cache: false, + statement_cache_capacity: 100, + busy_timeout: Duration::from_secs(5), + log_settings: Default::default(), + immutable: false, + vfs: None, + pragmas, + #[cfg(feature = "load-extension")] + extensions: Default::default(), + collations: Default::default(), + serialized: false, + thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{id}"))), + command_channel_size: 50, + row_channel_size: 50, + optimize_on_close: OptimizeOnClose::Disabled, + #[cfg(feature = "regexp")] + register_regexp_function: false, + } + } + + /// Sets the name of the database file. + /// + /// This is a low-level API, and SQLx will apply no special treatment for `":memory:"` as an + /// in-memory database using this method. Using [`SqliteConnectOptions::from_str()`][SqliteConnectOptions#from_str] may be + /// preferred for simple use cases. + pub fn filename(mut self, filename: impl AsRef) -> Self { + self.filename = Cow::Owned(filename.as_ref().to_owned()); + self + } + + /// Gets the current name of the database file. + pub fn get_filename(&self) -> &Path { + &self.filename + } + + /// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys). + /// + /// SQLx chooses to enable this by default so that foreign keys function as expected, + /// compared to other database flavors. + pub fn foreign_keys(self, on: bool) -> Self { + self.pragma("foreign_keys", if on { "ON" } else { "OFF" }) + } + + /// Set the [`SQLITE_OPEN_MEMORY` flag](https://sqlite.org/c3ref/open.html). + /// + /// By default, this is disabled. + pub fn in_memory(mut self, in_memory: bool) -> Self { + self.in_memory = in_memory; + self + } + + /// Set the [`SQLITE_OPEN_SHAREDCACHE` flag](https://sqlite.org/sharedcache.html). + /// + /// By default, this is disabled. + pub fn shared_cache(mut self, on: bool) -> Self { + self.shared_cache = on; + self + } + + /// Sets the [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) for the database connection. + /// + /// Journal modes are ephemeral per connection, with the exception of the + /// [Write-Ahead Log (WAL) mode](https://www.sqlite.org/wal.html). + /// + /// A database created in WAL mode retains the setting and will apply it to all connections + /// opened against it that don't set a `journal_mode`. + /// + /// Opening a connection to a database created in WAL mode with a different `journal_mode` will + /// erase the setting on the database, requiring an exclusive lock to do so. + /// You may get a `database is locked` (corresponding to `SQLITE_BUSY`) error if another + /// connection is accessing the database file at the same time. + /// + /// SQLx does not set a journal mode by default, to avoid unintentionally changing a database + /// into or out of WAL mode. + /// + /// The default journal mode for non-WAL databases is `DELETE`, or `MEMORY` for in-memory + /// databases. + /// + /// For consistency, any commands in `sqlx-cli` which create a SQLite database will create it + /// in WAL mode. + pub fn journal_mode(self, mode: SqliteJournalMode) -> Self { + self.pragma("journal_mode", mode.as_str()) + } + + /// Sets the [locking mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) for the database connection. + /// + /// The default locking mode is NORMAL. + pub fn locking_mode(self, mode: SqliteLockingMode) -> Self { + self.pragma("locking_mode", mode.as_str()) + } + + /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to open the database + /// for read-only access. + pub fn read_only(mut self, read_only: bool) -> Self { + self.read_only = read_only; + self + } + + /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to create the database file + /// if the file does not exist. + /// + /// By default, a new file **will not be created** if one is not found. + pub fn create_if_missing(mut self, create: bool) -> Self { + self.create_if_missing = create; + self + } + + /// Sets the capacity of the connection's statement cache in a number of stored + /// distinct statements. Caching is handled using LRU, meaning when the + /// amount of queries hits the defined limit, the oldest statement will get + /// dropped. + /// + /// The default cache capacity is 100 statements. + pub fn statement_cache_capacity(mut self, capacity: usize) -> Self { + self.statement_cache_capacity = capacity; + self + } + + /// Sets a timeout value to wait when the database is locked, before + /// returning a busy timeout error. + /// + /// The default busy timeout is 5 seconds. + pub fn busy_timeout(mut self, timeout: Duration) -> Self { + self.busy_timeout = timeout; + self + } + + /// Sets the [synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) setting for the database connection. + /// + /// The default synchronous settings is FULL. However, if durability is not a concern, + /// then NORMAL is normally all one needs in WAL mode. + pub fn synchronous(self, synchronous: SqliteSynchronous) -> Self { + self.pragma("synchronous", synchronous.as_str()) + } + + /// Sets the [auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) setting for the database connection. + /// + /// The default auto_vacuum setting is NONE. + /// + /// For existing databases, a change to this value does not take effect unless a + /// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed. + pub fn auto_vacuum(self, auto_vacuum: SqliteAutoVacuum) -> Self { + self.pragma("auto_vacuum", auto_vacuum.as_str()) + } + + /// Sets the [page_size](https://www.sqlite.org/pragma.html#pragma_page_size) setting for the database connection. + /// + /// The default page_size setting is 4096. + /// + /// For existing databases, a change to this value does not take effect unless a + /// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed. + /// However, it cannot be changed in WAL mode. + pub fn page_size(self, page_size: u32) -> Self { + self.pragma("page_size", page_size.to_string()) + } + + /// Sets custom initial pragma for the database connection. + pub fn pragma(mut self, key: K, value: V) -> Self + where + K: Into>, + V: Into>, + { + self.pragmas.insert(key.into(), Some(value.into())); + self + } + + /// Add a custom collation for comparing strings in SQL. + /// + /// If a collation with the same name already exists, it will be replaced. + /// + /// See [`sqlite3_create_collation()`](https://www.sqlite.org/c3ref/create_collation.html) for details. + /// + /// Note this excerpt: + /// > The collating function must obey the following properties for all strings A, B, and C: + /// > + /// > If A==B then B==A. + /// > If A==B and B==C then A==C. + /// > If A\A. + /// > If A + /// > If a collating function fails any of the above constraints and that collating function is + /// > registered and used, then the behavior of SQLite is undefined. + pub fn collation(mut self, name: N, collate: F) -> Self + where + N: Into>, + F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, + { + self.collations.push(Collation::new(name, collate)); + self + } + + /// Set to `true` to signal to SQLite that the database file is on read-only media. + /// + /// If enabled, SQLite assumes the database file _cannot_ be modified, even by higher + /// privileged processes, and so disables locking and change detection. This is intended + /// to improve performance but can produce incorrect query results or errors if the file + /// _does_ change. + /// + /// Note that this is different from the `SQLITE_OPEN_READONLY` flag set by + /// [`.read_only()`][Self::read_only], though the documentation suggests that this + /// does _imply_ `SQLITE_OPEN_READONLY`. + /// + /// See [`sqlite3_open`](https://www.sqlite.org/capi3ref.html#sqlite3_open) (subheading + /// "URI Filenames") for details. + pub fn immutable(mut self, immutable: bool) -> Self { + self.immutable = immutable; + self + } + + /// Sets the [threading mode](https://www.sqlite.org/threadsafe.html) for the database connection. + /// + /// The default setting is `false` corresponding to using `OPEN_NOMUTEX`. + /// If set to `true` then `OPEN_FULLMUTEX`. + /// + /// See [open](https://www.sqlite.org/c3ref/open.html) for more details. + /// + /// ### Note + /// Setting this to `true` may help if you are getting access violation errors or segmentation + /// faults, but will also incur a significant performance penalty. You should leave this + /// set to `false` if at all possible. + /// + /// If you do end up needing to set this to `true` for some reason, please + /// [open an issue](https://github.com/launchbadge/sqlx/issues/new/choose) as this may indicate + /// a concurrency bug in SQLx. Please provide clear instructions for reproducing the issue, + /// including a sample database schema if applicable. + pub fn serialized(mut self, serialized: bool) -> Self { + self.serialized = serialized; + self + } + + /// Provide a callback to generate the name of the background worker thread. + /// + /// The value passed to the callback is an auto-incremented integer for use as the thread ID. + pub fn thread_name( + mut self, + generator: impl Fn(u64) -> String + Send + Sync + 'static, + ) -> Self { + self.thread_name = Arc::new(DebugFn(generator)); + self + } + + /// Set the maximum number of commands to buffer for the worker thread before backpressure is + /// applied. + /// + /// Given that most commands sent to the worker thread involve waiting for a result, + /// the command channel is unlikely to fill up unless a lot queries are executed in a short + /// period but cancelled before their full resultsets are returned. + pub fn command_buffer_size(mut self, size: usize) -> Self { + self.command_channel_size = size; + self + } + + /// Set the maximum number of rows to buffer back to the calling task when a query is executed. + /// + /// If the calling task cannot keep up, backpressure will be applied to the worker thread + /// in order to limit CPU and memory usage. + pub fn row_buffer_size(mut self, size: usize) -> Self { + self.row_channel_size = size; + self + } + + /// Sets the [`vfs`](https://www.sqlite.org/vfs.html) parameter of the database connection. + /// + /// The default value is empty, and sqlite will use the default VFS object depending on the + /// operating system. + pub fn vfs(mut self, vfs_name: impl Into>) -> Self { + self.vfs = Some(vfs_name.into()); + self + } + + /// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database + /// connection at startup, using the default entrypoint. + /// + /// Most common SQLite extensions can be loaded using this method. + /// For extensions where you need to override the entry point, + /// use [`.extension_with_entrypoint()`]. + /// + /// Multiple extensions can be loaded by calling this method, + /// or [`.extension_with_entrypoint()`] where applicable, + /// once for each extension. + /// + /// Extension loading is only enabled during the initialization of the connection, + /// and disabled before `connect()` returns by setting + /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0. + /// + /// This will not enable the SQL `load_extension()` function. + /// + /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension + /// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint + /// + /// # Safety + /// This causes arbitrary DLLs on the filesystem to be loaded at runtime, + /// which can easily result in undefined behavior, memory corruption, + /// or exploitable vulnerabilities if misused. + /// + /// It is not possible to provide a truly safe version of this API. + /// + /// Use this method with care, and only load extensions that you trust. + /// + /// # Example + /// ```rust,no_run + /// # use sqlx_core::error::Error; + /// # use std::str::FromStr; + /// # use sqlx_sqlite::SqliteConnectOptions; + /// # fn options() -> Result { + /// let mut options = SqliteConnectOptions::from_str("sqlite://data.db")?; + /// + /// // SAFETY: these are trusted extensions. + /// unsafe { + /// options = options + /// .extension("vsv") + /// .extension("mod_spatialite"); + /// } + /// + /// # Ok(options) + /// # } + /// ``` + #[cfg(feature = "load-extension")] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))] + pub unsafe fn extension(mut self, extension_name: impl Into>) -> Self { + self.extensions.insert(extension_name.into(), None); + self + } + + /// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database + /// connection at startup, overriding the entrypoint. + /// + /// See also [`.extension()`] for extensions using the standard entrypoint name + /// `sqlite3_extension_init` or `sqlite3__init`. + /// + /// Multiple extensions can be loaded by calling this method, + /// or [`.extension()`] where applicable, + /// once for each extension. + /// + /// Extension loading is only enabled during the initialization of the connection, + /// and disabled before `connect()` returns by setting + /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0. + /// + /// This will not enable the SQL `load_extension()` function. + /// + /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension + /// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint + /// + /// # Safety + /// This causes arbitrary DLLs on the filesystem to be loaded at runtime, + /// which can easily result in undefined behavior, memory corruption, + /// or exploitable vulnerabilities if misused. + /// + /// If you specify the wrong entrypoint name, it _may_ simply result in an error, + /// or it may end up invoking the wrong routine, leading to undefined behavior. + /// + /// It is not possible to provide a truly safe version of this API. + /// + /// Use this method with care, only load extensions that you trust, + /// and double-check the entrypoint name with the extension's documentation or source code. + #[cfg(feature = "load-extension")] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))] + pub unsafe fn extension_with_entrypoint( + mut self, + extension_name: impl Into>, + entry_point: impl Into>, + ) -> Self { + self.extensions + .insert(extension_name.into(), Some(entry_point.into())); + self + } + + /// Execute `PRAGMA optimize;` on the SQLite connection before closing. + /// + /// The SQLite manual recommends using this for long-lived databases. + /// + /// This will collect and store statistics about the layout of data in your tables to help the query planner make better decisions. + /// Over the connection's lifetime, the query planner will make notes about which tables could use up-to-date statistics so this + /// command doesn't have to scan the whole database every time. Thus, the best time to execute this is on connection close. + /// + /// `analysis_limit` sets a soft limit on the maximum number of rows to scan per index. + /// It is equivalent to setting [`Self::analysis_limit`] but only takes effect for the `PRAGMA optimize;` call + /// and does not affect the behavior of any `ANALYZE` statements made during the connection's lifetime. + /// + /// If not `None`, the `analysis_limit` here overrides the global `analysis_limit` setting, + /// but only for the `PRAGMA optimize;` call. + /// + /// Not enabled by default. + /// + /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#automatically_running_analyze) for details. + pub fn optimize_on_close( + mut self, + enabled: bool, + analysis_limit: impl Into>, + ) -> Self { + self.optimize_on_close = if enabled { + OptimizeOnClose::Enabled { + analysis_limit: (analysis_limit.into()), + } + } else { + OptimizeOnClose::Disabled + }; + self + } + + /// Set a soft limit on the number of rows that `ANALYZE` touches per index. + /// + /// This also affects `PRAGMA optimize` which is set by [Self::optimize_on_close]. + /// + /// The value recommended by SQLite is `400`. There is no default. + /// + /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#approx) for details. + pub fn analysis_limit(mut self, limit: impl Into>) -> Self { + if let Some(limit) = limit.into() { + return self.pragma("analysis_limit", limit.to_string()); + } + self.pragmas.insert("analysis_limit".into(), None); + self + } + + /// Register a regexp function that allows using regular expressions in queries. + /// + /// ``` + /// # use std::str::FromStr; + /// # use sqlx::{ConnectOptions, Connection, Row}; + /// # use sqlx_sqlite::SqliteConnectOptions; + /// # async fn run() -> sqlx::Result<()> { + /// let mut sqlite = SqliteConnectOptions::from_str("sqlite://:memory:")? + /// .with_regexp() + /// .connect() + /// .await?; + /// let tables = sqlx::query("SELECT name FROM sqlite_schema WHERE name REGEXP 'foo(\\d+)bar'") + /// .fetch_all(&mut sqlite) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// This uses the [`regex`] crate, and is only enabled when you enable the `regex` feature is enabled on sqlx + #[cfg(feature = "regexp")] + pub fn with_regexp(mut self) -> Self { + self.register_regexp_function = true; + self + } + + #[cfg_attr(not(feature = "load-extension"), expect(unused_mut))] + pub(crate) fn apply_driver_config( + mut self, + config: &config::drivers::SqliteConfig, + ) -> crate::Result { + #[cfg(feature = "load-extension")] + for extension in &config.unsafe_load_extensions { + // SAFETY: the documentation warns the user about loading extensions + match extension { + config::drivers::SqliteExtension::Path(path) => { + self = unsafe { self.extension(path.clone()) } + } + config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => { + self = + unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) } + } + } + } + + #[cfg(not(feature = "load-extension"))] + if !config.unsafe_load_extensions.is_empty() { + return Err(sqlx_core::Error::Configuration( + format!( + "sqlx.toml specifies `drivers.sqlite.unsafe-load-extensions = {:?}` \ + but extension loading is not enabled; \ + enable the `sqlite-load-extension` feature of SQLx to use SQLite extensions", + config.unsafe_load_extensions, + ) + .into(), + )); + } + + Ok(self) + } +} diff --git a/vendor/sqlx-sqlite/src/options/parse.rs b/vendor/sqlx-sqlite/src/options/parse.rs new file mode 100644 index 0000000000..0530f4204c --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/parse.rs @@ -0,0 +1,231 @@ +use std::borrow::Cow; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use percent_encoding::{percent_decode_str, percent_encode, AsciiSet}; +use url::Url; + +use crate::error::Error; +use crate::SqliteConnectOptions; + +// https://www.sqlite.org/uri.html + +static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0); + +impl SqliteConnectOptions { + pub(crate) fn from_db_and_params(database: &str, params: Option<&str>) -> Result { + let mut options = Self::default(); + + if database == ":memory:" { + options.in_memory = true; + options.shared_cache = true; + let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed); + options.filename = Cow::Owned(PathBuf::from(format!("file:sqlx-in-memory-{seqno}"))); + } else { + // % decode to allow for `?` or `#` in the filename + options.filename = Cow::Owned( + Path::new( + &*percent_decode_str(database) + .decode_utf8() + .map_err(Error::config)?, + ) + .to_path_buf(), + ); + } + + if let Some(params) = params { + for (key, value) in url::form_urlencoded::parse(params.as_bytes()) { + match &*key { + // The mode query parameter determines if the new database is opened read-only, + // read-write, read-write and created if it does not exist, or that the + // database is a pure in-memory database that never interacts with disk, + // respectively. + "mode" => { + match &*value { + "ro" => { + options.read_only = true; + } + + // default + "rw" => {} + + "rwc" => { + options.create_if_missing = true; + } + + "memory" => { + options.in_memory = true; + options.shared_cache = true; + } + + _ => { + return Err(Error::Configuration( + format!("unknown value {value:?} for `mode`").into(), + )); + } + } + } + + // The cache query parameter specifies the cache behaviour across multiple + // connections to the same database within the process. A shared cache is + // essential for persisting data across connections to an in-memory database. + "cache" => match &*value { + "private" => { + options.shared_cache = false; + } + + "shared" => { + options.shared_cache = true; + } + + _ => { + return Err(Error::Configuration( + format!("unknown value {value:?} for `cache`").into(), + )); + } + }, + + "immutable" => match &*value { + "true" | "1" => { + options.immutable = true; + } + "false" | "0" => { + options.immutable = false; + } + _ => { + return Err(Error::Configuration( + format!("unknown value {value:?} for `immutable`").into(), + )); + } + }, + + "vfs" => options.vfs = Some(Cow::Owned(value.into_owned())), + + _ => { + return Err(Error::Configuration( + format!("unknown query parameter `{key}` while parsing connection URL") + .into(), + )); + } + } + } + } + + Ok(options) + } + + pub(crate) fn build_url(&self) -> Url { + // https://url.spec.whatwg.org/#path-percent-encode-set + static PATH_ENCODE_SET: AsciiSet = percent_encoding::CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'?') + .add(b'`') + .add(b'{') + .add(b'}'); + + let filename_encoded = percent_encode( + self.filename.as_os_str().as_encoded_bytes(), + &PATH_ENCODE_SET, + ); + + let mut url = Url::parse(&format!("sqlite://{filename_encoded}")) + .expect("BUG: generated un-parseable URL"); + + let mode = match (self.in_memory, self.create_if_missing, self.read_only) { + (true, _, _) => "memory", + (false, true, _) => "rwc", + (false, false, true) => "ro", + (false, false, false) => "rw", + }; + url.query_pairs_mut().append_pair("mode", mode); + + let cache = match self.shared_cache { + true => "shared", + false => "private", + }; + url.query_pairs_mut().append_pair("cache", cache); + + if self.immutable { + url.query_pairs_mut().append_pair("immutable", "true"); + } + + if let Some(vfs) = &self.vfs { + url.query_pairs_mut().append_pair("vfs", vfs); + } + + url + } +} + +impl FromStr for SqliteConnectOptions { + type Err = Error; + + fn from_str(mut url: &str) -> Result { + // remove scheme from the URL + url = url + .trim_start_matches("sqlite://") + .trim_start_matches("sqlite:"); + + let mut database_and_params = url.splitn(2, '?'); + + let database = database_and_params.next().unwrap_or_default(); + let params = database_and_params.next(); + + Self::from_db_and_params(database, params) + } +} + +#[test] +fn test_parse_in_memory() -> Result<(), Error> { + let options: SqliteConnectOptions = "sqlite::memory:".parse()?; + assert!(options.in_memory); + assert!(options.shared_cache); + + let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?; + assert!(options.in_memory); + assert!(options.shared_cache); + + let options: SqliteConnectOptions = "sqlite://:memory:".parse()?; + assert!(options.in_memory); + assert!(options.shared_cache); + + let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?; + assert!(options.in_memory); + assert!(!options.shared_cache); + + Ok(()) +} + +#[test] +fn test_parse_read_only() -> Result<(), Error> { + let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?; + assert!(options.read_only); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + Ok(()) +} + +#[test] +fn test_parse_shared_in_memory() -> Result<(), Error> { + let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?; + assert!(options.shared_cache); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + Ok(()) +} + +#[test] +fn it_returns_the_parsed_url() -> Result<(), Error> { + let url = "sqlite://test.db?mode=rw&cache=shared"; + let options: SqliteConnectOptions = url.parse()?; + + let expected_url = Url::parse(url).unwrap(); + assert_eq!(options.build_url(), expected_url); + + Ok(()) +} diff --git a/vendor/sqlx-sqlite/src/options/synchronous.rs b/vendor/sqlx-sqlite/src/options/synchronous.rs new file mode 100644 index 0000000000..709b805bc7 --- /dev/null +++ b/vendor/sqlx-sqlite/src/options/synchronous.rs @@ -0,0 +1,44 @@ +use crate::error::Error; +use std::str::FromStr; + +/// Refer to [SQLite documentation] for the meaning of various synchronous settings. +/// +/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_synchronous +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SqliteSynchronous { + Off, + Normal, + #[default] + Full, + Extra, +} + +impl SqliteSynchronous { + pub(crate) fn as_str(&self) -> &'static str { + match self { + SqliteSynchronous::Off => "OFF", + SqliteSynchronous::Normal => "NORMAL", + SqliteSynchronous::Full => "FULL", + SqliteSynchronous::Extra => "EXTRA", + } + } +} + +impl FromStr for SqliteSynchronous { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match &*s.to_ascii_lowercase() { + "off" => SqliteSynchronous::Off, + "normal" => SqliteSynchronous::Normal, + "full" => SqliteSynchronous::Full, + "extra" => SqliteSynchronous::Extra, + + _ => { + return Err(Error::Configuration( + format!("unknown value {s:?} for `synchronous`").into(), + )); + } + }) + } +} diff --git a/vendor/sqlx-sqlite/src/query_result.rs b/vendor/sqlx-sqlite/src/query_result.rs new file mode 100644 index 0000000000..8c8c27fcf4 --- /dev/null +++ b/vendor/sqlx-sqlite/src/query_result.rs @@ -0,0 +1,40 @@ +use std::iter::{Extend, IntoIterator}; + +#[derive(Debug, Default)] +pub struct SqliteQueryResult { + pub(super) changes: u64, + pub(super) last_insert_rowid: i64, +} + +impl SqliteQueryResult { + pub fn rows_affected(&self) -> u64 { + self.changes + } + + pub fn last_insert_rowid(&self) -> i64 { + self.last_insert_rowid + } +} + +impl Extend for SqliteQueryResult { + fn extend>(&mut self, iter: T) { + for elem in iter { + self.changes += elem.changes; + self.last_insert_rowid = elem.last_insert_rowid; + } + } +} + +#[cfg(feature = "any")] +impl From for sqlx_core::any::AnyQueryResult { + fn from(done: SqliteQueryResult) -> Self { + let last_insert_id = match done.last_insert_rowid() { + 0 => None, + n => Some(n), + }; + sqlx_core::any::AnyQueryResult { + rows_affected: done.rows_affected(), + last_insert_id, + } + } +} diff --git a/vendor/sqlx-sqlite/src/regexp.rs b/vendor/sqlx-sqlite/src/regexp.rs new file mode 100644 index 0000000000..735200e9be --- /dev/null +++ b/vendor/sqlx-sqlite/src/regexp.rs @@ -0,0 +1,281 @@ +#![deny(missing_docs, clippy::pedantic)] +#![allow(clippy::cast_sign_loss)] // some lengths returned from sqlite3 are `i32`, but rust needs `usize` + +//! Here be dragons +//! +//! We need to register a custom REGEX implementation for sqlite +//! some useful resources: +//! - rusqlite has an example implementation: +//! - sqlite supports registering custom C functions: +//! - sqlite also supports a `A REGEXP B` syntax, but ONLY if the user implements `regex(B, A)` +//! - Note that A and B are indeed swapped: the regex comes first, the field comes second +//! - +//! - sqlx has a way to safely get a sqlite3 pointer: +//! - +//! - + +use libsqlite3_sys as ffi; +use log::error; +use regex::Regex; +use std::sync::Arc; + +/// The function name for sqlite3. This must be "regexp\0" +static FN_NAME: &[u8] = b"regexp\0"; + +/// Register the regex function with sqlite. +/// +/// Returns the result code of `sqlite3_create_function_v2` +pub fn register(sqlite3: *mut ffi::sqlite3) -> i32 { + unsafe { + ffi::sqlite3_create_function_v2( + // the database connection + sqlite3, + // the function name. Must be up to 255 bytes, and 0-terminated + FN_NAME.as_ptr().cast(), + // the number of arguments this function accepts. We want 2 arguments: The regex and the field + 2, + // we want all our strings to be UTF8, and this function will return the same output with the same inputs + ffi::SQLITE_UTF8 | ffi::SQLITE_DETERMINISTIC, + // pointer to user data. We're not using user data + std::ptr::null_mut(), + // xFunc to be executed when we are invoked + Some(sqlite3_regexp_func), + // xStep, should be NULL for scalar functions + None, + // xFinal, should be NULL for scalar functions + None, + // xDestroy, called when this function is deregistered. Should be used to clean up our pointer to user-data + None, + ) + } +} + +/// A function to be called on each invocation of `regex(REGEX, FIELD)` from sqlite3 +/// +/// - `ctx`: a pointer to the current sqlite3 context +/// - `n_arg`: The length of `args` +/// - `args`: the arguments of this function call +unsafe extern "C" fn sqlite3_regexp_func( + ctx: *mut ffi::sqlite3_context, + n_arg: i32, + args: *mut *mut ffi::sqlite3_value, +) { + // check the arg size. sqlite3 should already ensure this is only 2 args but we want to double check + if n_arg != 2 { + eprintln!("n_arg expected to be 2, is {n_arg}"); + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + return; + } + + // arg0: Regex + let Some(regex) = get_regex_from_arg(ctx, *args, 0) else { + return; + }; + + // arg1: value + let Some(value) = get_text_from_arg(ctx, *args.add(1)) else { + return; + }; + + // if the regex matches the value, set the result int as 1, else as 0 + if regex.is_match(value) { + ffi::sqlite3_result_int(ctx, 1); + } else { + ffi::sqlite3_result_int(ctx, 0); + } +} + +/// Get the regex from the given `arg` at the given `index`. +/// +/// First this will check to see if the value exists in sqlite's `auxdata`. If it does, that regex will be returned. +/// sqlite is able to clean up this data at any point, but rust's [`Arc`] guarantees make sure things don't break. +/// +/// If this value does not exist in `auxdata`, [`try_load_value`] is called and a regex is created from this. If any of +/// those fail, a message is printed and `None` is returned. +/// +/// After this regex is created it is stored in `auxdata` and loaded again. If it fails to load, this means that +/// something inside of sqlite3 went wrong, and we return `None`. +/// +/// If this value is stored correctly, or if it already existed, the arc reference counter is increased and this value is returned. +unsafe fn get_regex_from_arg( + ctx: *mut ffi::sqlite3_context, + arg: *mut ffi::sqlite3_value, + index: i32, +) -> Option> { + // try to get the auxdata for this field + let ptr = ffi::sqlite3_get_auxdata(ctx, index); + if !ptr.is_null() { + // if we have it, turn it into an Arc. + // we need to make sure to call `increment_strong_count` because the returned `Arc` decrement this when it goes out of scope + let ptr = ptr as *const Regex; + Arc::increment_strong_count(ptr); + return Some(Arc::from_raw(ptr)); + } + // get the text for this field + let value = get_text_from_arg(ctx, arg)?; + // try to compile it into a regex + let regex = match Regex::new(value) { + Ok(regex) => Arc::new(regex), + Err(e) => { + error!("Invalid regex {value:?}: {e:?}"); + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + return None; + } + }; + // set the regex as auxdata for the next time around + ffi::sqlite3_set_auxdata( + ctx, + index, + // make sure to call `Arc::clone` here, setting the strong count to 2. + // this will be cleaned up at 2 points: + // - when the returned arc goes out of scope + // - when sqlite decides to clean it up an calls `cleanup_arc_regex_pointer` + Arc::into_raw(Arc::clone(®ex)) as *mut _, + Some(cleanup_arc_regex_pointer), + ); + Some(regex) +} + +/// Get a text reference of the value of `arg`. Returns `None` for NULL values. +/// +/// For non-NULL values, `sqlite3_value_text()` is called directly, which lets SQLite +/// coerce INTEGER, REAL, and BLOB values to their text representation. This matches +/// the coercion behavior documented at . +/// +/// The returned `&str` is valid for lifetime `'a` which can be determined by the caller. This lifetime should **not** +/// outlive `ctx`. +unsafe fn get_text_from_arg<'a>( + ctx: *mut ffi::sqlite3_context, + arg: *mut ffi::sqlite3_value, +) -> Option<&'a str> { + let ty = ffi::sqlite3_value_type(arg); + if ty == ffi::SQLITE_NULL { + return None; + } + let ptr = ffi::sqlite3_value_text(arg); + let len = ffi::sqlite3_value_bytes(arg); + let slice = std::slice::from_raw_parts(ptr.cast(), len as usize); + match std::str::from_utf8(slice) { + Ok(result) => Some(result), + Err(e) => { + log::error!("Incoming text is not valid UTF8: {e:?}"); + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + None + } + } +} + +/// Clean up the `Arc` that is stored in the given `ptr`. +unsafe extern "C" fn cleanup_arc_regex_pointer(ptr: *mut std::ffi::c_void) { + Arc::decrement_strong_count(ptr.cast::()); +} + +#[cfg(test)] +mod tests { + use sqlx::{ConnectOptions, Row}; + use std::str::FromStr; + + async fn test_db() -> crate::SqliteConnection { + let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:") + .unwrap() + .with_regexp() + .connect() + .await + .unwrap(); + sqlx::query("CREATE TABLE test (col TEXT NOT NULL)") + .execute(&mut conn) + .await + .unwrap(); + for i in 0..10 { + sqlx::query("INSERT INTO test VALUES (?)") + .bind(format!("value {i}")) + .execute(&mut conn) + .await + .unwrap(); + } + conn + } + + #[sqlx::test] + async fn test_regexp_does_not_fail() { + let mut conn = test_db().await; + let result = sqlx::query("SELECT col FROM test WHERE col REGEXP 'foo.*bar'") + .fetch_all(&mut conn) + .await + .expect("Could not execute query"); + assert!(result.is_empty()); + } + + #[sqlx::test] + async fn test_regexp_filters_correctly() { + let mut conn = test_db().await; + + let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '.*2'") + .fetch_all(&mut conn) + .await + .expect("Could not execute query"); + assert_eq!(result.len(), 1); + assert_eq!(result[0].get::(0), String::from("value 2")); + + let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '^3'") + .fetch_all(&mut conn) + .await + .expect("Could not execute query"); + assert!(result.is_empty()); + } + + #[sqlx::test] + async fn test_regexp_coerces_non_text_values() { + let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:") + .unwrap() + .with_regexp() + .connect() + .await + .unwrap(); + + // INTEGER coercion + let result: Option = sqlx::query_scalar("SELECT 123 REGEXP '23'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, Some(1)); + + // REAL coercion + let result: Option = sqlx::query_scalar("SELECT 12.5 REGEXP '12\\.5'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, Some(1)); + + // INTEGER column + sqlx::query("CREATE TABLE int_test (x INTEGER NOT NULL)") + .execute(&mut conn) + .await + .unwrap(); + sqlx::query("INSERT INTO int_test VALUES (123), (45)") + .execute(&mut conn) + .await + .unwrap(); + let rows: Vec = sqlx::query_scalar("SELECT x FROM int_test WHERE x REGEXP '23'") + .fetch_all(&mut conn) + .await + .unwrap(); + assert_eq!(rows, vec![123]); + + // NULL should return NULL, not match + let result: Option = sqlx::query_scalar("SELECT NULL REGEXP '.*'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, None); + } + + #[sqlx::test] + async fn test_invalid_regexp_should_fail() { + let mut conn = test_db().await; + let result = sqlx::query("SELECT col from test WHERE col REGEXP '(?:?)'") + .execute(&mut conn) + .await; + assert!(matches!(result, Err(sqlx::Error::Database(_)))); + } +} diff --git a/vendor/sqlx-sqlite/src/row.rs b/vendor/sqlx-sqlite/src/row.rs new file mode 100644 index 0000000000..ec038b6d40 --- /dev/null +++ b/vendor/sqlx-sqlite/src/row.rs @@ -0,0 +1,95 @@ +#![allow(clippy::rc_buffer)] + +use std::sync::Arc; + +use sqlx_core::column::ColumnIndex; +use sqlx_core::error::Error; +use sqlx_core::ext::ustr::UStr; +use sqlx_core::row::{debug_row, Row}; +use sqlx_core::HashMap; + +use crate::statement::StatementHandle; +use crate::{Sqlite, SqliteColumn, SqliteValue, SqliteValueRef}; + +/// Implementation of [`Row`] for SQLite. +pub struct SqliteRow { + pub(crate) values: Box<[SqliteValue]>, + pub(crate) columns: Arc>, + pub(crate) column_names: Arc>, +} + +// Accessing values from the statement object is +// safe across threads as long as we don't call [sqlite3_step] + +// we block ourselves from doing that by only exposing +// a set interface on [StatementHandle] + +unsafe impl Send for SqliteRow {} +unsafe impl Sync for SqliteRow {} + +impl SqliteRow { + pub(crate) fn current( + statement: &StatementHandle, + columns: &Arc>, + column_names: &Arc>, + ) -> Self { + let size = statement.column_count(); + let mut values = Vec::with_capacity(size); + + for i in 0..size { + values.push(unsafe { + let raw = statement.column_value(i); + + SqliteValue::dup(raw, Some(columns[i].type_info.clone())) + }); + } + + Self { + values: values.into_boxed_slice(), + columns: Arc::clone(columns), + column_names: Arc::clone(column_names), + } + } +} + +impl Row for SqliteRow { + type Database = Sqlite; + + fn columns(&self) -> &[SqliteColumn] { + &self.columns + } + + fn try_get_raw(&self, index: I) -> Result, Error> + where + I: ColumnIndex, + { + let index = index.index(self)?; + Ok(SqliteValueRef::value(&self.values[index])) + } +} + +impl ColumnIndex for &'_ str { + fn index(&self, row: &SqliteRow) -> Result { + row.column_names + .get(*self) + .ok_or_else(|| Error::ColumnNotFound((*self).into())) + .copied() + } +} + +impl std::fmt::Debug for SqliteRow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + debug_row(self, f) + } +} + +// #[cfg(feature = "any")] +// impl From for crate::any::AnyRow { +// #[inline] +// fn from(row: SqliteRow) -> Self { +// crate::any::AnyRow { +// columns: row.columns.iter().map(|col| col.clone().into()).collect(), +// kind: crate::any::row::AnyRowKind::Sqlite(row), +// } +// } +// } diff --git a/vendor/sqlx-sqlite/src/statement/handle.rs b/vendor/sqlx-sqlite/src/statement/handle.rs new file mode 100644 index 0000000000..c78ce98414 --- /dev/null +++ b/vendor/sqlx-sqlite/src/statement/handle.rs @@ -0,0 +1,466 @@ +use std::ffi::c_void; +use std::ffi::CStr; + +use crate::error::{BoxDynError, Error}; +use crate::type_info::DataType; +use crate::{SqliteError, SqliteTypeInfo}; +use libsqlite3_sys::{ + sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64, + sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name, + sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob, + sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name, + sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64, + sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name, + sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset, + sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata, + sqlite3_value, SQLITE_DONE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT, + SQLITE_UTF8, +}; +use sqlx_core::column::{ColumnOrigin, TableColumn}; +use std::os::raw::{c_char, c_int}; +use std::ptr; +use std::ptr::NonNull; +use std::slice::from_raw_parts; +use std::str::from_utf8; +use std::sync::Arc; + +#[derive(Debug)] +pub(crate) struct StatementHandle(NonNull); + +// access to SQLite3 statement handles are safe to send and share between threads +// as long as the `sqlite3_step` call is serialized. + +unsafe impl Send for StatementHandle {} + +// Most of the getters below allocate internally, and unsynchronized access is undefined. +// unsafe impl !Sync for StatementHandle {} + +macro_rules! expect_ret_valid { + ($fn_name:ident($($args:tt)*)) => {{ + let val = $fn_name($($args)*); + + TryFrom::try_from(val) + // This likely means UB in SQLite itself or our usage of it; + // signed integer overflow is UB in the C standard. + .unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name))) + }} +} + +macro_rules! check_col_idx { + ($idx:ident) => { + c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx)) + }; +} + +// might use some of this later +#[allow(dead_code)] +impl StatementHandle { + pub(super) fn new(ptr: NonNull) -> Self { + Self(ptr) + } + + #[inline] + pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 { + // O(c) access to the connection handle for this statement handle + // https://sqlite.org/c3ref/db_handle.html + sqlite3_db_handle(self.0.as_ptr()) + } + + pub(crate) fn read_only(&self) -> bool { + // https://sqlite.org/c3ref/stmt_readonly.html + unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 } + } + + pub(crate) fn sql(&self) -> &str { + // https://sqlite.org/c3ref/expanded_sql.html + unsafe { + let raw = sqlite3_sql(self.0.as_ptr()); + debug_assert!(!raw.is_null()); + + from_utf8(CStr::from_ptr(raw).to_bytes()) + .expect("sqlite3_sql() returned non-UTF-8 string") + } + } + + #[inline] + pub(crate) fn last_error(&mut self) -> SqliteError { + unsafe { SqliteError::new(self.db_handle()) } + } + + #[inline] + pub(crate) fn column_count(&self) -> usize { + // https://sqlite.org/c3ref/column_count.html + unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) } + } + + #[inline] + pub(crate) fn changes(&self) -> u64 { + // returns the number of changes of the *last* statement; not + // necessarily this statement. + // https://sqlite.org/c3ref/changes.html + unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) } + } + + #[inline] + pub(crate) fn column_name(&self, index: usize) -> &str { + // https://sqlite.org/c3ref/column_name.html + unsafe { + let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index)); + debug_assert!(!name.is_null()); + + from_utf8(CStr::from_ptr(name).to_bytes()) + .expect("sqlite3_column_name() returned non-UTF-8 column name") + } + } + + pub(crate) fn column_origin(&self, index: usize) -> ColumnOrigin { + if let Some((table, name)) = self + .column_table_name(index) + .zip(self.column_origin_name(index)) + { + let table: Arc = self + .column_db_name(index) + .filter(|&db| db != "main") + .map_or_else( + || table.into(), + // TODO: check that SQLite returns the names properly quoted if necessary + |db| format!("{db}.{table}").into(), + ); + + ColumnOrigin::Table(TableColumn { + table, + name: name.into(), + }) + } else { + ColumnOrigin::Expression + } + } + + fn column_db_name(&self, index: usize) -> Option<&str> { + unsafe { + let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index)); + + if !db_name.is_null() { + Some( + from_utf8(CStr::from_ptr(db_name).to_bytes()) + .expect("sqlite3_column_database_name() returned non-UTF-8 string"), + ) + } else { + None + } + } + } + + fn column_table_name(&self, index: usize) -> Option<&str> { + unsafe { + let table_name = sqlite3_column_table_name(self.0.as_ptr(), check_col_idx!(index)); + + if !table_name.is_null() { + Some( + from_utf8(CStr::from_ptr(table_name).to_bytes()) + .expect("sqlite3_column_table_name() returned non-UTF-8 string"), + ) + } else { + None + } + } + } + + fn column_origin_name(&self, index: usize) -> Option<&str> { + unsafe { + let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), check_col_idx!(index)); + + if !origin_name.is_null() { + Some( + from_utf8(CStr::from_ptr(origin_name).to_bytes()) + .expect("sqlite3_column_origin_name() returned non-UTF-8 string"), + ) + } else { + None + } + } + } + + pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo { + SqliteTypeInfo(DataType::from_code(self.column_type(index))) + } + + pub(crate) fn column_type_info_opt(&self, index: usize) -> Option { + match DataType::from_code(self.column_type(index)) { + DataType::Null => None, + dt => Some(SqliteTypeInfo(dt)), + } + } + + #[inline] + pub(crate) fn column_decltype(&self, index: usize) -> Option { + unsafe { + let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index)); + if decl.is_null() { + // If the Nth column of the result set is an expression or subquery, + // then a NULL pointer is returned. + return None; + } + + let decl = from_utf8(CStr::from_ptr(decl).to_bytes()) + .expect("sqlite3_column_decltype() returned non-UTF-8 string"); + let ty: DataType = decl.parse().ok()?; + + Some(SqliteTypeInfo(ty)) + } + } + + /// Use sqlite3_column_metadata to determine if a specific column is nullable. + /// + /// Returns None in the case of INTEGER PRIMARY KEYs + /// This is because this column is an alias to rowid if the table does not use a compound + /// primary key. In this case the row is not nullable, and the output of + /// sqlite3_column_metadata may be incorrect. + pub(crate) fn column_nullable(&self, index: usize) -> Result, Error> { + unsafe { + let index = check_col_idx!(index); + // https://sqlite.org/c3ref/column_database_name.html + // + // ### Note + // The returned string is valid until the prepared statement is destroyed using + // sqlite3_finalize() or until the statement is automatically reprepared by the + // first call to sqlite3_step() for a particular run or until the same information + // is requested again in a different encoding. + let db_name = sqlite3_column_database_name(self.0.as_ptr(), index); + let table_name = sqlite3_column_table_name(self.0.as_ptr(), index); + let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index); + if db_name.is_null() || table_name.is_null() || origin_name.is_null() { + return Ok(None); + } + + let mut not_null: c_int = 0; + let mut datatype: *const c_char = ptr::null(); + let mut primary_key: c_int = 0; + + // https://sqlite.org/c3ref/table_column_metadata.html + let status = sqlite3_table_column_metadata( + self.db_handle(), + db_name, + table_name, + origin_name, + &mut datatype, + // function docs state to provide NULL for return values you don't care about + ptr::null_mut(), + &mut not_null, + &mut primary_key, + ptr::null_mut(), + ); + + if status != SQLITE_OK { + // implementation note: the docs for sqlite3_table_column_metadata() specify + // that an error can be returned if the column came from a view; however, + // experimentally we found that the above functions give us the true origin + // for columns in views that came from real tables and so we should never hit this + // error; for view columns that are expressions we are given NULL for their origins + // so we don't need special handling for that case either. + // + // this is confirmed in the `tests/sqlite-macros.rs` integration test + return Err(SqliteError::new(self.db_handle()).into()); + } + + let datatype = CStr::from_ptr(datatype); + + Ok( + if primary_key != 0 + && datatype + .to_bytes() + .eq_ignore_ascii_case("integer".as_bytes()) + { + None + } else { + Some(not_null == 0) + }, + ) + } + } + + // Number Of SQL Parameters + #[inline] + pub(crate) fn bind_parameter_count(&self) -> usize { + // https://www.sqlite.org/c3ref/bind_parameter_count.html + unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) } + } + + // Name Of A Host Parameter + // NOTE: The first host parameter has an index of 1, not 0. + #[inline] + pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> { + unsafe { + // https://www.sqlite.org/c3ref/bind_parameter_name.html + let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index)); + if name.is_null() { + return None; + } + + Some( + from_utf8(CStr::from_ptr(name).to_bytes()) + .expect("sqlite3_bind_parameter_name() returned non-UTF-8 string"), + ) + } + } + + // Binding Values To Prepared Statements + // https://www.sqlite.org/c3ref/bind_blob.html + + #[inline] + pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int { + unsafe { + sqlite3_bind_blob64( + self.0.as_ptr(), + check_col_idx!(index), + v.as_ptr() as *const c_void, + v.len() as u64, + SQLITE_TRANSIENT(), + ) + } + } + + #[inline] + pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let encoding = SQLITE_UTF8 as u8; + + unsafe { + sqlite3_bind_text64( + self.0.as_ptr(), + check_col_idx!(index), + v.as_ptr() as *const c_char, + v.len() as u64, + SQLITE_TRANSIENT(), + encoding, + ) + } + } + + #[inline] + pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int { + unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) } + } + + #[inline] + pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int { + unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) } + } + + #[inline] + pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int { + unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) } + } + + #[inline] + pub(crate) fn bind_null(&self, index: usize) -> c_int { + unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) } + } + + // result values from the query + // https://www.sqlite.org/c3ref/column_blob.html + + #[inline] + pub(crate) fn column_type(&self, index: usize) -> c_int { + unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) } + } + + #[inline] + pub(crate) fn column_int(&self, index: usize) -> i32 { + unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 } + } + + #[inline] + pub(crate) fn column_int64(&self, index: usize) -> i64 { + unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 } + } + + #[inline] + pub(crate) fn column_double(&self, index: usize) -> f64 { + unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) } + } + + #[inline] + pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value { + unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) } + } + + pub(crate) fn column_blob(&self, index: usize) -> &[u8] { + let len = unsafe { + expect_ret_valid!(sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index))) + }; + + if len == 0 { + // empty blobs are NULL so just return an empty slice + return &[]; + } + + let ptr = + unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8; + debug_assert!(!ptr.is_null()); + + unsafe { from_raw_parts(ptr, len) } + } + + pub(crate) fn column_text(&self, index: usize) -> Result<&str, BoxDynError> { + Ok(from_utf8(self.column_blob(index))?) + } + + pub(crate) fn clear_bindings(&self) { + unsafe { sqlite3_clear_bindings(self.0.as_ptr()) }; + } + + pub(crate) fn reset(&mut self) -> Result<(), SqliteError> { + // SAFETY: we have exclusive access to the handle + unsafe { + if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK { + return Err(SqliteError::new(self.db_handle())); + } + } + + Ok(()) + } + + pub(crate) fn step(&mut self) -> Result { + // SAFETY: we have exclusive access to the handle + unsafe { + #[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))] + loop { + match sqlite3_step(self.0.as_ptr()) { + SQLITE_ROW => return Ok(true), + SQLITE_DONE => return Ok(false), + SQLITE_MISUSE => panic!("misuse!"), + #[cfg(feature = "unlock-notify")] + libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => { + // The shared cache is locked by another connection. Wait for unlock + // notification and try again. + super::unlock_notify::wait(self.db_handle())?; + // Need to reset the handle after the unlock + // (https://www.sqlite.org/unlock_notify.html) + sqlite3_reset(self.0.as_ptr()); + } + _ => return Err(SqliteError::new(self.db_handle())), + } + } + } + } +} + +impl Drop for StatementHandle { + fn drop(&mut self) { + // SAFETY: we have exclusive access to the `StatementHandle` here + unsafe { + // https://sqlite.org/c3ref/finalize.html + let status = sqlite3_finalize(self.0.as_ptr()); + if status == SQLITE_MISUSE { + // Panic in case of detected misuse of SQLite API. + // + // sqlite3_finalize returns it at least in the + // case of detected double free, i.e. calling + // sqlite3_finalize on already finalized + // statement. + panic!("Detected sqlite3_finalize misuse."); + } + } + } +} diff --git a/vendor/sqlx-sqlite/src/statement/mod.rs b/vendor/sqlx-sqlite/src/statement/mod.rs new file mode 100644 index 0000000000..93485397d3 --- /dev/null +++ b/vendor/sqlx-sqlite/src/statement/mod.rs @@ -0,0 +1,59 @@ +use crate::column::ColumnIndex; +use crate::error::Error; +use crate::ext::ustr::UStr; +use crate::{Sqlite, SqliteArguments, SqliteColumn, SqliteTypeInfo}; +use sqlx_core::sql_str::SqlStr; +use sqlx_core::{Either, HashMap}; +use std::sync::Arc; + +pub(crate) use sqlx_core::statement::*; + +mod handle; + +#[cfg(feature = "unlock-notify")] +pub(super) mod unlock_notify; +mod r#virtual; + +pub(crate) use handle::StatementHandle; +pub(crate) use r#virtual::VirtualStatement; + +#[derive(Debug, Clone)] +#[allow(clippy::rc_buffer)] +pub struct SqliteStatement { + pub(crate) sql: SqlStr, + pub(crate) parameters: usize, + pub(crate) columns: Arc>, + pub(crate) column_names: Arc>, +} + +impl Statement for SqliteStatement { + type Database = Sqlite; + + fn into_sql(self) -> SqlStr { + self.sql + } + + fn sql(&self) -> &SqlStr { + &self.sql + } + + fn parameters(&self) -> Option> { + Some(Either::Right(self.parameters)) + } + + fn columns(&self) -> &[SqliteColumn] { + &self.columns + } + + impl_statement_query!(SqliteArguments); +} + +impl ColumnIndex for &'_ str { + fn index(&self, statement: &SqliteStatement) -> Result { + statement + .column_names + .get(*self) + .ok_or_else(|| Error::ColumnNotFound((*self).into())) + .copied() + } +} diff --git a/vendor/sqlx-sqlite/src/statement/unlock_notify.rs b/vendor/sqlx-sqlite/src/statement/unlock_notify.rs new file mode 100644 index 0000000000..5821c23ae3 --- /dev/null +++ b/vendor/sqlx-sqlite/src/statement/unlock_notify.rs @@ -0,0 +1,65 @@ +use std::ffi::c_void; +use std::os::raw::c_int; +use std::slice; +use std::sync::{Condvar, Mutex}; + +use libsqlite3_sys::{sqlite3, sqlite3_unlock_notify, SQLITE_OK}; + +use crate::SqliteError; + +// Wait for unlock notification (https://www.sqlite.org/unlock_notify.html) +pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> { + let notify = Notify::new(); + + if sqlite3_unlock_notify( + conn, + Some(unlock_notify_cb), + ¬ify as *const Notify as *mut Notify as *mut _, + ) != SQLITE_OK + { + return Err(SqliteError::new(conn)); + } + + notify.wait(); + + Ok(()) +} + +unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) { + let ptr = ptr as *mut &Notify; + // We don't have a choice; we can't panic and unwind into FFI here. + let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0)); + + for notify in slice { + notify.fire(); + } +} + +struct Notify { + mutex: Mutex, + condvar: Condvar, +} + +impl Notify { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + // We only want to wait until the lock is available again. + #[allow(let_underscore_lock)] + let _ = self + .condvar + .wait_while(self.mutex.lock().unwrap(), |fired| !*fired) + .unwrap(); + } + + fn fire(&self) { + let mut lock = self.mutex.lock().unwrap(); + *lock = true; + self.condvar.notify_one(); + } +} diff --git a/vendor/sqlx-sqlite/src/statement/virtual.rs b/vendor/sqlx-sqlite/src/statement/virtual.rs new file mode 100644 index 0000000000..b25aa69e47 --- /dev/null +++ b/vendor/sqlx-sqlite/src/statement/virtual.rs @@ -0,0 +1,204 @@ +#![allow(clippy::rc_buffer)] + +use std::cmp; +use std::os::raw::c_char; +use std::ptr::{null, null_mut, NonNull}; +use std::sync::Arc; + +use libsqlite3_sys::{ + sqlite3, sqlite3_prepare_v3, sqlite3_stmt, SQLITE_OK, SQLITE_PREPARE_PERSISTENT, +}; + +use sqlx_core::bytes::{Buf, Bytes}; +use sqlx_core::error::Error; +use sqlx_core::ext::ustr::UStr; +use sqlx_core::{HashMap, SmallVec}; + +use crate::connection::ConnectionHandle; +use crate::statement::StatementHandle; +use crate::{SqliteColumn, SqliteError}; + +// A virtual statement consists of *zero* or more raw SQLite3 statements. We chop up a SQL statement +// on `;` to support multiple statements in one query. + +#[derive(Debug)] +pub struct VirtualStatement { + persistent: bool, + + /// the current index of the actual statement that is executing + /// if `None`, no statement is executing and `prepare()` must be called; + /// if `Some(self.handles.len())` and `self.tail.is_empty()`, + /// there are no more statements to execute and `reset()` must be called + index: Option, + + /// tail of the most recently prepared SQL statement within this container + tail: Bytes, + + /// underlying sqlite handles for each inner statement + /// a SQL query string in SQLite is broken up into N statements + /// we use a [`SmallVec`] to optimize for the most likely case of a single statement + pub(crate) handles: SmallVec<[StatementHandle; 1]>, + + // each set of columns + pub(crate) columns: SmallVec<[Arc>; 1]>, + + // each set of column names + pub(crate) column_names: SmallVec<[Arc>; 1]>, +} + +pub struct PreparedStatement<'a> { + pub(crate) handle: &'a mut StatementHandle, + pub(crate) columns: &'a Arc>, + pub(crate) column_names: &'a Arc>, +} + +impl VirtualStatement { + pub(crate) fn new(mut query: &str, persistent: bool) -> Result { + query = query.trim(); + + if query.len() > i32::MAX as usize { + return Err(err_protocol!( + "query string must be smaller than {} bytes", + i32::MAX + )); + } + + Ok(Self { + persistent, + tail: Bytes::from(String::from(query)), + handles: SmallVec::with_capacity(1), + index: None, + columns: SmallVec::with_capacity(1), + column_names: SmallVec::with_capacity(1), + }) + } + + pub(crate) fn prepare_next( + &mut self, + conn: &mut ConnectionHandle, + ) -> Result>, Error> { + // increment `self.index` up to `self.handles.len()` + self.index = self + .index + .map(|idx| cmp::min(idx + 1, self.handles.len())) + .or(Some(0)); + + while self.handles.len() <= self.index.unwrap_or(0) { + if self.tail.is_empty() { + return Ok(None); + } + + if let Some(statement) = prepare(conn.as_ptr(), &mut self.tail, self.persistent)? { + let num = statement.column_count(); + + let mut columns = Vec::with_capacity(num); + let mut column_names = HashMap::with_capacity(num); + + for i in 0..num { + let name: UStr = statement.column_name(i).to_owned().into(); + let type_info = statement + .column_decltype(i) + .unwrap_or_else(|| statement.column_type_info(i)); + + columns.push(SqliteColumn { + ordinal: i, + name: name.clone(), + type_info, + origin: statement.column_origin(i), + }); + + column_names.insert(name, i); + } + + self.handles.push(statement); + self.columns.push(Arc::new(columns)); + self.column_names.push(Arc::new(column_names)); + } + } + + Ok(self.current()) + } + + pub fn current(&mut self) -> Option> { + self.index + .filter(|&idx| idx < self.handles.len()) + .map(move |idx| PreparedStatement { + handle: &mut self.handles[idx], + columns: &self.columns[idx], + column_names: &self.column_names[idx], + }) + } + + pub fn reset(&mut self) -> Result<(), Error> { + self.index = None; + + for handle in self.handles.iter_mut() { + handle.reset()?; + handle.clear_bindings(); + } + + Ok(()) + } +} + +fn prepare( + conn: *mut sqlite3, + query: &mut Bytes, + persistent: bool, +) -> Result, Error> { + let mut flags = 0; + + // For some reason, when building with the `sqlcipher` feature enabled + // `SQLITE_PREPARE_PERSISTENT` ends up being `i32` instead of `u32`. Crazy, right? + #[allow(trivial_casts, clippy::unnecessary_cast)] + if persistent { + // SQLITE_PREPARE_PERSISTENT + // The SQLITE_PREPARE_PERSISTENT flag is a hint to the query + // planner that the prepared statement will be retained for a long time + // and probably reused many times. + flags |= SQLITE_PREPARE_PERSISTENT as u32; + } + + while !query.is_empty() { + let mut statement_handle: *mut sqlite3_stmt = null_mut(); + let mut tail: *const c_char = null(); + + let query_ptr = query.as_ptr() as *const c_char; + let query_len = i32::try_from(query.len()).map_err(|_| { + err_protocol!( + "query string too large for SQLite3 API ({} bytes); \ + try breaking it into smaller chunks (< 2 GiB), executed separately", + query.len() + ) + })?; + + // + let status = unsafe { + sqlite3_prepare_v3( + conn, + query_ptr, + query_len, + flags, + &mut statement_handle, + &mut tail, + ) + }; + + if status != SQLITE_OK { + return Err(unsafe { SqliteError::new(conn).into() }); + } + + // tail should point to the first byte past the end of the first SQL + // statement in zSql. these routines only compile the first statement, + // so tail is left pointing to what remains un-compiled. + + let n = (tail as usize) - (query_ptr as usize); + query.advance(n); + + if let Some(handle) = NonNull::new(statement_handle) { + return Ok(Some(StatementHandle::new(handle))); + } + } + + Ok(None) +} diff --git a/vendor/sqlx-sqlite/src/testing/mod.rs b/vendor/sqlx-sqlite/src/testing/mod.rs new file mode 100644 index 0000000000..058a24c52b --- /dev/null +++ b/vendor/sqlx-sqlite/src/testing/mod.rs @@ -0,0 +1,83 @@ +use crate::error::Error; +use crate::pool::PoolOptions; +use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport}; +use crate::{Sqlite, SqliteConnectOptions}; +use std::future::Future; +use std::path::{Path, PathBuf}; + +pub(crate) use sqlx_core::testing::*; + +const BASE_PATH: &str = "target/sqlx/test-dbs"; + +impl TestSupport for Sqlite { + fn test_context( + args: &TestArgs, + ) -> impl Future, Error>> + Send + '_ { + test_context(args) + } + + async fn cleanup_test(db_name: &str) -> Result<(), Error> { + crate::fs::remove_file(db_name).await?; + Ok(()) + } + + async fn cleanup_test_dbs() -> Result, Error> { + crate::fs::remove_dir_all(BASE_PATH).await?; + Ok(None) + } + + async fn snapshot(_conn: &mut Self::Connection) -> Result, Error> { + todo!() + } + + fn db_name(args: &TestArgs) -> String { + convert_path(args.test_path) + } +} + +async fn test_context(args: &TestArgs) -> Result, Error> { + let db_path = convert_path(args.test_path); + + if let Some(parent_path) = Path::parent(db_path.as_ref()) { + crate::fs::create_dir_all(parent_path) + .await + .expect("failed to create folders"); + } + + if Path::exists(db_path.as_ref()) { + crate::fs::remove_file(&db_path) + .await + .expect("failed to remove database from previous test run"); + } + + Ok(TestContext { + connect_opts: SqliteConnectOptions::new() + .filename(&db_path) + .create_if_missing(true), + // This doesn't really matter for SQLite as the databases are independent of each other. + // The main limitation is going to be the number of concurrent running tests. + pool_opts: PoolOptions::new().max_connections(1000), + db_name: db_path, + }) +} + +fn convert_path(test_path: &str) -> String { + let mut path = PathBuf::from(BASE_PATH); + + for segment in test_path.split("::") { + path.push(segment); + } + + path.set_extension("sqlite"); + + path.into_os_string() + .into_string() + .expect("path should be UTF-8") +} + +#[test] +fn test_convert_path() { + let path = convert_path("foo::bar::baz::quux"); + + assert_eq!(path, "target/sqlx/test-dbs/foo/bar/baz/quux.sqlite"); +} diff --git a/vendor/sqlx-sqlite/src/transaction.rs b/vendor/sqlx-sqlite/src/transaction.rs new file mode 100644 index 0000000000..1c57d0163f --- /dev/null +++ b/vendor/sqlx-sqlite/src/transaction.rs @@ -0,0 +1,35 @@ +use std::future::Future; + +use sqlx_core::transaction::TransactionManager; +use sqlx_core::{error::Error, sql_str::SqlStr}; + +use crate::{Sqlite, SqliteConnection}; + +/// Implementation of [`TransactionManager`] for SQLite. +pub struct SqliteTransactionManager; + +impl TransactionManager for SqliteTransactionManager { + type Database = Sqlite; + + async fn begin(conn: &mut SqliteConnection, statement: Option) -> Result<(), Error> { + conn.worker.begin(statement).await + } + + fn commit(conn: &mut SqliteConnection) -> impl Future> + Send + '_ { + conn.worker.commit() + } + + fn rollback( + conn: &mut SqliteConnection, + ) -> impl Future> + Send + '_ { + conn.worker.rollback() + } + + fn start_rollback(conn: &mut SqliteConnection) { + conn.worker.start_rollback().ok(); + } + + fn get_transaction_depth(conn: &SqliteConnection) -> usize { + conn.worker.shared.get_transaction_depth() + } +} diff --git a/vendor/sqlx-sqlite/src/type_checking.rs b/vendor/sqlx-sqlite/src/type_checking.rs new file mode 100644 index 0000000000..97af601c86 --- /dev/null +++ b/vendor/sqlx-sqlite/src/type_checking.rs @@ -0,0 +1,56 @@ +use crate::Sqlite; +#[allow(unused_imports)] +use sqlx_core as sqlx; + +// f32 is not included below as REAL represents a floating point value +// stored as an 8-byte IEEE floating point number (i.e. an f64) +// For more info see: https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes +impl_type_checking!( + Sqlite { + // Note that since the macro checks `column_type_info == ::type_info()` first, + // we can list `bool` without it being automatically picked for all integer types + // due to its `TypeInfo::compatible()` impl. + bool, + // Since it returns `DataType::Int4` for `type_info()`, + // `i32` should only be chosen IFF the column decltype is `INT4` + i32, + i64, + f64, + String, + Vec, + + #[cfg(feature = "uuid")] + sqlx::types::Uuid, + }, + ParamChecking::Weak, + // While there are type integrations that must be enabled via Cargo feature, + // SQLite's type system doesn't actually have any type that we cannot decode by default. + // + // The type integrations simply allow the user to skip some intermediate representation, + // which is usually TEXT. + feature-types: _info => None, + + // The expansion of the macro automatically applies the correct feature name + // and checks `[macros.preferred-crates]` + datetime-types: { + chrono: { + sqlx::types::chrono::NaiveDate, + + sqlx::types::chrono::NaiveDateTime, + + sqlx::types::chrono::DateTime + | sqlx::types::chrono::DateTime<_>, + }, + time: { + sqlx::types::time::OffsetDateTime, + + sqlx::types::time::PrimitiveDateTime, + + sqlx::types::time::Date, + }, + }, + numeric-types: { + bigdecimal: { }, + rust_decimal: { }, + }, +); diff --git a/vendor/sqlx-sqlite/src/type_info.rs b/vendor/sqlx-sqlite/src/type_info.rs new file mode 100644 index 0000000000..38a0fc4169 --- /dev/null +++ b/vendor/sqlx-sqlite/src/type_info.rs @@ -0,0 +1,161 @@ +use std::fmt::{self, Display, Formatter}; +use std::os::raw::c_int; +use std::str::FromStr; + +use libsqlite3_sys::{SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_TEXT}; + +use crate::error::BoxDynError; + +pub(crate) use sqlx_core::type_info::*; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] +pub(crate) enum DataType { + // These variants should correspond to `SQLITE_*` type constants. + Null, + /// Note: SQLite's type system has no notion of integer widths. + /// The `INTEGER` type affinity can store up to 8 byte integers, + /// making `i64` the only safe choice when mapping integer types to Rust. + Integer, + Float, + Text, + Blob, + + // Explicitly not supported: see documentation in `types/mod.rs` + #[allow(dead_code)] + Numeric, + + // non-standard extensions (chosen based on the column's declared type) + /// Chosen if the column's declared type is `BOOLEAN`. + Bool, + /// Chosen if the column's declared type is `INT4`; + /// instructs the macros to use `i32` instead of `i64`. + /// Legacy feature; no idea if this is actually used anywhere. + Int4, + Date, + Time, + Datetime, +} + +/// Type information for a SQLite type. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] +pub struct SqliteTypeInfo(pub(crate) DataType); + +impl Display for SqliteTypeInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad(self.name()) + } +} + +impl TypeInfo for SqliteTypeInfo { + fn is_null(&self) -> bool { + matches!(self.0, DataType::Null) + } + + fn name(&self) -> &str { + match self.0 { + DataType::Null => "NULL", + DataType::Text => "TEXT", + DataType::Float => "REAL", + DataType::Blob => "BLOB", + DataType::Int4 | DataType::Integer => "INTEGER", + DataType::Numeric => "NUMERIC", + + // non-standard extensions + DataType::Bool => "BOOLEAN", + DataType::Date => "DATE", + DataType::Time => "TIME", + DataType::Datetime => "DATETIME", + } + } +} + +impl DataType { + pub(crate) fn from_code(code: c_int) -> Self { + match code { + SQLITE_INTEGER => DataType::Integer, + SQLITE_FLOAT => DataType::Float, + SQLITE_BLOB => DataType::Blob, + SQLITE_NULL => DataType::Null, + SQLITE_TEXT => DataType::Text, + + // https://sqlite.org/c3ref/c_blob.html + _ => panic!("unknown data type code {code}"), + } + } +} + +// note: this implementation is particularly important as this is how the macros determine +// what Rust type maps to what *declared* SQL type +// +impl FromStr for DataType { + type Err = BoxDynError; + + fn from_str(s: &str) -> Result { + let s = s.to_ascii_lowercase(); + Ok(match &*s { + "int4" => DataType::Int4, + "int8" => DataType::Integer, + "boolean" | "bool" => DataType::Bool, + + "date" => DataType::Date, + "time" => DataType::Time, + "datetime" | "timestamp" => DataType::Datetime, + + _ if s.contains("int") => DataType::Integer, + + _ if s.contains("char") || s.contains("clob") || s.contains("text") => DataType::Text, + + _ if s.contains("blob") => DataType::Blob, + + _ if s.contains("real") || s.contains("floa") || s.contains("doub") => DataType::Float, + + _ => { + return Err(format!("unknown type: `{s}`").into()); + } + }) + } +} + +// #[cfg(feature = "any")] +// impl From for crate::any::AnyTypeInfo { +// #[inline] +// fn from(ty: SqliteTypeInfo) -> Self { +// crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Sqlite(ty)) +// } +// } + +#[test] +fn test_data_type_from_str() -> Result<(), BoxDynError> { + assert_eq!(DataType::Int4, "INT4".parse()?); + + assert_eq!(DataType::Integer, "INT".parse()?); + assert_eq!(DataType::Integer, "INTEGER".parse()?); + assert_eq!(DataType::Integer, "INTBIG".parse()?); + assert_eq!(DataType::Integer, "MEDIUMINT".parse()?); + + assert_eq!(DataType::Integer, "BIGINT".parse()?); + assert_eq!(DataType::Integer, "UNSIGNED BIG INT".parse()?); + assert_eq!(DataType::Integer, "INT8".parse()?); + + assert_eq!(DataType::Text, "CHARACTER(20)".parse()?); + assert_eq!(DataType::Text, "NCHAR(55)".parse()?); + assert_eq!(DataType::Text, "TEXT".parse()?); + assert_eq!(DataType::Text, "CLOB".parse()?); + + assert_eq!(DataType::Blob, "BLOB".parse()?); + + assert_eq!(DataType::Float, "REAL".parse()?); + assert_eq!(DataType::Float, "FLOAT".parse()?); + assert_eq!(DataType::Float, "DOUBLE PRECISION".parse()?); + + assert_eq!(DataType::Bool, "BOOLEAN".parse()?); + assert_eq!(DataType::Bool, "BOOL".parse()?); + + assert_eq!(DataType::Datetime, "DATETIME".parse()?); + assert_eq!(DataType::Time, "TIME".parse()?); + assert_eq!(DataType::Date, "DATE".parse()?); + + Ok(()) +} diff --git a/vendor/sqlx-sqlite/src/types/bool.rs b/vendor/sqlx-sqlite/src/types/bool.rs new file mode 100644 index 0000000000..9228eef236 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/bool.rs @@ -0,0 +1,31 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; + +impl Type for bool { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Bool) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Bool | DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for bool { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int((*self).into())); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for bool { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()? != 0) + } +} diff --git a/vendor/sqlx-sqlite/src/types/bytes.rs b/vendor/sqlx-sqlite/src/types/bytes.rs new file mode 100644 index 0000000000..6453007bc9 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/bytes.rs @@ -0,0 +1,105 @@ +use std::borrow::Cow; +use std::rc::Rc; +use std::sync::Arc; + +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; + +impl Type for [u8] { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Blob) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Blob | DataType::Text) + } +} + +impl Encode<'_, Sqlite> for &'_ [u8] { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec()))); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for &'r [u8] { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.blob_borrowed()) + } +} + +impl Encode<'_, Sqlite> for Box<[u8]> { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.into_vec()))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.clone().into_vec()))); + + Ok(IsNull::No) + } +} + +impl Type for Vec { + fn type_info() -> SqliteTypeInfo { + <&[u8] as Type>::type_info() + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + <&[u8] as Type>::compatible(ty) + } +} + +impl Encode<'_, Sqlite> for Vec { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.clone()))); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for Vec { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.blob_owned()) + } +} + +impl Encode<'_, Sqlite> for Cow<'_, [u8]> { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.into()))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec()))); + + Ok(IsNull::No) + } +} + +impl Encode<'_, Sqlite> for Arc<[u8]> { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + as Encode<'_, Sqlite>>::encode(self.to_vec(), args) + } +} + +impl Encode<'_, Sqlite> for Rc<[u8]> { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + as Encode<'_, Sqlite>>::encode(self.to_vec(), args) + } +} diff --git a/vendor/sqlx-sqlite/src/types/chrono.rs b/vendor/sqlx-sqlite/src/types/chrono.rs new file mode 100644 index 0000000000..8d987538d0 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/chrono.rs @@ -0,0 +1,220 @@ +use std::fmt::Display; + +use crate::value::ValueRef; +use crate::{ + decode::Decode, + encode::{Encode, IsNull}, + error::BoxDynError, + type_info::DataType, + types::Type, + Sqlite, SqliteArgumentsBuffer, SqliteTypeInfo, SqliteValueRef, +}; +use chrono::FixedOffset; +use chrono::{ + DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, Utc, +}; + +impl Type for DateTime { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Datetime) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + >::compatible(ty) + } +} + +impl Type for NaiveDateTime { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Datetime) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!( + ty.0, + DataType::Datetime + | DataType::Text + | DataType::Integer + | DataType::Int4 + | DataType::Float + ) + } +} + +impl Type for NaiveDate { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Date) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Date | DataType::Text) + } +} + +impl Type for NaiveTime { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Time) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Time | DataType::Text) + } +} + +impl Encode<'_, Sqlite> for DateTime +where + Tz::Offset: Display, +{ + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf) + } +} + +impl Encode<'_, Sqlite> for NaiveDateTime { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.format("%F %T%.f").to_string(), buf) + } +} + +impl Encode<'_, Sqlite> for NaiveDate { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.format("%F").to_string(), buf) + } +} + +impl Encode<'_, Sqlite> for NaiveTime { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.format("%T%.f").to_string(), buf) + } +} + +impl<'r> Decode<'r, Sqlite> for DateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(Utc.from_utc_datetime(&decode_datetime(value)?.naive_utc())) + } +} + +impl<'r> Decode<'r, Sqlite> for DateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(Local.from_utc_datetime(&decode_datetime(value)?.naive_utc())) + } +} + +impl<'r> Decode<'r, Sqlite> for DateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + decode_datetime(value) + } +} + +fn decode_datetime(value: SqliteValueRef<'_>) -> Result, BoxDynError> { + let dt = match value.type_info().0 { + DataType::Text => decode_datetime_from_text(value.text_borrowed()?), + DataType::Int4 | DataType::Integer => decode_datetime_from_int(value.int64()?), + DataType::Float => decode_datetime_from_float(value.double()?), + + _ => None, + }; + + if let Some(dt) = dt { + Ok(dt) + } else { + Err(format!("invalid datetime: {}", value.text_borrowed()?).into()) + } +} + +fn decode_datetime_from_text(value: &str) -> Option> { + if let Ok(dt) = DateTime::parse_from_rfc3339(value) { + return Some(dt); + } + + // Loop over common date time patterns, inspired by Diesel + // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L56-L97 + let sqlite_datetime_formats = &[ + // Most likely format + "%F %T%.f", + // Other formats in order of appearance in docs + "%F %R", + "%F %RZ", + "%F %R%:z", + "%F %T%.fZ", + "%F %T%.f%:z", + "%FT%R", + "%FT%RZ", + "%FT%R%:z", + "%FT%T%.f", + "%FT%T%.fZ", + "%FT%T%.f%:z", + ]; + + for format in sqlite_datetime_formats { + if let Ok(dt) = DateTime::parse_from_str(value, format) { + return Some(dt); + } + + if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) { + return Some(Utc.fix().from_utc_datetime(&dt)); + } + } + + None +} + +fn decode_datetime_from_int(value: i64) -> Option> { + Utc.fix().timestamp_opt(value, 0).single() +} + +fn decode_datetime_from_float(value: f64) -> Option> { + let epoch_in_julian_days = 2_440_587.5; + let seconds_in_day = 86400.0; + let timestamp = (value - epoch_in_julian_days) * seconds_in_day; + + if !timestamp.is_finite() { + return None; + } + + // We don't really have a choice but to do lossy casts for this conversion + // We checked above if the value is infinite or NaN which could otherwise cause problems + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + { + let seconds = timestamp.trunc() as i64; + let nanos = (timestamp.fract() * 1E9).abs() as u32; + + Utc.fix().timestamp_opt(seconds, nanos).single() + } +} + +impl<'r> Decode<'r, Sqlite> for NaiveDateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(decode_datetime(value)?.naive_local()) + } +} + +impl<'r> Decode<'r, Sqlite> for NaiveDate { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(NaiveDate::parse_from_str(value.text_borrowed()?, "%F")?) + } +} + +impl<'r> Decode<'r, Sqlite> for NaiveTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + let value = value.text_borrowed()?; + + // Loop over common time patterns, inspired by Diesel + // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L29-L47 + #[rustfmt::skip] // don't like how rustfmt mangles the comments + let sqlite_time_formats = &[ + // Most likely format + "%T.f", "%T%.f", + // Other formats in order of appearance in docs + "%R", "%RZ", "%T%.fZ", "%R%:z", "%T%.f%:z", + ]; + + for format in sqlite_time_formats { + if let Ok(dt) = NaiveTime::parse_from_str(value, format) { + return Ok(dt); + } + } + + Err(format!("invalid time: {value}").into()) + } +} diff --git a/vendor/sqlx-sqlite/src/types/float.rs b/vendor/sqlx-sqlite/src/types/float.rs new file mode 100644 index 0000000000..d23b3f3a08 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/float.rs @@ -0,0 +1,49 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; + +impl Type for f32 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Float) + } +} + +impl Encode<'_, Sqlite> for f32 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Double((*self).into())); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for f32 { + fn decode(value: SqliteValueRef<'r>) -> Result { + // Truncation is intentional + #[allow(clippy::cast_possible_truncation)] + Ok(value.double()? as f32) + } +} + +impl Type for f64 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Float) + } +} + +impl Encode<'_, Sqlite> for f64 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Double(*self)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for f64 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.double()?) + } +} diff --git a/vendor/sqlx-sqlite/src/types/int.rs b/vendor/sqlx-sqlite/src/types/int.rs new file mode 100644 index 0000000000..2cf4209ad6 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/int.rs @@ -0,0 +1,107 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; + +impl Type for i8 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Int4) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for i8 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int(*self as i32)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for i8 { + fn decode(value: SqliteValueRef<'r>) -> Result { + // NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates + // which leads to bugs, e.g.: + // https://github.com/launchbadge/sqlx/issues/3179 + // Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161 + Ok(value.int64()?.try_into()?) + } +} + +impl Type for i16 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Int4) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for i16 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int(*self as i32)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for i16 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?.try_into()?) + } +} + +impl Type for i32 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Int4) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for i32 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int(*self)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for i32 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?.try_into()?) + } +} + +impl Type for i64 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Integer) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for i64 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int64(*self)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for i64 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?) + } +} diff --git a/vendor/sqlx-sqlite/src/types/json.rs b/vendor/sqlx-sqlite/src/types/json.rs new file mode 100644 index 0000000000..dafd483658 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/json.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::{Json, Type}; +use crate::{type_info::DataType, Sqlite, SqliteTypeInfo, SqliteValueRef}; + +impl Type for Json { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Text) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + <&str as Type>::compatible(ty) + } +} + +impl Encode<'_, Sqlite> for Json +where + T: Serialize, +{ + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.encode_to_string()?, buf) + } +} + +impl<'r, T> Decode<'r, Sqlite> for Json +where + T: 'r + Deserialize<'r>, +{ + fn decode(value: SqliteValueRef<'r>) -> Result { + // Saves a pass over the data by making `serde_json` check UTF-8. + Self::decode_from_bytes(Decode::::decode(value)?) + } +} diff --git a/vendor/sqlx-sqlite/src/types/mod.rs b/vendor/sqlx-sqlite/src/types/mod.rs new file mode 100644 index 0000000000..d36af7ea14 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/mod.rs @@ -0,0 +1,240 @@ +//! Conversions between Rust and **SQLite** types. +//! +//! # Types +//! +//! | Rust type | SQLite type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `bool` | BOOLEAN | +//! | `i8` | INTEGER | +//! | `i16` | INTEGER | +//! | `i32` | INTEGER, INT4 | +//! | `i64` | BIGINT, INT8 | +//! | `u8` | INTEGER | +//! | `u16` | INTEGER | +//! | `u32` | INTEGER | +//! | `u64` | INTEGER (Decode only; see note) | +//! | `f32` | REAL | +//! | `f64` | REAL | +//! | `&str`, [`String`] | TEXT | +//! | `&[u8]`, `Vec` | BLOB | +//! +//! #### Note: Unsigned Integers +//! Decoding of unsigned integer types simply performs a checked conversion +//! to ensure that overflow does not occur. +//! +//! Encoding of the unsigned integer types `u8`, `u16` and `u32` is implemented by zero-extending to +//! the next-larger signed type. So `u8` becomes `i16`, `u16` becomes `i32`, and `u32` becomes `i64` +//! while still retaining their semantic values. +//! +//! SQLite stores integers in a variable-width encoding and always handles them in memory as 64-bit +//! signed values, so no space is wasted by this implicit widening. +//! +//! However, there is no corresponding larger type for `u64` in SQLite +//! (it would require a native 16-byte integer, i.e. the equivalent of `i128`), +//! and so encoding is not supported for this type. +//! +//! Bit-casting `u64` to `i64`, or storing it as `REAL`, `BLOB` or `TEXT`, +//! would change the semantics of the value in SQL and so violates the principle of least surprise. +//! +//! ### [`chrono`](https://crates.io/crates/chrono) +//! +//! Requires the `chrono` Cargo feature flag. +//! +//! | Rust type | Sqlite type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `chrono::NaiveDateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::NaiveDate` | DATE (TEXT only) | +//! | `chrono::NaiveTime` | TIME (TEXT only) | +//! +//! ##### NOTE: `DATETIME` conversions +//! SQLite may represent `DATETIME` values as one of three types: `TEXT`, `REAL`, or `INTEGER`. +//! Which one is used is entirely up to you and how you store timestamps in your database. +//! +//! The deserialization for `NaiveDateTime`, `DateTime` and `DateTime` infer the date +//! format from the type of the value they're being decoded from: +//! +//! * If `TEXT`, the format is assumed to be an ISO-8601 compatible datetime string. +//! A number of possible formats are tried; see `sqlx-sqlite/src/types/chrono.rs` for the current +//! set of formats. +//! * If `INTEGER`, it is expected to be the number of seconds since January 1, 1970 00:00 UTC, +//! as if returned from the `unixepoch()` function (without the `subsec` modifier). +//! * If `REAL`, it is expected to be the (possibly fractional) number of days since the Julian epoch, +//! November 24, 4714 BCE 12:00 UTC, as if returned from the `julianday()` function. +//! +//! These types will always encode to a datetime string, either +//! with a timezone offset (`DateTime` for any `Tz: TimeZone`) or without (`NaiveDateTime`). +//! +//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values +//! As stated previously, `DateTime` always encodes to a date-time string +//! _with_ a timezone offset, +//! in [RFC 3339 format][::chrono::DateTime::to_rfc3339_opts] (with `use_z: false`). +//! +//! However, most of SQLite's datetime functions +//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`) +//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset. +//! +//! This may cause problems with interoperability with other applications, and especially +//! when comparing datetime values, which compares the actual string values lexicographically. +//! +//! Date-time strings in the SQLite format will generally _not_ compare consistently +//! with date-time strings in the RFC 3339 format. +//! +//! We recommend that you decide up-front whether `DATETIME` values should be stored +//! with explicit time zones or not, and use the corresponding type +//! (and its corresponding offset, if applicable) _consistently_ throughout your +//! application: +//! +//! * RFC 3339 format: `DateTime` (e.g. `DateTime`, `DateTime`, `DateTime`) +//! * Changing or mixing and matching offsets may break comparisons with existing timestamps. +//! * `DateTime` is **not recommended** for portable applications. +//! * `DateTime` is only recommended if the offset is **constant**. +//! * SQLite format: `NaiveDateTime` +//! +//! Note that non-constant offsets may still cause issues when comparing timestamps, +//! as the comparison operators are not timezone-aware. +//! +//! ### [`time`](https://crates.io/crates/time) +//! +//! Requires the `time` Cargo feature flag. +//! +//! | Rust type | Sqlite type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `time::PrimitiveDateTime` | DATETIME (TEXT, INTEGER) | +//! | `time::OffsetDateTime` | DATETIME (TEXT, INTEGER) | +//! | `time::Date` | DATE (TEXT only) | +//! | `time::Time` | TIME (TEXT only) | +//! +//! ##### NOTE: `DATETIME` conversions +//! The behavior here is identical to the corresponding `chrono` types, minus the support for `REAL` +//! values as Julian days (it's just not implemented). +//! +//! `PrimitiveDateTime` and `OffsetDateTime` will always encode to a datetime string, either +//! with a timezone offset (`OffsetDateTime`) or without (`PrimitiveDateTime`). +//! +//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values +//! As stated previously, `OffsetDateTime` always encodes to a datetime string _with_ a timezone offset, +//! in [RFC 3339 format][::time::format_description::well_known::Rfc3339] (using `Z` for UTC offsets). +//! +//! However, most of SQLite's datetime functions +//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`) +//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset. +//! +//! This may cause problems with interoperability with other applications, and especially +//! when comparing datetime values, which compares the actual string values lexicographically. +//! +//! Date-time strings in the SQLite format will generally _not_ compare consistently +//! with date-time strings in the RFC 3339 format. +//! +//! We recommend that you decide up-front whether `DATETIME` values should be stored +//! with explicit time zones or not, and use the corresponding type +//! (and its corresponding offset, if applicable) _consistently_ throughout your +//! application: +//! +//! * RFC 3339 format: `OffsetDateTime` with a **constant** offset. +//! * Changing or mixing and matching offsets may break comparisons with existing timestamps. +//! * `OffsetDateTime::now_local()` is **not recommended** for portable applications. +//! * Non-UTC offsets are only recommended if the offset is **constant**. +//! * SQLite format: `PrimitiveDateTime` +//! +//! Note that non-constant offsets may still cause issues when comparing timestamps, +//! as the comparison operators are not timezone-aware. +//! +//! ### [`uuid`](https://crates.io/crates/uuid) +//! +//! Requires the `uuid` Cargo feature flag. +//! +//! | Rust type | Sqlite type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `uuid::Uuid` | BLOB, TEXT | +//! | `uuid::fmt::Hyphenated` | TEXT | +//! | `uuid::fmt::Simple` | TEXT | +//! +//! ### [`json`](https://crates.io/crates/serde_json) +//! +//! Requires the `json` Cargo feature flag. +//! +//! | Rust type | Sqlite type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | [`Json`] | TEXT | +//! | `serde_json::JsonValue` | TEXT | +//! | `&serde_json::value::RawValue` | TEXT | +//! +//! # Nullable +//! +//! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents +//! a potentially `NULL` value from SQLite. +//! +//! # Non-feature: `NUMERIC` / `rust_decimal` / `bigdecimal` Support +//! Support for mapping `rust_decimal::Decimal` and `bigdecimal::BigDecimal` to SQLite has been +//! deliberately omitted because SQLite does not have native support for high- +//! or arbitrary-precision decimal arithmetic, and to pretend so otherwise would be a +//! significant misstep in API design. +//! +//! The in-tree [`decimal.c`] extension is unfortunately not included in the [amalgamation], +//! which is used to build the bundled version of SQLite3 for `libsqlite3-sys` (which we have +//! enabled by default for the simpler setup experience), otherwise we could support that. +//! +//! The `NUMERIC` type affinity, while seemingly designed for storing decimal values, +//! stores non-integer real numbers as double-precision IEEE-754 floating point, +//! i.e. `REAL` in SQLite, `f64` in Rust, `double` in C/C++, etc. +//! +//! [Datatypes in SQLite: Type Affinity][type-affinity] (accessed 2023/11/20): +//! +//! > A column with NUMERIC affinity may contain values using all five storage classes. +//! > When text data is inserted into a NUMERIC column, the storage class of the text is converted to +//! > INTEGER or REAL (in order of preference) if the text is a well-formed integer or real literal, +//! > respectively. If the TEXT value is a well-formed integer literal that is too large to fit in a +//! > 64-bit signed integer, it is converted to REAL. For conversions between TEXT and REAL storage +//! > classes, only the first 15 significant decimal digits of the number are preserved. +//! +//! With the SQLite3 interactive CLI, we can see that a higher-precision value +//! (20 digits in this case) is rounded off: +//! +//! ```text +//! sqlite> CREATE TABLE foo(bar NUMERIC); +//! sqlite> INSERT INTO foo(bar) VALUES('1.2345678901234567890'); +//! sqlite> SELECT * FROM foo; +//! 1.23456789012346 +//! ``` +//! +//! It appears the `TEXT` storage class is only used if the value contains invalid characters +//! or extra whitespace. +//! +//! Thus, the `NUMERIC` type affinity is **unsuitable** for storage of high-precision decimal values +//! and should be **avoided at all costs**. +//! +//! Support for `rust_decimal` and `bigdecimal` would only be a trap because users will naturally +//! want to use the `NUMERIC` type affinity, and might otherwise encounter serious bugs caused by +//! rounding errors that they were deliberately avoiding when they chose an arbitrary-precision type +//! over a floating-point type in the first place. +//! +//! Instead, you should only use a type affinity that SQLite will not attempt to convert implicitly, +//! such as `TEXT` or `BLOB`, and map values to/from SQLite as strings. You can do this easily +//! using [the `Text` adapter]. +//! +//! +//! [`decimal.c`]: https://www.sqlite.org/floatingpoint.html#the_decimal_c_extension +//! [amalgamation]: https://www.sqlite.org/amalgamation.html +//! [type-affinity]: https://www.sqlite.org/datatype3.html#type_affinity +//! [the `Text` adapter]: Text + +pub(crate) use sqlx_core::types::*; + +mod bool; +mod bytes; +#[cfg(feature = "chrono")] +mod chrono; +mod float; +mod int; +#[cfg(feature = "json")] +mod json; +mod str; +mod text; +#[cfg(feature = "time")] +mod time; +mod uint; +#[cfg(feature = "uuid")] +mod uuid; diff --git a/vendor/sqlx-sqlite/src/types/str.rs b/vendor/sqlx-sqlite/src/types/str.rs new file mode 100644 index 0000000000..a28f778b63 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/str.rs @@ -0,0 +1,106 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; +use sqlx_core::database::Database; +use std::borrow::Cow; +use std::rc::Rc; +use std::sync::Arc; + +impl Type for str { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Text) + } +} + +impl Encode<'_, Sqlite> for &'_ str { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for &'r str { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.text_borrowed()?) + } +} + +impl Encode<'_, Sqlite> for Box { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::TextSlice(Arc::from(self))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); + + Ok(IsNull::No) + } +} + +impl Type for String { + fn type_info() -> SqliteTypeInfo { + <&str as Type>::type_info() + } +} + +impl Encode<'_, Sqlite> for String { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.clone()))); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for String { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.text_owned()?) + } +} + +impl Encode<'_, Sqlite> for Cow<'_, str> { + fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.into()))); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); + + Ok(IsNull::No) + } +} + +impl Encode<'_, Sqlite> for Arc { + fn encode(self, args: &mut ::ArgumentBuffer) -> Result + where + Self: Sized, + { + args.push(SqliteArgumentValue::TextSlice(self)); + + Ok(IsNull::No) + } + + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + >::encode(self.to_string(), args) + } +} + +impl Encode<'_, Sqlite> for Rc { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + >::encode(self.to_string(), args) + } +} diff --git a/vendor/sqlx-sqlite/src/types/text.rs b/vendor/sqlx-sqlite/src/types/text.rs new file mode 100644 index 0000000000..ef03caf5a0 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/text.rs @@ -0,0 +1,37 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::{Sqlite, SqliteTypeInfo, SqliteValueRef}; +use sqlx_core::decode::Decode; +use sqlx_core::encode::{Encode, IsNull}; +use sqlx_core::error::BoxDynError; +use sqlx_core::types::{Text, Type}; +use std::fmt::Display; +use std::str::FromStr; + +impl Type for Text { + fn type_info() -> SqliteTypeInfo { + >::type_info() + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + >::compatible(ty) + } +} + +impl Encode<'_, Sqlite> for Text +where + T: Display, +{ + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.0.to_string(), buf) + } +} + +impl<'r, T> Decode<'r, Sqlite> for Text +where + T: FromStr, + BoxDynError: From<::Err>, +{ + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(Self(value.with_temp_text(|text| text.parse::())??)) + } +} diff --git a/vendor/sqlx-sqlite/src/types/time.rs b/vendor/sqlx-sqlite/src/types/time.rs new file mode 100644 index 0000000000..0e0027882d --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/time.rs @@ -0,0 +1,319 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::value::ValueRef; +use crate::{ + decode::Decode, + encode::{Encode, IsNull}, + error::BoxDynError, + type_info::DataType, + types::Type, + Sqlite, SqliteTypeInfo, SqliteValueRef, +}; +use time::format_description::{well_known::Rfc3339, BorrowedFormatItem}; +use time::macros::format_description as fd; +use time::{Date, OffsetDateTime, PrimitiveDateTime, Time}; + +impl Type for OffsetDateTime { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Datetime) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + >::compatible(ty) + } +} + +impl Type for PrimitiveDateTime { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Datetime) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!( + ty.0, + DataType::Datetime | DataType::Text | DataType::Integer | DataType::Int4 + ) + } +} + +impl Type for Date { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Date) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Date | DataType::Text) + } +} + +impl Type for Time { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Time) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Time | DataType::Text) + } +} + +impl Encode<'_, Sqlite> for OffsetDateTime { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + Encode::::encode(self.format(&Rfc3339)?, buf) + } +} + +impl Encode<'_, Sqlite> for PrimitiveDateTime { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + let format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); + Encode::::encode(self.format(&format)?, buf) + } +} + +impl Encode<'_, Sqlite> for Date { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + let format = fd!("[year]-[month]-[day]"); + Encode::::encode(self.format(&format)?, buf) + } +} + +impl Encode<'_, Sqlite> for Time { + fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { + let format = fd!("[hour]:[minute]:[second].[subsecond]"); + Encode::::encode(self.format(&format)?, buf) + } +} + +impl<'r> Decode<'r, Sqlite> for OffsetDateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + decode_offset_datetime(value) + } +} + +impl<'r> Decode<'r, Sqlite> for PrimitiveDateTime { + fn decode(value: SqliteValueRef<'r>) -> Result { + decode_datetime(value) + } +} + +impl<'r> Decode<'r, Sqlite> for Date { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(Date::parse( + value.text_borrowed()?, + &fd!("[year]-[month]-[day]"), + )?) + } +} + +impl<'r> Decode<'r, Sqlite> for Time { + fn decode(value: SqliteValueRef<'r>) -> Result { + let value = value.text_borrowed()?; + + let sqlite_time_formats = &[ + fd!("[hour]:[minute]:[second].[subsecond]"), + fd!("[hour]:[minute]:[second]"), + fd!("[hour]:[minute]"), + ]; + + for format in sqlite_time_formats { + if let Ok(dt) = Time::parse(value, &format) { + return Ok(dt); + } + } + + Err(format!("invalid time: {value}").into()) + } +} + +fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result { + let dt = match value.type_info().0 { + DataType::Text => decode_offset_datetime_from_text(value.text_borrowed()?), + DataType::Int4 | DataType::Integer => { + Some(OffsetDateTime::from_unix_timestamp(value.int64()?)?) + } + + _ => None, + }; + + if let Some(dt) = dt { + Ok(dt) + } else { + Err(format!("invalid offset datetime: {}", value.text_borrowed()?).into()) + } +} + +fn decode_offset_datetime_from_text(value: &str) -> Option { + if let Ok(dt) = OffsetDateTime::parse(value, &Rfc3339) { + return Some(dt); + } + + if let Ok(dt) = OffsetDateTime::parse(value, formats::OFFSET_DATE_TIME) { + return Some(dt); + } + + if let Some(dt) = decode_datetime_from_text(value) { + return Some(dt.assume_utc()); + } + + None +} + +fn decode_datetime(value: SqliteValueRef<'_>) -> Result { + let dt = match value.type_info().0 { + DataType::Text => decode_datetime_from_text(value.text_borrowed()?), + DataType::Int4 | DataType::Integer => { + let parsed = OffsetDateTime::from_unix_timestamp(value.int64()?).unwrap(); + Some(PrimitiveDateTime::new(parsed.date(), parsed.time())) + } + + _ => None, + }; + + if let Some(dt) = dt { + Ok(dt) + } else { + Err(format!("invalid datetime: {}", value.text_borrowed()?).into()) + } +} + +fn decode_datetime_from_text(value: &str) -> Option { + let default_format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); + if let Ok(dt) = PrimitiveDateTime::parse(value, &default_format) { + return Some(dt); + } + + let formats = [ + BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_SPACE_SEPARATED), + BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_T_SEPARATED), + ]; + + if let Ok(dt) = PrimitiveDateTime::parse(value, &BorrowedFormatItem::First(&formats)) { + return Some(dt); + } + + None +} + +mod formats { + use time::format_description::BorrowedFormatItem::{Component, Literal, Optional}; + use time::format_description::{modifier, BorrowedFormatItem, Component::*}; + + const YEAR: BorrowedFormatItem<'_> = Component(Year({ + let mut value = modifier::Year::default(); + value.padding = modifier::Padding::Zero; + value.repr = modifier::YearRepr::Full; + value.iso_week_based = false; + value.sign_is_mandatory = false; + value + })); + + const MONTH: BorrowedFormatItem<'_> = Component(Month({ + let mut value = modifier::Month::default(); + value.padding = modifier::Padding::Zero; + value.repr = modifier::MonthRepr::Numerical; + value.case_sensitive = true; + value + })); + + const DAY: BorrowedFormatItem<'_> = Component(Day({ + let mut value = modifier::Day::default(); + value.padding = modifier::Padding::Zero; + value + })); + + const HOUR: BorrowedFormatItem<'_> = Component(Hour({ + let mut value = modifier::Hour::default(); + value.padding = modifier::Padding::Zero; + value.is_12_hour_clock = false; + value + })); + + const MINUTE: BorrowedFormatItem<'_> = Component(Minute({ + let mut value = modifier::Minute::default(); + value.padding = modifier::Padding::Zero; + value + })); + + const SECOND: BorrowedFormatItem<'_> = Component(Second({ + let mut value = modifier::Second::default(); + value.padding = modifier::Padding::Zero; + value + })); + + const SUBSECOND: BorrowedFormatItem<'_> = Component(Subsecond({ + let mut value = modifier::Subsecond::default(); + value.digits = modifier::SubsecondDigits::OneOrMore; + value + })); + + const OFFSET_HOUR: BorrowedFormatItem<'_> = Component(OffsetHour({ + let mut value = modifier::OffsetHour::default(); + value.sign_is_mandatory = true; + value.padding = modifier::Padding::Zero; + value + })); + + const OFFSET_MINUTE: BorrowedFormatItem<'_> = Component(OffsetMinute({ + let mut value = modifier::OffsetMinute::default(); + value.padding = modifier::Padding::Zero; + value + })); + + pub(super) const OFFSET_DATE_TIME: &[BorrowedFormatItem<'_>] = { + &[ + YEAR, + Literal(b"-"), + MONTH, + Literal(b"-"), + DAY, + Optional(&Literal(b" ")), + Optional(&Literal(b"T")), + HOUR, + Literal(b":"), + MINUTE, + Optional(&Literal(b":")), + Optional(&SECOND), + Optional(&Literal(b".")), + Optional(&SUBSECOND), + Optional(&OFFSET_HOUR), + Optional(&Literal(b":")), + Optional(&OFFSET_MINUTE), + ] + }; + + pub(super) const PRIMITIVE_DATE_TIME_SPACE_SEPARATED: &[BorrowedFormatItem<'_>] = { + &[ + YEAR, + Literal(b"-"), + MONTH, + Literal(b"-"), + DAY, + Literal(b" "), + HOUR, + Literal(b":"), + MINUTE, + Optional(&Literal(b":")), + Optional(&SECOND), + Optional(&Literal(b".")), + Optional(&SUBSECOND), + Optional(&Literal(b"Z")), + ] + }; + + pub(super) const PRIMITIVE_DATE_TIME_T_SEPARATED: &[BorrowedFormatItem<'_>] = { + &[ + YEAR, + Literal(b"-"), + MONTH, + Literal(b"-"), + DAY, + Literal(b"T"), + HOUR, + Literal(b":"), + MINUTE, + Optional(&Literal(b":")), + Optional(&SECOND), + Optional(&Literal(b".")), + Optional(&SUBSECOND), + Optional(&Literal(b"Z")), + ] + }; +} diff --git a/vendor/sqlx-sqlite/src/types/uint.rs b/vendor/sqlx-sqlite/src/types/uint.rs new file mode 100644 index 0000000000..1e8b1c5392 --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/uint.rs @@ -0,0 +1,99 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; + +impl Type for u8 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Int4) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for u8 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int(*self as i32)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for u8 { + fn decode(value: SqliteValueRef<'r>) -> Result { + // NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates + // which leads to bugs, e.g.: + // https://github.com/launchbadge/sqlx/issues/3179 + // Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161 + Ok(value.int64()?.try_into()?) + } +} + +impl Type for u16 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Int4) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for u16 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int(*self as i32)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for u16 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?.try_into()?) + } +} + +impl Type for u32 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Integer) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl Encode<'_, Sqlite> for u32 { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Int64(*self as i64)); + + Ok(IsNull::No) + } +} + +impl<'r> Decode<'r, Sqlite> for u32 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?.try_into()?) + } +} + +impl Type for u64 { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Integer) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Int4 | DataType::Integer) + } +} + +impl<'r> Decode<'r, Sqlite> for u64 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int64()?.try_into()?) + } +} diff --git a/vendor/sqlx-sqlite/src/types/uuid.rs b/vendor/sqlx-sqlite/src/types/uuid.rs new file mode 100644 index 0000000000..718ca5fb5d --- /dev/null +++ b/vendor/sqlx-sqlite/src/types/uuid.rs @@ -0,0 +1,85 @@ +use crate::arguments::SqliteArgumentsBuffer; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::type_info::DataType; +use crate::types::Type; +use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; +use std::sync::Arc; +use uuid::{ + fmt::{Hyphenated, Simple}, + Uuid, +}; + +impl Type for Uuid { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Blob) + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + matches!(ty.0, DataType::Blob | DataType::Text) + } +} + +impl Encode<'_, Sqlite> for Uuid { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Blob(Arc::new( + self.as_bytes().to_vec(), + ))); + + Ok(IsNull::No) + } +} + +impl Decode<'_, Sqlite> for Uuid { + fn decode(value: SqliteValueRef<'_>) -> Result { + // construct a Uuid from the returned bytes + Uuid::from_slice(value.blob_borrowed()).map_err(Into::into) + } +} + +impl Type for Hyphenated { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Text) + } +} + +impl Encode<'_, Sqlite> for Hyphenated { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); + + Ok(IsNull::No) + } +} + +impl Decode<'_, Sqlite> for Hyphenated { + fn decode(value: SqliteValueRef<'_>) -> Result { + let uuid: Result = + Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into); + + Ok(uuid?.hyphenated()) + } +} + +impl Type for Simple { + fn type_info() -> SqliteTypeInfo { + SqliteTypeInfo(DataType::Text) + } +} + +impl Encode<'_, Sqlite> for Simple { + fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { + args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); + + Ok(IsNull::No) + } +} + +impl Decode<'_, Sqlite> for Simple { + fn decode(value: SqliteValueRef<'_>) -> Result { + let uuid: Result = + Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into); + + Ok(uuid?.simple()) + } +} diff --git a/vendor/sqlx-sqlite/src/value.rs b/vendor/sqlx-sqlite/src/value.rs new file mode 100644 index 0000000000..bdf376c187 --- /dev/null +++ b/vendor/sqlx-sqlite/src/value.rs @@ -0,0 +1,416 @@ +use std::borrow::Cow; +use std::cell::OnceCell; +use std::ptr::NonNull; +use std::slice; +use std::str; + +use libsqlite3_sys::{ + sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double, + sqlite3_value_dup, sqlite3_value_free, sqlite3_value_int64, sqlite3_value_type, +}; +use sqlx_core::type_info::TypeInfo; +pub(crate) use sqlx_core::value::{Value, ValueRef}; + +use crate::type_info::DataType; +use crate::{Sqlite, SqliteError, SqliteTypeInfo}; + +/// An owned handle to a [`sqlite3_value`]. +/// +/// # Note: Decoding is Stateful +/// The [`sqlite3_value` interface][value-methods] reserves the right to be stateful: +/// +/// > Other interfaces might change the datatype for an sqlite3_value object. +/// > For example, if the datatype is initially SQLITE_INTEGER and sqlite3_value_text(V) is called +/// > to extract a text value for that integer, then subsequent calls to sqlite3_value_type(V) +/// > might return SQLITE_TEXT. Whether or not a persistent internal datatype conversion occurs is +/// > undefined and may change from one release of SQLite to the next. +/// +/// Thus, this type is `!Sync` and [`SqliteValueRef`] is `!Send` and `!Sync` to prevent data races. +/// +/// Additionally, this statefulness means that the return values of `sqlite3_value_bytes()` and +/// `sqlite3_value_blob()` could be invalidated by later calls to other `sqlite3_value*` methods. +/// +/// To prevent undefined behavior from accessing dangling pointers, this type (and any +/// [`SqliteValueRef`] instances created from it) remembers when it was used to decode a +/// borrowed `&[u8]` or `&str` and returns an error if it is used to decode any other type. +/// +/// To bypass this error, you must prove that no outstanding borrows exist. +/// +/// This may be done in one of a few ways: +/// * If you hold mutable access, call [`Self::reset_borrow()`] which resets the borrowed state. +/// * If you have an immutable reference, call [`Self::clone()`] to get a new instance +/// with no outstanding borrows. +/// * If you hold a [`SqliteValueRef`], call [`SqliteValueRef::to_owned()`] +/// to get a new `SqliteValue` with no outstanding borrows. +/// +/// This is *only* necessary if using the same `SqliteValue` or [`SqliteValueRef`] to decode +/// multiple different types. The vast majority of use-cases employing once-through decoding +/// should not have to worry about this. +/// +/// [`sqlite3_value`]: https://www.sqlite.org/c3ref/value.html +/// [value-methods]: https://www.sqlite.org/c3ref/value_blob.html +pub struct SqliteValue(ValueHandle); + +/// A borrowed reference to a [`sqlite3_value`]. +/// +/// Semantically, this behaves as a reference to [`SqliteValue`]. +/// +/// # Note: Decoding is Stateful +/// See [`SqliteValue`] for details. +pub struct SqliteValueRef<'r>(Cow<'r, ValueHandle>); + +impl SqliteValue { + // SAFETY: The sqlite3_value must be non-null and SQLite must not free it. It will be freed on drop. + pub(crate) unsafe fn dup( + value: *mut sqlite3_value, + column_type: Option, + ) -> Self { + debug_assert!(!value.is_null()); + let handle = ValueHandle::try_dup_of(value, column_type) + .expect("SQLite failed to allocate memory for duplicated value"); + Self(handle) + } + + /// Prove that there are no outstanding borrows of this instance. + /// + /// Call this after decoding a borrowed `&[u8]` or `&str` + /// to reset the internal borrowed state and allow decoding of other types. + pub fn reset_borrow(&mut self) { + self.0.reset_blob_borrow(); + } + + /// Call [`sqlite3_value_dup()`] to create a new instance of this type. + /// + /// Returns an error if the call returns a null pointer, indicating that + /// SQLite was unable to allocate the additional memory required. + /// + /// Non-panicking version of [`Self::clone()`]. + /// + /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html + pub fn try_clone(&self) -> Result { + self.0.try_dup().map(Self) + } +} + +impl Clone for SqliteValue { + /// Call [`sqlite3_value_dup()`] to create a new instance of this type. + /// + /// # Panics + /// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition. + /// + /// See [`Self::try_clone()`] for a non-panicking version. + /// + /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html + fn clone(&self) -> Self { + self.try_clone().expect("failed to clone `SqliteValue`") + } +} + +impl Value for SqliteValue { + type Database = Sqlite; + + fn as_ref(&self) -> SqliteValueRef<'_> { + SqliteValueRef::value(self) + } + + fn type_info(&self) -> Cow<'_, SqliteTypeInfo> { + Cow::Owned(self.0.type_info()) + } + + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl<'r> SqliteValueRef<'r> { + /// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`]. + /// + /// Returns an error if the call returns a null pointer, indicating that + /// SQLite was unable to allocate the additional memory required. + /// + /// Non-panicking version of [`Self::try_to_owned()`]. + /// + /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html + pub fn try_to_owned(&self) -> Result { + self.0.try_dup().map(SqliteValue) + } + + pub(crate) fn value(value: &'r SqliteValue) -> Self { + Self(Cow::Borrowed(&value.0)) + } + + /// # Safety + /// The supplied sqlite3_value must not be null and SQLite must free it. + /// It will not be freed on drop. + /// The lifetime on this struct should tie it to whatever scope it's valid for before SQLite frees it. + #[allow(unused)] + pub(crate) unsafe fn borrowed(value: *mut sqlite3_value) -> Self { + debug_assert!(!value.is_null()); + let handle = ValueHandle::temporary(NonNull::new_unchecked(value)); + Self(Cow::Owned(handle)) + } + + // NOTE: `int()` is deliberately omitted because it will silently truncate a wider value, + // which is likely to cause bugs: + // https://github.com/launchbadge/sqlx/issues/3179 + // (Similar bug in Postgres): https://github.com/launchbadge/sqlx/issues/3161 + pub(super) fn int64(&self) -> Result { + self.0.int64() + } + + pub(super) fn double(&self) -> Result { + self.0.double() + } + + pub(super) fn blob_borrowed(&self) -> &'r [u8] { + // SAFETY: lifetime is matched to `'r` + unsafe { self.0.blob_borrowed() } + } + + pub(super) fn with_temp_blob(&self, op: impl FnOnce(&[u8]) -> R) -> R { + self.0.with_blob(op) + } + + pub(super) fn blob_owned(&self) -> Vec { + self.with_temp_blob(|blob| blob.to_vec()) + } + + pub(super) fn text_borrowed(&self) -> Result<&'r str, str::Utf8Error> { + // SAFETY: lifetime is matched to `'r` + unsafe { self.0.text_borrowed() } + } + + pub(super) fn with_temp_text( + &self, + op: impl FnOnce(&str) -> R, + ) -> Result { + self.0.with_blob(|blob| str::from_utf8(blob).map(op)) + } + + pub(super) fn text_owned(&self) -> Result { + self.with_temp_text(|text| text.to_string()) + } +} + +impl<'r> ValueRef<'r> for SqliteValueRef<'r> { + type Database = Sqlite; + + /// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`]. + /// + /// # Panics + /// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition. + /// + /// See [`Self::try_to_owned()`] for a non-panicking version. + /// + /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html + fn to_owned(&self) -> SqliteValue { + SqliteValue( + self.0 + .try_dup() + .expect("failed to convert SqliteValueRef to owned SqliteValue"), + ) + } + + fn type_info(&self) -> Cow<'_, SqliteTypeInfo> { + Cow::Owned(self.0.type_info()) + } + + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +pub(crate) struct ValueHandle { + value: NonNull, + column_type: Option, + // Note: `std::cell` version + borrowed_blob: OnceCell, + free_on_drop: bool, +} + +struct Blob { + ptr: *const u8, + len: usize, +} + +#[derive(Debug, thiserror::Error)] +#[error("given `SqliteValue` was previously decoded as BLOB or TEXT; `SqliteValue::reset_borrow()` must be called first")] +pub(crate) struct BorrowedBlobError; + +// SAFE: only protected value objects are stored in SqliteValue +unsafe impl Send for ValueHandle {} + +// SAFETY: the `sqlite3_value_*()` methods reserve the right to be stateful, +// which means method calls aren't thread-safe without mutual exclusion. +// +// impl !Sync for ValueHandle {} + +impl ValueHandle { + /// # Safety + /// The `sqlite3_value` must be valid and SQLite must not free it. It will be freed on drop. + unsafe fn try_dup_of( + value: *mut sqlite3_value, + column_type: Option, + ) -> Result { + // SAFETY: caller must ensure `value` is valid. + let value = + unsafe { NonNull::new(sqlite3_value_dup(value)).ok_or_else(SqliteError::nomem)? }; + + Ok(Self { + value, + column_type, + borrowed_blob: OnceCell::new(), + free_on_drop: true, + }) + } + + fn temporary(value: NonNull) -> Self { + Self { + value, + column_type: None, + borrowed_blob: OnceCell::new(), + free_on_drop: false, + } + } + + fn try_dup(&self) -> Result { + // SAFETY: `value` is initialized + unsafe { Self::try_dup_of(self.value.as_ptr(), self.column_type.clone()) } + } + + fn value_type_info(&self) -> SqliteTypeInfo { + SqliteTypeInfo(DataType::from_code(unsafe { + sqlite3_value_type(self.value.as_ptr()) + })) + } + + fn type_info(&self) -> SqliteTypeInfo { + let value_type = self.value_type_info(); + + // Assume the actual value type is more accurate, if it's not NULL. + match &self.column_type { + Some(column_type) if value_type.is_null() => column_type.clone(), + _ => value_type, + } + } + + fn int64(&self) -> Result { + // SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()` + self.assert_blob_not_borrowed()?; + + Ok(unsafe { sqlite3_value_int64(self.value.as_ptr()) }) + } + + fn double(&self) -> Result { + // SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()` + self.assert_blob_not_borrowed()?; + + Ok(unsafe { sqlite3_value_double(self.value.as_ptr()) }) + } + + fn is_null(&self) -> bool { + self.value_type_info().is_null() + } +} + +impl Clone for ValueHandle { + fn clone(&self) -> Self { + self.try_dup().unwrap() + } +} + +impl Drop for ValueHandle { + fn drop(&mut self) { + if self.free_on_drop { + unsafe { + sqlite3_value_free(self.value.as_ptr()); + } + } + } +} + +impl ValueHandle { + fn assert_blob_not_borrowed(&self) -> Result<(), BorrowedBlobError> { + if self.borrowed_blob.get().is_none() { + Ok(()) + } else { + Err(BorrowedBlobError) + } + } + + fn reset_blob_borrow(&mut self) { + self.borrowed_blob.take(); + } + + fn get_blob(&self) -> Option { + if let Some(blob) = self.borrowed_blob.get() { + return Some(Blob { ..*blob }); + } + + // SAFETY: calling `sqlite3_value_bytes` from multiple threads at once is a data race. + let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) }; + + // This likely means UB in SQLite itself or our usage of it; + // signed integer overflow is UB in the C standard. + let len = usize::try_from(len).unwrap_or_else(|_| { + panic!("sqlite3_value_bytes() returned value out of range for usize: {len}") + }); + + if len == 0 { + // empty blobs are NULL + return None; + } + + let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8; + debug_assert!(!ptr.is_null()); + + Some(Blob { ptr, len }) + } + + fn with_blob(&self, with_blob: impl FnOnce(&[u8]) -> R) -> R { + let Some(blob) = self.get_blob() else { + return with_blob(&[]); + }; + + // SAFETY: the slice cannot outlive the call + with_blob(unsafe { blob.as_slice() }) + } + + /// # Safety + /// Caller must ensure lifetime '`b` cannot outlive `self`. + unsafe fn blob_borrowed<'a>(&self) -> &'a [u8] { + let Some(blob) = self.get_blob() else { + return &[]; + }; + + // SAFETY: we need to store that the blob was borrowed + // to prevent + let blob = self.borrowed_blob.get_or_init(|| blob); + + unsafe { blob.as_slice() } + } + + /// # Safety + /// Caller must ensure lifetime '`b` cannot outlive `self`. + unsafe fn text_borrowed<'b>(&self) -> Result<&'b str, str::Utf8Error> { + let Some(blob) = self.get_blob() else { + return Ok(""); + }; + + // SAFETY: lifetime of `blob` will be tied to `'b`. + let s = str::from_utf8(unsafe { blob.as_slice() })?; + + // We only store the borrow after we ensure the string is valid. + self.borrowed_blob.set(blob).ok(); + + Ok(s) + } +} + +impl Blob { + /// # Safety + /// `'a` must not outlive the `sqlite3_value` this blob came from. + unsafe fn as_slice<'a>(&self) -> &'a [u8] { + slice::from_raw_parts(self.ptr, self.len) + } +} From bf05bc6aeb7a677604616ac97c1f55fdc2615880 Mon Sep 17 00:00:00 2001 From: Adrien Langou Date: Mon, 9 Nov 2026 16:15:31 +0100 Subject: [PATCH 3/3] fix(deps): defer SQLite until a compatible SQLx release Remove the vendored SQLx SQLite driver and its workspace patches. Keep the released dependency graph while waiting for SQLx to accept patched libsqlite3-sys >=0.39.0. Record a temporary RUSTSEC-2026-0459 exception with the upstream fix and an explicit removal condition. Preserve the h2 remediation. Signed-off-by: Adrien Langou --- .gitattributes | 4 - Cargo.lock | 6 +- Cargo.toml | 5 - deny.toml | 3 + e2e/rust/Cargo.lock | 8 +- e2e/rust/Cargo.toml | 4 - examples/governance-interceptor/Cargo.lock | 6 +- examples/governance-interceptor/Cargo.toml | 4 - examples/isolation-backend-v8/Cargo.lock | 8 +- examples/isolation-backend-v8/Cargo.toml | 4 - .../Cargo.lock | 6 +- .../Cargo.toml | 4 - scripts/update_license_headers.py | 2 - tests/suites/conformance/Cargo.lock | 6 +- tests/suites/conformance/Cargo.toml | 4 - tests/suites/drivers/podman/Cargo.lock | 6 +- tests/suites/drivers/podman/Cargo.toml | 4 - tests/suites/features/Cargo.lock | 6 +- tests/suites/features/Cargo.toml | 4 - tests/support/Cargo.lock | 6 +- tests/support/Cargo.toml | 4 - tests/testkit/Cargo.lock | 6 +- tests/testkit/Cargo.toml | 4 - vendor/sqlx-sqlite/Cargo.toml | 198 -- vendor/sqlx-sqlite/LICENSE-APACHE | 202 -- vendor/sqlx-sqlite/LICENSE-MIT | 26 - vendor/sqlx-sqlite/README.openshell.md | 30 - vendor/sqlx-sqlite/src/any.rs | 242 --- vendor/sqlx-sqlite/src/arguments.rs | 148 -- vendor/sqlx-sqlite/src/column.rs | 35 - .../sqlx-sqlite/src/connection/collation.rs | 158 -- vendor/sqlx-sqlite/src/connection/describe.rs | 101 - .../sqlx-sqlite/src/connection/deserialize.rs | 299 --- .../sqlx-sqlite/src/connection/establish.rs | 271 --- vendor/sqlx-sqlite/src/connection/execute.rs | 132 -- vendor/sqlx-sqlite/src/connection/executor.rs | 101 - vendor/sqlx-sqlite/src/connection/explain.rs | 1844 ----------------- vendor/sqlx-sqlite/src/connection/handle.rs | 151 -- vendor/sqlx-sqlite/src/connection/intmap.rs | 175 -- vendor/sqlx-sqlite/src/connection/mod.rs | 599 ------ .../src/connection/preupdate_hook.rs | 157 -- vendor/sqlx-sqlite/src/connection/worker.rs | 573 ----- vendor/sqlx-sqlite/src/database.rs | 39 - vendor/sqlx-sqlite/src/error.rs | 139 -- vendor/sqlx-sqlite/src/lib.rs | 183 -- vendor/sqlx-sqlite/src/logger.rs | 443 ---- vendor/sqlx-sqlite/src/migrate.rs | 297 --- vendor/sqlx-sqlite/src/options/auto_vacuum.rs | 38 - vendor/sqlx-sqlite/src/options/connect.rs | 83 - .../sqlx-sqlite/src/options/journal_mode.rs | 50 - .../sqlx-sqlite/src/options/locking_mode.rs | 38 - vendor/sqlx-sqlite/src/options/mod.rs | 675 ------ vendor/sqlx-sqlite/src/options/parse.rs | 231 --- vendor/sqlx-sqlite/src/options/synchronous.rs | 44 - vendor/sqlx-sqlite/src/query_result.rs | 40 - vendor/sqlx-sqlite/src/regexp.rs | 281 --- vendor/sqlx-sqlite/src/row.rs | 95 - vendor/sqlx-sqlite/src/statement/handle.rs | 466 ----- vendor/sqlx-sqlite/src/statement/mod.rs | 59 - .../src/statement/unlock_notify.rs | 65 - vendor/sqlx-sqlite/src/statement/virtual.rs | 204 -- vendor/sqlx-sqlite/src/testing/mod.rs | 83 - vendor/sqlx-sqlite/src/transaction.rs | 35 - vendor/sqlx-sqlite/src/type_checking.rs | 56 - vendor/sqlx-sqlite/src/type_info.rs | 161 -- vendor/sqlx-sqlite/src/types/bool.rs | 31 - vendor/sqlx-sqlite/src/types/bytes.rs | 105 - vendor/sqlx-sqlite/src/types/chrono.rs | 220 -- vendor/sqlx-sqlite/src/types/float.rs | 49 - vendor/sqlx-sqlite/src/types/int.rs | 107 - vendor/sqlx-sqlite/src/types/json.rs | 37 - vendor/sqlx-sqlite/src/types/mod.rs | 240 --- vendor/sqlx-sqlite/src/types/str.rs | 106 - vendor/sqlx-sqlite/src/types/text.rs | 37 - vendor/sqlx-sqlite/src/types/time.rs | 319 --- vendor/sqlx-sqlite/src/types/uint.rs | 99 - vendor/sqlx-sqlite/src/types/uuid.rs | 85 - vendor/sqlx-sqlite/src/value.rs | 416 ---- 78 files changed, 45 insertions(+), 11167 deletions(-) delete mode 100644 vendor/sqlx-sqlite/Cargo.toml delete mode 100644 vendor/sqlx-sqlite/LICENSE-APACHE delete mode 100644 vendor/sqlx-sqlite/LICENSE-MIT delete mode 100644 vendor/sqlx-sqlite/README.openshell.md delete mode 100644 vendor/sqlx-sqlite/src/any.rs delete mode 100644 vendor/sqlx-sqlite/src/arguments.rs delete mode 100644 vendor/sqlx-sqlite/src/column.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/collation.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/describe.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/deserialize.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/establish.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/execute.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/executor.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/explain.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/handle.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/intmap.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/mod.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/preupdate_hook.rs delete mode 100644 vendor/sqlx-sqlite/src/connection/worker.rs delete mode 100644 vendor/sqlx-sqlite/src/database.rs delete mode 100644 vendor/sqlx-sqlite/src/error.rs delete mode 100644 vendor/sqlx-sqlite/src/lib.rs delete mode 100644 vendor/sqlx-sqlite/src/logger.rs delete mode 100644 vendor/sqlx-sqlite/src/migrate.rs delete mode 100644 vendor/sqlx-sqlite/src/options/auto_vacuum.rs delete mode 100644 vendor/sqlx-sqlite/src/options/connect.rs delete mode 100644 vendor/sqlx-sqlite/src/options/journal_mode.rs delete mode 100644 vendor/sqlx-sqlite/src/options/locking_mode.rs delete mode 100644 vendor/sqlx-sqlite/src/options/mod.rs delete mode 100644 vendor/sqlx-sqlite/src/options/parse.rs delete mode 100644 vendor/sqlx-sqlite/src/options/synchronous.rs delete mode 100644 vendor/sqlx-sqlite/src/query_result.rs delete mode 100644 vendor/sqlx-sqlite/src/regexp.rs delete mode 100644 vendor/sqlx-sqlite/src/row.rs delete mode 100644 vendor/sqlx-sqlite/src/statement/handle.rs delete mode 100644 vendor/sqlx-sqlite/src/statement/mod.rs delete mode 100644 vendor/sqlx-sqlite/src/statement/unlock_notify.rs delete mode 100644 vendor/sqlx-sqlite/src/statement/virtual.rs delete mode 100644 vendor/sqlx-sqlite/src/testing/mod.rs delete mode 100644 vendor/sqlx-sqlite/src/transaction.rs delete mode 100644 vendor/sqlx-sqlite/src/type_checking.rs delete mode 100644 vendor/sqlx-sqlite/src/type_info.rs delete mode 100644 vendor/sqlx-sqlite/src/types/bool.rs delete mode 100644 vendor/sqlx-sqlite/src/types/bytes.rs delete mode 100644 vendor/sqlx-sqlite/src/types/chrono.rs delete mode 100644 vendor/sqlx-sqlite/src/types/float.rs delete mode 100644 vendor/sqlx-sqlite/src/types/int.rs delete mode 100644 vendor/sqlx-sqlite/src/types/json.rs delete mode 100644 vendor/sqlx-sqlite/src/types/mod.rs delete mode 100644 vendor/sqlx-sqlite/src/types/str.rs delete mode 100644 vendor/sqlx-sqlite/src/types/text.rs delete mode 100644 vendor/sqlx-sqlite/src/types/time.rs delete mode 100644 vendor/sqlx-sqlite/src/types/uint.rs delete mode 100644 vendor/sqlx-sqlite/src/types/uuid.rs delete mode 100644 vendor/sqlx-sqlite/src/value.rs diff --git a/.gitattributes b/.gitattributes index d9c2a6099d..11a48c1820 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,3 @@ crates/openshell-core/src/proto/openshell.*.rs linguist-generated # Vendored OCSF schemas fetched from schema.ocsf.io crates/openshell-ocsf-validate/schemas/v*/** linguist-generated - -# Keep the SQLx import separate in reviews and preserve its rustdoc line breaks. -vendor/sqlx-sqlite/** linguist-vendored -vendor/sqlx-sqlite/src/connection/deserialize.rs whitespace=-blank-at-eol diff --git a/Cargo.lock b/Cargo.lock index 8ebb3730e3..d178930a4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3672,9 +3672,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -7856,6 +7856,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/Cargo.toml b/Cargo.toml index 49ed89aca0..5254c11284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,6 @@ [workspace] resolver = "2" members = ["crates/*"] -exclude = ["vendor/sqlx-sqlite"] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "vendor/sqlx-sqlite" } [workspace.package] version = "0.0.0" diff --git a/deny.toml b/deny.toml index b813b44dd4..08ac2d3606 100644 --- a/deny.toml +++ b/deny.toml @@ -15,6 +15,9 @@ unmaintained = "workspace" maximum-db-staleness = "P30D" ignore = [ { id = "RUSTSEC-2023-0071", reason = "rsa Marvin attack — required by russh/ssh-key and RSA test fixtures; no patched release is available" }, + # Temporary: SQLx's SQLite dependency-bound update is merged but unreleased. + # https://github.com/transact-rs/sqlx/pull/4502 + { id = "RUSTSEC-2026-0459", reason = "sqlx-sqlite 0.9.0 requires libsqlite3-sys <0.38.0; remove this exception when a released SQLx accepts patched libsqlite3-sys >=0.39.0" }, ] # -- Licenses ------------------------------------------------------------------ diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index e2a50ca690..e664206f5c 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -1739,9 +1739,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -3181,6 +3181,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", @@ -3296,7 +3298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.52.0", diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 0bad43abfd..b0938d9625 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -319,7 +319,3 @@ missing_panics_doc = "allow" name = "live_internet_traffic_perf" path = "tests/live_internet_traffic_perf.rs" required-features = ["e2e-host-gateway"] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/governance-interceptor/Cargo.lock b/examples/governance-interceptor/Cargo.lock index 1c2e61d24e..fa08aa3aba 100644 --- a/examples/governance-interceptor/Cargo.lock +++ b/examples/governance-interceptor/Cargo.lock @@ -1627,9 +1627,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -2997,6 +2997,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/examples/governance-interceptor/Cargo.toml b/examples/governance-interceptor/Cargo.toml index 4b245278fb..a2ab52ef99 100644 --- a/examples/governance-interceptor/Cargo.toml +++ b/examples/governance-interceptor/Cargo.toml @@ -32,7 +32,3 @@ path = "src/main.rs" [[bin]] name = "governance-smoke-client" path = "src/smoke_client.rs" - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/isolation-backend-v8/Cargo.lock b/examples/isolation-backend-v8/Cargo.lock index 5d2e73a167..54445e64e4 100644 --- a/examples/isolation-backend-v8/Cargo.lock +++ b/examples/isolation-backend-v8/Cargo.lock @@ -1886,9 +1886,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -3250,6 +3250,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", @@ -3398,7 +3400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix 1.1.5", "windows-sys 0.61.2", diff --git a/examples/isolation-backend-v8/Cargo.toml b/examples/isolation-backend-v8/Cargo.toml index fe6c8fd381..4be42380a4 100644 --- a/examples/isolation-backend-v8/Cargo.toml +++ b/examples/isolation-backend-v8/Cargo.toml @@ -41,7 +41,3 @@ tonic-prost-build = "0.14" [[bin]] name = "js-sbx" path = "src/main.rs" - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/examples/supervisor-middleware-content-guard/Cargo.lock b/examples/supervisor-middleware-content-guard/Cargo.lock index f4f46338d7..f453c06919 100644 --- a/examples/supervisor-middleware-content-guard/Cargo.lock +++ b/examples/supervisor-middleware-content-guard/Cargo.lock @@ -1775,9 +1775,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -3119,6 +3119,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/examples/supervisor-middleware-content-guard/Cargo.toml b/examples/supervisor-middleware-content-guard/Cargo.toml index 9fe5cf4230..89e7b7a85e 100644 --- a/examples/supervisor-middleware-content-guard/Cargo.toml +++ b/examples/supervisor-middleware-content-guard/Cargo.toml @@ -26,7 +26,3 @@ openshell-policy = { path = "../../crates/openshell-policy" } [[bin]] name = "supervisor-middleware-content-guard" path = "src/main.rs" - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/scripts/update_license_headers.py b/scripts/update_license_headers.py index 9abaa410d5..bb00feaa4f 100755 --- a/scripts/update_license_headers.py +++ b/scripts/update_license_headers.py @@ -84,8 +84,6 @@ ".cache", "python/openshell/_proto", "sdk/typescript/src/gen", - # Preserve upstream SQLx copyrights and its MIT/Apache-2.0 licenses. - "vendor/sqlx-sqlite", } # Individual filenames to skip. diff --git a/tests/suites/conformance/Cargo.lock b/tests/suites/conformance/Cargo.lock index 75b892dbf8..ea04cd150d 100644 --- a/tests/suites/conformance/Cargo.lock +++ b/tests/suites/conformance/Cargo.lock @@ -1740,9 +1740,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -3280,6 +3280,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/conformance/Cargo.toml b/tests/suites/conformance/Cargo.toml index 66bc498f2b..99274d8dfa 100644 --- a/tests/suites/conformance/Cargo.toml +++ b/tests/suites/conformance/Cargo.toml @@ -27,7 +27,3 @@ tempfile = "3" tokio = { version = "1", features = ["time"] } [workspace] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../../vendor/sqlx-sqlite" } diff --git a/tests/suites/drivers/podman/Cargo.lock b/tests/suites/drivers/podman/Cargo.lock index 33b57b8d23..6633a3e703 100644 --- a/tests/suites/drivers/podman/Cargo.lock +++ b/tests/suites/drivers/podman/Cargo.lock @@ -1739,9 +1739,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -3296,6 +3296,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/drivers/podman/Cargo.toml b/tests/suites/drivers/podman/Cargo.toml index cfc6ba6c44..02c0304a1c 100644 --- a/tests/suites/drivers/podman/Cargo.toml +++ b/tests/suites/drivers/podman/Cargo.toml @@ -18,7 +18,3 @@ openshell-testkit = { path = "../../../testkit/testkit" } podman-support = { path = "../../../support/podman" } [workspace] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../../../vendor/sqlx-sqlite" } diff --git a/tests/suites/features/Cargo.lock b/tests/suites/features/Cargo.lock index fb6739ed33..bb1a1427ee 100644 --- a/tests/suites/features/Cargo.lock +++ b/tests/suites/features/Cargo.lock @@ -1766,9 +1766,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -3328,6 +3328,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/suites/features/Cargo.toml b/tests/suites/features/Cargo.toml index ff429cb27e..af907b4b15 100644 --- a/tests/suites/features/Cargo.toml +++ b/tests/suites/features/Cargo.toml @@ -4,7 +4,3 @@ [workspace] resolver = "2" members = ["host-gateway", "keycloak-provider-refresh", "kubernetes-updates", "oci-image"] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../../vendor/sqlx-sqlite" } diff --git a/tests/support/Cargo.lock b/tests/support/Cargo.lock index 30552f5496..f8b591438f 100644 --- a/tests/support/Cargo.lock +++ b/tests/support/Cargo.lock @@ -1748,9 +1748,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -3296,6 +3296,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/support/Cargo.toml b/tests/support/Cargo.toml index e6f202617b..f56ee275f5 100644 --- a/tests/support/Cargo.toml +++ b/tests/support/Cargo.toml @@ -6,7 +6,3 @@ [workspace] resolver = "2" members = ["container-engine", "keycloak", "podman"] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/tests/testkit/Cargo.lock b/tests/testkit/Cargo.lock index 3adacc41c1..db2e79b6ca 100644 --- a/tests/testkit/Cargo.lock +++ b/tests/testkit/Cargo.lock @@ -1730,9 +1730,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" -version = "0.39.1" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -3272,6 +3272,8 @@ dependencies = [ [[package]] name = "sqlx-sqlite" version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", diff --git a/tests/testkit/Cargo.toml b/tests/testkit/Cargo.toml index df14368d94..17a5e5b052 100644 --- a/tests/testkit/Cargo.toml +++ b/tests/testkit/Cargo.toml @@ -6,7 +6,3 @@ [workspace] resolver = "2" members = ["testkit", "testkit-macros"] - -[patch.crates-io] -# Temporary SQLite security backport; see vendor/sqlx-sqlite/README.openshell.md. -sqlx-sqlite = { path = "../../vendor/sqlx-sqlite" } diff --git a/vendor/sqlx-sqlite/Cargo.toml b/vendor/sqlx-sqlite/Cargo.toml deleted file mode 100644 index 9188ea900f..0000000000 --- a/vendor/sqlx-sqlite/Cargo.toml +++ /dev/null @@ -1,198 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -rust-version = "1.94.0" -name = "sqlx-sqlite" -version = "0.9.0" -authors = [ - "Ryan Leckey ", - "Austin Bonander ", - "Chloe Ross ", - "Daniel Akhterov ", -] -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "SQLite driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." -documentation = "https://docs.rs/sqlx" -readme = false -license = "MIT OR Apache-2.0" -repository = "https://github.com/launchbadge/sqlx" - -[package.metadata.docs.rs] -features = ["__unstable_docs"] - -[features] -_unstable-all-sqlite-features = [ - "deserialize", - "load-extension", - "preupdate-hook", - "unlock-notify", -] -_unstable-all-types = [ - "json", - "chrono", - "time", - "uuid", -] -_unstable-docs = [ - "bundled", - "any", - "_unstable-all-types", - "_unstable-all-sqlite-features", -] -any = ["sqlx-core/any"] -bigdecimal = [] -bundled = ["libsqlite3-sys/bundled"] -chrono = [ - "dep:chrono", - "sqlx-core/chrono", -] -deserialize = [] -json = [ - "sqlx-core/json", - "serde", -] -load-extension = [] -migrate = ["sqlx-core/migrate"] -offline = [ - "sqlx-core/offline", - "serde", -] -preupdate-hook = ["libsqlite3-sys/preupdate_hook"] -regexp = ["dep:regex"] -rust_decimal = [] -sqlx-toml = ["sqlx-core/sqlx-toml"] -time = [ - "dep:time", - "sqlx-core/time", -] -unbundled = ["libsqlite3-sys/buildtime_bindgen"] -unlock-notify = ["libsqlite3-sys/unlock_notify"] -uuid = [ - "dep:uuid", - "sqlx-core/uuid", -] - -[lib] -name = "sqlx_sqlite" -path = "src/lib.rs" - -[dependencies.atoi] -version = "2.0" - -[dependencies.chrono] -version = "0.4.34" -features = [ - "std", - "clock", -] -optional = true -default-features = false - -[dependencies.flume] -version = "0.12.0" -features = ["async"] -default-features = false - -[dependencies.form_urlencoded] -version = "1.2.2" - -[dependencies.futures-channel] -version = "0.3.32" -features = [ - "sink", - "alloc", - "std", -] -default-features = false - -[dependencies.futures-core] -version = "0.3.32" -default-features = false - -[dependencies.futures-executor] -version = "0.3.32" - -[dependencies.futures-intrusive] -version = "0.5.0" - -[dependencies.futures-util] -version = "0.3.32" -features = [ - "alloc", - "sink", -] -default-features = false - -[dependencies.libsqlite3-sys] -version = ">=0.39.0, <0.40.0" -features = [ - "pkg-config", - "vcpkg", -] -default-features = false - -[dependencies.log] -version = "0.4.18" - -[dependencies.percent-encoding] -version = "2.3.0" - -[dependencies.regex] -version = "1.6.0" -optional = true - -[dependencies.serde] -version = "1.0.219" -features = ["derive"] -optional = true - -[dependencies.sqlx-core] -version = "=0.9.0" - -[dependencies.thiserror] -version = "2.0.18" -features = ["std"] -default-features = false - -[dependencies.time] -version = "0.3.47" -features = [ - "formatting", - "parsing", - "macros", -] -optional = true - -[dependencies.tracing] -version = "0.1.37" -features = ["log"] - -[dependencies.url] -version = "2.2.2" - -[dependencies.uuid] -version = "1.12.1" -optional = true - -[dev-dependencies] - -[lints.clippy] -cast_possible_truncation = "deny" -cast_possible_wrap = "deny" -cast_sign_loss = "deny" -disallowed_methods = "deny" diff --git a/vendor/sqlx-sqlite/LICENSE-APACHE b/vendor/sqlx-sqlite/LICENSE-APACHE deleted file mode 100644 index e14699fb14..0000000000 --- a/vendor/sqlx-sqlite/LICENSE-APACHE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, -and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by -the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all -other entities that control, are controlled by, or are under common -control with that entity. For the purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or -otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity -exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, -including but not limited to software source code, documentation -source, and configuration files. - -"Object" form shall mean any form resulting from mechanical -transformation or translation of a Source form, including but -not limited to compiled object code, generated documentation, -and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or -Object form, made available under the License, as indicated by a -copyright notice that is included in or attached to the work -(an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object -form, that is based on (or derived from) the Work and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. For the purposes -of this License, Derivative Works shall not include works that remain -separable from, or merely link (or bind by name) to the interfaces of, -the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including -the original version of the Work and any modifications or additions -to that Work or Derivative Works thereof, that is intentionally -submitted to Licensor for inclusion in the Work by the copyright owner -or by an individual or Legal Entity authorized to submit on behalf of -the copyright owner. For the purposes of this definition, "submitted" -means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, -and issue tracking systems that are managed by, or on behalf of, the -Licensor for the purpose of discussing and improving the Work, but -excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity -on behalf of whom a Contribution has been received by Licensor and -subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the -Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -(except as stated in this section) patent license to make, have made, -use, offer to sell, sell, import, and otherwise transfer the Work, -where such license applies only to those patent claims licensable -by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) -with the Work to which such Contribution(s) was submitted. If You -institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work -or a Contribution incorporated within the Work constitutes direct -or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate -as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the -Work or Derivative Works thereof in any medium, with or without -modifications, and in Source or Object form, provided that You -meet the following conditions: - -(a) You must give any other recipients of the Work or -Derivative Works a copy of this License; and - -(b) You must cause any modified files to carry prominent notices -stating that You changed the files; and - -(c) You must retain, in the Source form of any Derivative Works -that You distribute, all copyright, patent, trademark, and -attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of -the Derivative Works; and - -(d) If the Work includes a "NOTICE" text file as part of its -distribution, then any Derivative Works that You distribute must -include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not -pertain to any part of the Derivative Works, in at least one -of the following places: within a NOTICE text file distributed -as part of the Derivative Works; within the Source form or -documentation, if provided along with the Derivative Works; or, -within a display generated by the Derivative Works, if and -wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and -do not modify the License. You may add Your own attribution -notices within Derivative Works that You distribute, alongside -or as an addendum to the NOTICE text from the Work, provided -that such additional attribution notices cannot be construed -as modifying the License. - -You may add Your own copyright statement to Your modifications and -may provide additional or different license terms and conditions -for use, reproduction, or distribution of Your modifications, or -for any such Derivative Works as a whole, provided Your use, -reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, -any Contribution intentionally submitted for inclusion in the Work -by You to the Licensor shall be under the terms and conditions of -this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify -the terms of any separate license agreement you may have executed -with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade -names, trademarks, service marks, or product names of the Licensor, -except as required for reasonable and customary use in describing the -origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or -agreed to in writing, Licensor provides the Work (and each -Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied, including, without limitation, any warranties or conditions -of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any -risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, -whether in tort (including negligence), contract, or otherwise, -unless required by applicable law (such as deliberate and grossly -negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, -incidental, or consequential damages of any character arising as a -result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses), even if such Contributor -has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing -the Work or Derivative Works thereof, You may choose to offer, -and charge a fee for, acceptance of support, warranty, indemnity, -or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only -on Your own behalf and on Your sole responsibility, not on behalf -of any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason -of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following -boilerplate notice, with the fields enclosed by brackets "[]" -replaced with your own identifying information. (Don't include -the brackets!) The text should be enclosed in the appropriate -comment syntax for the file format. We also recommend that a -file or class name and description of purpose be included on the -same "printed page" as the copyright notice for easier -identification within third-party archives. - -Copyright (C) SQLx Contributors -Portions of this work Copyright (C) LaunchBadge, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/sqlx-sqlite/LICENSE-MIT b/vendor/sqlx-sqlite/LICENSE-MIT deleted file mode 100644 index 6fe828e8ea..0000000000 --- a/vendor/sqlx-sqlite/LICENSE-MIT +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (C) SQLx Contributors -Portions of this work Copyright (C) LaunchBadge, LLC - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/vendor/sqlx-sqlite/README.openshell.md b/vendor/sqlx-sqlite/README.openshell.md deleted file mode 100644 index 2b3534aec0..0000000000 --- a/vendor/sqlx-sqlite/README.openshell.md +++ /dev/null @@ -1,30 +0,0 @@ -# Temporary SQLx SQLite security backport - -This is the published `sqlx-sqlite` 0.9.0 crate with its SQLite dependency -constraint changed to `>=0.39.0, <0.40.0`. The minimum excludes versions affected -by [RUSTSEC-2026-0459](https://rustsec.org/advisories/RUSTSEC-2026-0459.html). -The upper bound follows the upstream changes -[94aafe3](https://github.com/transact-rs/sqlx/commit/94aafe3a68884d923b0798a767c8d7f6cfda89d2) -and [10462fa](https://github.com/transact-rs/sqlx/commit/10462fa4414c6c7f1ed1d39bb31678e3b726878c), -which have not yet been published in a SQLx release. - -The Rust sources are unchanged. The archive's license-path placeholders are -replaced with the complete license texts from the published source commit. -Its unused lockfile, workspace manifest template, and Cargo cache metadata are -omitted. A targeted Git whitespace attribute preserves upstream rustdoc hard -line breaks in `src/connection/deserialize.rs`. - -- Source: -- Published source commit: `003b698e99e024f3621b8043a2426fde5b741171` -- Archive SHA-256: `488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c` -- Licenses: [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) - -OpenShell's Cargo workspaces patch this crate by path so the build continues to -use registry dependencies without a Git-source exception. Their lockfiles select -`libsqlite3-sys` 0.39.1, which bundles SQLite 3.54.1. SQLite features and the -SQLx 0.9 API remain unchanged. - -Remove this directory, the workspace patches, the root workspace exclusion, -the Git attributes, and the license-header exclusion once a published SQLx -release accepts a patched `libsqlite3-sys`. Update the affected lockfiles and -rerun the persistence and dependency-policy checks when removing the backport. diff --git a/vendor/sqlx-sqlite/src/any.rs b/vendor/sqlx-sqlite/src/any.rs deleted file mode 100644 index b3a5af5543..0000000000 --- a/vendor/sqlx-sqlite/src/any.rs +++ /dev/null @@ -1,242 +0,0 @@ -use crate::{ - Either, Sqlite, SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnectOptions, - SqliteConnection, SqliteQueryResult, SqliteRow, SqliteTransactionManager, SqliteTypeInfo, -}; -use futures_core::future::BoxFuture; -use futures_core::stream::BoxStream; -use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt}; - -use sqlx_core::any::{ - AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, - AnyStatement, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, -}; -use sqlx_core::sql_str::SqlStr; - -use crate::arguments::SqliteArgumentsBuffer; -use crate::type_info::DataType; -use sqlx_core::connection::{ConnectOptions, Connection}; -use sqlx_core::database::Database; -use sqlx_core::executor::Executor; -use sqlx_core::transaction::TransactionManager; -use std::pin::pin; -use std::sync::Arc; - -sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Sqlite); - -impl AnyConnectionBackend for SqliteConnection { - fn name(&self) -> &str { - ::NAME - } - - fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { - Connection::close(*self).boxed() - } - - fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { - Connection::close_hard(*self).boxed() - } - - fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { - Connection::ping(self).boxed() - } - - fn begin(&mut self, statement: Option) -> BoxFuture<'_, sqlx_core::Result<()>> { - SqliteTransactionManager::begin(self, statement).boxed() - } - - fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { - SqliteTransactionManager::commit(self).boxed() - } - - fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { - SqliteTransactionManager::rollback(self).boxed() - } - - fn start_rollback(&mut self) { - SqliteTransactionManager::start_rollback(self) - } - - fn get_transaction_depth(&self) -> usize { - SqliteTransactionManager::get_transaction_depth(self) - } - - fn shrink_buffers(&mut self) { - // NO-OP. - } - - fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { - Connection::flush(self).boxed() - } - - fn should_flush(&self) -> bool { - Connection::should_flush(self) - } - - #[cfg(feature = "migrate")] - fn as_migrate( - &mut self, - ) -> sqlx_core::Result<&mut (dyn sqlx_core::migrate::Migrate + Send + 'static)> { - Ok(self) - } - - fn fetch_many( - &mut self, - query: SqlStr, - persistent: bool, - arguments: Option, - ) -> BoxStream<'_, sqlx_core::Result>> { - let persistent = persistent && arguments.is_some(); - let args = arguments.map(map_arguments); - - Box::pin( - self.worker - .execute(query, args, self.row_channel_size, persistent, None) - .map_ok(flume::Receiver::into_stream) - .try_flatten_stream() - .map( - move |res: sqlx_core::Result>| match res? { - Either::Left(result) => Ok(Either::Left(map_result(result))), - Either::Right(row) => Ok(Either::Right(AnyRow::try_from(&row)?)), - }, - ), - ) - } - - fn fetch_optional( - &mut self, - query: SqlStr, - persistent: bool, - arguments: Option, - ) -> BoxFuture<'_, sqlx_core::Result>> { - let persistent = persistent && arguments.is_some(); - let args = arguments.map(map_arguments); - - Box::pin(async move { - let mut stream = pin!( - self.worker - .execute(query, args, self.row_channel_size, persistent, Some(1)) - .map_ok(flume::Receiver::into_stream) - .await? - ); - - if let Some(Either::Right(row)) = stream.try_next().await? { - return Ok(Some(AnyRow::try_from(&row)?)); - } - - Ok(None) - }) - } - - fn prepare_with<'c, 'q: 'c>( - &'c mut self, - sql: SqlStr, - _parameters: &[AnyTypeInfo], - ) -> BoxFuture<'c, sqlx_core::Result> { - Box::pin(async move { - let statement = Executor::prepare_with(self, sql, &[]).await?; - let column_names = statement.column_names.clone(); - AnyStatement::try_from_statement(statement, column_names) - }) - } - - #[cfg(feature = "offline")] - fn describe( - &mut self, - sql: SqlStr, - ) -> BoxFuture<'_, sqlx_core::Result>> { - Box::pin(async move { Executor::describe(self, sql).await?.try_into_any() }) - } -} - -impl<'a> TryFrom<&'a SqliteTypeInfo> for AnyTypeInfo { - type Error = sqlx_core::Error; - - fn try_from(sqlite_type: &'a SqliteTypeInfo) -> Result { - Ok(AnyTypeInfo { - kind: match &sqlite_type.0 { - DataType::Null => AnyTypeInfoKind::Null, - DataType::Int4 => AnyTypeInfoKind::Integer, - DataType::Integer => AnyTypeInfoKind::BigInt, - DataType::Float => AnyTypeInfoKind::Double, - DataType::Blob => AnyTypeInfoKind::Blob, - DataType::Text => AnyTypeInfoKind::Text, - _ => { - return Err(sqlx_core::Error::AnyDriverError( - format!("Any driver does not support the SQLite type {sqlite_type:?}") - .into(), - )) - } - }, - }) - } -} - -impl<'a> TryFrom<&'a SqliteColumn> for AnyColumn { - type Error = sqlx_core::Error; - - fn try_from(col: &'a SqliteColumn) -> Result { - let type_info = - AnyTypeInfo::try_from(&col.type_info).map_err(|e| sqlx_core::Error::ColumnDecode { - index: col.name.to_string(), - source: e.into(), - })?; - - Ok(AnyColumn { - ordinal: col.ordinal, - name: col.name.clone(), - type_info, - }) - } -} - -impl<'a> TryFrom<&'a SqliteRow> for AnyRow { - type Error = sqlx_core::Error; - - fn try_from(row: &'a SqliteRow) -> Result { - AnyRow::map_from(row, row.column_names.clone()) - } -} - -impl<'a> TryFrom<&'a AnyConnectOptions> for SqliteConnectOptions { - type Error = sqlx_core::Error; - - fn try_from(opts: &'a AnyConnectOptions) -> Result { - let mut opts_out = SqliteConnectOptions::from_url(&opts.database_url)?; - opts_out.log_settings = opts.log_settings.clone(); - - Ok(opts_out) - } -} - -// Infallible alternative to AnyArguments::convert_into() -fn map_arguments(args: AnyArguments) -> SqliteArguments { - let values = args - .values - .0 - .into_iter() - .map(|val| match val { - AnyValueKind::Null(_) => SqliteArgumentValue::Null, - AnyValueKind::Bool(b) => SqliteArgumentValue::Int(b as i32), - AnyValueKind::SmallInt(i) => SqliteArgumentValue::Int(i as i32), - AnyValueKind::Integer(i) => SqliteArgumentValue::Int(i), - AnyValueKind::BigInt(i) => SqliteArgumentValue::Int64(i), - AnyValueKind::Real(r) => SqliteArgumentValue::Double(r as f64), - AnyValueKind::Double(d) => SqliteArgumentValue::Double(d), - AnyValueKind::Text(t) => SqliteArgumentValue::Text(Arc::new(t.to_string())), - AnyValueKind::Blob(b) => SqliteArgumentValue::Blob(Arc::new(b.to_vec())), - // AnyValueKind is `#[non_exhaustive]` but we should have covered everything - _ => unreachable!("BUG: missing mapping for {val:?}"), - }) - .collect(); - - SqliteArguments { - values: SqliteArgumentsBuffer::new(values), - } -} - -fn map_result(res: SqliteQueryResult) -> AnyQueryResult { - AnyQueryResult { - rows_affected: res.rows_affected(), - last_insert_id: None, - } -} diff --git a/vendor/sqlx-sqlite/src/arguments.rs b/vendor/sqlx-sqlite/src/arguments.rs deleted file mode 100644 index 4fd7da5067..0000000000 --- a/vendor/sqlx-sqlite/src/arguments.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::encode::{Encode, IsNull}; -use crate::error::Error; -use crate::statement::StatementHandle; -use crate::Sqlite; -use atoi::atoi; -use libsqlite3_sys::SQLITE_OK; -use std::sync::Arc; - -pub(crate) use sqlx_core::arguments::*; -use sqlx_core::error::BoxDynError; - -#[derive(Debug, Clone)] -pub enum SqliteArgumentValue { - Null, - Text(Arc), - TextSlice(Arc), - Blob(Arc>), - Double(f64), - Int(i32), - Int64(i64), -} - -#[derive(Default, Debug, Clone)] -pub struct SqliteArguments { - pub(crate) values: SqliteArgumentsBuffer, -} - -#[derive(Default, Debug, Clone)] -pub struct SqliteArgumentsBuffer(Vec); - -impl SqliteArguments { - pub(crate) fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> - where - T: Encode<'t, Sqlite>, - { - let value_length_before_encoding = self.values.0.len(); - - match value.encode(&mut self.values) { - Ok(IsNull::Yes) => self.values.0.push(SqliteArgumentValue::Null), - Ok(IsNull::No) => {} - Err(error) => { - // reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind - self.values.0.truncate(value_length_before_encoding); - return Err(error); - } - }; - - Ok(()) - } -} - -impl Arguments for SqliteArguments { - type Database = Sqlite; - - fn reserve(&mut self, len: usize, _size_hint: usize) { - self.values.0.reserve(len); - } - - fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> - where - T: Encode<'t, Self::Database>, - { - self.add(value) - } - - fn len(&self) -> usize { - self.values.0.len() - } -} - -impl SqliteArguments { - pub(super) fn bind(&self, handle: &mut StatementHandle, offset: usize) -> Result { - let mut arg_i = offset; - // for handle in &statement.handles { - - let cnt = handle.bind_parameter_count(); - - for param_i in 1..=cnt { - // figure out the index of this bind parameter into our argument tuple - let n: usize = if let Some(name) = handle.bind_parameter_name(param_i) { - if let Some(name) = name.strip_prefix('?') { - // parameter should have the form ?NNN - atoi(name.as_bytes()).expect("parameter of the form ?NNN") - } else if let Some(name) = name.strip_prefix('$') { - // parameter should have the form $NNN - atoi(name.as_bytes()).ok_or_else(|| { - err_protocol!( - "parameters with non-integer names are not currently supported: {}", - name - ) - })? - } else { - return Err(err_protocol!("unsupported SQL parameter format: {}", name)); - } - } else { - arg_i += 1; - arg_i - }; - - if n > self.values.0.len() { - // SQLite treats unbound variables as NULL - // we reproduce this here - // If you are reading this and think this should be an error, open an issue and we can - // discuss configuring this somehow - // Note that the query macros have a different way of enforcing - // argument arity - break; - } - - self.values.0[n - 1].bind(handle, param_i)?; - } - - Ok(arg_i - offset) - } -} - -impl SqliteArgumentsBuffer { - #[allow(dead_code)] // clippy incorrectly reports this as unused - pub(crate) fn new(values: Vec) -> SqliteArgumentsBuffer { - Self(values) - } - - pub(crate) fn push(&mut self, value: SqliteArgumentValue) { - self.0.push(value); - } -} - -impl SqliteArgumentValue { - fn bind(&self, handle: &mut StatementHandle, i: usize) -> Result<(), Error> { - use SqliteArgumentValue::*; - - let status = match self { - Text(v) => handle.bind_text(i, v), - TextSlice(v) => handle.bind_text(i, v), - Blob(v) => handle.bind_blob(i, v), - Int(v) => handle.bind_int(i, *v), - Int64(v) => handle.bind_int64(i, *v), - Double(v) => handle.bind_double(i, *v), - Null => handle.bind_null(i), - }; - - if status != SQLITE_OK { - return Err(handle.last_error().into()); - } - - Ok(()) - } -} diff --git a/vendor/sqlx-sqlite/src/column.rs b/vendor/sqlx-sqlite/src/column.rs deleted file mode 100644 index d319bd46a8..0000000000 --- a/vendor/sqlx-sqlite/src/column.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::ext::ustr::UStr; -use crate::{Sqlite, SqliteTypeInfo}; - -pub(crate) use sqlx_core::column::*; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub struct SqliteColumn { - pub(crate) name: UStr, - pub(crate) ordinal: usize, - pub(crate) type_info: SqliteTypeInfo, - - #[cfg_attr(feature = "offline", serde(default))] - pub(crate) origin: ColumnOrigin, -} - -impl Column for SqliteColumn { - type Database = Sqlite; - - fn ordinal(&self) -> usize { - self.ordinal - } - - fn name(&self) -> &str { - &self.name - } - - fn type_info(&self) -> &SqliteTypeInfo { - &self.type_info - } - - fn origin(&self) -> ColumnOrigin { - self.origin.clone() - } -} diff --git a/vendor/sqlx-sqlite/src/connection/collation.rs b/vendor/sqlx-sqlite/src/connection/collation.rs deleted file mode 100644 index 229e780adf..0000000000 --- a/vendor/sqlx-sqlite/src/connection/collation.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::cmp::Ordering; -use std::ffi::CString; -use std::fmt::{self, Debug, Formatter}; -use std::os::raw::{c_int, c_void}; -use std::slice; -use std::sync::Arc; - -use libsqlite3_sys::{sqlite3_create_collation_v2, SQLITE_OK, SQLITE_UTF8}; - -use crate::connection::handle::ConnectionHandle; -use crate::error::Error; - -#[derive(Clone)] -pub struct Collation { - name: Arc, - #[allow(clippy::type_complexity)] - collate: Arc Ordering + Send + Sync + 'static>, - // SAFETY: these must match the concrete type of `collate` - call: unsafe extern "C" fn( - arg1: *mut c_void, - arg2: c_int, - arg3: *const c_void, - arg4: c_int, - arg5: *const c_void, - ) -> c_int, - free: unsafe extern "C" fn(*mut c_void), -} - -impl Collation { - pub fn new(name: N, collate: F) -> Self - where - N: Into>, - F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, - { - unsafe extern "C" fn drop_arc_value(p: *mut c_void) { - drop(Arc::from_raw(p as *mut T)); - } - - Collation { - name: name.into(), - collate: Arc::new(collate), - call: call_boxed_closure::, - free: drop_arc_value::, - } - } - - pub(crate) fn create(&self, handle: &mut ConnectionHandle) -> Result<(), Error> { - let raw_f = Arc::into_raw(Arc::clone(&self.collate)); - let c_name = CString::new(&*self.name) - .map_err(|_| err_protocol!("invalid collation name: {:?}", self.name))?; - let flags = SQLITE_UTF8; - let r = unsafe { - sqlite3_create_collation_v2( - handle.as_ptr(), - c_name.as_ptr(), - flags, - raw_f as *mut c_void, - Some(self.call), - Some(self.free), - ) - }; - - if r == SQLITE_OK { - Ok(()) - } else { - // The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails. - drop(unsafe { Arc::from_raw(raw_f) }); - Err(handle.expect_error().into()) - } - } -} - -impl Debug for Collation { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Collation") - .field("name", &self.name) - .finish_non_exhaustive() - } -} - -pub(crate) fn create_collation( - handle: &mut ConnectionHandle, - name: &str, - compare: F, -) -> Result<(), Error> -where - F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, -{ - unsafe extern "C" fn free_boxed_value(p: *mut c_void) { - drop(Box::from_raw(p as *mut T)); - } - - let boxed_f: *mut F = Box::into_raw(Box::new(compare)); - let c_name = - CString::new(name).map_err(|_| err_protocol!("invalid collation name: {}", name))?; - let flags = SQLITE_UTF8; - let r = unsafe { - sqlite3_create_collation_v2( - handle.as_ptr(), - c_name.as_ptr(), - flags, - boxed_f as *mut c_void, - Some(call_boxed_closure::), - Some(free_boxed_value::), - ) - }; - - if r == SQLITE_OK { - Ok(()) - } else { - // The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails. - drop(unsafe { Box::from_raw(boxed_f) }); - Err(handle.expect_error().into()) - } -} - -unsafe extern "C" fn call_boxed_closure( - data: *mut c_void, - left_len: c_int, - left_ptr: *const c_void, - right_len: c_int, - right_ptr: *const c_void, -) -> c_int -where - C: Fn(&str, &str) -> Ordering, -{ - let boxed_f: *mut C = data as *mut C; - - // Note: unwinding is now caught at the FFI boundary: - // https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding - assert!(!boxed_f.is_null()); - - let left_len = - usize::try_from(left_len).unwrap_or_else(|_| panic!("left_len out of range: {left_len}")); - - let right_len = usize::try_from(right_len) - .unwrap_or_else(|_| panic!("right_len out of range: {right_len}")); - - // SQLite explicitly documents that invalid UTF-8 may be passed into - // application-defined collating sequences. The safe `Fn(&str, &str)` - // signature exposed to users must never observe invalid UTF-8, so - // lossily coerce the raw bytes here. - let s1 = { - let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len); - String::from_utf8_lossy(c_slice) - }; - let s2 = { - let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len); - String::from_utf8_lossy(c_slice) - }; - let t = (*boxed_f)(&s1, &s2); - - match t { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - } -} diff --git a/vendor/sqlx-sqlite/src/connection/describe.rs b/vendor/sqlx-sqlite/src/connection/describe.rs deleted file mode 100644 index 400c671d96..0000000000 --- a/vendor/sqlx-sqlite/src/connection/describe.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::connection::explain::explain; -use crate::connection::ConnectionState; -use crate::describe::Describe; -use crate::error::Error; -use crate::statement::VirtualStatement; -use crate::type_info::DataType; -use crate::{Sqlite, SqliteColumn}; -use sqlx_core::sql_str::SqlStr; -use sqlx_core::Either; -use std::convert::identity; - -pub(crate) fn describe( - conn: &mut ConnectionState, - query: SqlStr, -) -> Result, Error> { - // describing a statement from SQLite can be involved - // each SQLx statement is comprised of multiple SQL statements - - let mut statement = VirtualStatement::new(query.as_str(), false)?; - - let mut columns = Vec::new(); - let mut nullable = Vec::new(); - let mut num_params = 0; - - // we start by finding the first statement that *can* return results - while let Some(stmt) = statement.prepare_next(&mut conn.handle)? { - num_params += stmt.handle.bind_parameter_count(); - - let mut stepped = false; - - let num = stmt.handle.column_count(); - if num == 0 { - // no columns in this statement; skip - continue; - } - - // next we try to use [column_decltype] to inspect the type of each column - columns.reserve(num); - - // as a last resort, we explain the original query and attempt to - // infer what would the expression types be as a fallback - // to [column_decltype] - - // if explain.. fails, ignore the failure and we'll have no fallback - let (fallback, fallback_nullable) = match explain(conn, stmt.handle.sql()) { - Ok(v) => v, - Err(error) => { - tracing::debug!(%error, "describe: explain introspection failed"); - - (vec![], vec![]) - } - }; - - for col in 0..num { - let name = stmt.handle.column_name(col).to_owned(); - - let origin = stmt.handle.column_origin(col); - - let type_info = if let Some(ty) = stmt.handle.column_decltype(col) { - ty - } else { - // if that fails, we back up and attempt to step the statement - // once *if* its read-only and then use [column_type] as a - // fallback to [column_decltype] - if !stepped && stmt.handle.read_only() { - stepped = true; - let _ = stmt.handle.step(); - } - - let mut ty = stmt.handle.column_type_info(col); - - if ty.0 == DataType::Null { - if let Some(fallback) = fallback.get(col).cloned() { - ty = fallback; - } - } - - ty - }; - - // check explain - let col_nullable = stmt.handle.column_nullable(col)?; - let exp_nullable = fallback_nullable.get(col).copied().and_then(identity); - - nullable.push(exp_nullable.or(col_nullable)); - - columns.push(SqliteColumn { - name: name.into(), - type_info, - ordinal: col, - origin, - }); - } - } - - Ok(Describe { - columns, - parameters: Some(Either::Right(num_params)), - nullable, - }) -} diff --git a/vendor/sqlx-sqlite/src/connection/deserialize.rs b/vendor/sqlx-sqlite/src/connection/deserialize.rs deleted file mode 100644 index 5056eb6784..0000000000 --- a/vendor/sqlx-sqlite/src/connection/deserialize.rs +++ /dev/null @@ -1,299 +0,0 @@ -use super::ConnectionState; -use crate::{error::Error, SqliteConnection, SqliteError}; -use libsqlite3_sys::{ - sqlite3_deserialize, sqlite3_free, sqlite3_malloc64, sqlite3_serialize, - SQLITE_DESERIALIZE_FREEONCLOSE, SQLITE_DESERIALIZE_READONLY, SQLITE_DESERIALIZE_RESIZEABLE, - SQLITE_NOMEM, SQLITE_OK, -}; -use std::ffi::c_char; -use std::fmt::Debug; -use std::{ - ops::{Deref, DerefMut}, - ptr, - ptr::NonNull, -}; - -impl SqliteConnection { - /// Serialize the given SQLite database schema using [`sqlite3_serialize()`]. - /// - /// The returned buffer is a SQLite managed allocation containing the equivalent data - /// as writing the database to disk. It is freed on-drop. - /// - /// To serialize the primary, unqualified schema (`main`), pass `None` for the schema name. - /// - /// # Errors - /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`). - /// * [`Error::Database`] if the schema does not exist or another error occurs. - /// - /// [`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))] - pub async fn serialize(&mut self, schema: Option<&str>) -> Result { - let schema = schema.map(SchemaName::try_from).transpose()?; - - self.worker.serialize(schema).await - } - - /// Deserialize a SQLite database from a buffer into the specified schema using [`sqlite3_deserialize()`]. - /// - /// The given schema will be disconnected and re-connected as an in-memory database - /// backed by `data`, which should be the serialized form of a database previously returned - /// by a call to [`Self::serialize()`], documented as being equivalent to - /// the contents of the database file on disk. - /// - /// An error will be returned if a schema with the given name is not already attached. - /// You can use `ATTACH ':memory' as ""` to create an empty schema first. - /// - /// Pass `None` to deserialize to the primary, unqualified schema (`main`). - /// - /// The SQLite connection will take ownership of `data` and will free it when the connection - /// is closed or the schema is detached ([`SQLITE_DESERIALIZE_FREEONCLOSE`][deserialize-flags]). - /// - /// If `read_only` is `true`, the schema is opened as read-only ([`SQLITE_DESERIALIZE_READONLY`][deserialize-flags]). - /// If `false`, the schema is marked as resizable ([`SQLITE_DESERIALIZE_RESIZABLE`][deserialize-flags]). - /// - /// If the database is in WAL mode, an error is returned. - /// See [`sqlite3_deserialize()`] for details. - /// - /// # Errors - /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`). - /// * [`Error::Database`] if an error occurs during deserialization. - /// - /// [`sqlite3_deserialize()`]: https://sqlite.org/c3ref/deserialize.html - /// [deserialize-flags]: https://sqlite.org/c3ref/c_deserialize_freeonclose.html - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))] - pub async fn deserialize( - &mut self, - schema: Option<&str>, - data: SqliteOwnedBuf, - read_only: bool, - ) -> Result<(), Error> { - let schema = schema.map(SchemaName::try_from).transpose()?; - - self.worker.deserialize(schema, data, read_only).await - } -} - -pub(crate) fn serialize( - conn: &mut ConnectionState, - schema: Option, -) -> Result { - let mut size = 0; - - let buf = unsafe { - let ptr = sqlite3_serialize( - conn.handle.as_ptr(), - schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr), - &mut size, - 0, - ); - - // looking at the source, `sqlite3_serialize` actually sets `size = -1` on error: - // https://github.com/sqlite/sqlite/blob/da5f81387843f92652128087a8f8ecef0b79461d/src/memdb.c#L776 - usize::try_from(size) - .ok() - .and_then(|size| SqliteOwnedBuf::from_raw(ptr, size)) - }; - - if let Some(buf) = buf { - return Ok(buf); - } - - if let Some(error) = conn.handle.last_error() { - return Err(error.into()); - } - - if size > 0 { - // If `size` is positive but `sqlite3_serialize` still returned NULL, - // the most likely culprit is an out-of-memory condition. - return Err(SqliteError::from_code(SQLITE_NOMEM).into()); - } - - // Otherwise, the schema was probably not found. - // We return the equivalent error as when you try to execute `PRAGMA .page_count` - // against a non-existent schema. - Err(SqliteError::generic(format!( - "database {} does not exist", - schema.as_ref().map_or("main", SchemaName::as_str) - )) - .into()) -} - -pub(crate) fn deserialize( - conn: &mut ConnectionState, - schema: Option, - data: SqliteOwnedBuf, - read_only: bool, -) -> Result<(), Error> { - // SQLITE_DESERIALIZE_FREEONCLOSE causes SQLite to take ownership of the buffer - let mut flags = SQLITE_DESERIALIZE_FREEONCLOSE; - if read_only { - flags |= SQLITE_DESERIALIZE_READONLY; - } else { - flags |= SQLITE_DESERIALIZE_RESIZEABLE; - } - - let (buf, size) = data.into_raw(); - - let rc = unsafe { - sqlite3_deserialize( - conn.handle.as_ptr(), - schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr), - buf, - i64::try_from(size).unwrap(), - i64::try_from(size).unwrap(), - flags, - ) - }; - - match rc { - SQLITE_OK => Ok(()), - SQLITE_NOMEM => Err(SqliteError::from_code(SQLITE_NOMEM).into()), - // SQLite unfortunately doesn't set any specific message for deserialization errors. - _ => Err(SqliteError::generic("an error occurred during deserialization").into()), - } -} - -/// Memory buffer owned and allocated by SQLite. Freed on drop. -/// -/// Intended primarily for use with [`SqliteConnection::serialize()`] and [`SqliteConnection::deserialize()`]. -/// -/// Can be created from `&[u8]` using the `TryFrom` impl. The slice must not be empty. -#[derive(Debug)] -pub struct SqliteOwnedBuf { - ptr: NonNull, - size: usize, -} - -unsafe impl Send for SqliteOwnedBuf {} -unsafe impl Sync for SqliteOwnedBuf {} - -impl Drop for SqliteOwnedBuf { - fn drop(&mut self) { - unsafe { - sqlite3_free(self.ptr.as_ptr().cast()); - } - } -} - -impl SqliteOwnedBuf { - /// Uses `sqlite3_malloc` to allocate a buffer and returns a pointer to it. - /// - /// # Safety - /// The allocated buffer is uninitialized. - unsafe fn with_capacity(size: usize) -> Option { - let ptr = sqlite3_malloc64(u64::try_from(size).unwrap()).cast::(); - Self::from_raw(ptr, size) - } - - /// Creates a new mem buffer from a pointer that has been created with sqlite_malloc - /// - /// # Safety: - /// * The pointer must point to a valid allocation created by `sqlite3_malloc()`, or `NULL`. - unsafe fn from_raw(ptr: *mut u8, size: usize) -> Option { - Some(Self { - ptr: NonNull::new(ptr)?, - size, - }) - } - - fn into_raw(self) -> (*mut u8, usize) { - let raw = (self.ptr.as_ptr(), self.size); - // this is used in sqlite_deserialize and - // underlying buffer must not be freed - std::mem::forget(self); - raw - } -} - -/// # Errors -/// Returns [`Error::InvalidArgument`] if the slice is empty. -impl TryFrom<&[u8]> for SqliteOwnedBuf { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - unsafe { - // SAFETY: `buf` is not initialized until `ptr::copy_nonoverlapping` completes. - let mut buf = Self::with_capacity(bytes.len()).ok_or_else(|| { - Error::InvalidArgument("SQLite owned buffer cannot be empty".to_string()) - })?; - ptr::copy_nonoverlapping(bytes.as_ptr(), buf.ptr.as_mut(), buf.size); - Ok(buf) - } - } -} - -impl Deref for SqliteOwnedBuf { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) } - } -} - -impl DerefMut for SqliteOwnedBuf { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.size) } - } -} - -impl AsRef<[u8]> for SqliteOwnedBuf { - fn as_ref(&self) -> &[u8] { - self.deref() - } -} - -impl AsMut<[u8]> for SqliteOwnedBuf { - fn as_mut(&mut self) -> &mut [u8] { - self.deref_mut() - } -} - -/// Checked schema name to pass to SQLite. -/// -/// # Safety: -/// * Valid UTF-8 (not guaranteed by `CString`) -/// * No internal zero bytes (`\0`) (not guaranteed by `String`) -/// * Terminated with a zero byte (`\0`) (not guaranteed by `String`) -#[derive(Debug)] -pub(crate) struct SchemaName(Box); - -impl SchemaName { - /// Get the schema name as a string without the zero byte terminator. - pub fn as_str(&self) -> &str { - &self.0[..self.0.len() - 1] - } - - /// Get a pointer to the string data, suitable for passing as C's `*const char`. - /// - /// # Safety - /// The string data is guaranteed to be terminated with a zero byte. - pub fn as_ptr(&self) -> *const c_char { - self.0.as_ptr() as *const c_char - } -} - -impl<'a> TryFrom<&'a str> for SchemaName { - type Error = Error; - - fn try_from(name: &'a str) -> Result { - // SAFETY: we must ensure that the string does not contain an internal NULL byte - if let Some(pos) = name.as_bytes().iter().position(|&b| b == 0) { - return Err(Error::InvalidArgument(format!( - "schema name {name:?} contains a zero byte at index {pos}" - ))); - } - - let capacity = name.len().checked_add(1).unwrap(); - - let mut s = String::new(); - // `String::with_capacity()` does not guarantee that it will not overallocate, - // which might mean an unnecessary reallocation to make `capacity == len` - // in the conversion to `Box`. - s.reserve_exact(capacity); - - s.push_str(name); - s.push('\0'); - - Ok(SchemaName(s.into())) - } -} diff --git a/vendor/sqlx-sqlite/src/connection/establish.rs b/vendor/sqlx-sqlite/src/connection/establish.rs deleted file mode 100644 index cedf752e6a..0000000000 --- a/vendor/sqlx-sqlite/src/connection/establish.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::connection::handle::ConnectionHandle; -use crate::connection::LogSettings; -use crate::connection::{ConnectionState, Statements}; -use crate::error::Error; -use crate::SqliteConnectOptions; -use libsqlite3_sys::{ - sqlite3_busy_timeout, SQLITE_OPEN_CREATE, SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_MEMORY, - SQLITE_OPEN_NOMUTEX, SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, - SQLITE_OPEN_SHAREDCACHE, SQLITE_OPEN_URI, -}; -use percent_encoding::NON_ALPHANUMERIC; -use std::collections::BTreeMap; -use std::ffi::CString; -use std::io; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::time::Duration; - -#[cfg(feature = "load-extension")] -use sqlx_core::IndexMap; - -// This was originally `AtomicU64` but that's not supported on MIPS (or PowerPC): -// https://github.com/launchbadge/sqlx/issues/2859 -// https://doc.rust-lang.org/stable/std/sync/atomic/index.html#portability -static THREAD_ID: AtomicUsize = AtomicUsize::new(0); - -pub struct EstablishParams { - filename: CString, - open_flags: i32, - busy_timeout: Duration, - statement_cache_capacity: usize, - log_settings: LogSettings, - #[cfg(feature = "load-extension")] - extensions: IndexMap>, - pub(crate) thread_name: String, - pub(crate) command_channel_size: usize, - #[cfg(feature = "regexp")] - register_regexp_function: bool, -} - -impl EstablishParams { - pub fn from_options(options: &SqliteConnectOptions) -> Result { - let mut filename = options - .filename - .to_str() - .ok_or_else(|| { - io::Error::new( - io::ErrorKind::InvalidData, - "filename passed to SQLite must be valid UTF-8", - ) - })? - .to_owned(); - - // Set common flags we expect to have in sqlite - let mut flags = SQLITE_OPEN_URI; - - // By default, we connect to an in-memory database. - // [SQLITE_OPEN_NOMUTEX] will instruct [sqlite3_open_v2] to return an error if it - // cannot satisfy our wish for a thread-safe, lock-free connection object - - flags |= if options.serialized { - SQLITE_OPEN_FULLMUTEX - } else { - SQLITE_OPEN_NOMUTEX - }; - - flags |= if options.read_only { - SQLITE_OPEN_READONLY - } else if options.create_if_missing { - SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - } else { - SQLITE_OPEN_READWRITE - }; - - if options.in_memory { - flags |= SQLITE_OPEN_MEMORY; - } - - flags |= if options.shared_cache { - SQLITE_OPEN_SHAREDCACHE - } else { - SQLITE_OPEN_PRIVATECACHE - }; - - let mut query_params = BTreeMap::new(); - - if options.immutable { - query_params.insert("immutable", "true"); - } - - if let Some(vfs) = options.vfs.as_deref() { - query_params.insert("vfs", vfs); - } - - if !query_params.is_empty() { - filename = format!( - "file:{}?", - percent_encoding::percent_encode(filename.as_bytes(), NON_ALPHANUMERIC), - ); - - // Suffix serializer automatically handles `&` separators for us. - let filename_len = filename.len(); - filename = form_urlencoded::Serializer::for_suffix(filename, filename_len) - .extend_pairs(query_params) - .finish(); - } - - let filename = CString::new(filename).map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "filename passed to SQLite must not contain nul bytes", - ) - })?; - - #[cfg(feature = "load-extension")] - let extensions = options - .extensions - .iter() - .map(|(name, entry)| { - let entry = entry - .as_ref() - .map(|e| { - CString::new(e.as_bytes()).map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "extension entrypoint names passed to SQLite must not contain nul bytes" - ) - }) - }) - .transpose()?; - Ok(( - CString::new(name.as_bytes()).map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "extension names passed to SQLite must not contain nul bytes", - ) - })?, - entry, - )) - }) - .collect::>, io::Error>>()?; - - let thread_id = THREAD_ID.fetch_add(1, Ordering::AcqRel); - - Ok(Self { - filename, - open_flags: flags, - busy_timeout: options.busy_timeout, - statement_cache_capacity: options.statement_cache_capacity, - log_settings: options.log_settings.clone(), - #[cfg(feature = "load-extension")] - extensions, - thread_name: (options.thread_name)(thread_id as u64), - command_channel_size: options.command_channel_size, - #[cfg(feature = "regexp")] - register_regexp_function: options.register_regexp_function, - }) - } - - pub(crate) fn establish(&self) -> Result { - let mut handle = ConnectionHandle::open(&self.filename, self.open_flags)?; - - #[cfg(feature = "load-extension")] - unsafe { - self.apply_extensions(&mut handle)?; - } - - #[cfg(feature = "regexp")] - if self.register_regexp_function { - // configure a `regexp` function for sqlite, it does not come with one by default - let status = crate::regexp::register(handle.as_ptr()); - if status != libsqlite3_sys::SQLITE_OK { - return Err(Error::Database(Box::new(handle.expect_error()))); - } - } - - // Configure a busy timeout - // This causes SQLite to automatically sleep in increasing intervals until the time - // when there is something locked during [sqlite3_step]. - // - // We also need to convert the u128 value to i32, checking we're not overflowing. - let ms = i32::try_from(self.busy_timeout.as_millis()) - .expect("Given busy timeout value is too big."); - - handle.call_with_result(|db| unsafe { sqlite3_busy_timeout(db, ms) })?; - - Ok(ConnectionState { - handle, - statements: Statements::new(self.statement_cache_capacity), - log_settings: self.log_settings.clone(), - progress_handler_callback: None, - update_hook_callback: None, - #[cfg(feature = "preupdate-hook")] - preupdate_hook_callback: None, - commit_hook_callback: None, - rollback_hook_callback: None, - }) - } - - #[cfg(feature = "load-extension")] - unsafe fn apply_extensions(&self, handle: &mut ConnectionHandle) -> Result<(), Error> { - use libsqlite3_sys::{sqlite3_free, sqlite3_load_extension}; - use std::ffi::{c_int, CStr}; - use std::ptr; - - /// `true` enables *just* `sqlite3_load_extension`, false disables *all* extension loading. - fn enable_load_extension( - handle: &mut ConnectionHandle, - enabled: bool, - ) -> Result<(), Error> { - use libsqlite3_sys::{sqlite3_db_config, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION}; - - // SAFETY: we have exclusive access and this matches the expected signature - // - handle.call_with_result(|db| unsafe { - // https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.bool-char-as-int - sqlite3_db_config( - db, - SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, - enabled as c_int, - ptr::null_mut::(), - ) - })?; - - Ok(()) - } - - if self.extensions.is_empty() { - return Ok(()); - } - - // We enable extension loading only so long as *we're* doing it. - enable_load_extension(handle, true)?; - - for (name, entrypoint) in &self.extensions { - let name_ptr = name.as_ptr(); - let entrypoint_ptr = entrypoint.as_ref().map_or_else(ptr::null, |s| s.as_ptr()); - let mut err_msg_ptr = ptr::null_mut(); - - // SAFETY: - // * we have exclusive access - // * all pointers are initialized - // * we warn the user about loading extensions in documentation - handle - .call_with_result(|db| unsafe { - sqlite3_load_extension(db, name_ptr, entrypoint_ptr, &mut err_msg_ptr) - }) - .map_err(|e| { - if !err_msg_ptr.is_null() { - // SAFETY: pointer is not-null, - // and we copy the error message to an allocation we own. - let err_msg = unsafe { CStr::from_ptr(err_msg_ptr) } - // In practice, the string *should* be UTF-8. - .to_string_lossy() - .into_owned(); - - // SAFETY: we're expected to free the error message afterward. - unsafe { - sqlite3_free(err_msg_ptr.cast()); - } - - e.with_message(err_msg) - } else { - e - } - })?; - } - - // We then disable extension loading immediately afterward. - enable_load_extension(handle, false) - } -} diff --git a/vendor/sqlx-sqlite/src/connection/execute.rs b/vendor/sqlx-sqlite/src/connection/execute.rs deleted file mode 100644 index 733a1abbe6..0000000000 --- a/vendor/sqlx-sqlite/src/connection/execute.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::connection::{ConnectionHandle, ConnectionState}; -use crate::error::Error; -use crate::logger::QueryLogger; -use crate::statement::{StatementHandle, VirtualStatement}; -use crate::{SqliteArguments, SqliteQueryResult, SqliteRow}; -use sqlx_core::sql_str::SqlSafeStr; -use sqlx_core::Either; - -pub struct ExecuteIter<'a> { - handle: &'a mut ConnectionHandle, - statement: &'a mut VirtualStatement, - logger: QueryLogger, - args: Option, - - /// since a `VirtualStatement` can encompass multiple actual statements, - /// this keeps track of the number of arguments so far - args_used: usize, - - goto_next: bool, -} - -pub(crate) fn iter( - conn: &mut ConnectionState, - query: impl SqlSafeStr, - args: Option, - persistent: bool, -) -> Result, Error> { - let query = query.into_sql_str(); - // fetch the cached statement or allocate a new one - let statement = conn.statements.get(query.as_str(), persistent)?; - - let logger = QueryLogger::new(query, conn.log_settings.clone()); - - Ok(ExecuteIter { - handle: &mut conn.handle, - statement, - logger, - args, - args_used: 0, - goto_next: true, - }) -} - -fn bind( - statement: &mut StatementHandle, - arguments: &Option, - offset: usize, -) -> Result { - let mut n = 0; - - if let Some(arguments) = arguments { - n = arguments.bind(statement, offset)?; - } - - Ok(n) -} - -impl ExecuteIter<'_> { - pub fn finish(self) -> Result<(), Error> { - for res in self { - let _ = res?; - } - - Ok(()) - } -} - -impl Iterator for ExecuteIter<'_> { - type Item = Result, Error>; - - fn next(&mut self) -> Option { - let statement = if self.goto_next { - let statement = match self.statement.prepare_next(self.handle) { - Ok(Some(statement)) => statement, - Ok(None) => return None, - Err(e) => return Some(Err(e)), - }; - - self.goto_next = false; - - // sanity check: ensure the VM is reset and the bindings are cleared - if let Err(e) = statement.handle.reset() { - return Some(Err(e.into())); - } - - statement.handle.clear_bindings(); - - match bind(statement.handle, &self.args, self.args_used) { - Ok(args_used) => self.args_used += args_used, - Err(e) => return Some(Err(e)), - } - - statement - } else { - self.statement.current()? - }; - - match statement.handle.step() { - Ok(true) => { - self.logger.increment_rows_returned(); - - Some(Ok(Either::Right(SqliteRow::current( - statement.handle, - statement.columns, - statement.column_names, - )))) - } - Ok(false) => { - let last_insert_rowid = self.handle.last_insert_rowid(); - - let changes = statement.handle.changes(); - self.logger.increase_rows_affected(changes); - - let done = SqliteQueryResult { - changes, - last_insert_rowid, - }; - - self.goto_next = true; - - Some(Ok(Either::Left(done))) - } - Err(e) => Some(Err(e.into())), - } - } -} - -impl Drop for ExecuteIter<'_> { - fn drop(&mut self) { - self.statement.reset().ok(); - } -} diff --git a/vendor/sqlx-sqlite/src/connection/executor.rs b/vendor/sqlx-sqlite/src/connection/executor.rs deleted file mode 100644 index a62d3349a5..0000000000 --- a/vendor/sqlx-sqlite/src/connection/executor.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{ - Sqlite, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, SqliteTypeInfo, -}; -use futures_core::future::BoxFuture; -use futures_core::stream::BoxStream; -use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; -use sqlx_core::error::Error; -use sqlx_core::executor::{Execute, Executor}; -use sqlx_core::sql_str::SqlStr; -use sqlx_core::Either; -use std::{future, pin::pin}; - -impl<'c> Executor<'c> for &'c mut SqliteConnection { - type Database = Sqlite; - - fn fetch_many<'e, 'q, E>( - self, - mut query: E, - ) -> BoxStream<'e, Result, Error>> - where - 'c: 'e, - E: Execute<'q, Self::Database>, - 'q: 'e, - E: 'q, - { - let arguments = match query.take_arguments().map_err(Error::Encode) { - Ok(arguments) => arguments, - Err(error) => return stream::once(future::ready(Err(error))).boxed(), - }; - let persistent = query.persistent() && arguments.is_some(); - let sql = query.sql(); - - Box::pin( - self.worker - .execute(sql, arguments, self.row_channel_size, persistent, None) - .map_ok(flume::Receiver::into_stream) - .try_flatten_stream(), - ) - } - - fn fetch_optional<'e, 'q, E>( - self, - mut query: E, - ) -> BoxFuture<'e, Result, Error>> - where - 'c: 'e, - E: Execute<'q, Self::Database>, - 'q: 'e, - E: 'q, - { - let arguments = match query.take_arguments().map_err(Error::Encode) { - Ok(arguments) => arguments, - Err(error) => return future::ready(Err(error)).boxed(), - }; - let persistent = query.persistent() && arguments.is_some(); - - Box::pin(async move { - let sql = query.sql(); - let mut stream = pin!(self - .worker - .execute(sql, arguments, self.row_channel_size, persistent, Some(1)) - .map_ok(flume::Receiver::into_stream) - .try_flatten_stream()); - - while let Some(res) = stream.try_next().await? { - if let Either::Right(row) = res { - return Ok(Some(row)); - } - } - - Ok(None) - }) - } - - fn prepare_with<'e>( - self, - sql: SqlStr, - _parameters: &[SqliteTypeInfo], - ) -> BoxFuture<'e, Result> - where - 'c: 'e, - { - Box::pin(async move { - let statement = self.worker.prepare(sql).await?; - - Ok(statement) - }) - } - - #[doc(hidden)] - #[cfg(feature = "offline")] - fn describe<'e>( - self, - sql: SqlStr, - ) -> BoxFuture<'e, Result, Error>> - where - 'c: 'e, - { - Box::pin(async move { self.worker.describe(sql).await }) - } -} diff --git a/vendor/sqlx-sqlite/src/connection/explain.rs b/vendor/sqlx-sqlite/src/connection/explain.rs deleted file mode 100644 index 550f21557e..0000000000 --- a/vendor/sqlx-sqlite/src/connection/explain.rs +++ /dev/null @@ -1,1844 +0,0 @@ -// Bad casts in this module SHOULD NOT result in a SQL injection -// https://github.com/launchbadge/sqlx/issues/3440 -#![allow( - clippy::cast_possible_truncation, - clippy::cast_possible_wrap, - clippy::cast_sign_loss -)] -use crate::connection::intmap::IntMap; -use crate::connection::{execute, ConnectionState}; -use crate::error::Error; -use crate::from_row::FromRow; -use crate::logger::{BranchParent, BranchResult, DebugDiff}; -use crate::type_info::DataType; -use crate::SqliteTypeInfo; -use sqlx_core::sql_str::AssertSqlSafe; -use sqlx_core::{hash_map, HashMap}; -use std::fmt::Debug; -use std::str::from_utf8; - -// affinity -const SQLITE_AFF_NONE: u8 = 0x40; /* '@' */ -const SQLITE_AFF_BLOB: u8 = 0x41; /* 'A' */ -const SQLITE_AFF_TEXT: u8 = 0x42; /* 'B' */ -const SQLITE_AFF_NUMERIC: u8 = 0x43; /* 'C' */ -const SQLITE_AFF_INTEGER: u8 = 0x44; /* 'D' */ -const SQLITE_AFF_REAL: u8 = 0x45; /* 'E' */ - -// opcodes -const OP_INIT: &str = "Init"; -const OP_GOTO: &str = "Goto"; -const OP_DECR_JUMP_ZERO: &str = "DecrJumpZero"; -const OP_DELETE: &str = "Delete"; -const OP_ELSE_EQ: &str = "ElseEq"; -const OP_EQ: &str = "Eq"; -const OP_END_COROUTINE: &str = "EndCoroutine"; -const OP_FILTER: &str = "Filter"; -const OP_FK_IF_ZERO: &str = "FkIfZero"; -const OP_FOUND: &str = "Found"; -const OP_GE: &str = "Ge"; -const OP_GO_SUB: &str = "Gosub"; -const OP_GT: &str = "Gt"; -const OP_IDX_GE: &str = "IdxGE"; -const OP_IDX_GT: &str = "IdxGT"; -const OP_IDX_LE: &str = "IdxLE"; -const OP_IDX_LT: &str = "IdxLT"; -const OP_IDX_ROWID: &str = "IdxRowid"; -const OP_IF: &str = "If"; -const OP_IF_NO_HOPE: &str = "IfNoHope"; -const OP_IF_NOT: &str = "IfNot"; -const OP_IF_NOT_OPEN: &str = "IfNotOpen"; -const OP_IF_NOT_ZERO: &str = "IfNotZero"; -const OP_IF_NULL_ROW: &str = "IfNullRow"; -const OP_IF_POS: &str = "IfPos"; -const OP_IF_SMALLER: &str = "IfSmaller"; -const OP_INCR_VACUUM: &str = "IncrVacuum"; -const OP_INIT_COROUTINE: &str = "InitCoroutine"; -const OP_IS_NULL: &str = "IsNull"; -const OP_IS_NULL_OR_TYPE: &str = "IsNullOrType"; -const OP_LAST: &str = "Last"; -const OP_LE: &str = "Le"; -const OP_LT: &str = "Lt"; -const OP_MUST_BE_INT: &str = "MustBeInt"; -const OP_NE: &str = "Ne"; -const OP_NEXT: &str = "Next"; -const OP_NO_CONFLICT: &str = "NoConflict"; -const OP_NOT_EXISTS: &str = "NotExists"; -const OP_NOT_NULL: &str = "NotNull"; -const OP_ONCE: &str = "Once"; -const OP_PREV: &str = "Prev"; -const OP_PROGRAM: &str = "Program"; -const OP_RETURN: &str = "Return"; -const OP_REWIND: &str = "Rewind"; -const OP_ROW_DATA: &str = "RowData"; -const OP_ROW_SET_READ: &str = "RowSetRead"; -const OP_ROW_SET_TEST: &str = "RowSetTest"; -const OP_SEEK_GE: &str = "SeekGE"; -const OP_SEEK_GT: &str = "SeekGT"; -const OP_SEEK_LE: &str = "SeekLE"; -const OP_SEEK_LT: &str = "SeekLT"; -const OP_SEEK_ROW_ID: &str = "SeekRowid"; -const OP_SEEK_SCAN: &str = "SeekScan"; -const OP_SEQUENCE: &str = "Sequence"; -const OP_SEQUENCE_TEST: &str = "SequenceTest"; -const OP_SORT: &str = "Sort"; -const OP_SORTER_DATA: &str = "SorterData"; -const OP_SORTER_INSERT: &str = "SorterInsert"; -const OP_SORTER_NEXT: &str = "SorterNext"; -const OP_SORTER_OPEN: &str = "SorterOpen"; -const OP_SORTER_SORT: &str = "SorterSort"; -const OP_V_FILTER: &str = "VFilter"; -const OP_V_NEXT: &str = "VNext"; -const OP_YIELD: &str = "Yield"; -const OP_JUMP: &str = "Jump"; -const OP_COLUMN: &str = "Column"; -const OP_MAKE_RECORD: &str = "MakeRecord"; -const OP_INSERT: &str = "Insert"; -const OP_IDX_INSERT: &str = "IdxInsert"; -const OP_OPEN_DUP: &str = "OpenDup"; -const OP_OPEN_PSEUDO: &str = "OpenPseudo"; -const OP_OPEN_READ: &str = "OpenRead"; -const OP_OPEN_WRITE: &str = "OpenWrite"; -const OP_OPEN_EPHEMERAL: &str = "OpenEphemeral"; -const OP_OPEN_AUTOINDEX: &str = "OpenAutoindex"; -const OP_AGG_FINAL: &str = "AggFinal"; -const OP_AGG_VALUE: &str = "AggValue"; -const OP_AGG_STEP: &str = "AggStep"; -const OP_FUNCTION: &str = "Function"; -const OP_MOVE: &str = "Move"; -const OP_COPY: &str = "Copy"; -const OP_SCOPY: &str = "SCopy"; -const OP_NULL: &str = "Null"; -const OP_NULL_ROW: &str = "NullRow"; -const OP_INT_COPY: &str = "IntCopy"; -const OP_CAST: &str = "Cast"; -const OP_STRING8: &str = "String8"; -const OP_INT64: &str = "Int64"; -const OP_INTEGER: &str = "Integer"; -const OP_REAL: &str = "Real"; -const OP_NOT: &str = "Not"; -const OP_BLOB: &str = "Blob"; -const OP_VARIABLE: &str = "Variable"; -const OP_COUNT: &str = "Count"; -const OP_ROWID: &str = "Rowid"; -const OP_NEWROWID: &str = "NewRowid"; -const OP_OR: &str = "Or"; -const OP_AND: &str = "And"; -const OP_BIT_AND: &str = "BitAnd"; -const OP_BIT_OR: &str = "BitOr"; -const OP_SHIFT_LEFT: &str = "ShiftLeft"; -const OP_SHIFT_RIGHT: &str = "ShiftRight"; -const OP_ADD: &str = "Add"; -const OP_SUBTRACT: &str = "Subtract"; -const OP_MULTIPLY: &str = "Multiply"; -const OP_DIVIDE: &str = "Divide"; -const OP_REMAINDER: &str = "Remainder"; -const OP_CONCAT: &str = "Concat"; -const OP_OFFSET_LIMIT: &str = "OffsetLimit"; -const OP_RESULT_ROW: &str = "ResultRow"; -const OP_HALT: &str = "Halt"; -const OP_HALT_IF_NULL: &str = "HaltIfNull"; - -const MAX_LOOP_COUNT: u8 = 2; -const MAX_TOTAL_INSTRUCTION_COUNT: u32 = 100_000; - -#[derive(Clone, Eq, PartialEq, Hash)] -enum ColumnType { - Single { - datatype: DataType, - nullable: Option, - }, - Record(IntMap), -} - -impl Default for ColumnType { - fn default() -> Self { - Self::Single { - datatype: DataType::Null, - nullable: None, - } - } -} - -impl ColumnType { - fn null() -> Self { - Self::Single { - datatype: DataType::Null, - nullable: Some(true), - } - } - fn map_to_datatype(&self) -> DataType { - match self { - Self::Single { datatype, .. } => *datatype, - Self::Record(_) => DataType::Null, //If we're trying to coerce to a regular Datatype, we can assume a Record is invalid for the context - } - } - fn map_to_nullable(&self) -> Option { - match self { - Self::Single { nullable, .. } => *nullable, - Self::Record(_) => None, //If we're trying to coerce to a regular Datatype, we can assume a Record is invalid for the context - } - } -} - -impl core::fmt::Debug for ColumnType { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Single { datatype, nullable } => { - let nullable_str = match nullable { - Some(true) => "NULL", - Some(false) => "NOT NULL", - None => "NULL?", - }; - write!(f, "{:?} {}", datatype, nullable_str) - } - Self::Record(columns) => { - f.write_str("Record(")?; - let mut column_iter = columns.iter(); - if let Some(item) = column_iter.next() { - write!(f, "{:?}", item)?; - for item in column_iter { - write!(f, ", {:?}", item)?; - } - } - f.write_str(")") - } - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum RegDataType { - Single(ColumnType), - Int(i64), -} - -impl RegDataType { - fn map_to_datatype(&self) -> DataType { - match self { - RegDataType::Single(d) => d.map_to_datatype(), - RegDataType::Int(_) => DataType::Integer, - } - } - fn map_to_nullable(&self) -> Option { - match self { - RegDataType::Single(d) => d.map_to_nullable(), - RegDataType::Int(_) => Some(false), - } - } - fn map_to_columntype(&self) -> ColumnType { - match self { - RegDataType::Single(d) => d.clone(), - RegDataType::Int(_) => ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }, - } - } -} - -impl Default for RegDataType { - fn default() -> Self { - Self::Single(ColumnType::default()) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -struct TableDataType { - cols: IntMap, - is_empty: Option, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum CursorDataType { - Normal(i64), - Pseudo(i64), -} - -impl CursorDataType { - fn columns( - &self, - tables: &IntMap, - registers: &IntMap, - ) -> IntMap { - match self { - Self::Normal(i) => match tables.get(i) { - Some(tab) => tab.cols.clone(), - None => IntMap::new(), - }, - Self::Pseudo(i) => match registers.get(i) { - Some(RegDataType::Single(ColumnType::Record(r))) => r.clone(), - _ => IntMap::new(), - }, - } - } - - fn columns_ref<'s, 'r, 'o>( - &'s self, - tables: &'r IntMap, - registers: &'r IntMap, - ) -> Option<&'o IntMap> - where - 's: 'o, - 'r: 'o, - { - match self { - Self::Normal(i) => match tables.get(i) { - Some(tab) => Some(&tab.cols), - None => None, - }, - Self::Pseudo(i) => match registers.get(i) { - Some(RegDataType::Single(ColumnType::Record(r))) => Some(r), - _ => None, - }, - } - } - - fn columns_mut<'s, 'r, 'o>( - &'s self, - tables: &'r mut IntMap, - registers: &'r mut IntMap, - ) -> Option<&'o mut IntMap> - where - 's: 'o, - 'r: 'o, - { - match self { - Self::Normal(i) => match tables.get_mut(i) { - Some(tab) => Some(&mut tab.cols), - None => None, - }, - Self::Pseudo(i) => match registers.get_mut(i) { - Some(RegDataType::Single(ColumnType::Record(r))) => Some(r), - _ => None, - }, - } - } - - fn table_mut<'s, 'r, 'o>( - &'s self, - tables: &'r mut IntMap, - ) -> Option<&'o mut TableDataType> - where - 's: 'o, - 'r: 'o, - { - match self { - Self::Normal(i) => match tables.get_mut(i) { - Some(tab) => Some(tab), - None => None, - }, - _ => None, - } - } - - fn is_empty(&self, tables: &IntMap) -> Option { - match self { - Self::Normal(i) => match tables.get(i) { - Some(tab) => tab.is_empty, - None => Some(true), - }, - Self::Pseudo(_) => Some(false), //pseudo cursors have exactly one row - } - } -} - -#[allow(clippy::wildcard_in_or_patterns)] -fn affinity_to_type(affinity: u8) -> DataType { - match affinity { - SQLITE_AFF_BLOB => DataType::Blob, - SQLITE_AFF_INTEGER => DataType::Integer, - SQLITE_AFF_NUMERIC => DataType::Numeric, - SQLITE_AFF_REAL => DataType::Float, - SQLITE_AFF_TEXT => DataType::Text, - - SQLITE_AFF_NONE | _ => DataType::Null, - } -} - -#[allow(clippy::wildcard_in_or_patterns)] -fn opcode_to_type(op: &str) -> DataType { - match op { - OP_REAL => DataType::Float, - OP_BLOB => DataType::Blob, - OP_AND | OP_OR => DataType::Bool, - OP_NEWROWID | OP_IDX_ROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => { - DataType::Integer - } - OP_STRING8 => DataType::Text, - OP_COLUMN | _ => DataType::Null, - } -} - -fn root_block_columns( - conn: &mut ConnectionState, -) -> Result>, Error> { - let table_block_columns: Vec<(i64, i64, i64, String, bool)> = execute::iter( - conn, - "SELECT s.dbnum, s.rootpage, col.cid as colnum, col.type, col.\"notnull\" - FROM ( - select 1 dbnum, tss.* from temp.sqlite_schema tss - UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss - ) s - JOIN pragma_table_info(s.name) AS col - WHERE s.type = 'table' - UNION ALL - SELECT s.dbnum, s.rootpage, idx.seqno as colnum, col.type, col.\"notnull\" - FROM ( - select 1 dbnum, tss.* from temp.sqlite_schema tss - UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss - ) s - JOIN pragma_index_info(s.name) AS idx - LEFT JOIN pragma_table_info(s.tbl_name) as col - ON col.cid = idx.cid - WHERE s.type = 'index'", - None, - false, - )? - .filter_map(|res| res.map(|either| either.right()).transpose()) - .map(|row| FromRow::from_row(&row?)) - .collect::, Error>>()?; - - let mut row_info: HashMap<(i64, i64), IntMap> = HashMap::new(); - for (dbnum, block, colnum, datatype, notnull) in table_block_columns { - let row_info = row_info.entry((dbnum, block)).or_default(); - row_info.insert( - colnum, - ColumnType::Single { - datatype: datatype.parse().unwrap_or(DataType::Null), - nullable: Some(!notnull), - }, - ); - } - - Ok(row_info) -} - -struct Sequence(i64); - -impl Sequence { - pub fn new() -> Self { - Self(0) - } - pub fn next(&mut self) -> i64 { - let curr = self.0; - self.0 += 1; - curr - } -} - -#[derive(Debug)] -struct QueryState { - // The number of times each instruction has been visited - pub visited: Vec, - // A unique identifier of the query branch - pub branch_id: i64, - // How many instructions have been executed on this branch (NOT the same as program_i, which is the currently executing instruction of the program) - pub instruction_counter: i64, - // Parent branch this branch was forked from (if any) - pub branch_parent: Option, - // State of the virtual machine - pub mem: MemoryState, - // Results published by the execution - pub result: Option, Option)>>, -} - -impl From<&QueryState> for MemoryState { - fn from(val: &QueryState) -> Self { - val.mem.clone() - } -} - -impl From for MemoryState { - fn from(val: QueryState) -> Self { - val.mem - } -} - -impl From<&QueryState> for BranchParent { - fn from(val: &QueryState) -> Self { - Self { - id: val.branch_id, - idx: val.instruction_counter, - } - } -} - -impl QueryState { - fn get_reference(&self) -> BranchParent { - BranchParent { - id: self.branch_id, - idx: self.instruction_counter, - } - } - fn new_branch(&self, branch_seq: &mut Sequence) -> Self { - Self { - visited: self.visited.clone(), - branch_id: branch_seq.next(), - instruction_counter: 0, - branch_parent: Some(BranchParent { - id: self.branch_id, - idx: self.instruction_counter - 1, //instruction counter is incremented at the start of processing an instruction, so need to subtract 1 to get the 'current' instruction - }), - mem: self.mem.clone(), - result: self.result.clone(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct MemoryState { - // Next instruction to execute - pub program_i: usize, - // Registers - pub r: IntMap, - // Rows that pointers point to - pub p: IntMap, - // Table definitions pointed to by pointers - pub t: IntMap, -} - -impl DebugDiff for MemoryState { - fn diff(&self, prev: &Self) -> String { - let r_diff = self.r.diff(&prev.r); - let p_diff = self.p.diff(&prev.p); - let t_diff = self.t.diff(&prev.t); - - let mut differences = String::new(); - for (i, v) in r_diff { - if !differences.is_empty() { - differences.push('\n'); - } - differences.push_str(&format!("r[{}]={:?}", i, v)) - } - for (i, v) in p_diff { - if !differences.is_empty() { - differences.push('\n'); - } - differences.push_str(&format!("p[{}]={:?}", i, v)) - } - for (i, v) in t_diff { - if !differences.is_empty() { - differences.push('\n'); - } - differences.push_str(&format!("t[{}]={:?}", i, v)) - } - differences - } -} - -struct BranchList { - states: Vec, - visited_branch_state: HashMap, -} - -impl BranchList { - pub fn new(state: QueryState) -> Self { - Self { - states: vec![state], - visited_branch_state: HashMap::new(), - } - } - pub fn push( - &mut self, - mut state: QueryState, - logger: &mut crate::logger::QueryPlanLogger<'_, R, MemoryState, P>, - ) { - logger.add_branch(&state, &state.branch_parent.unwrap()); - match self.visited_branch_state.entry(state.mem) { - hash_map::Entry::Vacant(entry) => { - //this state is not identical to another state, so it will need to be processed - state.mem = entry.key().clone(); //replace state.mem since .entry() moved it - entry.insert(state.get_reference()); - self.states.push(state); - } - hash_map::Entry::Occupied(entry) => { - //already saw a state identical to this one, so no point in processing it - state.mem = entry.key().clone(); //replace state.mem since .entry() moved it - logger.add_result(state, BranchResult::Dedup(*entry.get())); - } - } - } - pub fn pop(&mut self) -> Option { - self.states.pop() - } -} - -// Opcode Reference: https://sqlite.org/opcode.html -pub(super) fn explain( - conn: &mut ConnectionState, - query: &str, -) -> Result<(Vec, Vec>), Error> { - let root_block_cols = root_block_columns(conn)?; - let program: Vec<(i64, String, i64, i64, i64, Vec)> = - execute::iter(conn, AssertSqlSafe(format!("EXPLAIN {query}")), None, false)? - .filter_map(|res| res.map(|either| either.right()).transpose()) - .map(|row| FromRow::from_row(&row?)) - .collect::, Error>>()?; - let program_size = program.len(); - - let mut logger = crate::logger::QueryPlanLogger::new(query, &program); - let mut branch_seq = Sequence::new(); - let mut states = BranchList::new(QueryState { - visited: vec![0; program_size], - branch_id: branch_seq.next(), - branch_parent: None, - instruction_counter: 0, - result: None, - mem: MemoryState { - program_i: 0, - r: IntMap::new(), - t: IntMap::new(), - p: IntMap::new(), - }, - }); - - let mut gas = MAX_TOTAL_INSTRUCTION_COUNT; - let mut result_states = Vec::new(); - - while let Some(mut state) = states.pop() { - while state.mem.program_i < program_size { - let (_, ref opcode, p1, p2, p3, ref p4) = program[state.mem.program_i]; - - logger.add_operation(state.mem.program_i, &state); - state.instruction_counter += 1; - - //limit the number of 'instructions' that can be evaluated - if gas > 0 { - gas -= 1; - } else { - logger.add_result(state, BranchResult::GasLimit); - break; - } - - if state.visited[state.mem.program_i] > MAX_LOOP_COUNT { - logger.add_result(state, BranchResult::LoopLimit); - //avoid (infinite) loops by breaking if we ever hit the same instruction twice - break; - } - - state.visited[state.mem.program_i] += 1; - - match &**opcode { - OP_INIT => { - // start at - state.mem.program_i = p2 as usize; - continue; - } - - OP_GOTO => { - // goto - - state.mem.program_i = p2 as usize; - continue; - } - - OP_GO_SUB => { - // store current instruction in r[p1], goto - state - .mem - .r - .insert(p1, RegDataType::Int(state.mem.program_i as i64)); - state.mem.program_i = p2 as usize; - continue; - } - - OP_FK_IF_ZERO => { - // goto if no constraints are unsatisfied (assumed to be true) - - state.mem.program_i = p2 as usize; - continue; - } - - OP_DECR_JUMP_ZERO | OP_ELSE_EQ | OP_EQ | OP_FILTER | OP_FOUND | OP_GE | OP_GT - | OP_IDX_GE | OP_IDX_GT | OP_IDX_LE | OP_IDX_LT | OP_IF_NO_HOPE | OP_IF_NOT - | OP_IF_NOT_OPEN | OP_IF_NOT_ZERO | OP_IF_NULL_ROW | OP_IF_SMALLER - | OP_INCR_VACUUM | OP_IS_NULL_OR_TYPE | OP_LE | OP_LT | OP_NE | OP_NEXT - | OP_NO_CONFLICT | OP_NOT_EXISTS | OP_ONCE | OP_PREV | OP_PROGRAM - | OP_ROW_SET_READ | OP_ROW_SET_TEST | OP_SEEK_GE | OP_SEEK_GT | OP_SEEK_LE - | OP_SEEK_LT | OP_SEEK_ROW_ID | OP_SEEK_SCAN | OP_SEQUENCE_TEST - | OP_SORTER_NEXT | OP_V_FILTER | OP_V_NEXT => { - // goto or next instruction (depending on actual values) - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - states.push(branch_state, &mut logger); - - state.mem.program_i += 1; - continue; - } - - OP_IS_NULL => { - // goto if p1 is null - - //branch if maybe null - let might_branch = match state.mem.r.get(&p1) { - Some(r_p1) => !matches!(r_p1.map_to_nullable(), Some(false)), - _ => false, - }; - - //nobranch if maybe not null - let might_not_branch = match state.mem.r.get(&p1) { - Some(r_p1) => r_p1.map_to_nullable() != Some(false), - _ => false, - }; - - if might_branch { - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - branch_state - .mem - .r - .insert(p1, RegDataType::Single(ColumnType::default())); - - states.push(branch_state, &mut logger); - } - - if might_not_branch { - state.mem.program_i += 1; - if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = - state.mem.r.get_mut(&p1) - { - *nullable = Some(false); - } - continue; - } else { - logger.add_result(state, BranchResult::Branched); - break; - } - } - - OP_NOT_NULL => { - // goto if p1 is not null - - let might_branch = match state.mem.r.get(&p1) { - Some(r_p1) => !matches!(r_p1.map_to_datatype(), DataType::Null), - _ => false, - }; - - let might_not_branch = match state.mem.r.get(&p1) { - Some(r_p1) => !matches!(r_p1.map_to_nullable(), Some(false)), - _ => false, - }; - - if might_branch { - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = - branch_state.mem.r.get_mut(&p1) - { - *nullable = Some(false); - } - - states.push(branch_state, &mut logger); - } - - if might_not_branch { - state.mem.program_i += 1; - state - .mem - .r - .insert(p1, RegDataType::Single(ColumnType::default())); - continue; - } else { - logger.add_result(state, BranchResult::Branched); - break; - } - } - - OP_MUST_BE_INT => { - // if p1 can be coerced to int, continue - // if p1 cannot be coerced to int, error if p2 == 0, else jump to p2 - - //don't bother checking actual types, just don't branch to instruction 0 - if p2 != 0 { - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - states.push(branch_state, &mut logger); - } - - state.mem.program_i += 1; - continue; - } - - OP_IF => { - // goto if r[p1] is true (1) or r[p1] is null and p3 is nonzero - - let might_branch = match state.mem.r.get(&p1) { - Some(RegDataType::Int(r_p1)) => *r_p1 != 0, - _ => true, - }; - - let might_not_branch = match state.mem.r.get(&p1) { - Some(RegDataType::Int(r_p1)) => *r_p1 == 0, - _ => true, - }; - - if might_branch { - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - if p3 == 0 { - branch_state.mem.r.insert(p1, RegDataType::Int(1)); - } - - states.push(branch_state, &mut logger); - } - - if might_not_branch { - state.mem.program_i += 1; - if p3 == 0 { - state.mem.r.insert(p1, RegDataType::Int(0)); - } - continue; - } else { - logger.add_result(state, BranchResult::Branched); - break; - } - } - - OP_IF_POS => { - // goto if r[p1] is true (1) or r[p1] is null and p3 is nonzero - - // as a workaround for large offset clauses, both branches will be attempted after 1 loop - - let might_branch = match state.mem.r.get(&p1) { - Some(RegDataType::Int(r_p1)) => *r_p1 >= 1, - _ => true, - }; - - let might_not_branch = match state.mem.r.get(&p1) { - Some(RegDataType::Int(r_p1)) => *r_p1 < 1, - _ => true, - }; - - let loop_detected = state.visited[state.mem.program_i] > 1; - if might_branch || loop_detected { - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - if let Some(RegDataType::Int(r_p1)) = branch_state.mem.r.get_mut(&p1) { - *r_p1 -= 1; - } - states.push(branch_state, &mut logger); - } - - if might_not_branch { - state.mem.program_i += 1; - continue; - } else if loop_detected { - state.mem.program_i += 1; - if matches!(state.mem.r.get_mut(&p1), Some(RegDataType::Int(..))) { - //forget the exact value, in case some later cares - state.mem.r.insert( - p1, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } - continue; - } else { - logger.add_result(state, BranchResult::Branched); - break; - } - } - - OP_REWIND | OP_LAST | OP_SORT | OP_SORTER_SORT => { - // goto if cursor p1 is empty and p2 != 0, else next instruction - - if p2 == 0 { - state.mem.program_i += 1; - continue; - } - - if let Some(cursor) = state.mem.p.get(&p1) { - if matches!(cursor.is_empty(&state.mem.t), None | Some(true)) { - //only take this branch if the cursor is empty - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - - if let Some(cur) = branch_state.mem.p.get(&p1) { - if let Some(tab) = cur.table_mut(&mut branch_state.mem.t) { - tab.is_empty = Some(true); - } - } - states.push(branch_state, &mut logger); - } - - if matches!(cursor.is_empty(&state.mem.t), None | Some(false)) { - //only take this branch if the cursor is non-empty - state.mem.program_i += 1; - continue; - } else { - logger.add_result(state, BranchResult::Branched); - break; - } - } - - logger.add_result(state, BranchResult::Branched); - break; - } - - OP_INIT_COROUTINE => { - // goto or next instruction (depending on actual values) - - state.mem.r.insert(p1, RegDataType::Int(p3)); - - if p2 != 0 { - state.mem.program_i = p2 as usize; - } else { - state.mem.program_i += 1; - } - continue; - } - - OP_END_COROUTINE => { - // jump to p2 of the yield instruction pointed at by register p1 - - if let Some(RegDataType::Int(yield_i)) = state.mem.r.get(&p1) { - if let Some((_, yield_op, _, yield_p2, _, _)) = - program.get(*yield_i as usize) - { - if OP_YIELD == yield_op.as_str() { - state.mem.program_i = (*yield_p2) as usize; - state.mem.r.remove(&p1); - continue; - } else { - logger.add_result(state, BranchResult::Error); - break; - } - } else { - logger.add_result(state, BranchResult::Error); - break; - } - } else { - logger.add_result(state, BranchResult::Error); - break; - } - } - - OP_RETURN => { - // jump to the instruction after the instruction pointed at by register p1 - - if let Some(RegDataType::Int(return_i)) = state.mem.r.get(&p1) { - state.mem.program_i = (*return_i + 1) as usize; - state.mem.r.remove(&p1); - continue; - } else if p3 == 1 { - state.mem.program_i += 1; - continue; - } else { - logger.add_result(state, BranchResult::Error); - break; - } - } - - OP_YIELD => { - // jump to p2 of the yield instruction pointed at by register p1, store prior instruction in p1 - - if let Some(RegDataType::Int(yield_i)) = state.mem.r.get_mut(&p1) { - let program_i: usize = state.mem.program_i; - - //if yielding to a yield operation, go to the NEXT instruction after that instruction - if program - .get(*yield_i as usize) - .map(|(_, yield_op, _, _, _, _)| yield_op.as_str()) - == Some(OP_YIELD) - { - state.mem.program_i = (*yield_i + 1) as usize; - *yield_i = program_i as i64; - continue; - } else { - state.mem.program_i = *yield_i as usize; - *yield_i = program_i as i64; - continue; - } - } else { - logger.add_result(state, BranchResult::Error); - break; - } - } - - OP_JUMP => { - // goto one of , , or based on the result of a prior compare - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p1 as usize; - states.push(branch_state, &mut logger); - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p2 as usize; - states.push(branch_state, &mut logger); - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i = p3 as usize; - states.push(branch_state, &mut logger); - } - - OP_COLUMN => { - //Get the row stored at p1, or NULL; get the column stored at p2, or NULL - let value: ColumnType = state - .mem - .p - .get(&p1) - .and_then(|c| c.columns_ref(&state.mem.t, &state.mem.r)) - .and_then(|cc| cc.get(&p2)) - .cloned() - .unwrap_or_default(); - - // insert into p3 the datatype of the col - state.mem.r.insert(p3, RegDataType::Single(value)); - } - - OP_SEQUENCE => { - //Copy sequence number from cursor p1 to register p2, increment cursor p1 sequence number - - //Cursor emulation doesn't sequence value, but it is an int - state.mem.r.insert( - p2, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } - - OP_ROW_DATA | OP_SORTER_DATA => { - //Get entire row from cursor p1, store it into register p2 - if let Some(record) = state - .mem - .p - .get(&p1) - .map(|c| c.columns(&state.mem.t, &state.mem.r)) - { - state - .mem - .r - .insert(p2, RegDataType::Single(ColumnType::Record(record))); - } else { - state - .mem - .r - .insert(p2, RegDataType::Single(ColumnType::Record(IntMap::new()))); - } - } - - OP_MAKE_RECORD => { - // p3 = Record([p1 .. p1 + p2]) - let mut record = Vec::with_capacity(p2 as usize); - for reg in p1..p1 + p2 { - record.push( - state - .mem - .r - .get(®) - .map(|d| d.map_to_columntype()) - .unwrap_or(ColumnType::default()), - ); - } - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Record(IntMap::from_dense_record(&record))), - ); - } - - OP_INSERT | OP_IDX_INSERT | OP_SORTER_INSERT => { - if let Some(RegDataType::Single(columntype)) = state.mem.r.get(&p2) { - match columntype { - ColumnType::Record(record) => { - if let Some(TableDataType { cols, is_empty }) = state - .mem - .p - .get(&p1) - .and_then(|cur| cur.table_mut(&mut state.mem.t)) - { - // Insert the record into wherever pointer p1 is - *cols = record.clone(); - *is_empty = Some(false); - } - } - ColumnType::Single { - datatype: DataType::Null, - nullable: _, - } => { - if let Some(TableDataType { is_empty, .. }) = state - .mem - .p - .get(&p1) - .and_then(|cur| cur.table_mut(&mut state.mem.t)) - { - // Insert a null record into wherever pointer p1 is - *is_empty = Some(false); - } - } - _ => {} - } - } - //Noop if the register p2 isn't a record, or if pointer p1 does not exist - } - - OP_DELETE => { - // delete a record from cursor p1 - if let Some(TableDataType { is_empty, .. }) = state - .mem - .p - .get(&p1) - .and_then(|cur| cur.table_mut(&mut state.mem.t)) - { - if *is_empty == Some(false) { - *is_empty = None; //the cursor might be empty now - } - } - } - - OP_OPEN_PSEUDO => { - // Create a cursor p1 aliasing the record from register p2 - state.mem.p.insert(p1, CursorDataType::Pseudo(p2)); - } - - OP_OPEN_DUP => { - if let Some(cur) = state.mem.p.get(&p2) { - state.mem.p.insert(p1, cur.clone()); - } - } - - OP_OPEN_READ | OP_OPEN_WRITE => { - //Create a new pointer which is referenced by p1, take column metadata from db schema if found - let table_info = if p3 == 0 || p3 == 1 { - if let Some(columns) = root_block_cols.get(&(p3, p2)) { - TableDataType { - cols: columns.clone(), - is_empty: None, - } - } else { - TableDataType { - cols: IntMap::new(), - is_empty: None, - } - } - } else { - TableDataType { - cols: IntMap::new(), - is_empty: None, - } - }; - - state.mem.t.insert(state.mem.program_i as i64, table_info); - state - .mem - .p - .insert(p1, CursorDataType::Normal(state.mem.program_i as i64)); - } - - OP_OPEN_EPHEMERAL | OP_OPEN_AUTOINDEX | OP_SORTER_OPEN => { - //Create a new pointer which is referenced by p1 - let table_info = TableDataType { - cols: IntMap::from_elem(ColumnType::null(), p2 as usize), - is_empty: Some(true), - }; - - state.mem.t.insert(state.mem.program_i as i64, table_info); - state - .mem - .p - .insert(p1, CursorDataType::Normal(state.mem.program_i as i64)); - } - - OP_VARIABLE => { - // r[p2] = - state - .mem - .r - .insert(p2, RegDataType::Single(ColumnType::null())); - } - - // if there is a value in p3, and the query passes, then - // we know that it is not nullable - OP_HALT_IF_NULL => { - if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) = - state.mem.r.get_mut(&p3) - { - *nullable = Some(false); - } - } - - OP_FUNCTION => { - // r[p3] = func( _ ), registered function name is in p4 - match from_utf8(p4).map_err(Error::protocol)? { - "last_insert_rowid(0)" => { - // last_insert_rowid() -> INTEGER - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } - "date(-1)" | "time(-1)" | "datetime(-1)" | "strftime(-1)" => { - // date|time|datetime|strftime(...) -> TEXT - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Text, - nullable: Some(p2 != 0), //never a null result if no argument provided - }), - ); - } - "julianday(-1)" => { - // julianday(...) -> REAL - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Float, - nullable: Some(p2 != 0), //never a null result if no argument provided - }), - ); - } - "unixepoch(-1)" => { - // unixepoch(p2...) -> INTEGER - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(p2 != 0), //never a null result if no argument provided - }), - ); - } - - _ => logger.add_unknown_operation(state.mem.program_i), - } - } - - OP_NULL_ROW => { - // all columns in cursor X are potentially nullable - if let Some(cols) = state - .mem - .p - .get_mut(&p1) - .and_then(|c| c.columns_mut(&mut state.mem.t, &mut state.mem.r)) - { - for col in cols.values_mut() { - if let ColumnType::Single { - ref mut nullable, .. - } = col - { - *nullable = Some(true); - } - } - } - //else we don't know about the cursor - } - - OP_AGG_STEP | OP_AGG_VALUE => { - //assume that AGG_FINAL will be called - let p4 = from_utf8(p4).map_err(Error::protocol)?; - - if p4.starts_with("count(") - || p4.starts_with("row_number(") - || p4.starts_with("rank(") - || p4.starts_with("dense_rank(") - || p4.starts_with("ntile(") - { - // count(_) -> INTEGER - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } else if p4.starts_with("percent_rank(") || p4.starts_with("cume_dist") { - // percent_rank(_) -> REAL - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Float, - nullable: Some(false), - }), - ); - } else if p4.starts_with("sum(") { - if let Some(r_p2) = state.mem.r.get(&p2) { - let datatype = match r_p2.map_to_datatype() { - // The result of a `SUM()` can be arbitrarily large - DataType::Integer | DataType::Int4 | DataType::Bool => { - DataType::Integer - } - _ => DataType::Float, - }; - let nullable = r_p2.map_to_nullable(); - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { datatype, nullable }), - ); - } - } else if p4.starts_with("lead(") || p4.starts_with("lag(") { - if let Some(r_p2) = state.mem.r.get(&p2) { - let datatype = r_p2.map_to_datatype(); - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype, - nullable: Some(true), - }), - ); - } - } else if let Some(v) = state.mem.r.get(&p2).cloned() { - // r[p3] = AGG ( r[p2] ) - state.mem.r.insert(p3, v); - } - } - - OP_AGG_FINAL => { - let p4 = from_utf8(p4).map_err(Error::protocol)?; - - if p4.starts_with("count(") - || p4.starts_with("row_number(") - || p4.starts_with("rank(") - || p4.starts_with("dense_rank(") - || p4.starts_with("ntile(") - { - // count(_) -> INTEGER - state.mem.r.insert( - p1, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } else if p4.starts_with("percent_rank(") || p4.starts_with("cume_dist") { - // percent_rank(_) -> REAL - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Float, - nullable: Some(false), - }), - ); - } else if p4.starts_with("lead(") || p4.starts_with("lag(") { - if let Some(r_p2) = state.mem.r.get(&p2) { - let datatype = r_p2.map_to_datatype(); - state.mem.r.insert( - p3, - RegDataType::Single(ColumnType::Single { - datatype, - nullable: Some(true), - }), - ); - } - } - } - - OP_CAST => { - // affinity(r[p1]) - if let Some(v) = state.mem.r.get_mut(&p1) { - *v = RegDataType::Single(ColumnType::Single { - datatype: affinity_to_type(p2 as u8), - nullable: v.map_to_nullable(), - }); - } - } - - OP_SCOPY | OP_INT_COPY => { - // r[p2] = r[p1] - if let Some(v) = state.mem.r.get(&p1).cloned() { - state.mem.r.insert(p2, v); - } - } - - OP_COPY => { - // r[p2..=p2+p3] = r[p1..=p1+p3] - if p3 >= 0 { - for i in 0..=p3 { - let src = p1 + i; - let dst = p2 + i; - if let Some(v) = state.mem.r.get(&src).cloned() { - state.mem.r.insert(dst, v); - } - } - } - } - - OP_MOVE => { - // r[p2..p2+p3] = r[p1..p1+p3]; r[p1..p1+p3] = null - if p3 >= 1 { - for i in 0..p3 { - let src = p1 + i; - let dst = p2 + i; - if let Some(v) = state.mem.r.get(&src).cloned() { - state.mem.r.insert(dst, v); - state - .mem - .r - .insert(src, RegDataType::Single(ColumnType::null())); - } - } - } - } - - OP_INTEGER => { - // r[p2] = p1 - state.mem.r.insert(p2, RegDataType::Int(p1)); - } - - OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_IDX_ROWID - | OP_NEWROWID => { - // r[p2] = - state.mem.r.insert( - p2, - RegDataType::Single(ColumnType::Single { - datatype: opcode_to_type(opcode), - nullable: Some(false), - }), - ); - } - - OP_NOT => { - // r[p2] = NOT r[p1] - if let Some(a) = state.mem.r.get(&p1).cloned() { - state.mem.r.insert(p2, a); - } - } - - OP_NULL => { - // r[p2..p3] = null - let idx_range = if p2 < p3 { p2..=p3 } else { p2..=p2 }; - - for idx in idx_range { - state - .mem - .r - .insert(idx, RegDataType::Single(ColumnType::null())); - } - } - - OP_OR | OP_AND | OP_BIT_AND | OP_BIT_OR | OP_SHIFT_LEFT | OP_SHIFT_RIGHT - | OP_ADD | OP_SUBTRACT | OP_MULTIPLY | OP_DIVIDE | OP_REMAINDER | OP_CONCAT => { - // r[p3] = r[p1] + r[p2] - let value = match (state.mem.r.get(&p1), state.mem.r.get(&p2)) { - (Some(a), Some(b)) => RegDataType::Single(ColumnType::Single { - datatype: if matches!(a.map_to_datatype(), DataType::Null) { - b.map_to_datatype() - } else { - a.map_to_datatype() - }, - nullable: match (a.map_to_nullable(), b.map_to_nullable()) { - (Some(a_n), Some(b_n)) => Some(a_n | b_n), - (Some(a_n), None) => Some(a_n), - (None, Some(b_n)) => Some(b_n), - (None, None) => None, - }, - }), - (Some(v), None) => RegDataType::Single(ColumnType::Single { - datatype: v.map_to_datatype(), - nullable: None, - }), - (None, Some(v)) => RegDataType::Single(ColumnType::Single { - datatype: v.map_to_datatype(), - nullable: None, - }), - _ => RegDataType::default(), - }; - - state.mem.r.insert(p3, value); - } - - OP_OFFSET_LIMIT => { - // r[p2] = if r[p2] < 0 { r[p1] } else if r[p1]<0 { -1 } else { r[p1] + r[p3] } - state.mem.r.insert( - p2, - RegDataType::Single(ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false), - }), - ); - } - - OP_RESULT_ROW => { - // output = r[p1 .. p1 + p2] - let result: Vec<_> = (p1..p1 + p2) - .map(|i| { - state - .mem - .r - .get(&i) - .map(RegDataType::map_to_columntype) - .unwrap_or_default() - }) - .collect(); - - let mut branch_state = state.new_branch(&mut branch_seq); - branch_state.mem.program_i += 1; - states.push(branch_state, &mut logger); - - logger.add_result( - state, - BranchResult::Result(IntMap::from_dense_record(&result)), - ); - - result_states.push(result); - break; - } - - OP_HALT => { - logger.add_result(state, BranchResult::Halt); - break; - } - - _ => { - // ignore unsupported operations - // if we fail to find an r later, we just give up - logger.add_unknown_operation(state.mem.program_i); - } - } - - state.mem.program_i += 1; - } - } - - let mut output: Vec> = Vec::new(); - let mut nullable: Vec> = Vec::new(); - - while let Some(result) = result_states.pop() { - // find the datatype info from each ResultRow execution - for (idx, this_col) in result.into_iter().enumerate() { - let this_type = this_col.map_to_datatype(); - let this_nullable = this_col.map_to_nullable(); - if output.len() == idx { - output.push(Some(SqliteTypeInfo(this_type))); - } else if output[idx].is_none() - || matches!(output[idx], Some(SqliteTypeInfo(DataType::Null))) - && !matches!(this_type, DataType::Null) - { - output[idx] = Some(SqliteTypeInfo(this_type)); - } - - if nullable.len() == idx { - nullable.push(this_nullable); - } else if let Some(ref mut null) = nullable[idx] { - //if any ResultRow's column is nullable, the final result is nullable - if let Some(this_null) = this_nullable { - *null |= this_null; - } - } else { - nullable[idx] = this_nullable; - } - } - } - - let output = output - .into_iter() - .map(|o| o.unwrap_or(SqliteTypeInfo(DataType::Null))) - .collect(); - - Ok((output, nullable)) -} - -#[test] -fn test_root_block_columns_has_types() { - use crate::SqliteConnectOptions; - use std::str::FromStr; - let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); - let mut conn = super::EstablishParams::from_options(&conn_options) - .unwrap() - .establish() - .unwrap(); - - assert!(execute::iter( - &mut conn, - r"CREATE TABLE t(a INTEGER PRIMARY KEY, b_null TEXT NULL, b TEXT NOT NULL);", - None, - false - ) - .unwrap() - .next() - .is_some()); - assert!( - execute::iter(&mut conn, r"CREATE INDEX i1 on t (a,b_null);", None, false) - .unwrap() - .next() - .is_some() - ); - assert!(execute::iter( - &mut conn, - r"CREATE UNIQUE INDEX i2 on t (a,b_null);", - None, - false - ) - .unwrap() - .next() - .is_some()); - assert!(execute::iter( - &mut conn, - r"CREATE TABLE t2(a INTEGER NOT NULL, b_null NUMERIC NULL, b NUMERIC NOT NULL);", - None, - false - ) - .unwrap() - .next() - .is_some()); - assert!(execute::iter( - &mut conn, - r"CREATE INDEX t2i1 on t2 (a,b_null);", - None, - false - ) - .unwrap() - .next() - .is_some()); - assert!(execute::iter( - &mut conn, - r"CREATE UNIQUE INDEX t2i2 on t2 (a,b);", - None, - false - ) - .unwrap() - .next() - .is_some()); - - assert!(execute::iter( - &mut conn, - r"CREATE TEMPORARY TABLE t3(a TEXT PRIMARY KEY, b REAL NOT NULL, b_null REAL NULL);", - None, - false - ) - .unwrap() - .next() - .is_some()); - - let table_block_nums: HashMap = execute::iter( - &mut conn, - r"select name, 0 db_seq, rootpage from main.sqlite_schema UNION ALL select name, 1 db_seq, rootpage from temp.sqlite_schema", - None, - false, - ) - .unwrap() - .filter_map(|res| res.map(|either| either.right()).transpose()) - .map(|row| FromRow::from_row(row.as_ref().unwrap())) - .map(|row| row.map(|(name,seq,block)|(name,(seq,block)))) - .collect::, Error>>() - .unwrap(); - - let root_block_cols = root_block_columns(&mut conn).unwrap(); - - // there should be 7 tables/indexes created explicitly, plus 1 autoindex for t3 - assert_eq!(8, root_block_cols.len()); - - //prove that we have some information for each table & index - for (name, db_seq_block) in dbg!(&table_block_nums) { - assert!( - root_block_cols.contains_key(db_seq_block), - "{:?}", - (name, db_seq_block) - ); - } - - //prove that each block has the correct information - { - let table_db_block = table_block_nums["t"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(true) //sqlite primary key columns are nullable unless declared not null - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Text, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&1) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Text, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&2) - ); - } - - { - let table_db_block = table_block_nums["i1"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(true) //sqlite primary key columns are nullable unless declared not null - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Text, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&1) - ); - } - - { - let table_db_block = table_block_nums["i2"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(true) //sqlite primary key columns are nullable unless declared not null - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Text, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&1) - ); - } - - { - let table_db_block = table_block_nums["t2"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Null, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&1) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Null, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&2) - ); - } - - { - let table_db_block = table_block_nums["t2i1"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Null, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&1) - ); - } - - { - let table_db_block = table_block_nums["t2i2"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Integer, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Null, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&1) - ); - } - - { - let table_db_block = table_block_nums["t3"]; - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Text, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&0) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Float, - nullable: Some(false) - }), - root_block_cols[&table_db_block].get(&1) - ); - assert_eq!( - Some(&ColumnType::Single { - datatype: DataType::Float, - nullable: Some(true) - }), - root_block_cols[&table_db_block].get(&2) - ); - } -} - -#[test] -fn test_explain() { - use crate::SqliteConnectOptions; - use std::str::FromStr; - let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); - let mut conn = super::EstablishParams::from_options(&conn_options) - .unwrap() - .establish() - .unwrap(); - - assert!(execute::iter( - &mut conn, - r"CREATE TABLE an_alias(a INTEGER PRIMARY KEY);", - None, - false - ) - .unwrap() - .next() - .is_some()); - - assert!(execute::iter( - &mut conn, - r"CREATE TABLE not_an_alias(a INT PRIMARY KEY);", - None, - false - ) - .unwrap() - .next() - .is_some()); - - assert!( - if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM an_alias") { - ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] - } else { - false - } - ); - assert!( - if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM not_an_alias") { - ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(true)] - } else { - false - } - ); - assert!( - if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM an_alias") { - ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] - } else { - false - } - ); - assert!( - if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM not_an_alias") { - ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] - } else { - false - } - ); -} diff --git a/vendor/sqlx-sqlite/src/connection/handle.rs b/vendor/sqlx-sqlite/src/connection/handle.rs deleted file mode 100644 index e89179b30e..0000000000 --- a/vendor/sqlx-sqlite/src/connection/handle.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::ffi::{c_int, CStr, CString}; -use std::ptr::NonNull; -use std::{io, ptr}; - -use crate::error::Error; -use libsqlite3_sys::{ - sqlite3, sqlite3_close, sqlite3_exec, sqlite3_extended_result_codes, sqlite3_get_autocommit, - sqlite3_last_insert_rowid, sqlite3_open_v2, SQLITE_OK, -}; - -use crate::SqliteError; - -/// Managed SQLite3 database handle. -/// The database handle will be closed when this is dropped. -#[derive(Debug)] -pub(crate) struct ConnectionHandle(NonNull); - -// A SQLite3 handle is safe to send between threads, provided not more than -// one is accessing it at the same time. This is upheld as long as [SQLITE_CONFIG_MULTITHREAD] is -// enabled and [SQLITE_THREADSAFE] was enabled when sqlite was compiled. We refuse to work -// if these conditions are not upheld. -// -// -// - -unsafe impl Send for ConnectionHandle {} - -impl ConnectionHandle { - pub(crate) fn open(filename: &CStr, flags: c_int) -> Result { - let mut handle = ptr::null_mut(); - - // - let status = unsafe { sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, ptr::null()) }; - - // SAFETY: the database is still initialized as long as the pointer is not `NULL`. - // We need to close it even if there's an error. - let mut handle = Self(NonNull::new(handle).ok_or_else(|| { - Error::Io(io::Error::new( - io::ErrorKind::OutOfMemory, - "SQLite is unable to allocate memory to hold the sqlite3 object", - )) - })?); - - if status != SQLITE_OK { - return Err(Error::Database(Box::new(handle.expect_error()))); - } - - // Enable extended result codes - // https://www.sqlite.org/c3ref/extended_result_codes.html - unsafe { - // This only returns a non-OK code if SQLite is built with `SQLITE_ENABLE_API_ARMOR` - // and the database pointer is `NULL` or already closed. - // - // The invariants of this type guarantee that neither is true. - sqlite3_extended_result_codes(handle.as_ptr(), 1); - } - - Ok(handle) - } - - #[inline] - pub(crate) fn as_ptr(&self) -> *mut sqlite3 { - self.0.as_ptr() - } - - pub(crate) fn as_non_null_ptr(&self) -> NonNull { - self.0 - } - - pub(crate) fn call_with_result( - &mut self, - call: impl FnOnce(*mut sqlite3) -> c_int, - ) -> Result<(), SqliteError> { - let res = call(self.as_ptr()); - - if res == SQLITE_OK { - Ok(()) - } else { - Err(self - .last_error() - .unwrap_or_else(|| SqliteError::from_code(res))) - } - } - - pub(crate) fn in_transaction(&mut self) -> bool { - // SAFETY: we have exclusive access to the database handle - let ret = unsafe { sqlite3_get_autocommit(self.as_ptr()) }; - ret == 0 - } - - pub(crate) fn last_insert_rowid(&mut self) -> i64 { - // SAFETY: we have exclusive access to the database handle - unsafe { sqlite3_last_insert_rowid(self.as_ptr()) } - } - - pub(crate) fn last_error(&mut self) -> Option { - // SAFETY: we have exclusive access to the database handle - unsafe { SqliteError::try_new(self.as_ptr()) } - } - - #[track_caller] - pub(crate) fn expect_error(&mut self) -> SqliteError { - self.last_error() - .expect("expected error code to be set in current context") - } - - pub(crate) fn exec(&mut self, query: impl Into) -> Result<(), Error> { - let query = query.into(); - let query = CString::new(query).map_err(|_| err_protocol!("query contains nul bytes"))?; - - // SAFETY: we have exclusive access to the database handle - unsafe { - #[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))] - loop { - let status = sqlite3_exec( - self.as_ptr(), - query.as_ptr(), - // callback if we wanted result rows - None, - // callback data - ptr::null_mut(), - // out-pointer for the error message, we just use `SqliteError::new()` - ptr::null_mut(), - ); - - match status { - SQLITE_OK => return Ok(()), - #[cfg(feature = "unlock-notify")] - libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => { - crate::statement::unlock_notify::wait(self.as_ptr())? - } - _ => return Err(SqliteError::new(self.as_ptr()).into()), - } - } - } - } -} - -impl Drop for ConnectionHandle { - fn drop(&mut self) { - unsafe { - // https://sqlite.org/c3ref/close.html - let status = sqlite3_close(self.0.as_ptr()); - if status != SQLITE_OK { - // this should *only* happen due to an internal bug in SQLite where we left - // SQLite handles open - panic!("{}", SqliteError::new(self.0.as_ptr())); - } - } - } -} diff --git a/vendor/sqlx-sqlite/src/connection/intmap.rs b/vendor/sqlx-sqlite/src/connection/intmap.rs deleted file mode 100644 index f8308067cc..0000000000 --- a/vendor/sqlx-sqlite/src/connection/intmap.rs +++ /dev/null @@ -1,175 +0,0 @@ -// Bad casts in this module SHOULD NOT result in a SQL injection -// https://github.com/launchbadge/sqlx/issues/3440 -#![allow( - clippy::cast_possible_truncation, - clippy::cast_possible_wrap, - clippy::cast_sign_loss -)] -use std::cmp::Ordering; -use std::{fmt::Debug, hash::Hash}; - -/// Simplistic map implementation built on a Vec of Options (index = key) -#[derive(Debug, Clone, Eq)] -pub(crate) struct IntMap(Vec>); - -impl Default for IntMap { - fn default() -> Self { - IntMap(Vec::new()) - } -} - -impl IntMap { - pub(crate) fn new() -> Self { - Self(Vec::new()) - } - - pub(crate) fn expand(&mut self, size: i64) -> usize { - let idx = usize::try_from(size).expect("negative column index unsupported"); - if idx >= self.0.len() { - let new_len = idx.checked_add(1).expect("idx + 1 overflowed"); - - self.0.resize_with(new_len, || None); - } - idx - } - - pub(crate) fn values_mut(&mut self) -> impl Iterator { - self.0.iter_mut().filter_map(Option::as_mut) - } - - pub(crate) fn values(&self) -> impl Iterator { - self.0.iter().filter_map(Option::as_ref) - } - - pub(crate) fn get(&self, idx: &i64) -> Option<&V> { - let idx: usize = (*idx) - .try_into() - .expect("negative column index unsupported"); - - match self.0.get(idx) { - Some(Some(v)) => Some(v), - _ => None, - } - } - - pub(crate) fn get_mut(&mut self, idx: &i64) -> Option<&mut V> { - let idx: usize = (*idx) - .try_into() - .expect("negative column index unsupported"); - match self.0.get_mut(idx) { - Some(Some(v)) => Some(v), - _ => None, - } - } - - pub(crate) fn insert(&mut self, idx: i64, value: V) -> Option { - let idx: usize = self.expand(idx); - - self.0[idx].replace(value) - } - - pub(crate) fn remove(&mut self, idx: &i64) -> Option { - let idx: usize = (*idx) - .try_into() - .expect("negative column index unsupported"); - - let item = self.0.get_mut(idx); - match item { - Some(content) => content.take(), - None => None, - } - } - - pub(crate) fn iter(&self) -> impl Iterator> { - self.0.iter().map(Option::as_ref) - } - - pub(crate) fn iter_entries(&self) -> impl Iterator { - self.0 - .iter() - .enumerate() - .filter_map(|(i, v)| v.as_ref().map(|v: &V| (i as i64, v))) - } - - pub(crate) fn last_index(&self) -> Option { - self.0.iter().rposition(|v| v.is_some()).map(|i| i as i64) - } -} - -impl IntMap { - pub(crate) fn get_mut_or_default(&mut self, idx: &i64) -> &mut V { - let idx: usize = self.expand(*idx); - self.0[idx].get_or_insert_default() - } -} - -impl IntMap { - pub(crate) fn from_elem(elem: V, len: usize) -> Self { - Self(vec![Some(elem); len]) - } - pub(crate) fn from_dense_record(record: &[V]) -> Self { - Self(record.iter().cloned().map(Some).collect()) - } -} - -impl IntMap { - /// get the additions to this intmap compared to the prev intmap - pub(crate) fn diff<'a, 'b, 'c>( - &'a self, - prev: &'b Self, - ) -> impl Iterator)> - where - 'a: 'c, - 'b: 'c, - { - let self_pad = if prev.0.len() > self.0.len() { - prev.0.len() - self.0.len() - } else { - 0 - }; - self.iter() - .chain(std::iter::repeat_n(None, self_pad)) - .zip(prev.iter().chain(std::iter::repeat(None))) - .enumerate() - .filter(|(_i, (n, p))| n != p) - .map(|(i, (n, _p))| (i, n)) - } -} - -impl Hash for IntMap { - fn hash(&self, state: &mut H) { - for value in self.values() { - value.hash(state); - } - } -} - -impl PartialEq for IntMap { - fn eq(&self, other: &Self) -> bool { - match self.0.len().cmp(&other.0.len()) { - Ordering::Greater => { - self.0[..other.0.len()] == other.0 - && self.0[other.0.len()..].iter().all(Option::is_none) - } - Ordering::Less => { - other.0[..self.0.len()] == self.0 - && other.0[self.0.len()..].iter().all(Option::is_none) - } - Ordering::Equal => self.0 == other.0, - } - } -} - -impl FromIterator<(i64, V)> for IntMap { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let mut result = Self(Vec::new()); - for (idx, val) in iter { - let idx = result.expand(idx); - result.0[idx] = Some(val); - } - result - } -} diff --git a/vendor/sqlx-sqlite/src/connection/mod.rs b/vendor/sqlx-sqlite/src/connection/mod.rs deleted file mode 100644 index 218c747143..0000000000 --- a/vendor/sqlx-sqlite/src/connection/mod.rs +++ /dev/null @@ -1,599 +0,0 @@ -use std::cmp::Ordering; -use std::ffi::CStr; -use std::fmt::Write; -use std::fmt::{self, Debug, Formatter}; -use std::future::Future; -use std::os::raw::{c_char, c_int, c_void}; -use std::panic::catch_unwind; -use std::ptr; -use std::ptr::NonNull; - -use futures_intrusive::sync::MutexGuard; -use libsqlite3_sys::{ - sqlite3, sqlite3_commit_hook, sqlite3_progress_handler, sqlite3_rollback_hook, - sqlite3_update_hook, SQLITE_DELETE, SQLITE_INSERT, SQLITE_UPDATE, -}; -#[cfg(feature = "preupdate-hook")] -pub use preupdate_hook::*; - -pub(crate) use handle::ConnectionHandle; -use sqlx_core::common::StatementCache; -pub(crate) use sqlx_core::connection::*; -use sqlx_core::error::Error; -use sqlx_core::executor::Executor; -use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr}; -use sqlx_core::transaction::Transaction; - -use crate::connection::establish::EstablishParams; -use crate::connection::worker::ConnectionWorker; -use crate::options::OptimizeOnClose; -use crate::statement::VirtualStatement; -use crate::{Sqlite, SqliteConnectOptions, SqliteError}; - -pub(crate) mod collation; -pub(crate) mod describe; -pub(crate) mod establish; -pub(crate) mod execute; -mod executor; -mod explain; -mod handle; -pub(crate) mod intmap; -#[cfg(feature = "preupdate-hook")] -mod preupdate_hook; - -#[cfg(feature = "deserialize")] -pub(crate) mod deserialize; - -mod worker; - -/// A connection to an open [Sqlite] database. -/// -/// Because SQLite is an in-process database accessed by blocking API calls, SQLx uses a background -/// thread and communicates with it via channels to allow non-blocking access to the database. -/// -/// Dropping this struct will signal the worker thread to quit and close the database, though -/// if an error occurs there is no way to pass it back to the user this way. -/// -/// You can explicitly call [`.close()`][Self::close] to ensure the database is closed successfully -/// or get an error otherwise. -pub struct SqliteConnection { - optimize_on_close: OptimizeOnClose, - pub(crate) worker: ConnectionWorker, - pub(crate) row_channel_size: usize, -} - -pub struct LockedSqliteHandle<'a> { - pub(crate) guard: MutexGuard<'a, ConnectionState>, -} - -/// Represents a callback handler that will be shared with the underlying sqlite3 connection. -pub(crate) struct Handler(NonNull bool + Send + 'static>); -unsafe impl Send for Handler {} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum SqliteOperation { - Insert, - Update, - Delete, - Unknown(i32), -} - -impl From for SqliteOperation { - fn from(value: i32) -> Self { - match value { - SQLITE_INSERT => SqliteOperation::Insert, - SQLITE_UPDATE => SqliteOperation::Update, - SQLITE_DELETE => SqliteOperation::Delete, - code => SqliteOperation::Unknown(code), - } - } -} - -pub struct UpdateHookResult<'a> { - pub operation: SqliteOperation, - pub database: &'a str, - pub table: &'a str, - pub rowid: i64, -} - -pub(crate) struct UpdateHookHandler(NonNull); -unsafe impl Send for UpdateHookHandler {} - -pub(crate) struct CommitHookHandler(NonNull bool + Send + 'static>); -unsafe impl Send for CommitHookHandler {} - -pub(crate) struct RollbackHookHandler(NonNull); -unsafe impl Send for RollbackHookHandler {} - -pub(crate) struct ConnectionState { - pub(crate) handle: ConnectionHandle, - - pub(crate) statements: Statements, - - log_settings: LogSettings, - - /// Stores the progress handler set on the current connection. If the handler returns `false`, - /// the query is interrupted. - progress_handler_callback: Option, - - update_hook_callback: Option, - #[cfg(feature = "preupdate-hook")] - preupdate_hook_callback: Option, - - commit_hook_callback: Option, - - rollback_hook_callback: Option, -} - -impl ConnectionState { - /// Drops the `progress_handler_callback` if it exists. - pub(crate) fn remove_progress_handler(&mut self) { - if let Some(mut handler) = self.progress_handler_callback.take() { - unsafe { - sqlite3_progress_handler(self.handle.as_ptr(), 0, None, ptr::null_mut()); - let _ = { Box::from_raw(handler.0.as_mut()) }; - } - } - } - - pub(crate) fn remove_update_hook(&mut self) { - if let Some(mut handler) = self.update_hook_callback.take() { - unsafe { - sqlite3_update_hook(self.handle.as_ptr(), None, ptr::null_mut()); - let _ = { Box::from_raw(handler.0.as_mut()) }; - } - } - } - - #[cfg(feature = "preupdate-hook")] - pub(crate) fn remove_preupdate_hook(&mut self) { - if let Some(mut handler) = self.preupdate_hook_callback.take() { - unsafe { - libsqlite3_sys::sqlite3_preupdate_hook(self.handle.as_ptr(), None, ptr::null_mut()); - let _ = { Box::from_raw(handler.0.as_mut()) }; - } - } - } - - pub(crate) fn remove_commit_hook(&mut self) { - if let Some(mut handler) = self.commit_hook_callback.take() { - unsafe { - sqlite3_commit_hook(self.handle.as_ptr(), None, ptr::null_mut()); - let _ = { Box::from_raw(handler.0.as_mut()) }; - } - } - } - - pub(crate) fn remove_rollback_hook(&mut self) { - if let Some(mut handler) = self.rollback_hook_callback.take() { - unsafe { - sqlite3_rollback_hook(self.handle.as_ptr(), None, ptr::null_mut()); - let _ = { Box::from_raw(handler.0.as_mut()) }; - } - } - } -} - -pub(crate) struct Statements { - // cache of semi-persistent statements - cached: StatementCache, - // most recent non-persistent statement - temp: Option, -} - -impl SqliteConnection { - pub(crate) async fn establish(options: &SqliteConnectOptions) -> Result { - let params = EstablishParams::from_options(options)?; - let worker = ConnectionWorker::establish(params).await?; - Ok(Self { - optimize_on_close: options.optimize_on_close.clone(), - worker, - row_channel_size: options.row_channel_size, - }) - } - - /// Lock the SQLite database handle out from the worker thread so direct SQLite API calls can - /// be made safely. - /// - /// Returns an error if the worker thread crashed. - pub async fn lock_handle(&mut self) -> Result, Error> { - let guard = self.worker.unlock_db().await?; - - Ok(LockedSqliteHandle { guard }) - } -} - -impl Debug for SqliteConnection { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("SqliteConnection") - .field("row_channel_size", &self.row_channel_size) - .field("cached_statements_size", &self.cached_statements_size()) - .finish() - } -} - -impl Connection for SqliteConnection { - type Database = Sqlite; - - type Options = SqliteConnectOptions; - - async fn close(mut self) -> Result<(), Error> { - if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close { - let mut pragma_string = String::new(); - if let Some(limit) = analysis_limit { - write!(pragma_string, "PRAGMA analysis_limit = {limit}; ").ok(); - } - pragma_string.push_str("PRAGMA optimize;"); - self.execute(AssertSqlSafe(pragma_string)).await?; - } - let shutdown = self.worker.shutdown(); - // Drop the statement worker, which should - // cover all references to the connection handle outside of the worker thread - drop(self); - // Ensure the worker thread has terminated - shutdown.await - } - - async fn close_hard(self) -> Result<(), Error> { - drop(self); - Ok(()) - } - - /// Ensure the background worker thread is alive and accepting commands. - fn ping(&mut self) -> impl Future> + Send + '_ { - self.worker.ping() - } - - fn begin( - &mut self, - ) -> impl Future, Error>> + Send + '_ { - Transaction::begin(self, None) - } - - fn begin_with( - &mut self, - statement: impl SqlSafeStr, - ) -> impl Future, Error>> + Send + '_ - where - Self: Sized, - { - Transaction::begin(self, Some(statement.into_sql_str())) - } - - fn cached_statements_size(&self) -> usize { - self.worker.shared.get_cached_statements_size() - } - - fn clear_cached_statements(&mut self) -> impl Future> + Send + '_ { - self.worker.clear_cache() - } - - #[inline] - fn shrink_buffers(&mut self) { - // No-op. - } - - #[doc(hidden)] - async fn flush(&mut self) -> Result<(), Error> { - // For SQLite, FLUSH does effectively nothing... - // Well, we could use this to ensure that the command channel has been cleared, - // but it would only develop a backlog if a lot of queries are executed and then cancelled - // partway through, and then this would only make that situation worse. - Ok(()) - } - - #[doc(hidden)] - fn should_flush(&self) -> bool { - false - } -} - -/// Implements a C binding to a progress callback. The function returns `0` if the -/// user-provided callback returns `true`, and `1` otherwise to signal an interrupt. -extern "C" fn progress_callback(callback: *mut c_void) -> c_int -where - F: FnMut() -> bool, -{ - unsafe { - let r = catch_unwind(|| { - let callback: *mut F = callback.cast::(); - (*callback)() - }); - c_int::from(!r.unwrap_or_default()) - } -} - -extern "C" fn update_hook( - callback: *mut c_void, - op_code: c_int, - database: *const c_char, - table: *const c_char, - rowid: i64, -) where - F: FnMut(UpdateHookResult), -{ - unsafe { - let _ = catch_unwind(|| { - let callback: *mut F = callback.cast::(); - let operation: SqliteOperation = op_code.into(); - let database = CStr::from_ptr(database).to_str().unwrap_or_default(); - let table = CStr::from_ptr(table).to_str().unwrap_or_default(); - (*callback)(UpdateHookResult { - operation, - database, - table, - rowid, - }) - }); - } -} - -extern "C" fn commit_hook(callback: *mut c_void) -> c_int -where - F: FnMut() -> bool, -{ - unsafe { - let r = catch_unwind(|| { - let callback: *mut F = callback.cast::(); - (*callback)() - }); - c_int::from(!r.unwrap_or_default()) - } -} - -extern "C" fn rollback_hook(callback: *mut c_void) -where - F: FnMut(), -{ - unsafe { - let _ = catch_unwind(|| { - let callback: *mut F = callback.cast::(); - (*callback)() - }); - } -} - -impl LockedSqliteHandle<'_> { - /// Returns the underlying sqlite3* connection handle. - /// - /// As long as this `LockedSqliteHandle` exists, it is guaranteed that the background thread - /// is not making FFI calls on this database handle or any of its statements. - /// - /// ### Note: The `sqlite3` type is semver-exempt. - /// This API exposes the `sqlite3` type from `libsqlite3-sys` crate for type safety. - /// However, we reserve the right to upgrade `libsqlite3-sys` as necessary. - /// - /// Thus, if you are making direct calls via `libsqlite3-sys` you should pin the version - /// of SQLx that you're using, and upgrade it and `libsqlite3-sys` manually as new - /// versions are released. - /// - /// See [the driver root docs][crate] for details. - pub fn as_raw_handle(&mut self) -> NonNull { - self.guard.handle.as_non_null_ptr() - } - - /// Apply a collation to the open database. - /// - /// See [`SqliteConnectOptions::collation()`] for details. - pub fn create_collation( - &mut self, - name: &str, - compare: impl Fn(&str, &str) -> Ordering + Send + Sync + 'static, - ) -> Result<(), Error> { - collation::create_collation(&mut self.guard.handle, name, compare) - } - - /// Sets a progress handler that is invoked periodically during long running calls. If the progress callback - /// returns `false`, then the operation is interrupted. - /// - /// `num_ops` is the approximate number of [virtual machine instructions](https://www.sqlite.org/opcode.html) - /// that are evaluated between successive invocations of the callback. If `num_ops` is less than one then the - /// progress handler is disabled. - /// - /// Only a single progress handler may be defined at one time per database connection; setting a new progress - /// handler cancels the old one. - /// - /// The progress handler callback must not do anything that will modify the database connection that invoked - /// the progress handler. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections - /// in this context. - pub fn set_progress_handler(&mut self, num_ops: i32, callback: F) - where - F: FnMut() -> bool + Send + 'static, - { - unsafe { - let callback_boxed = Box::new(callback); - // SAFETY: `Box::into_raw()` always returns a non-null pointer. - let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); - let handler = callback.as_ptr() as *mut _; - self.guard.remove_progress_handler(); - self.guard.progress_handler_callback = Some(Handler(callback)); - - sqlite3_progress_handler( - self.as_raw_handle().as_mut(), - num_ops, - Some(progress_callback::), - handler, - ); - } - } - - pub fn set_update_hook(&mut self, callback: F) - where - F: FnMut(UpdateHookResult) + Send + 'static, - { - unsafe { - let callback_boxed = Box::new(callback); - // SAFETY: `Box::into_raw()` always returns a non-null pointer. - let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); - let handler = callback.as_ptr() as *mut _; - self.guard.remove_update_hook(); - self.guard.update_hook_callback = Some(UpdateHookHandler(callback)); - - sqlite3_update_hook( - self.as_raw_handle().as_mut(), - Some(update_hook::), - handler, - ); - } - } - - /// Registers a hook that is invoked prior to each `INSERT`, `UPDATE`, and `DELETE` operation on a database table. - /// At most one preupdate hook may be registered at a time on a single database connection. - /// - /// The preupdate hook only fires for changes to real database tables; - /// it is not invoked for changes to virtual tables or to system tables like sqlite_sequence or sqlite_stat1. - /// - /// See https://sqlite.org/c3ref/preupdate_count.html - #[cfg(feature = "preupdate-hook")] - pub fn set_preupdate_hook(&mut self, callback: F) - where - F: FnMut(PreupdateHookResult) + Send + 'static, - { - unsafe { - let callback_boxed = Box::new(callback); - // SAFETY: `Box::into_raw()` always returns a non-null pointer. - let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); - let handler = callback.as_ptr() as *mut _; - self.guard.remove_preupdate_hook(); - self.guard.preupdate_hook_callback = Some(PreupdateHookHandler(callback)); - - libsqlite3_sys::sqlite3_preupdate_hook( - self.as_raw_handle().as_mut(), - Some(preupdate_hook::), - handler, - ); - } - } - - /// Sets a commit hook that is invoked whenever a transaction is committed. If the commit hook callback - /// returns `false`, then the operation is turned into a ROLLBACK. - /// - /// Only a single commit hook may be defined at one time per database connection; setting a new commit hook - /// overrides the old one. - /// - /// The commit hook callback must not do anything that will modify the database connection that invoked - /// the commit hook. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections - /// in this context. - /// - /// See https://www.sqlite.org/c3ref/commit_hook.html - pub fn set_commit_hook(&mut self, callback: F) - where - F: FnMut() -> bool + Send + 'static, - { - unsafe { - let callback_boxed = Box::new(callback); - // SAFETY: `Box::into_raw()` always returns a non-null pointer. - let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); - let handler = callback.as_ptr() as *mut _; - self.guard.remove_commit_hook(); - self.guard.commit_hook_callback = Some(CommitHookHandler(callback)); - - sqlite3_commit_hook( - self.as_raw_handle().as_mut(), - Some(commit_hook::), - handler, - ); - } - } - - /// Sets a rollback hook that is invoked whenever a transaction rollback occurs. The rollback callback is not - /// invoked if a transaction is automatically rolled back because the database connection is closed. - /// - /// See https://www.sqlite.org/c3ref/commit_hook.html - pub fn set_rollback_hook(&mut self, callback: F) - where - F: FnMut() + Send + 'static, - { - unsafe { - let callback_boxed = Box::new(callback); - // SAFETY: `Box::into_raw()` always returns a non-null pointer. - let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed)); - let handler = callback.as_ptr() as *mut _; - self.guard.remove_rollback_hook(); - self.guard.rollback_hook_callback = Some(RollbackHookHandler(callback)); - - sqlite3_rollback_hook( - self.as_raw_handle().as_mut(), - Some(rollback_hook::), - handler, - ); - } - } - - /// Removes the progress handler on a database connection. The method does nothing if no handler was set. - pub fn remove_progress_handler(&mut self) { - self.guard.remove_progress_handler(); - } - - pub fn remove_update_hook(&mut self) { - self.guard.remove_update_hook(); - } - - #[cfg(feature = "preupdate-hook")] - pub fn remove_preupdate_hook(&mut self) { - self.guard.remove_preupdate_hook(); - } - - pub fn remove_commit_hook(&mut self) { - self.guard.remove_commit_hook(); - } - - pub fn remove_rollback_hook(&mut self) { - self.guard.remove_rollback_hook(); - } - - pub fn last_error(&mut self) -> Option { - self.guard.handle.last_error() - } -} - -impl Drop for ConnectionState { - fn drop(&mut self) { - // explicitly drop statements before the connection handle is dropped - self.statements.clear(); - self.remove_progress_handler(); - self.remove_update_hook(); - self.remove_commit_hook(); - self.remove_rollback_hook(); - } -} - -impl Statements { - fn new(capacity: usize) -> Self { - Statements { - cached: StatementCache::new(capacity), - temp: None, - } - } - - fn get(&mut self, query: &str, persistent: bool) -> Result<&mut VirtualStatement, Error> { - if !persistent || !self.cached.is_enabled() { - return Ok(self.temp.insert(VirtualStatement::new(query, false)?)); - } - - let exists = self.cached.contains_key(query); - - if !exists { - let statement = VirtualStatement::new(query, true)?; - self.cached.insert(query, statement); - } - - let statement = self.cached.get_mut(query).unwrap(); - - if exists { - // as this statement has been executed before, we reset before continuing - statement.reset()?; - } - - Ok(statement) - } - - fn len(&self) -> usize { - self.cached.len() - } - - fn clear(&mut self) { - self.cached.clear(); - self.temp = None; - } -} diff --git a/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs b/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs deleted file mode 100644 index 2759588873..0000000000 --- a/vendor/sqlx-sqlite/src/connection/preupdate_hook.rs +++ /dev/null @@ -1,157 +0,0 @@ -use super::SqliteOperation; -use crate::{SqliteError, SqliteValueRef}; - -use libsqlite3_sys::{ - sqlite3, sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_new, - sqlite3_preupdate_old, sqlite3_value, SQLITE_OK, -}; -use std::ffi::CStr; -use std::marker::PhantomData; -use std::os::raw::{c_char, c_int, c_void}; -use std::panic::catch_unwind; -use std::ptr; -use std::ptr::NonNull; - -#[derive(Debug, thiserror::Error)] -pub enum PreupdateError { - /// Error returned from the database. - #[error("error returned from database: {0}")] - Database(#[source] SqliteError), - /// Index is not within the valid column range - #[error("{0} is not within the valid column range")] - ColumnIndexOutOfBounds(i32), - /// Column value accessor was invoked from an invalid operation - #[error("column value accessor was invoked from an invalid operation")] - InvalidOperation, -} - -pub(crate) struct PreupdateHookHandler( - pub(super) NonNull, -); -unsafe impl Send for PreupdateHookHandler {} - -#[derive(Debug)] -pub struct PreupdateHookResult<'a> { - pub operation: SqliteOperation, - pub database: &'a str, - pub table: &'a str, - db: *mut sqlite3, - // The database pointer should not be usable after the preupdate hook. - // The lifetime on this struct needs to ensure it cannot outlive the callback. - _db_lifetime: PhantomData<&'a ()>, - old_row_id: i64, - new_row_id: i64, -} - -impl<'a> PreupdateHookResult<'a> { - /// Gets the amount of columns in the row being inserted, deleted, or updated. - pub fn get_column_count(&self) -> i32 { - unsafe { sqlite3_preupdate_count(self.db) } - } - - /// Gets the depth of the query that triggered the preupdate hook. - /// Returns 0 if the preupdate callback was invoked as a result of - /// a direct insert, update, or delete operation; - /// 1 for inserts, updates, or deletes invoked by top-level triggers; - /// 2 for changes resulting from triggers called by top-level triggers; and so forth. - pub fn get_query_depth(&self) -> i32 { - unsafe { sqlite3_preupdate_depth(self.db) } - } - - /// Gets the row id of the row being updated/deleted. - /// Returns an error if called from an insert operation. - pub fn get_old_row_id(&self) -> Result { - if self.operation == SqliteOperation::Insert { - return Err(PreupdateError::InvalidOperation); - } - Ok(self.old_row_id) - } - - /// Gets the row id of the row being inserted/updated. - /// Returns an error if called from a delete operation. - pub fn get_new_row_id(&self) -> Result { - if self.operation == SqliteOperation::Delete { - return Err(PreupdateError::InvalidOperation); - } - Ok(self.new_row_id) - } - - /// Gets the value of the row being updated/deleted at the specified index. - /// Returns an error if called from an insert operation or the index is out of bounds. - pub fn get_old_column_value(&self, i: i32) -> Result, PreupdateError> { - if self.operation == SqliteOperation::Insert { - return Err(PreupdateError::InvalidOperation); - } - self.validate_column_index(i)?; - - let mut p_value: *mut sqlite3_value = ptr::null_mut(); - unsafe { - let ret = sqlite3_preupdate_old(self.db, i, &mut p_value); - self.get_value(ret, p_value) - } - } - - /// Gets the value of the row being inserted/updated at the specified index. - /// Returns an error if called from a delete operation or the index is out of bounds. - pub fn get_new_column_value(&self, i: i32) -> Result, PreupdateError> { - if self.operation == SqliteOperation::Delete { - return Err(PreupdateError::InvalidOperation); - } - self.validate_column_index(i)?; - - let mut p_value: *mut sqlite3_value = ptr::null_mut(); - unsafe { - let ret = sqlite3_preupdate_new(self.db, i, &mut p_value); - self.get_value(ret, p_value) - } - } - - fn validate_column_index(&self, i: i32) -> Result<(), PreupdateError> { - if i < 0 || i >= self.get_column_count() { - return Err(PreupdateError::ColumnIndexOutOfBounds(i)); - } - Ok(()) - } - - unsafe fn get_value( - &self, - ret: i32, - p_value: *mut sqlite3_value, - ) -> Result, PreupdateError> { - if ret != SQLITE_OK { - return Err(PreupdateError::Database(SqliteError::new(self.db))); - } - Ok(SqliteValueRef::borrowed(p_value)) - } -} - -pub(super) extern "C" fn preupdate_hook( - callback: *mut c_void, - db: *mut sqlite3, - op_code: c_int, - database: *const c_char, - table: *const c_char, - old_row_id: i64, - new_row_id: i64, -) where - F: FnMut(PreupdateHookResult) + Send + 'static, -{ - unsafe { - let _ = catch_unwind(|| { - let callback: *mut F = callback.cast::(); - let operation: SqliteOperation = op_code.into(); - let database = CStr::from_ptr(database).to_str().unwrap_or_default(); - let table = CStr::from_ptr(table).to_str().unwrap_or_default(); - - (*callback)(PreupdateHookResult { - operation, - database, - table, - old_row_id, - new_row_id, - db, - _db_lifetime: PhantomData, - }) - }); - } -} diff --git a/vendor/sqlx-sqlite/src/connection/worker.rs b/vendor/sqlx-sqlite/src/connection/worker.rs deleted file mode 100644 index 2585dc1312..0000000000 --- a/vendor/sqlx-sqlite/src/connection/worker.rs +++ /dev/null @@ -1,573 +0,0 @@ -use std::future::Future; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread; - -use futures_channel::oneshot; -use futures_intrusive::sync::{Mutex, MutexGuard}; -use sqlx_core::sql_str::SqlStr; -use tracing::span::Span; - -use sqlx_core::error::Error; -use sqlx_core::transaction::{ - begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql, -}; -use sqlx_core::Either; - -use crate::connection::establish::EstablishParams; -use crate::connection::execute; -use crate::connection::ConnectionState; -use crate::{SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStatement}; - -#[cfg(feature = "deserialize")] -use crate::connection::deserialize::{deserialize, serialize, SchemaName, SqliteOwnedBuf}; - -// Each SQLite connection has a dedicated thread. - -// TODO: Tweak this so that we can use a thread pool per pool of SQLite3 connections to reduce -// OS resource usage. Low priority because a high concurrent load for SQLite3 is very -// unlikely. - -pub(crate) struct ConnectionWorker { - command_tx: flume::Sender<(Command, tracing::Span)>, - /// Mutex for locking access to the database. - pub(crate) shared: Arc, -} - -pub(crate) struct WorkerSharedState { - transaction_depth: AtomicUsize, - cached_statements_size: AtomicUsize, - pub(crate) conn: Mutex, -} - -impl WorkerSharedState { - pub(crate) fn get_transaction_depth(&self) -> usize { - self.transaction_depth.load(Ordering::Acquire) - } - - pub(crate) fn get_cached_statements_size(&self) -> usize { - self.cached_statements_size.load(Ordering::Acquire) - } -} - -enum Command { - Prepare { - query: SqlStr, - tx: oneshot::Sender>, - }, - #[cfg(feature = "offline")] - Describe { - query: SqlStr, - tx: oneshot::Sender, Error>>, - }, - Execute { - query: SqlStr, - arguments: Option, - persistent: bool, - tx: flume::Sender, Error>>, - limit: Option, - }, - #[cfg(feature = "deserialize")] - Serialize { - schema: Option, - tx: oneshot::Sender>, - }, - #[cfg(feature = "deserialize")] - Deserialize { - schema: Option, - data: SqliteOwnedBuf, - read_only: bool, - tx: oneshot::Sender>, - }, - Begin { - tx: rendezvous_oneshot::Sender>, - statement: Option, - }, - Commit { - tx: rendezvous_oneshot::Sender>, - }, - Rollback { - tx: Option>>, - }, - UnlockDb, - ClearCache { - tx: oneshot::Sender<()>, - }, - Ping { - tx: oneshot::Sender<()>, - }, - Shutdown { - tx: oneshot::Sender<()>, - }, -} - -impl ConnectionWorker { - pub(crate) async fn establish(params: EstablishParams) -> Result { - let (establish_tx, establish_rx) = oneshot::channel(); - - thread::Builder::new() - .name(params.thread_name.clone()) - .spawn(move || { - let (command_tx, command_rx) = flume::bounded(params.command_channel_size); - - let conn = match params.establish() { - Ok(conn) => conn, - Err(e) => { - establish_tx.send(Err(e)).ok(); - return; - } - }; - - let shared = Arc::new(WorkerSharedState { - transaction_depth: AtomicUsize::new(0), - cached_statements_size: AtomicUsize::new(0), - // note: must be fair because in `Command::UnlockDb` we unlock the mutex - // and then immediately try to relock it; an unfair mutex would immediately - // grant us the lock even if another task is waiting. - conn: Mutex::new(conn, true), - }); - let mut conn = shared.conn.try_lock().unwrap(); - - if establish_tx - .send(Ok(Self { - command_tx, - shared: Arc::clone(&shared), - })) - .is_err() - { - return; - } - - // If COMMIT or ROLLBACK is processed but not acknowledged, there would be another - // ROLLBACK sent when the `Transaction` drops. We need to ignore it otherwise we - // would rollback an already completed transaction. - let mut ignore_next_start_rollback = false; - - for (cmd, span) in command_rx { - let _guard = span.enter(); - match cmd { - Command::Prepare { query, tx } => { - tx.send(prepare(&mut conn, query)).ok(); - - // This may issue an unnecessary write on failure, - // but it doesn't matter in the grand scheme of things. - update_cached_statements_size( - &conn, - &shared.cached_statements_size, - ); - } - #[cfg(feature = "offline")] - Command::Describe { query, tx } => { - tx.send(crate::connection::describe::describe(&mut conn, query)).ok(); - } - Command::Execute { - query, - arguments, - persistent, - tx, - limit - } => { - let iter = match execute::iter(&mut conn, query, arguments, persistent) - { - Ok(iter) => iter, - Err(e) => { - tx.send(Err(e)).ok(); - continue; - } - }; - - match limit { - None => { - for res in iter { - let has_error = res.is_err(); - if tx.send(res).is_err() || has_error { - break; - } - } - }, - Some(limit) => { - let mut iter = iter; - let mut rows_returned = 0; - - while let Some(res) = iter.next() { - if let Ok(ok) = &res { - if ok.is_right() { - rows_returned += 1; - if rows_returned >= limit { - drop(iter); - let _ = tx.send(res); - break; - } - } - } - let has_error = res.is_err(); - if tx.send(res).is_err() || has_error { - break; - } - } - }, - } - - update_cached_statements_size(&conn, &shared.cached_statements_size); - } - Command::Begin { tx, statement } => { - let depth = shared.transaction_depth.load(Ordering::Acquire); - - let is_custom_statement = statement.is_some(); - let statement = match statement { - // custom `BEGIN` statements are not allowed if - // we're already in a transaction (we need to - // issue a `SAVEPOINT` instead) - Some(_) if depth > 0 => { - if tx.blocking_send(Err(Error::InvalidSavePointStatement)).is_err() { - break; - } - continue; - }, - Some(statement) => statement, - None => begin_ansi_transaction_sql(depth), - }; - let res = - conn.handle - .exec(statement.as_str()) - .and_then(|res| { - if is_custom_statement && !conn.handle.in_transaction() { - return Err(Error::BeginFailed) - } - - shared.transaction_depth.fetch_add(1, Ordering::Release); - - Ok(res) - }); - let res_ok = res.is_ok(); - - if tx.blocking_send(res).is_err() && res_ok { - // The BEGIN was processed but not acknowledged. This means no - // `Transaction` was created and so there is no way to commit / - // rollback this transaction. We need to roll it back - // immediately otherwise it would remain started forever. - if let Err(error) = conn - .handle - .exec(rollback_ansi_transaction_sql(depth + 1).as_str()) - .map(|_| { - shared.transaction_depth.fetch_sub(1, Ordering::Release); - }) - { - // The rollback failed. To prevent leaving the connection - // in an inconsistent state we shutdown this worker which - // causes any subsequent operation on the connection to fail. - tracing::error!(%error, "failed to rollback cancelled transaction"); - break; - } - } - } - Command::Commit { tx } => { - let depth = shared.transaction_depth.load(Ordering::Acquire); - - let res = if depth > 0 { - conn.handle - .exec(commit_ansi_transaction_sql(depth).as_str()) - .map(|_| { - shared.transaction_depth.fetch_sub(1, Ordering::Release); - }) - } else { - Ok(()) - }; - let res_ok = res.is_ok(); - - if tx.blocking_send(res).is_err() && res_ok { - // The COMMIT was processed but not acknowledged. This means that - // the `Transaction` doesn't know it was committed and will try to - // rollback on drop. We need to ignore that rollback. - ignore_next_start_rollback = true; - } - } - Command::Rollback { tx } => { - if ignore_next_start_rollback && tx.is_none() { - ignore_next_start_rollback = false; - continue; - } - - let depth = shared.transaction_depth.load(Ordering::Acquire); - - let res = if depth > 0 { - conn.handle - .exec(rollback_ansi_transaction_sql(depth).as_str()) - .map(|_| { - shared.transaction_depth.fetch_sub(1, Ordering::Release); - }) - } else { - Ok(()) - }; - - let res_ok = res.is_ok(); - - if let Some(tx) = tx { - if tx.blocking_send(res).is_err() && res_ok { - // The ROLLBACK was processed but not acknowledged. This means - // that the `Transaction` doesn't know it was rolled back and - // will try to rollback again on drop. We need to ignore that - // rollback. - ignore_next_start_rollback = true; - } - } - } - #[cfg(feature = "deserialize")] - Command::Serialize { schema, tx } => { - tx.send(serialize(&mut conn, schema)).ok(); - } - #[cfg(feature = "deserialize")] - Command::Deserialize { schema, data, read_only, tx } => { - tx.send(deserialize(&mut conn, schema, data, read_only)).ok(); - } - Command::ClearCache { tx } => { - conn.statements.clear(); - update_cached_statements_size(&conn, &shared.cached_statements_size); - tx.send(()).ok(); - } - Command::UnlockDb => { - drop(conn); - conn = futures_executor::block_on(shared.conn.lock()); - } - Command::Ping { tx } => { - tx.send(()).ok(); - } - Command::Shutdown { tx } => { - // drop the connection references before sending confirmation - // and ending the command loop - drop(conn); - drop(shared); - let _ = tx.send(()); - return; - } - } - } - })?; - - establish_rx.await.map_err(|_| Error::WorkerCrashed)? - } - - pub(crate) async fn prepare(&mut self, query: SqlStr) -> Result { - self.oneshot_cmd(|tx| Command::Prepare { query, tx }) - .await? - } - - #[cfg(feature = "offline")] - pub(crate) async fn describe( - &mut self, - query: SqlStr, - ) -> Result, Error> { - self.oneshot_cmd(|tx| Command::Describe { query, tx }) - .await? - } - - pub(crate) async fn execute( - &mut self, - query: SqlStr, - args: Option, - chan_size: usize, - persistent: bool, - limit: Option, - ) -> Result, Error>>, Error> { - let (tx, rx) = flume::bounded(chan_size); - - self.command_tx - .send_async(( - Command::Execute { - query, - arguments: args, - persistent, - tx, - limit, - }, - Span::current(), - )) - .await - .map_err(|_| Error::WorkerCrashed)?; - - Ok(rx) - } - - pub(crate) async fn begin(&mut self, statement: Option) -> Result<(), Error> { - self.oneshot_cmd_with_ack(|tx| Command::Begin { tx, statement }) - .await? - } - - pub(crate) async fn commit(&mut self) -> Result<(), Error> { - self.oneshot_cmd_with_ack(|tx| Command::Commit { tx }) - .await? - } - - pub(crate) async fn rollback(&mut self) -> Result<(), Error> { - self.oneshot_cmd_with_ack(|tx| Command::Rollback { tx: Some(tx) }) - .await? - } - - pub(crate) fn start_rollback(&mut self) -> Result<(), Error> { - self.command_tx - .send((Command::Rollback { tx: None }, Span::current())) - .map_err(|_| Error::WorkerCrashed) - } - - pub(crate) async fn ping(&mut self) -> Result<(), Error> { - self.oneshot_cmd(|tx| Command::Ping { tx }).await - } - - #[cfg(feature = "deserialize")] - pub(crate) async fn deserialize( - &mut self, - schema: Option, - data: SqliteOwnedBuf, - read_only: bool, - ) -> Result<(), Error> { - self.oneshot_cmd(|tx| Command::Deserialize { - schema, - data, - read_only, - tx, - }) - .await? - } - - #[cfg(feature = "deserialize")] - pub(crate) async fn serialize( - &mut self, - schema: Option, - ) -> Result { - self.oneshot_cmd(|tx| Command::Serialize { schema, tx }) - .await? - } - - async fn oneshot_cmd(&mut self, command: F) -> Result - where - F: FnOnce(oneshot::Sender) -> Command, - { - let (tx, rx) = oneshot::channel(); - - self.command_tx - .send_async((command(tx), Span::current())) - .await - .map_err(|_| Error::WorkerCrashed)?; - - rx.await.map_err(|_| Error::WorkerCrashed) - } - - async fn oneshot_cmd_with_ack(&mut self, command: F) -> Result - where - F: FnOnce(rendezvous_oneshot::Sender) -> Command, - { - let (tx, rx) = rendezvous_oneshot::channel(); - - self.command_tx - .send_async((command(tx), Span::current())) - .await - .map_err(|_| Error::WorkerCrashed)?; - - rx.recv().await.map_err(|_| Error::WorkerCrashed) - } - - pub(crate) async fn clear_cache(&mut self) -> Result<(), Error> { - self.oneshot_cmd(|tx| Command::ClearCache { tx }).await - } - - pub(crate) async fn unlock_db(&mut self) -> Result, Error> { - let (guard, res) = futures_util::future::join( - // we need to join the wait queue for the lock before we send the message - self.shared.conn.lock(), - self.command_tx - .send_async((Command::UnlockDb, Span::current())), - ) - .await; - - res.map_err(|_| Error::WorkerCrashed)?; - - Ok(guard) - } - - /// Send a command to the worker to shut down the processing thread. - /// - /// A `WorkerCrashed` error may be returned if the thread has already stopped. - pub(crate) fn shutdown(&mut self) -> impl Future> { - let (tx, rx) = oneshot::channel(); - - let send_res = self - .command_tx - .send((Command::Shutdown { tx }, Span::current())) - .map_err(|_| Error::WorkerCrashed); - - async move { - send_res?; - - // wait for the response - rx.await.map_err(|_| Error::WorkerCrashed) - } - } -} - -fn prepare(conn: &mut ConnectionState, query: SqlStr) -> Result { - // prepare statement object (or checkout from cache) - let statement = conn.statements.get(query.as_str(), true)?; - - let mut parameters = 0; - let mut columns = None; - let mut column_names = None; - - while let Some(statement) = statement.prepare_next(&mut conn.handle)? { - parameters += statement.handle.bind_parameter_count(); - - // the first non-empty statement is chosen as the statement we pull columns from - if !statement.columns.is_empty() && columns.is_none() { - columns = Some(Arc::clone(statement.columns)); - column_names = Some(Arc::clone(statement.column_names)); - } - } - - Ok(SqliteStatement { - sql: query, - columns: columns.unwrap_or_default(), - column_names: column_names.unwrap_or_default(), - parameters, - }) -} - -fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) { - size.store(conn.statements.len(), Ordering::Release); -} - -// A oneshot channel where send completes only after the receiver receives the value. -mod rendezvous_oneshot { - use super::oneshot::{self, Canceled}; - - pub fn channel() -> (Sender, Receiver) { - let (inner_tx, inner_rx) = oneshot::channel(); - (Sender { inner: inner_tx }, Receiver { inner: inner_rx }) - } - - pub struct Sender { - inner: oneshot::Sender<(T, oneshot::Sender<()>)>, - } - - impl Sender { - pub async fn send(self, value: T) -> Result<(), Canceled> { - let (ack_tx, ack_rx) = oneshot::channel(); - self.inner.send((value, ack_tx)).map_err(|_| Canceled)?; - ack_rx.await - } - - pub fn blocking_send(self, value: T) -> Result<(), Canceled> { - futures_executor::block_on(self.send(value)) - } - } - - pub struct Receiver { - inner: oneshot::Receiver<(T, oneshot::Sender<()>)>, - } - - impl Receiver { - pub async fn recv(self) -> Result { - let (value, ack_tx) = self.inner.await?; - ack_tx.send(()).map_err(|_| Canceled)?; - Ok(value) - } - } -} diff --git a/vendor/sqlx-sqlite/src/database.rs b/vendor/sqlx-sqlite/src/database.rs deleted file mode 100644 index ca52d1cf6b..0000000000 --- a/vendor/sqlx-sqlite/src/database.rs +++ /dev/null @@ -1,39 +0,0 @@ -pub(crate) use sqlx_core::database::{Database, HasStatementCache}; - -use crate::arguments::SqliteArgumentsBuffer; -use crate::{ - SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, - SqliteTransactionManager, SqliteTypeInfo, SqliteValue, SqliteValueRef, -}; - -/// Sqlite database driver. -#[derive(Debug)] -pub struct Sqlite; - -impl Database for Sqlite { - type Connection = SqliteConnection; - - type TransactionManager = SqliteTransactionManager; - - type Row = SqliteRow; - - type QueryResult = SqliteQueryResult; - - type Column = SqliteColumn; - - type TypeInfo = SqliteTypeInfo; - - type Value = SqliteValue; - type ValueRef<'r> = SqliteValueRef<'r>; - - type Arguments = SqliteArguments; - type ArgumentBuffer = SqliteArgumentsBuffer; - - type Statement = SqliteStatement; - - const NAME: &'static str = "SQLite"; - - const URL_SCHEMES: &'static [&'static str] = &["sqlite"]; -} - -impl HasStatementCache for Sqlite {} diff --git a/vendor/sqlx-sqlite/src/error.rs b/vendor/sqlx-sqlite/src/error.rs deleted file mode 100644 index 5510e25d97..0000000000 --- a/vendor/sqlx-sqlite/src/error.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::error::Error as StdError; -use std::ffi::CStr; -use std::fmt::{self, Display, Formatter}; -use std::os::raw::c_int; -use std::{borrow::Cow, str}; - -use libsqlite3_sys::{ - sqlite3, sqlite3_errmsg, sqlite3_errstr, sqlite3_extended_errcode, SQLITE_CONSTRAINT_CHECK, - SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY, - SQLITE_CONSTRAINT_UNIQUE, SQLITE_ERROR, SQLITE_NOMEM, -}; - -pub(crate) use sqlx_core::error::*; - -// Error Codes And Messages -// https://www.sqlite.org/c3ref/errcode.html - -#[derive(Debug)] -pub struct SqliteError { - code: c_int, - message: Cow<'static, str>, -} - -impl SqliteError { - pub(crate) unsafe fn new(handle: *mut sqlite3) -> Self { - Self::try_new(handle).expect("There should be an error") - } - - pub(crate) unsafe fn try_new(handle: *mut sqlite3) -> Option { - // returns the extended result code even when extended result codes are disabled - let code: c_int = unsafe { sqlite3_extended_errcode(handle) }; - - if code == 0 { - return None; - } - - // return English-language text that describes the error - let message = unsafe { - let msg = sqlite3_errmsg(handle); - debug_assert!(!msg.is_null()); - - String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()).into_owned() - }; - - Some(Self { - code, - message: message.into(), - }) - } - - /// For errors during extension load, the error message is supplied via a separate pointer - #[allow(dead_code)] - pub(crate) fn with_message(mut self, error_msg: String) -> Self { - self.message = error_msg.into(); - self - } - - #[allow(dead_code)] - pub(crate) fn from_code(code: c_int) -> Self { - let message = unsafe { - let errstr = sqlite3_errstr(code); - - if !errstr.is_null() { - // SAFETY: `errstr` is guaranteed to be UTF-8 - // The lifetime of the string is "internally managed"; - // the implementation just selects from an array of static strings. - // We copy to an owned buffer in case `libsqlite3` is dynamically loaded somehow. - Cow::Owned(str::from_utf8_unchecked(CStr::from_ptr(errstr).to_bytes()).into()) - } else { - Cow::Borrowed("") - } - }; - - SqliteError { code, message } - } - - #[allow(dead_code)] - pub(crate) fn generic(message: impl Into>) -> Self { - Self { - code: SQLITE_ERROR, - message: message.into(), - } - } - - /// Return `SQLITE_NOMEM`. - pub(crate) fn nomem() -> Self { - Self::from_code(SQLITE_NOMEM) - } -} - -impl Display for SqliteError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // We include the code as some produce ambiguous messages: - // SQLITE_BUSY: "database is locked" - // SQLITE_LOCKED: "database table is locked" - // Sadly there's no function to get the string label back from an error code. - write!(f, "(code: {}) {}", self.code, self.message) - } -} - -impl StdError for SqliteError {} - -impl DatabaseError for SqliteError { - #[inline] - fn message(&self) -> &str { - &self.message - } - - /// The extended result code. - #[inline] - fn code(&self) -> Option> { - Some(format!("{}", self.code).into()) - } - - #[doc(hidden)] - fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { - self - } - - #[doc(hidden)] - fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { - self - } - - #[doc(hidden)] - fn into_error(self: Box) -> Box { - self - } - - fn kind(&self) -> ErrorKind { - match self.code { - SQLITE_CONSTRAINT_UNIQUE | SQLITE_CONSTRAINT_PRIMARYKEY => ErrorKind::UniqueViolation, - SQLITE_CONSTRAINT_FOREIGNKEY => ErrorKind::ForeignKeyViolation, - SQLITE_CONSTRAINT_NOTNULL => ErrorKind::NotNullViolation, - SQLITE_CONSTRAINT_CHECK => ErrorKind::CheckViolation, - _ => ErrorKind::Other, - } - } -} diff --git a/vendor/sqlx-sqlite/src/lib.rs b/vendor/sqlx-sqlite/src/lib.rs deleted file mode 100644 index 17b4bed1ca..0000000000 --- a/vendor/sqlx-sqlite/src/lib.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! **SQLite** database driver. -//! -//! ### Note: `libsqlite3-sys` Version -//! This driver uses the `libsqlite3-sys` crate which links the native library for SQLite 3. -//! Only one version of `libsqlite3-sys` may appear in the dependency tree of your project. -//! -//! As of SQLx 0.9.0, the version of `libsqlite3-sys` is now a range instead of any specific version. -//! See the `Cargo.toml` of the `sqlx-sqlite` crate for the current version range. -//! -//! If you are using `rusqlite` or any other crate that indirectly depends on `libsqlite3-sys`, -//! this should allow Cargo to select a compatible version. -//! -//! If Cargo **fails to select a compatible version**, this means the other crate is using -//! a `libsqlite3-sys` version outside of this range. -//! -//! We may increase the *maximum* version of the range at our discretion, -//! in patch (SemVer-compatible) releases, to allow users to upgrade to newer versions as desired. -//! -//! The *minimum* version of the range may be increased over time to drop very old or -//! insecure versions of SQLite, but this will only occur in major (SemVer-incompatible) releases. -//! -//! Note that this means a `cargo update` may increase the `libsqlite3-sys` version, -//! which could, in rare cases, break your build. -//! -//! To prevent this, you can pin the `libsqlite3-sys` version in your own dependencies: -//! -//! ```toml -//! [dependencies] -//! # for example, if 0.35.0 breaks the build -//! libsqlite3-sys = "0.34" -//! ``` -//! -//! ### Static Linking (Default) -//! The `sqlite` feature enables the `bundled` feature of `libsqlite3-sys`, -//! which builds SQLite 3 from included source code and statically links it into the final binary. -//! -//! This requires some C build tools to be installed on the system; see -//! [the `rusqlite` README][rusqlite-readme-building] for details. -//! -//! This version of SQLite is generally much newer than system-installed versions of SQLite -//! (especially for LTS Linux distributions), and can be updated with a `cargo update`, -//! so this is the recommended option for ease of use and keeping up-to-date. -//! -//! ### Dynamic linking -//! To dynamically link to an existing SQLite library, the `sqlite-unbundled` feature can be used -//! instead. -//! -//! This allows updating SQLite independently of SQLx or using forked versions, but you must have -//! SQLite installed on the system or provide a path to the library at build time (see -//! [the `rusqlite` README][rusqlite-readme-building] for details). -//! -//! Note that this _may_ result in link errors if the SQLite version is too old, -//! or has [certain features disabled at compile-time](https://www.sqlite.org/compile.html). -//! -//! SQLite version `3.20.0` (released August 2018) or newer is recommended. -//! -//! **Please check your SQLite version and the flags it was built with before opening -//! a GitHub issue because of errors in `libsqlite3-sys`.** Thank you. -//! -//! [rusqlite-readme-building]: https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys -//! -//! ### Optional Features -//! -//! The following features -//! - -// SQLite is a C library. All interactions require FFI which is unsafe. -// All unsafe blocks should have comments pointing to SQLite docs and ensuring that we maintain -// invariants. -#![allow(unsafe_code)] - -#[macro_use] -extern crate sqlx_core; - -use std::sync::atomic::AtomicBool; - -pub use arguments::{SqliteArgumentValue, SqliteArguments, SqliteArgumentsBuffer}; -pub use column::SqliteColumn; -#[cfg(feature = "deserialize")] -#[cfg_attr(docsrs, doc(cfg(feature = "deserialize")))] -pub use connection::deserialize::SqliteOwnedBuf; -#[cfg(feature = "preupdate-hook")] -#[cfg_attr(docsrs, doc(cfg(feature = "preupdate-hook")))] -pub use connection::PreupdateHookResult; -pub use connection::{LockedSqliteHandle, SqliteConnection, SqliteOperation, UpdateHookResult}; -pub use database::Sqlite; -pub use error::SqliteError; -pub use options::{ - SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqliteLockingMode, SqliteSynchronous, -}; -pub use query_result::SqliteQueryResult; -pub use row::SqliteRow; -pub use statement::SqliteStatement; -pub use transaction::SqliteTransactionManager; -pub use type_info::SqliteTypeInfo; -pub use value::{SqliteValue, SqliteValueRef}; - -use crate::connection::establish::EstablishParams; - -pub(crate) use sqlx_core::driver_prelude::*; - -use sqlx_core::config; -use sqlx_core::describe::Describe; -use sqlx_core::error::Error; -use sqlx_core::executor::Executor; -use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr}; - -mod arguments; -mod column; -mod connection; -mod database; -mod error; -mod logger; -mod options; -mod query_result; -mod row; -mod statement; -mod transaction; -mod type_checking; -mod type_info; -pub mod types; -mod value; - -#[cfg(feature = "any")] -pub mod any; - -#[cfg(feature = "regexp")] -mod regexp; - -#[cfg(feature = "migrate")] -mod migrate; - -#[cfg(feature = "migrate")] -mod testing; - -/// An alias for [`Pool`][crate::pool::Pool], specialized for SQLite. -pub type SqlitePool = crate::pool::Pool; - -/// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for SQLite. -pub type SqlitePoolOptions = crate::pool::PoolOptions; - -/// An alias for [`Executor<'_, Database = Sqlite>`][Executor]. -pub trait SqliteExecutor<'c>: Executor<'c, Database = Sqlite> {} -impl<'c, T: Executor<'c, Database = Sqlite>> SqliteExecutor<'c> for T {} - -/// An alias for [`Transaction`][sqlx_core::transaction::Transaction], specialized for SQLite. -pub type SqliteTransaction<'c> = sqlx_core::transaction::Transaction<'c, Sqlite>; - -// NOTE: required due to the lack of lazy normalization -impl_into_arguments_for_arguments!(SqliteArguments); -impl_column_index_for_row!(SqliteRow); -impl_column_index_for_statement!(SqliteStatement); -impl_acquire!(Sqlite, SqliteConnection); - -// required because some databases have a different handling of NULL -impl_encode_for_option!(Sqlite); - -/// UNSTABLE: for use by `sqlx-cli` only. -#[doc(hidden)] -pub static CREATE_DB_WAL: AtomicBool = AtomicBool::new(true); - -/// UNSTABLE: for use by `sqlite-macros-core` only. -#[doc(hidden)] -pub fn describe_blocking( - query: &str, - database_url: &str, - driver_config: &config::drivers::Config, -) -> Result, Error> { - let mut opts: SqliteConnectOptions = database_url.parse()?; - - opts = opts.apply_driver_config(&driver_config.sqlite)?; - - let params = EstablishParams::from_options(&opts)?; - let mut conn = params.establish()?; - - // Execute any ancillary `PRAGMA`s - connection::execute::iter(&mut conn, AssertSqlSafe(opts.pragma_string()), None, false)? - .finish()?; - - connection::describe::describe(&mut conn, AssertSqlSafe(query.to_string()).into_sql_str()) - - // SQLite database is closed immediately when `conn` is dropped -} diff --git a/vendor/sqlx-sqlite/src/logger.rs b/vendor/sqlx-sqlite/src/logger.rs deleted file mode 100644 index 1464a730c7..0000000000 --- a/vendor/sqlx-sqlite/src/logger.rs +++ /dev/null @@ -1,443 +0,0 @@ -// Bad casts in this module SHOULD NOT result in a SQL injection -// https://github.com/launchbadge/sqlx/issues/3440 -#![allow( - clippy::cast_possible_truncation, - clippy::cast_possible_wrap, - clippy::cast_sign_loss -)] - -use crate::connection::intmap::IntMap; -use std::collections::HashSet; -use std::fmt::Debug; -use std::hash::Hash; - -pub(crate) use sqlx_core::logger::*; - -#[derive(Debug)] -pub(crate) enum BranchResult { - Result(R), - Dedup(BranchParent), - Halt, - Error, - GasLimit, - LoopLimit, - Branched, -} - -#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, Ord, PartialOrd)] -pub(crate) struct BranchParent { - pub id: i64, - pub idx: i64, -} - -#[derive(Debug)] -pub(crate) struct InstructionHistory { - pub program_i: usize, - pub state: S, -} - -pub(crate) trait DebugDiff { - fn diff(&self, prev: &Self) -> String; -} - -pub struct QueryPlanLogger<'q, R: Debug + 'static, S: Debug + DebugDiff + 'static, P: Debug> { - sql: &'q str, - unknown_operations: HashSet, - branch_origins: IntMap, - branch_results: IntMap>, - branch_operations: IntMap>>, - program: &'q [P], -} - -/// convert a string into dot format -fn dot_escape_string(value: impl AsRef) -> String { - value - .as_ref() - .replace('\\', r#"\\"#) - .replace('"', "'") - .replace('\n', r#"\n"#) - .to_string() -} - -impl core::fmt::Display for QueryPlanLogger<'_, R, S, P> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - //writes query plan history in dot format - f.write_str("digraph {\n")?; - - f.write_str("subgraph operations {\n")?; - f.write_str("style=\"rounded\";\nnode [shape=\"point\"];\n")?; - - let all_states: std::collections::HashMap> = self - .branch_operations - .iter_entries() - .flat_map( - |(branch_id, instructions): (i64, &IntMap>)| { - instructions.iter_entries().map( - move |(idx, ih): (i64, &InstructionHistory)| { - (BranchParent { id: branch_id, idx }, ih) - }, - ) - }, - ) - .collect(); - - let mut instruction_uses: IntMap> = Default::default(); - for (k, state) in all_states.iter() { - let entry = instruction_uses.get_mut_or_default(&(state.program_i as i64)); - entry.push(*k); - } - - let mut branch_children: std::collections::HashMap> = - Default::default(); - - let mut branched_with_state: std::collections::HashSet = Default::default(); - - for (branch_id, branch_parent) in self.branch_origins.iter_entries() { - let entry = branch_children.entry(*branch_parent).or_default(); - entry.push(BranchParent { - id: branch_id, - idx: 0, - }); - } - - for (idx, instruction) in self.program.iter().enumerate() { - let escaped_instruction = dot_escape_string(format!("{:?}", instruction)); - write!( - f, - "subgraph cluster_{} {{ label=\"{}\"", - idx, escaped_instruction - )?; - - if self.unknown_operations.contains(&idx) { - f.write_str(" style=dashed")?; - } - - f.write_str(";\n")?; - - let mut state_list: std::collections::BTreeMap< - String, - Vec<(BranchParent, Option)>, - > = Default::default(); - - write!(f, "i{}[style=invis];", idx)?; - - if let Some(this_instruction_uses) = instruction_uses.get(&(idx as i64)) { - for curr_ref in this_instruction_uses.iter() { - if let Some(curr_state) = all_states.get(curr_ref) { - let next_ref = BranchParent { - id: curr_ref.id, - idx: curr_ref.idx + 1, - }; - - if let Some(next_state) = all_states.get(&next_ref) { - let state_diff = next_state.state.diff(&curr_state.state); - - state_list - .entry(state_diff) - .or_default() - .push((*curr_ref, Some(next_ref))); - } else { - state_list - .entry(Default::default()) - .or_default() - .push((*curr_ref, None)); - }; - - if let Some(children) = branch_children.get(curr_ref) { - for next_ref in children { - if let Some(next_state) = all_states.get(next_ref) { - let state_diff = next_state.state.diff(&curr_state.state); - - if !state_diff.is_empty() { - branched_with_state.insert(*next_ref); - } - - state_list - .entry(state_diff) - .or_default() - .push((*curr_ref, Some(*next_ref))); - } - } - }; - } - } - - for curr_ref in this_instruction_uses { - if branch_children.contains_key(curr_ref) { - write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?; - } - } - } else { - write!(f, "i{}->i{}[style=invis];", idx - 1, idx)?; - } - - for (state_num, (state_diff, ref_list)) in state_list.iter().enumerate() { - if !state_diff.is_empty() { - let escaped_state = dot_escape_string(state_diff); - write!( - f, - "subgraph \"cluster_i{}s{}\" {{\nlabel=\"{}\"\n", - idx, state_num, escaped_state - )?; - } - - for (curr_ref, next_ref) in ref_list { - if let Some(next_ref) = next_ref { - let next_program_i = all_states - .get(next_ref) - .map(|s| s.program_i.to_string()) - .unwrap_or_default(); - - if branched_with_state.contains(next_ref) { - write!( - f, - "\"b{}p{}_b{}p{}\"[tooltip=\"next:{}\"];", - curr_ref.id, - curr_ref.idx, - next_ref.id, - next_ref.idx, - next_program_i - )?; - continue; - } else { - write!( - f, - "\"b{}p{}\"[tooltip=\"next:{}\"];", - curr_ref.id, curr_ref.idx, next_program_i - )?; - } - } else { - write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?; - } - } - - if !state_diff.is_empty() { - f.write_str("}\n")?; - } - } - - f.write_str("}\n")?; - } - - f.write_str("};\n")?; //subgraph operations - - let max_branch_id: i64 = [ - self.branch_operations.last_index().unwrap_or(0), - self.branch_results.last_index().unwrap_or(0), - self.branch_results.last_index().unwrap_or(0), - ] - .into_iter() - .max() - .unwrap_or(0); - - f.write_str("subgraph branches {\n")?; - for branch_id in 0..=max_branch_id { - write!(f, "subgraph b{}{{", branch_id)?; - - let branch_num = branch_id as usize; - let color_names = [ - "blue", - "red", - "cyan", - "yellow", - "green", - "magenta", - "orange", - "purple", - "orangered", - "sienna", - "olivedrab", - "pink", - ]; - let color_name_root = color_names[branch_num % color_names.len()]; - let color_name_suffix = match (branch_num / color_names.len()) % 4 { - 0 => "1", - 1 => "4", - 2 => "3", - 3 => "2", - _ => "", - }; //colors are easily confused after color_names.len() * 2, and outright reused after color_names.len() * 4 - write!( - f, - "edge [colorscheme=x11 color={}{}];", - color_name_root, color_name_suffix - )?; - - let mut instruction_list: Vec<(BranchParent, &InstructionHistory)> = Vec::new(); - if let Some(parent) = self.branch_origins.get(&branch_id) { - if let Some(parent_state) = all_states.get(parent) { - instruction_list.push((*parent, parent_state)); - } - } - if let Some(instructions) = self.branch_operations.get(&branch_id) { - for instruction in instructions.iter_entries() { - instruction_list.push(( - BranchParent { - id: branch_id, - idx: instruction.0, - }, - instruction.1, - )) - } - } - - let mut instructions_iter = instruction_list.into_iter(); - - if let Some((cur_ref, _)) = instructions_iter.next() { - let mut prev_ref = cur_ref; - - for (cur_ref, _) in instructions_iter { - if branched_with_state.contains(&cur_ref) { - writeln!( - f, - "\"b{}p{}\" -> \"b{}p{}_b{}p{}\" -> \"b{}p{}\"", - prev_ref.id, - prev_ref.idx, - prev_ref.id, - prev_ref.idx, - cur_ref.id, - cur_ref.idx, - cur_ref.id, - cur_ref.idx - )?; - } else { - write!( - f, - "\"b{}p{}\" -> \"b{}p{}\";", - prev_ref.id, prev_ref.idx, cur_ref.id, cur_ref.idx - )?; - } - prev_ref = cur_ref; - } - - //draw edge to the result of this branch - if let Some(result) = self.branch_results.get(&branch_id) { - if let BranchResult::Dedup(dedup_ref) = result { - write!( - f, - "\"b{}p{}\"->\"b{}p{}\" [style=dotted]", - prev_ref.id, prev_ref.idx, dedup_ref.id, dedup_ref.idx - )?; - } else { - let escaped_result = dot_escape_string(format!("{:?}", result)); - write!( - f, - "\"b{}p{}\" ->\"{}\"; \"{}\" [shape=box];", - prev_ref.id, prev_ref.idx, escaped_result, escaped_result - )?; - } - } else { - write!( - f, - "\"b{}p{}\" ->\"NoResult\"; \"NoResult\" [shape=box];", - prev_ref.id, prev_ref.idx - )?; - } - } - f.write_str("};\n")?; - } - f.write_str("};\n")?; //branches - - f.write_str("}\n")?; - Ok(()) - } -} - -impl<'q, R: Debug, S: Debug + DebugDiff, P: Debug> QueryPlanLogger<'q, R, S, P> { - pub fn new(sql: &'q str, program: &'q [P]) -> Self { - Self { - sql, - unknown_operations: HashSet::new(), - branch_origins: IntMap::new(), - branch_results: IntMap::new(), - branch_operations: IntMap::new(), - program, - } - } - - pub fn log_enabled(&self) -> bool { - log::log_enabled!(target: "sqlx::explain", log::Level::Trace) - || private_tracing_dynamic_enabled!(target: "sqlx::explain", tracing::Level::TRACE) - } - - pub fn add_branch(&mut self, state: I, parent: &BranchParent) - where - BranchParent: From, - { - if !self.log_enabled() { - return; - } - let branch: BranchParent = BranchParent::from(state); - self.branch_origins.insert(branch.id, *parent); - } - - pub fn add_operation(&mut self, program_i: usize, state: I) - where - BranchParent: From, - S: From, - { - if !self.log_enabled() { - return; - } - let branch: BranchParent = BranchParent::from(state); - let state: S = S::from(state); - self.branch_operations - .get_mut_or_default(&branch.id) - .insert(branch.idx, InstructionHistory { program_i, state }); - } - - pub fn add_result(&mut self, state: I, result: BranchResult) - where - BranchParent: for<'a> From<&'a I>, - S: From, - { - if !self.log_enabled() { - return; - } - let branch: BranchParent = BranchParent::from(&state); - self.branch_results.insert(branch.id, result); - } - - pub fn add_unknown_operation(&mut self, operation: usize) { - if !self.log_enabled() { - return; - } - self.unknown_operations.insert(operation); - } - - pub fn finish(&self) { - if !self.log_enabled() { - return; - } - - let mut summary = parse_query_summary(self.sql); - - let sql = if summary != self.sql { - summary.push_str(" …"); - format!( - "\n\n{}\n", - self.sql /* - sqlformat::format( - self.sql, - &sqlformat::QueryParams::None, - sqlformat::FormatOptions::default() - ) - */ - ) - } else { - String::new() - }; - - sqlx_core::private_tracing_dynamic_event!( - target: "sqlx::explain", - tracing::Level::TRACE, - "{}; program:\n{}\n\n{:?}", summary, self, sql - ); - } -} - -impl Drop for QueryPlanLogger<'_, R, S, P> { - fn drop(&mut self) { - self.finish(); - } -} diff --git a/vendor/sqlx-sqlite/src/migrate.rs b/vendor/sqlx-sqlite/src/migrate.rs deleted file mode 100644 index 5864023891..0000000000 --- a/vendor/sqlx-sqlite/src/migrate.rs +++ /dev/null @@ -1,297 +0,0 @@ -use crate::connection::{ConnectOptions, Connection}; -use crate::error::Error; -use crate::executor::Executor; -use crate::fs; -use crate::migrate::MigrateError; -use crate::migrate::{AppliedMigration, Migration}; -use crate::migrate::{Migrate, MigrateDatabase}; -use crate::query::query; -use crate::query_as::query_as; -use crate::{Sqlite, SqliteConnectOptions, SqliteConnection, SqliteJournalMode}; -use futures_core::future::BoxFuture; -use sqlx_core::sql_str::AssertSqlSafe; -use std::str::FromStr; -use std::sync::atomic::Ordering; -use std::time::Duration; -use std::time::Instant; - -pub(crate) use sqlx_core::migrate::*; -use sqlx_core::query_scalar::query_scalar; - -impl MigrateDatabase for Sqlite { - async fn create_database(url: &str) -> Result<(), Error> { - let mut opts = SqliteConnectOptions::from_str(url)?.create_if_missing(true); - - // Since it doesn't make sense to include this flag in the connection URL, - // we just use an `AtomicBool` to pass it. - if super::CREATE_DB_WAL.load(Ordering::Acquire) { - opts = opts.journal_mode(SqliteJournalMode::Wal); - } - - // Opening a connection to sqlite creates the database - opts.connect() - .await? - // Ensure WAL mode tempfiles are cleaned up - .close() - .await?; - - Ok(()) - } - - async fn database_exists(url: &str) -> Result { - let options = SqliteConnectOptions::from_str(url)?; - - if options.in_memory { - Ok(true) - } else { - Ok(options.filename.exists()) - } - } - - async fn drop_database(url: &str) -> Result<(), Error> { - let options = SqliteConnectOptions::from_str(url)?; - - if !options.in_memory { - fs::remove_file(&*options.filename).await?; - } - - Ok(()) - } -} - -impl Migrate for SqliteConnection { - fn create_schema_if_not_exists<'e>( - &'e mut self, - schema_name: &'e str, - ) -> BoxFuture<'e, Result<(), MigrateError>> { - Box::pin(async move { - // Check if the schema already exists; if so, don't error. - let schema_version: Option = query_scalar(AssertSqlSafe(format!( - "PRAGMA {schema_name}.schema_version" - ))) - .fetch_optional(&mut *self) - .await?; - - if schema_version.is_some() { - return Ok(()); - } - - Err(MigrateError::CreateSchemasNotSupported( - format!("cannot create new schema {schema_name}; creation of additional schemas in SQLite requires attaching extra database files"), - )) - }) - } - - fn ensure_migrations_table<'e>( - &'e mut self, - table_name: &'e str, - ) -> BoxFuture<'e, Result<(), MigrateError>> { - Box::pin(async move { - // language=SQLite - self.execute(AssertSqlSafe(format!( - r#" -CREATE TABLE IF NOT EXISTS {table_name} ( - version BIGINT PRIMARY KEY, - description TEXT NOT NULL, - installed_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - success BOOLEAN NOT NULL, - checksum BLOB NOT NULL, - execution_time BIGINT NOT NULL -); - "# - ))) - .await?; - - Ok(()) - }) - } - - fn dirty_version<'e>( - &'e mut self, - table_name: &'e str, - ) -> BoxFuture<'e, Result, MigrateError>> { - Box::pin(async move { - // language=SQLite - let row: Option<(i64,)> = query_as(AssertSqlSafe(format!( - "SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1" - ))) - .fetch_optional(self) - .await?; - - Ok(row.map(|r| r.0)) - }) - } - - fn list_applied_migrations<'e>( - &'e mut self, - table_name: &'e str, - ) -> BoxFuture<'e, Result, MigrateError>> { - Box::pin(async move { - // language=SQLite - let rows: Vec<(i64, Vec)> = query_as(AssertSqlSafe(format!( - "SELECT version, checksum FROM {table_name} ORDER BY version" - ))) - .fetch_all(self) - .await?; - - let migrations = rows - .into_iter() - .map(|(version, checksum)| AppliedMigration { - version, - checksum: checksum.into(), - }) - .collect(); - - Ok(migrations) - }) - } - - fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { - Box::pin(async move { Ok(()) }) - } - - fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { - Box::pin(async move { Ok(()) }) - } - - fn apply<'e>( - &'e mut self, - table_name: &'e str, - migration: &'e Migration, - ) -> BoxFuture<'e, Result> { - Box::pin(async move { - let start = Instant::now(); - - if migration.no_tx { - execute_migration(self, table_name, migration).await?; - } else { - // Use a single transaction for the actual migration script and the essential bookkeeping so we never - // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. - // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for - // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 - // and update it once the actual transaction completed. - let mut tx = self.begin().await?; - execute_migration(&mut tx, table_name, migration).await?; - tx.commit().await?; - } - - // Update `elapsed_time`. - // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept - // this small risk since this value is not super important. - let elapsed = start.elapsed(); - - // language=SQLite - #[allow(clippy::cast_possible_truncation)] - let _ = query(AssertSqlSafe(format!( - r#" - UPDATE {table_name} - SET execution_time = ?1 - WHERE version = ?2 - "# - ))) - .bind(elapsed.as_nanos() as i64) - .bind(migration.version) - .execute(self) - .await?; - - Ok(elapsed) - }) - } - - fn revert<'e>( - &'e mut self, - table_name: &'e str, - migration: &'e Migration, - ) -> BoxFuture<'e, Result> { - Box::pin(async move { - let start = Instant::now(); - - if migration.no_tx { - revert_migration(self, table_name, migration).await?; - } else { - // Use a single transaction for the actual migration script and the essential bookkeeping so we never - // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. - let mut tx = self.begin().await?; - revert_migration(&mut tx, table_name, migration).await?; - tx.commit().await?; - } - - let elapsed = start.elapsed(); - - Ok(elapsed) - }) - } - - fn skip<'e>( - &'e mut self, - table_name: &'e str, - migration: &'e Migration, - ) -> BoxFuture<'e, Result<(), MigrateError>> { - Box::pin(async move { - // language=SQLite - let _ = query(AssertSqlSafe(format!( - r#" - INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) - VALUES ( ?1, ?2, TRUE, ?3, -1 ) - "# - ))) - .bind(migration.version) - .bind(&*migration.description) - .bind(&*migration.checksum) - .execute(self) - .await?; - - Ok(()) - }) - } -} - -async fn execute_migration( - conn: &mut SqliteConnection, - table_name: &str, - migration: &Migration, -) -> Result<(), MigrateError> { - let _ = conn - .execute(migration.sql.clone()) - .await - .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; - - // language=SQLite - let _ = query(AssertSqlSafe(format!( - r#" - INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) - VALUES ( ?1, ?2, TRUE, ?3, -1 ) - "# - ))) - .bind(migration.version) - .bind(&*migration.description) - .bind(&*migration.checksum) - .execute(conn) - .await?; - - Ok(()) -} - -async fn revert_migration( - conn: &mut SqliteConnection, - table_name: &str, - migration: &Migration, -) -> Result<(), MigrateError> { - let _ = conn - .execute(migration.sql.clone()) - .await - .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; - - // language=SQLite - let _ = query(AssertSqlSafe(format!( - r#" - DELETE FROM {table_name} - WHERE version = ?1 - "# - ))) - .bind(migration.version) - .execute(conn) - .await?; - - Ok(()) -} diff --git a/vendor/sqlx-sqlite/src/options/auto_vacuum.rs b/vendor/sqlx-sqlite/src/options/auto_vacuum.rs deleted file mode 100644 index 582631c5bd..0000000000 --- a/vendor/sqlx-sqlite/src/options/auto_vacuum.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::error::Error; -use std::str::FromStr; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum SqliteAutoVacuum { - #[default] - None, - Full, - Incremental, -} - -impl SqliteAutoVacuum { - pub(crate) fn as_str(&self) -> &'static str { - match self { - SqliteAutoVacuum::None => "NONE", - SqliteAutoVacuum::Full => "FULL", - SqliteAutoVacuum::Incremental => "INCREMENTAL", - } - } -} - -impl FromStr for SqliteAutoVacuum { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match &*s.to_ascii_lowercase() { - "none" => SqliteAutoVacuum::None, - "full" => SqliteAutoVacuum::Full, - "incremental" => SqliteAutoVacuum::Incremental, - - _ => { - return Err(Error::Configuration( - format!("unknown value {s:?} for `auto_vacuum`").into(), - )); - } - }) - } -} diff --git a/vendor/sqlx-sqlite/src/options/connect.rs b/vendor/sqlx-sqlite/src/options/connect.rs deleted file mode 100644 index cfa25872b2..0000000000 --- a/vendor/sqlx-sqlite/src/options/connect.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{SqliteConnectOptions, SqliteConnection}; -use log::LevelFilter; -use sqlx_core::config; -use sqlx_core::connection::ConnectOptions; -use sqlx_core::error::Error; -use sqlx_core::executor::Executor; -use sqlx_core::sql_str::AssertSqlSafe; -use std::fmt::Write; -use std::str::FromStr; -use std::time::Duration; -use url::Url; - -impl ConnectOptions for SqliteConnectOptions { - type Connection = SqliteConnection; - - fn from_url(url: &Url) -> Result { - // SQLite URL parsing is handled specially; - // we want to treat the following URLs as equivalent: - // - // * sqlite:foo.db - // * sqlite://foo.db - // - // If we used `Url::path()`, the latter would return an empty string - // because `foo.db` gets parsed as the hostname. - Self::from_str(url.as_str()) - } - - fn to_url_lossy(&self) -> Url { - self.build_url() - } - - async fn connect(&self) -> Result - where - Self::Connection: Sized, - { - let mut conn = SqliteConnection::establish(self).await?; - - // Execute PRAGMAs - conn.execute(AssertSqlSafe(self.pragma_string())).await?; - - if !self.collations.is_empty() { - let mut locked = conn.lock_handle().await?; - - for collation in &self.collations { - collation.create(&mut locked.guard.handle)?; - } - } - - Ok(conn) - } - - fn log_statements(mut self, level: LevelFilter) -> Self { - self.log_settings.log_statements(level); - self - } - - fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { - self.log_settings.log_slow_statements(level, duration); - self - } - - fn __unstable_apply_driver_config( - self, - config: &config::drivers::Config, - ) -> crate::Result { - self.apply_driver_config(&config.sqlite) - } -} - -impl SqliteConnectOptions { - /// Collect all `PRAMGA` commands into a single string - pub(crate) fn pragma_string(&self) -> String { - let mut string = String::new(); - - for (key, opt_value) in &self.pragmas { - if let Some(value) = opt_value { - write!(string, "PRAGMA {key} = {value}; ").ok(); - } - } - - string - } -} diff --git a/vendor/sqlx-sqlite/src/options/journal_mode.rs b/vendor/sqlx-sqlite/src/options/journal_mode.rs deleted file mode 100644 index 5750ce97ec..0000000000 --- a/vendor/sqlx-sqlite/src/options/journal_mode.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::error::Error; -use std::str::FromStr; - -/// Refer to [SQLite documentation] for the meaning of the database journaling mode. -/// -/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_journal_mode -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum SqliteJournalMode { - Delete, - Truncate, - Persist, - Memory, - #[default] - Wal, - Off, -} - -impl SqliteJournalMode { - pub(crate) fn as_str(&self) -> &'static str { - match self { - SqliteJournalMode::Delete => "DELETE", - SqliteJournalMode::Truncate => "TRUNCATE", - SqliteJournalMode::Persist => "PERSIST", - SqliteJournalMode::Memory => "MEMORY", - SqliteJournalMode::Wal => "WAL", - SqliteJournalMode::Off => "OFF", - } - } -} - -impl FromStr for SqliteJournalMode { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match &*s.to_ascii_lowercase() { - "delete" => SqliteJournalMode::Delete, - "truncate" => SqliteJournalMode::Truncate, - "persist" => SqliteJournalMode::Persist, - "memory" => SqliteJournalMode::Memory, - "wal" => SqliteJournalMode::Wal, - "off" => SqliteJournalMode::Off, - - _ => { - return Err(Error::Configuration( - format!("unknown value {s:?} for `journal_mode`").into(), - )); - } - }) - } -} diff --git a/vendor/sqlx-sqlite/src/options/locking_mode.rs b/vendor/sqlx-sqlite/src/options/locking_mode.rs deleted file mode 100644 index 99afd43eef..0000000000 --- a/vendor/sqlx-sqlite/src/options/locking_mode.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::error::Error; -use std::str::FromStr; - -/// Refer to [SQLite documentation] for the meaning of the connection locking mode. -/// -/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_locking_mode -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum SqliteLockingMode { - #[default] - Normal, - Exclusive, -} - -impl SqliteLockingMode { - pub(crate) fn as_str(&self) -> &'static str { - match self { - SqliteLockingMode::Normal => "NORMAL", - SqliteLockingMode::Exclusive => "EXCLUSIVE", - } - } -} - -impl FromStr for SqliteLockingMode { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match &*s.to_ascii_lowercase() { - "normal" => SqliteLockingMode::Normal, - "exclusive" => SqliteLockingMode::Exclusive, - - _ => { - return Err(Error::Configuration( - format!("unknown value {s:?} for `locking_mode`").into(), - )); - } - }) - } -} diff --git a/vendor/sqlx-sqlite/src/options/mod.rs b/vendor/sqlx-sqlite/src/options/mod.rs deleted file mode 100644 index 0cafc4d08c..0000000000 --- a/vendor/sqlx-sqlite/src/options/mod.rs +++ /dev/null @@ -1,675 +0,0 @@ -use std::path::Path; - -mod auto_vacuum; -mod connect; -mod journal_mode; -mod locking_mode; -mod parse; -mod synchronous; - -use crate::connection::LogSettings; -pub use auto_vacuum::SqliteAutoVacuum; -pub use journal_mode::SqliteJournalMode; -pub use locking_mode::SqliteLockingMode; -use std::cmp::Ordering; -use std::sync::Arc; -use std::{borrow::Cow, time::Duration}; -pub use synchronous::SqliteSynchronous; - -use crate::common::DebugFn; -use crate::connection::collation::Collation; -use sqlx_core::{config, IndexMap}; - -/// Options and flags which can be used to configure a SQLite connection. -/// -/// A value of `SqliteConnectOptions` can be parsed from a connection URL, -/// as described by [SQLite](https://www.sqlite.org/uri.html). -/// -/// This type also implements [`FromStr`][std::str::FromStr] so you can parse it from a string -/// containing a connection URL and then further adjust options if necessary (see example below). -/// -/// | URL | Description | -/// | -- | -- | -/// `sqlite::memory:` | Open an in-memory database. | -/// `sqlite:data.db` | Open the file `data.db` in the current directory. | -/// `sqlite://data.db` | Open the file `data.db` in the current directory. | -/// `sqlite:///data.db` | Open the file `data.db` from the root (`/`) directory. | -/// `sqlite://data.db?mode=ro` | Open the file `data.db` for read-only access. | -/// -/// # Example -/// -/// ```rust,no_run -/// # async fn example() -> sqlx::Result<()> { -/// use sqlx::ConnectOptions; -/// use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool}; -/// use std::str::FromStr; -/// -/// let opts = SqliteConnectOptions::from_str("sqlite://data.db")? -/// .journal_mode(SqliteJournalMode::Wal) -/// .read_only(true); -/// -/// // use in a pool -/// let pool = SqlitePool::connect_with(opts).await?; -/// -/// // or connect directly -/// # let opts = SqliteConnectOptions::from_str("sqlite://data.db")?; -/// let conn = opts.connect().await?; -/// # -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone, Debug)] -pub struct SqliteConnectOptions { - pub(crate) filename: Cow<'static, Path>, - pub(crate) in_memory: bool, - pub(crate) read_only: bool, - pub(crate) create_if_missing: bool, - pub(crate) shared_cache: bool, - pub(crate) statement_cache_capacity: usize, - pub(crate) busy_timeout: Duration, - pub(crate) log_settings: LogSettings, - pub(crate) immutable: bool, - pub(crate) vfs: Option>, - - pub(crate) pragmas: IndexMap, Option>>, - - /// Extensions are specified as a pair of \, the majority - /// of SQLite extensions will use the default entry points specified in the docs, these should - /// be added to the map with a `None` value. - /// - #[cfg(feature = "load-extension")] - pub(crate) extensions: IndexMap, Option>>, - - pub(crate) command_channel_size: usize, - pub(crate) row_channel_size: usize, - - pub(crate) collations: Vec, - - pub(crate) serialized: bool, - pub(crate) thread_name: Arc String + Send + Sync + 'static>>, - - pub(crate) optimize_on_close: OptimizeOnClose, - - #[cfg(feature = "regexp")] - pub(crate) register_regexp_function: bool, -} - -#[derive(Clone, Debug)] -pub enum OptimizeOnClose { - Enabled { analysis_limit: Option }, - Disabled, -} - -impl Default for SqliteConnectOptions { - fn default() -> Self { - Self::new() - } -} - -impl SqliteConnectOptions { - /// Construct `Self` with default options. - /// - /// See the source of this method for the current defaults. - pub fn new() -> Self { - let mut pragmas: IndexMap, Option>> = IndexMap::new(); - - // Standard pragmas - // - // Most of these don't actually need to be sent because they would be set to their - // default values anyway. See the SQLite documentation for default values of these PRAGMAs: - // https://www.sqlite.org/pragma.html - // - // However, by inserting into the map here, we can ensure that they're set in the proper - // order, even if they're overwritten later by their respective setters or - // directly by `pragma()` - - // SQLCipher special case: if the `key` pragma is set, it must be executed first. - pragmas.insert("key".into(), None); - - // Other SQLCipher pragmas that has to be after the key, but before any other operation on the database. - // https://www.zetetic.net/sqlcipher/sqlcipher-api/ - - // Bytes of the database file that is not encrypted - // Default for SQLCipher v4 is 0 - // If greater than zero 'cipher_salt' pragma must be also defined - pragmas.insert("cipher_plaintext_header_size".into(), None); - - // Allows to provide salt manually - // By default SQLCipher sets salt automatically, use only in conjunction with - // 'cipher_plaintext_header_size' pragma - pragmas.insert("cipher_salt".into(), None); - - // Number of iterations used in PBKDF2 key derivation. - // Default for SQLCipher v4 is 256000 - pragmas.insert("kdf_iter".into(), None); - - // Define KDF algorithm to be used. - // Default for SQLCipher v4 is PBKDF2_HMAC_SHA512. - pragmas.insert("cipher_kdf_algorithm".into(), None); - - // Enable or disable HMAC functionality. - // Default for SQLCipher v4 is 1. - pragmas.insert("cipher_use_hmac".into(), None); - - // Set default encryption settings depending on the version 1,2,3, or 4. - pragmas.insert("cipher_compatibility".into(), None); - - // Page size of encrypted database. - // Default for SQLCipher v4 is 4096. - pragmas.insert("cipher_page_size".into(), None); - - // Choose algorithm used for HMAC. - // Default for SQLCipher v4 is HMAC_SHA512. - pragmas.insert("cipher_hmac_algorithm".into(), None); - - // Normally, page_size must be set before any other action on the database. - // Defaults to 4096 for new databases. - pragmas.insert("page_size".into(), None); - - // locking_mode should be set before journal_mode: - // https://www.sqlite.org/wal.html#use_of_wal_without_shared_memory - pragmas.insert("locking_mode".into(), None); - - // `auto_vacuum` needs to be executed before `journal_mode`, if set. - // - // Otherwise, a change in the `journal_mode` setting appears to mark even an empty database as dirty, - // requiring a `vacuum` command to be executed to actually apply the new `auto_vacuum` setting. - pragmas.insert("auto_vacuum".into(), None); - - // Don't set `journal_mode` unless the user requested it. - // WAL mode is a permanent setting for created databases and changing into or out of it - // requires an exclusive lock that can't be waited on with `sqlite3_busy_timeout()`. - // https://github.com/launchbadge/sqlx/pull/1930#issuecomment-1168165414 - pragmas.insert("journal_mode".into(), None); - - // We choose to enable foreign key enforcement by default, though SQLite normally - // leaves it off for backward compatibility: https://www.sqlite.org/foreignkeys.html#fk_enable - pragmas.insert("foreign_keys".into(), Some("ON".into())); - - // The `synchronous` pragma defaults to FULL - // https://www.sqlite.org/compile.html#default_synchronous. - pragmas.insert("synchronous".into(), None); - - // Soft limit on the number of rows that `ANALYZE` touches per index. - pragmas.insert("analysis_limit".into(), None); - - Self { - filename: Cow::Borrowed(Path::new(":memory:")), - in_memory: false, - read_only: false, - create_if_missing: false, - shared_cache: false, - statement_cache_capacity: 100, - busy_timeout: Duration::from_secs(5), - log_settings: Default::default(), - immutable: false, - vfs: None, - pragmas, - #[cfg(feature = "load-extension")] - extensions: Default::default(), - collations: Default::default(), - serialized: false, - thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{id}"))), - command_channel_size: 50, - row_channel_size: 50, - optimize_on_close: OptimizeOnClose::Disabled, - #[cfg(feature = "regexp")] - register_regexp_function: false, - } - } - - /// Sets the name of the database file. - /// - /// This is a low-level API, and SQLx will apply no special treatment for `":memory:"` as an - /// in-memory database using this method. Using [`SqliteConnectOptions::from_str()`][SqliteConnectOptions#from_str] may be - /// preferred for simple use cases. - pub fn filename(mut self, filename: impl AsRef) -> Self { - self.filename = Cow::Owned(filename.as_ref().to_owned()); - self - } - - /// Gets the current name of the database file. - pub fn get_filename(&self) -> &Path { - &self.filename - } - - /// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys). - /// - /// SQLx chooses to enable this by default so that foreign keys function as expected, - /// compared to other database flavors. - pub fn foreign_keys(self, on: bool) -> Self { - self.pragma("foreign_keys", if on { "ON" } else { "OFF" }) - } - - /// Set the [`SQLITE_OPEN_MEMORY` flag](https://sqlite.org/c3ref/open.html). - /// - /// By default, this is disabled. - pub fn in_memory(mut self, in_memory: bool) -> Self { - self.in_memory = in_memory; - self - } - - /// Set the [`SQLITE_OPEN_SHAREDCACHE` flag](https://sqlite.org/sharedcache.html). - /// - /// By default, this is disabled. - pub fn shared_cache(mut self, on: bool) -> Self { - self.shared_cache = on; - self - } - - /// Sets the [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) for the database connection. - /// - /// Journal modes are ephemeral per connection, with the exception of the - /// [Write-Ahead Log (WAL) mode](https://www.sqlite.org/wal.html). - /// - /// A database created in WAL mode retains the setting and will apply it to all connections - /// opened against it that don't set a `journal_mode`. - /// - /// Opening a connection to a database created in WAL mode with a different `journal_mode` will - /// erase the setting on the database, requiring an exclusive lock to do so. - /// You may get a `database is locked` (corresponding to `SQLITE_BUSY`) error if another - /// connection is accessing the database file at the same time. - /// - /// SQLx does not set a journal mode by default, to avoid unintentionally changing a database - /// into or out of WAL mode. - /// - /// The default journal mode for non-WAL databases is `DELETE`, or `MEMORY` for in-memory - /// databases. - /// - /// For consistency, any commands in `sqlx-cli` which create a SQLite database will create it - /// in WAL mode. - pub fn journal_mode(self, mode: SqliteJournalMode) -> Self { - self.pragma("journal_mode", mode.as_str()) - } - - /// Sets the [locking mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) for the database connection. - /// - /// The default locking mode is NORMAL. - pub fn locking_mode(self, mode: SqliteLockingMode) -> Self { - self.pragma("locking_mode", mode.as_str()) - } - - /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to open the database - /// for read-only access. - pub fn read_only(mut self, read_only: bool) -> Self { - self.read_only = read_only; - self - } - - /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to create the database file - /// if the file does not exist. - /// - /// By default, a new file **will not be created** if one is not found. - pub fn create_if_missing(mut self, create: bool) -> Self { - self.create_if_missing = create; - self - } - - /// Sets the capacity of the connection's statement cache in a number of stored - /// distinct statements. Caching is handled using LRU, meaning when the - /// amount of queries hits the defined limit, the oldest statement will get - /// dropped. - /// - /// The default cache capacity is 100 statements. - pub fn statement_cache_capacity(mut self, capacity: usize) -> Self { - self.statement_cache_capacity = capacity; - self - } - - /// Sets a timeout value to wait when the database is locked, before - /// returning a busy timeout error. - /// - /// The default busy timeout is 5 seconds. - pub fn busy_timeout(mut self, timeout: Duration) -> Self { - self.busy_timeout = timeout; - self - } - - /// Sets the [synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) setting for the database connection. - /// - /// The default synchronous settings is FULL. However, if durability is not a concern, - /// then NORMAL is normally all one needs in WAL mode. - pub fn synchronous(self, synchronous: SqliteSynchronous) -> Self { - self.pragma("synchronous", synchronous.as_str()) - } - - /// Sets the [auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) setting for the database connection. - /// - /// The default auto_vacuum setting is NONE. - /// - /// For existing databases, a change to this value does not take effect unless a - /// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed. - pub fn auto_vacuum(self, auto_vacuum: SqliteAutoVacuum) -> Self { - self.pragma("auto_vacuum", auto_vacuum.as_str()) - } - - /// Sets the [page_size](https://www.sqlite.org/pragma.html#pragma_page_size) setting for the database connection. - /// - /// The default page_size setting is 4096. - /// - /// For existing databases, a change to this value does not take effect unless a - /// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed. - /// However, it cannot be changed in WAL mode. - pub fn page_size(self, page_size: u32) -> Self { - self.pragma("page_size", page_size.to_string()) - } - - /// Sets custom initial pragma for the database connection. - pub fn pragma(mut self, key: K, value: V) -> Self - where - K: Into>, - V: Into>, - { - self.pragmas.insert(key.into(), Some(value.into())); - self - } - - /// Add a custom collation for comparing strings in SQL. - /// - /// If a collation with the same name already exists, it will be replaced. - /// - /// See [`sqlite3_create_collation()`](https://www.sqlite.org/c3ref/create_collation.html) for details. - /// - /// Note this excerpt: - /// > The collating function must obey the following properties for all strings A, B, and C: - /// > - /// > If A==B then B==A. - /// > If A==B and B==C then A==C. - /// > If A\A. - /// > If A - /// > If a collating function fails any of the above constraints and that collating function is - /// > registered and used, then the behavior of SQLite is undefined. - pub fn collation(mut self, name: N, collate: F) -> Self - where - N: Into>, - F: Fn(&str, &str) -> Ordering + Send + Sync + 'static, - { - self.collations.push(Collation::new(name, collate)); - self - } - - /// Set to `true` to signal to SQLite that the database file is on read-only media. - /// - /// If enabled, SQLite assumes the database file _cannot_ be modified, even by higher - /// privileged processes, and so disables locking and change detection. This is intended - /// to improve performance but can produce incorrect query results or errors if the file - /// _does_ change. - /// - /// Note that this is different from the `SQLITE_OPEN_READONLY` flag set by - /// [`.read_only()`][Self::read_only], though the documentation suggests that this - /// does _imply_ `SQLITE_OPEN_READONLY`. - /// - /// See [`sqlite3_open`](https://www.sqlite.org/capi3ref.html#sqlite3_open) (subheading - /// "URI Filenames") for details. - pub fn immutable(mut self, immutable: bool) -> Self { - self.immutable = immutable; - self - } - - /// Sets the [threading mode](https://www.sqlite.org/threadsafe.html) for the database connection. - /// - /// The default setting is `false` corresponding to using `OPEN_NOMUTEX`. - /// If set to `true` then `OPEN_FULLMUTEX`. - /// - /// See [open](https://www.sqlite.org/c3ref/open.html) for more details. - /// - /// ### Note - /// Setting this to `true` may help if you are getting access violation errors or segmentation - /// faults, but will also incur a significant performance penalty. You should leave this - /// set to `false` if at all possible. - /// - /// If you do end up needing to set this to `true` for some reason, please - /// [open an issue](https://github.com/launchbadge/sqlx/issues/new/choose) as this may indicate - /// a concurrency bug in SQLx. Please provide clear instructions for reproducing the issue, - /// including a sample database schema if applicable. - pub fn serialized(mut self, serialized: bool) -> Self { - self.serialized = serialized; - self - } - - /// Provide a callback to generate the name of the background worker thread. - /// - /// The value passed to the callback is an auto-incremented integer for use as the thread ID. - pub fn thread_name( - mut self, - generator: impl Fn(u64) -> String + Send + Sync + 'static, - ) -> Self { - self.thread_name = Arc::new(DebugFn(generator)); - self - } - - /// Set the maximum number of commands to buffer for the worker thread before backpressure is - /// applied. - /// - /// Given that most commands sent to the worker thread involve waiting for a result, - /// the command channel is unlikely to fill up unless a lot queries are executed in a short - /// period but cancelled before their full resultsets are returned. - pub fn command_buffer_size(mut self, size: usize) -> Self { - self.command_channel_size = size; - self - } - - /// Set the maximum number of rows to buffer back to the calling task when a query is executed. - /// - /// If the calling task cannot keep up, backpressure will be applied to the worker thread - /// in order to limit CPU and memory usage. - pub fn row_buffer_size(mut self, size: usize) -> Self { - self.row_channel_size = size; - self - } - - /// Sets the [`vfs`](https://www.sqlite.org/vfs.html) parameter of the database connection. - /// - /// The default value is empty, and sqlite will use the default VFS object depending on the - /// operating system. - pub fn vfs(mut self, vfs_name: impl Into>) -> Self { - self.vfs = Some(vfs_name.into()); - self - } - - /// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database - /// connection at startup, using the default entrypoint. - /// - /// Most common SQLite extensions can be loaded using this method. - /// For extensions where you need to override the entry point, - /// use [`.extension_with_entrypoint()`]. - /// - /// Multiple extensions can be loaded by calling this method, - /// or [`.extension_with_entrypoint()`] where applicable, - /// once for each extension. - /// - /// Extension loading is only enabled during the initialization of the connection, - /// and disabled before `connect()` returns by setting - /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0. - /// - /// This will not enable the SQL `load_extension()` function. - /// - /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension - /// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint - /// - /// # Safety - /// This causes arbitrary DLLs on the filesystem to be loaded at runtime, - /// which can easily result in undefined behavior, memory corruption, - /// or exploitable vulnerabilities if misused. - /// - /// It is not possible to provide a truly safe version of this API. - /// - /// Use this method with care, and only load extensions that you trust. - /// - /// # Example - /// ```rust,no_run - /// # use sqlx_core::error::Error; - /// # use std::str::FromStr; - /// # use sqlx_sqlite::SqliteConnectOptions; - /// # fn options() -> Result { - /// let mut options = SqliteConnectOptions::from_str("sqlite://data.db")?; - /// - /// // SAFETY: these are trusted extensions. - /// unsafe { - /// options = options - /// .extension("vsv") - /// .extension("mod_spatialite"); - /// } - /// - /// # Ok(options) - /// # } - /// ``` - #[cfg(feature = "load-extension")] - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))] - pub unsafe fn extension(mut self, extension_name: impl Into>) -> Self { - self.extensions.insert(extension_name.into(), None); - self - } - - /// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database - /// connection at startup, overriding the entrypoint. - /// - /// See also [`.extension()`] for extensions using the standard entrypoint name - /// `sqlite3_extension_init` or `sqlite3__init`. - /// - /// Multiple extensions can be loaded by calling this method, - /// or [`.extension()`] where applicable, - /// once for each extension. - /// - /// Extension loading is only enabled during the initialization of the connection, - /// and disabled before `connect()` returns by setting - /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0. - /// - /// This will not enable the SQL `load_extension()` function. - /// - /// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension - /// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint - /// - /// # Safety - /// This causes arbitrary DLLs on the filesystem to be loaded at runtime, - /// which can easily result in undefined behavior, memory corruption, - /// or exploitable vulnerabilities if misused. - /// - /// If you specify the wrong entrypoint name, it _may_ simply result in an error, - /// or it may end up invoking the wrong routine, leading to undefined behavior. - /// - /// It is not possible to provide a truly safe version of this API. - /// - /// Use this method with care, only load extensions that you trust, - /// and double-check the entrypoint name with the extension's documentation or source code. - #[cfg(feature = "load-extension")] - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))] - pub unsafe fn extension_with_entrypoint( - mut self, - extension_name: impl Into>, - entry_point: impl Into>, - ) -> Self { - self.extensions - .insert(extension_name.into(), Some(entry_point.into())); - self - } - - /// Execute `PRAGMA optimize;` on the SQLite connection before closing. - /// - /// The SQLite manual recommends using this for long-lived databases. - /// - /// This will collect and store statistics about the layout of data in your tables to help the query planner make better decisions. - /// Over the connection's lifetime, the query planner will make notes about which tables could use up-to-date statistics so this - /// command doesn't have to scan the whole database every time. Thus, the best time to execute this is on connection close. - /// - /// `analysis_limit` sets a soft limit on the maximum number of rows to scan per index. - /// It is equivalent to setting [`Self::analysis_limit`] but only takes effect for the `PRAGMA optimize;` call - /// and does not affect the behavior of any `ANALYZE` statements made during the connection's lifetime. - /// - /// If not `None`, the `analysis_limit` here overrides the global `analysis_limit` setting, - /// but only for the `PRAGMA optimize;` call. - /// - /// Not enabled by default. - /// - /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#automatically_running_analyze) for details. - pub fn optimize_on_close( - mut self, - enabled: bool, - analysis_limit: impl Into>, - ) -> Self { - self.optimize_on_close = if enabled { - OptimizeOnClose::Enabled { - analysis_limit: (analysis_limit.into()), - } - } else { - OptimizeOnClose::Disabled - }; - self - } - - /// Set a soft limit on the number of rows that `ANALYZE` touches per index. - /// - /// This also affects `PRAGMA optimize` which is set by [Self::optimize_on_close]. - /// - /// The value recommended by SQLite is `400`. There is no default. - /// - /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#approx) for details. - pub fn analysis_limit(mut self, limit: impl Into>) -> Self { - if let Some(limit) = limit.into() { - return self.pragma("analysis_limit", limit.to_string()); - } - self.pragmas.insert("analysis_limit".into(), None); - self - } - - /// Register a regexp function that allows using regular expressions in queries. - /// - /// ``` - /// # use std::str::FromStr; - /// # use sqlx::{ConnectOptions, Connection, Row}; - /// # use sqlx_sqlite::SqliteConnectOptions; - /// # async fn run() -> sqlx::Result<()> { - /// let mut sqlite = SqliteConnectOptions::from_str("sqlite://:memory:")? - /// .with_regexp() - /// .connect() - /// .await?; - /// let tables = sqlx::query("SELECT name FROM sqlite_schema WHERE name REGEXP 'foo(\\d+)bar'") - /// .fetch_all(&mut sqlite) - /// .await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// This uses the [`regex`] crate, and is only enabled when you enable the `regex` feature is enabled on sqlx - #[cfg(feature = "regexp")] - pub fn with_regexp(mut self) -> Self { - self.register_regexp_function = true; - self - } - - #[cfg_attr(not(feature = "load-extension"), expect(unused_mut))] - pub(crate) fn apply_driver_config( - mut self, - config: &config::drivers::SqliteConfig, - ) -> crate::Result { - #[cfg(feature = "load-extension")] - for extension in &config.unsafe_load_extensions { - // SAFETY: the documentation warns the user about loading extensions - match extension { - config::drivers::SqliteExtension::Path(path) => { - self = unsafe { self.extension(path.clone()) } - } - config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => { - self = - unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) } - } - } - } - - #[cfg(not(feature = "load-extension"))] - if !config.unsafe_load_extensions.is_empty() { - return Err(sqlx_core::Error::Configuration( - format!( - "sqlx.toml specifies `drivers.sqlite.unsafe-load-extensions = {:?}` \ - but extension loading is not enabled; \ - enable the `sqlite-load-extension` feature of SQLx to use SQLite extensions", - config.unsafe_load_extensions, - ) - .into(), - )); - } - - Ok(self) - } -} diff --git a/vendor/sqlx-sqlite/src/options/parse.rs b/vendor/sqlx-sqlite/src/options/parse.rs deleted file mode 100644 index 0530f4204c..0000000000 --- a/vendor/sqlx-sqlite/src/options/parse.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::borrow::Cow; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use percent_encoding::{percent_decode_str, percent_encode, AsciiSet}; -use url::Url; - -use crate::error::Error; -use crate::SqliteConnectOptions; - -// https://www.sqlite.org/uri.html - -static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0); - -impl SqliteConnectOptions { - pub(crate) fn from_db_and_params(database: &str, params: Option<&str>) -> Result { - let mut options = Self::default(); - - if database == ":memory:" { - options.in_memory = true; - options.shared_cache = true; - let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed); - options.filename = Cow::Owned(PathBuf::from(format!("file:sqlx-in-memory-{seqno}"))); - } else { - // % decode to allow for `?` or `#` in the filename - options.filename = Cow::Owned( - Path::new( - &*percent_decode_str(database) - .decode_utf8() - .map_err(Error::config)?, - ) - .to_path_buf(), - ); - } - - if let Some(params) = params { - for (key, value) in url::form_urlencoded::parse(params.as_bytes()) { - match &*key { - // The mode query parameter determines if the new database is opened read-only, - // read-write, read-write and created if it does not exist, or that the - // database is a pure in-memory database that never interacts with disk, - // respectively. - "mode" => { - match &*value { - "ro" => { - options.read_only = true; - } - - // default - "rw" => {} - - "rwc" => { - options.create_if_missing = true; - } - - "memory" => { - options.in_memory = true; - options.shared_cache = true; - } - - _ => { - return Err(Error::Configuration( - format!("unknown value {value:?} for `mode`").into(), - )); - } - } - } - - // The cache query parameter specifies the cache behaviour across multiple - // connections to the same database within the process. A shared cache is - // essential for persisting data across connections to an in-memory database. - "cache" => match &*value { - "private" => { - options.shared_cache = false; - } - - "shared" => { - options.shared_cache = true; - } - - _ => { - return Err(Error::Configuration( - format!("unknown value {value:?} for `cache`").into(), - )); - } - }, - - "immutable" => match &*value { - "true" | "1" => { - options.immutable = true; - } - "false" | "0" => { - options.immutable = false; - } - _ => { - return Err(Error::Configuration( - format!("unknown value {value:?} for `immutable`").into(), - )); - } - }, - - "vfs" => options.vfs = Some(Cow::Owned(value.into_owned())), - - _ => { - return Err(Error::Configuration( - format!("unknown query parameter `{key}` while parsing connection URL") - .into(), - )); - } - } - } - } - - Ok(options) - } - - pub(crate) fn build_url(&self) -> Url { - // https://url.spec.whatwg.org/#path-percent-encode-set - static PATH_ENCODE_SET: AsciiSet = percent_encoding::CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'`') - .add(b'{') - .add(b'}'); - - let filename_encoded = percent_encode( - self.filename.as_os_str().as_encoded_bytes(), - &PATH_ENCODE_SET, - ); - - let mut url = Url::parse(&format!("sqlite://{filename_encoded}")) - .expect("BUG: generated un-parseable URL"); - - let mode = match (self.in_memory, self.create_if_missing, self.read_only) { - (true, _, _) => "memory", - (false, true, _) => "rwc", - (false, false, true) => "ro", - (false, false, false) => "rw", - }; - url.query_pairs_mut().append_pair("mode", mode); - - let cache = match self.shared_cache { - true => "shared", - false => "private", - }; - url.query_pairs_mut().append_pair("cache", cache); - - if self.immutable { - url.query_pairs_mut().append_pair("immutable", "true"); - } - - if let Some(vfs) = &self.vfs { - url.query_pairs_mut().append_pair("vfs", vfs); - } - - url - } -} - -impl FromStr for SqliteConnectOptions { - type Err = Error; - - fn from_str(mut url: &str) -> Result { - // remove scheme from the URL - url = url - .trim_start_matches("sqlite://") - .trim_start_matches("sqlite:"); - - let mut database_and_params = url.splitn(2, '?'); - - let database = database_and_params.next().unwrap_or_default(); - let params = database_and_params.next(); - - Self::from_db_and_params(database, params) - } -} - -#[test] -fn test_parse_in_memory() -> Result<(), Error> { - let options: SqliteConnectOptions = "sqlite::memory:".parse()?; - assert!(options.in_memory); - assert!(options.shared_cache); - - let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?; - assert!(options.in_memory); - assert!(options.shared_cache); - - let options: SqliteConnectOptions = "sqlite://:memory:".parse()?; - assert!(options.in_memory); - assert!(options.shared_cache); - - let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?; - assert!(options.in_memory); - assert!(!options.shared_cache); - - Ok(()) -} - -#[test] -fn test_parse_read_only() -> Result<(), Error> { - let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?; - assert!(options.read_only); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - Ok(()) -} - -#[test] -fn test_parse_shared_in_memory() -> Result<(), Error> { - let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?; - assert!(options.shared_cache); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - Ok(()) -} - -#[test] -fn it_returns_the_parsed_url() -> Result<(), Error> { - let url = "sqlite://test.db?mode=rw&cache=shared"; - let options: SqliteConnectOptions = url.parse()?; - - let expected_url = Url::parse(url).unwrap(); - assert_eq!(options.build_url(), expected_url); - - Ok(()) -} diff --git a/vendor/sqlx-sqlite/src/options/synchronous.rs b/vendor/sqlx-sqlite/src/options/synchronous.rs deleted file mode 100644 index 709b805bc7..0000000000 --- a/vendor/sqlx-sqlite/src/options/synchronous.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::error::Error; -use std::str::FromStr; - -/// Refer to [SQLite documentation] for the meaning of various synchronous settings. -/// -/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_synchronous -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum SqliteSynchronous { - Off, - Normal, - #[default] - Full, - Extra, -} - -impl SqliteSynchronous { - pub(crate) fn as_str(&self) -> &'static str { - match self { - SqliteSynchronous::Off => "OFF", - SqliteSynchronous::Normal => "NORMAL", - SqliteSynchronous::Full => "FULL", - SqliteSynchronous::Extra => "EXTRA", - } - } -} - -impl FromStr for SqliteSynchronous { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match &*s.to_ascii_lowercase() { - "off" => SqliteSynchronous::Off, - "normal" => SqliteSynchronous::Normal, - "full" => SqliteSynchronous::Full, - "extra" => SqliteSynchronous::Extra, - - _ => { - return Err(Error::Configuration( - format!("unknown value {s:?} for `synchronous`").into(), - )); - } - }) - } -} diff --git a/vendor/sqlx-sqlite/src/query_result.rs b/vendor/sqlx-sqlite/src/query_result.rs deleted file mode 100644 index 8c8c27fcf4..0000000000 --- a/vendor/sqlx-sqlite/src/query_result.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::iter::{Extend, IntoIterator}; - -#[derive(Debug, Default)] -pub struct SqliteQueryResult { - pub(super) changes: u64, - pub(super) last_insert_rowid: i64, -} - -impl SqliteQueryResult { - pub fn rows_affected(&self) -> u64 { - self.changes - } - - pub fn last_insert_rowid(&self) -> i64 { - self.last_insert_rowid - } -} - -impl Extend for SqliteQueryResult { - fn extend>(&mut self, iter: T) { - for elem in iter { - self.changes += elem.changes; - self.last_insert_rowid = elem.last_insert_rowid; - } - } -} - -#[cfg(feature = "any")] -impl From for sqlx_core::any::AnyQueryResult { - fn from(done: SqliteQueryResult) -> Self { - let last_insert_id = match done.last_insert_rowid() { - 0 => None, - n => Some(n), - }; - sqlx_core::any::AnyQueryResult { - rows_affected: done.rows_affected(), - last_insert_id, - } - } -} diff --git a/vendor/sqlx-sqlite/src/regexp.rs b/vendor/sqlx-sqlite/src/regexp.rs deleted file mode 100644 index 735200e9be..0000000000 --- a/vendor/sqlx-sqlite/src/regexp.rs +++ /dev/null @@ -1,281 +0,0 @@ -#![deny(missing_docs, clippy::pedantic)] -#![allow(clippy::cast_sign_loss)] // some lengths returned from sqlite3 are `i32`, but rust needs `usize` - -//! Here be dragons -//! -//! We need to register a custom REGEX implementation for sqlite -//! some useful resources: -//! - rusqlite has an example implementation: -//! - sqlite supports registering custom C functions: -//! - sqlite also supports a `A REGEXP B` syntax, but ONLY if the user implements `regex(B, A)` -//! - Note that A and B are indeed swapped: the regex comes first, the field comes second -//! - -//! - sqlx has a way to safely get a sqlite3 pointer: -//! - -//! - - -use libsqlite3_sys as ffi; -use log::error; -use regex::Regex; -use std::sync::Arc; - -/// The function name for sqlite3. This must be "regexp\0" -static FN_NAME: &[u8] = b"regexp\0"; - -/// Register the regex function with sqlite. -/// -/// Returns the result code of `sqlite3_create_function_v2` -pub fn register(sqlite3: *mut ffi::sqlite3) -> i32 { - unsafe { - ffi::sqlite3_create_function_v2( - // the database connection - sqlite3, - // the function name. Must be up to 255 bytes, and 0-terminated - FN_NAME.as_ptr().cast(), - // the number of arguments this function accepts. We want 2 arguments: The regex and the field - 2, - // we want all our strings to be UTF8, and this function will return the same output with the same inputs - ffi::SQLITE_UTF8 | ffi::SQLITE_DETERMINISTIC, - // pointer to user data. We're not using user data - std::ptr::null_mut(), - // xFunc to be executed when we are invoked - Some(sqlite3_regexp_func), - // xStep, should be NULL for scalar functions - None, - // xFinal, should be NULL for scalar functions - None, - // xDestroy, called when this function is deregistered. Should be used to clean up our pointer to user-data - None, - ) - } -} - -/// A function to be called on each invocation of `regex(REGEX, FIELD)` from sqlite3 -/// -/// - `ctx`: a pointer to the current sqlite3 context -/// - `n_arg`: The length of `args` -/// - `args`: the arguments of this function call -unsafe extern "C" fn sqlite3_regexp_func( - ctx: *mut ffi::sqlite3_context, - n_arg: i32, - args: *mut *mut ffi::sqlite3_value, -) { - // check the arg size. sqlite3 should already ensure this is only 2 args but we want to double check - if n_arg != 2 { - eprintln!("n_arg expected to be 2, is {n_arg}"); - ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); - return; - } - - // arg0: Regex - let Some(regex) = get_regex_from_arg(ctx, *args, 0) else { - return; - }; - - // arg1: value - let Some(value) = get_text_from_arg(ctx, *args.add(1)) else { - return; - }; - - // if the regex matches the value, set the result int as 1, else as 0 - if regex.is_match(value) { - ffi::sqlite3_result_int(ctx, 1); - } else { - ffi::sqlite3_result_int(ctx, 0); - } -} - -/// Get the regex from the given `arg` at the given `index`. -/// -/// First this will check to see if the value exists in sqlite's `auxdata`. If it does, that regex will be returned. -/// sqlite is able to clean up this data at any point, but rust's [`Arc`] guarantees make sure things don't break. -/// -/// If this value does not exist in `auxdata`, [`try_load_value`] is called and a regex is created from this. If any of -/// those fail, a message is printed and `None` is returned. -/// -/// After this regex is created it is stored in `auxdata` and loaded again. If it fails to load, this means that -/// something inside of sqlite3 went wrong, and we return `None`. -/// -/// If this value is stored correctly, or if it already existed, the arc reference counter is increased and this value is returned. -unsafe fn get_regex_from_arg( - ctx: *mut ffi::sqlite3_context, - arg: *mut ffi::sqlite3_value, - index: i32, -) -> Option> { - // try to get the auxdata for this field - let ptr = ffi::sqlite3_get_auxdata(ctx, index); - if !ptr.is_null() { - // if we have it, turn it into an Arc. - // we need to make sure to call `increment_strong_count` because the returned `Arc` decrement this when it goes out of scope - let ptr = ptr as *const Regex; - Arc::increment_strong_count(ptr); - return Some(Arc::from_raw(ptr)); - } - // get the text for this field - let value = get_text_from_arg(ctx, arg)?; - // try to compile it into a regex - let regex = match Regex::new(value) { - Ok(regex) => Arc::new(regex), - Err(e) => { - error!("Invalid regex {value:?}: {e:?}"); - ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); - return None; - } - }; - // set the regex as auxdata for the next time around - ffi::sqlite3_set_auxdata( - ctx, - index, - // make sure to call `Arc::clone` here, setting the strong count to 2. - // this will be cleaned up at 2 points: - // - when the returned arc goes out of scope - // - when sqlite decides to clean it up an calls `cleanup_arc_regex_pointer` - Arc::into_raw(Arc::clone(®ex)) as *mut _, - Some(cleanup_arc_regex_pointer), - ); - Some(regex) -} - -/// Get a text reference of the value of `arg`. Returns `None` for NULL values. -/// -/// For non-NULL values, `sqlite3_value_text()` is called directly, which lets SQLite -/// coerce INTEGER, REAL, and BLOB values to their text representation. This matches -/// the coercion behavior documented at . -/// -/// The returned `&str` is valid for lifetime `'a` which can be determined by the caller. This lifetime should **not** -/// outlive `ctx`. -unsafe fn get_text_from_arg<'a>( - ctx: *mut ffi::sqlite3_context, - arg: *mut ffi::sqlite3_value, -) -> Option<&'a str> { - let ty = ffi::sqlite3_value_type(arg); - if ty == ffi::SQLITE_NULL { - return None; - } - let ptr = ffi::sqlite3_value_text(arg); - let len = ffi::sqlite3_value_bytes(arg); - let slice = std::slice::from_raw_parts(ptr.cast(), len as usize); - match std::str::from_utf8(slice) { - Ok(result) => Some(result), - Err(e) => { - log::error!("Incoming text is not valid UTF8: {e:?}"); - ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); - None - } - } -} - -/// Clean up the `Arc` that is stored in the given `ptr`. -unsafe extern "C" fn cleanup_arc_regex_pointer(ptr: *mut std::ffi::c_void) { - Arc::decrement_strong_count(ptr.cast::()); -} - -#[cfg(test)] -mod tests { - use sqlx::{ConnectOptions, Row}; - use std::str::FromStr; - - async fn test_db() -> crate::SqliteConnection { - let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:") - .unwrap() - .with_regexp() - .connect() - .await - .unwrap(); - sqlx::query("CREATE TABLE test (col TEXT NOT NULL)") - .execute(&mut conn) - .await - .unwrap(); - for i in 0..10 { - sqlx::query("INSERT INTO test VALUES (?)") - .bind(format!("value {i}")) - .execute(&mut conn) - .await - .unwrap(); - } - conn - } - - #[sqlx::test] - async fn test_regexp_does_not_fail() { - let mut conn = test_db().await; - let result = sqlx::query("SELECT col FROM test WHERE col REGEXP 'foo.*bar'") - .fetch_all(&mut conn) - .await - .expect("Could not execute query"); - assert!(result.is_empty()); - } - - #[sqlx::test] - async fn test_regexp_filters_correctly() { - let mut conn = test_db().await; - - let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '.*2'") - .fetch_all(&mut conn) - .await - .expect("Could not execute query"); - assert_eq!(result.len(), 1); - assert_eq!(result[0].get::(0), String::from("value 2")); - - let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '^3'") - .fetch_all(&mut conn) - .await - .expect("Could not execute query"); - assert!(result.is_empty()); - } - - #[sqlx::test] - async fn test_regexp_coerces_non_text_values() { - let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:") - .unwrap() - .with_regexp() - .connect() - .await - .unwrap(); - - // INTEGER coercion - let result: Option = sqlx::query_scalar("SELECT 123 REGEXP '23'") - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(result, Some(1)); - - // REAL coercion - let result: Option = sqlx::query_scalar("SELECT 12.5 REGEXP '12\\.5'") - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(result, Some(1)); - - // INTEGER column - sqlx::query("CREATE TABLE int_test (x INTEGER NOT NULL)") - .execute(&mut conn) - .await - .unwrap(); - sqlx::query("INSERT INTO int_test VALUES (123), (45)") - .execute(&mut conn) - .await - .unwrap(); - let rows: Vec = sqlx::query_scalar("SELECT x FROM int_test WHERE x REGEXP '23'") - .fetch_all(&mut conn) - .await - .unwrap(); - assert_eq!(rows, vec![123]); - - // NULL should return NULL, not match - let result: Option = sqlx::query_scalar("SELECT NULL REGEXP '.*'") - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(result, None); - } - - #[sqlx::test] - async fn test_invalid_regexp_should_fail() { - let mut conn = test_db().await; - let result = sqlx::query("SELECT col from test WHERE col REGEXP '(?:?)'") - .execute(&mut conn) - .await; - assert!(matches!(result, Err(sqlx::Error::Database(_)))); - } -} diff --git a/vendor/sqlx-sqlite/src/row.rs b/vendor/sqlx-sqlite/src/row.rs deleted file mode 100644 index ec038b6d40..0000000000 --- a/vendor/sqlx-sqlite/src/row.rs +++ /dev/null @@ -1,95 +0,0 @@ -#![allow(clippy::rc_buffer)] - -use std::sync::Arc; - -use sqlx_core::column::ColumnIndex; -use sqlx_core::error::Error; -use sqlx_core::ext::ustr::UStr; -use sqlx_core::row::{debug_row, Row}; -use sqlx_core::HashMap; - -use crate::statement::StatementHandle; -use crate::{Sqlite, SqliteColumn, SqliteValue, SqliteValueRef}; - -/// Implementation of [`Row`] for SQLite. -pub struct SqliteRow { - pub(crate) values: Box<[SqliteValue]>, - pub(crate) columns: Arc>, - pub(crate) column_names: Arc>, -} - -// Accessing values from the statement object is -// safe across threads as long as we don't call [sqlite3_step] - -// we block ourselves from doing that by only exposing -// a set interface on [StatementHandle] - -unsafe impl Send for SqliteRow {} -unsafe impl Sync for SqliteRow {} - -impl SqliteRow { - pub(crate) fn current( - statement: &StatementHandle, - columns: &Arc>, - column_names: &Arc>, - ) -> Self { - let size = statement.column_count(); - let mut values = Vec::with_capacity(size); - - for i in 0..size { - values.push(unsafe { - let raw = statement.column_value(i); - - SqliteValue::dup(raw, Some(columns[i].type_info.clone())) - }); - } - - Self { - values: values.into_boxed_slice(), - columns: Arc::clone(columns), - column_names: Arc::clone(column_names), - } - } -} - -impl Row for SqliteRow { - type Database = Sqlite; - - fn columns(&self) -> &[SqliteColumn] { - &self.columns - } - - fn try_get_raw(&self, index: I) -> Result, Error> - where - I: ColumnIndex, - { - let index = index.index(self)?; - Ok(SqliteValueRef::value(&self.values[index])) - } -} - -impl ColumnIndex for &'_ str { - fn index(&self, row: &SqliteRow) -> Result { - row.column_names - .get(*self) - .ok_or_else(|| Error::ColumnNotFound((*self).into())) - .copied() - } -} - -impl std::fmt::Debug for SqliteRow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - debug_row(self, f) - } -} - -// #[cfg(feature = "any")] -// impl From for crate::any::AnyRow { -// #[inline] -// fn from(row: SqliteRow) -> Self { -// crate::any::AnyRow { -// columns: row.columns.iter().map(|col| col.clone().into()).collect(), -// kind: crate::any::row::AnyRowKind::Sqlite(row), -// } -// } -// } diff --git a/vendor/sqlx-sqlite/src/statement/handle.rs b/vendor/sqlx-sqlite/src/statement/handle.rs deleted file mode 100644 index c78ce98414..0000000000 --- a/vendor/sqlx-sqlite/src/statement/handle.rs +++ /dev/null @@ -1,466 +0,0 @@ -use std::ffi::c_void; -use std::ffi::CStr; - -use crate::error::{BoxDynError, Error}; -use crate::type_info::DataType; -use crate::{SqliteError, SqliteTypeInfo}; -use libsqlite3_sys::{ - sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64, - sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name, - sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob, - sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name, - sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64, - sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name, - sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset, - sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata, - sqlite3_value, SQLITE_DONE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT, - SQLITE_UTF8, -}; -use sqlx_core::column::{ColumnOrigin, TableColumn}; -use std::os::raw::{c_char, c_int}; -use std::ptr; -use std::ptr::NonNull; -use std::slice::from_raw_parts; -use std::str::from_utf8; -use std::sync::Arc; - -#[derive(Debug)] -pub(crate) struct StatementHandle(NonNull); - -// access to SQLite3 statement handles are safe to send and share between threads -// as long as the `sqlite3_step` call is serialized. - -unsafe impl Send for StatementHandle {} - -// Most of the getters below allocate internally, and unsynchronized access is undefined. -// unsafe impl !Sync for StatementHandle {} - -macro_rules! expect_ret_valid { - ($fn_name:ident($($args:tt)*)) => {{ - let val = $fn_name($($args)*); - - TryFrom::try_from(val) - // This likely means UB in SQLite itself or our usage of it; - // signed integer overflow is UB in the C standard. - .unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name))) - }} -} - -macro_rules! check_col_idx { - ($idx:ident) => { - c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx)) - }; -} - -// might use some of this later -#[allow(dead_code)] -impl StatementHandle { - pub(super) fn new(ptr: NonNull) -> Self { - Self(ptr) - } - - #[inline] - pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 { - // O(c) access to the connection handle for this statement handle - // https://sqlite.org/c3ref/db_handle.html - sqlite3_db_handle(self.0.as_ptr()) - } - - pub(crate) fn read_only(&self) -> bool { - // https://sqlite.org/c3ref/stmt_readonly.html - unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 } - } - - pub(crate) fn sql(&self) -> &str { - // https://sqlite.org/c3ref/expanded_sql.html - unsafe { - let raw = sqlite3_sql(self.0.as_ptr()); - debug_assert!(!raw.is_null()); - - from_utf8(CStr::from_ptr(raw).to_bytes()) - .expect("sqlite3_sql() returned non-UTF-8 string") - } - } - - #[inline] - pub(crate) fn last_error(&mut self) -> SqliteError { - unsafe { SqliteError::new(self.db_handle()) } - } - - #[inline] - pub(crate) fn column_count(&self) -> usize { - // https://sqlite.org/c3ref/column_count.html - unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) } - } - - #[inline] - pub(crate) fn changes(&self) -> u64 { - // returns the number of changes of the *last* statement; not - // necessarily this statement. - // https://sqlite.org/c3ref/changes.html - unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) } - } - - #[inline] - pub(crate) fn column_name(&self, index: usize) -> &str { - // https://sqlite.org/c3ref/column_name.html - unsafe { - let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index)); - debug_assert!(!name.is_null()); - - from_utf8(CStr::from_ptr(name).to_bytes()) - .expect("sqlite3_column_name() returned non-UTF-8 column name") - } - } - - pub(crate) fn column_origin(&self, index: usize) -> ColumnOrigin { - if let Some((table, name)) = self - .column_table_name(index) - .zip(self.column_origin_name(index)) - { - let table: Arc = self - .column_db_name(index) - .filter(|&db| db != "main") - .map_or_else( - || table.into(), - // TODO: check that SQLite returns the names properly quoted if necessary - |db| format!("{db}.{table}").into(), - ); - - ColumnOrigin::Table(TableColumn { - table, - name: name.into(), - }) - } else { - ColumnOrigin::Expression - } - } - - fn column_db_name(&self, index: usize) -> Option<&str> { - unsafe { - let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index)); - - if !db_name.is_null() { - Some( - from_utf8(CStr::from_ptr(db_name).to_bytes()) - .expect("sqlite3_column_database_name() returned non-UTF-8 string"), - ) - } else { - None - } - } - } - - fn column_table_name(&self, index: usize) -> Option<&str> { - unsafe { - let table_name = sqlite3_column_table_name(self.0.as_ptr(), check_col_idx!(index)); - - if !table_name.is_null() { - Some( - from_utf8(CStr::from_ptr(table_name).to_bytes()) - .expect("sqlite3_column_table_name() returned non-UTF-8 string"), - ) - } else { - None - } - } - } - - fn column_origin_name(&self, index: usize) -> Option<&str> { - unsafe { - let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), check_col_idx!(index)); - - if !origin_name.is_null() { - Some( - from_utf8(CStr::from_ptr(origin_name).to_bytes()) - .expect("sqlite3_column_origin_name() returned non-UTF-8 string"), - ) - } else { - None - } - } - } - - pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo { - SqliteTypeInfo(DataType::from_code(self.column_type(index))) - } - - pub(crate) fn column_type_info_opt(&self, index: usize) -> Option { - match DataType::from_code(self.column_type(index)) { - DataType::Null => None, - dt => Some(SqliteTypeInfo(dt)), - } - } - - #[inline] - pub(crate) fn column_decltype(&self, index: usize) -> Option { - unsafe { - let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index)); - if decl.is_null() { - // If the Nth column of the result set is an expression or subquery, - // then a NULL pointer is returned. - return None; - } - - let decl = from_utf8(CStr::from_ptr(decl).to_bytes()) - .expect("sqlite3_column_decltype() returned non-UTF-8 string"); - let ty: DataType = decl.parse().ok()?; - - Some(SqliteTypeInfo(ty)) - } - } - - /// Use sqlite3_column_metadata to determine if a specific column is nullable. - /// - /// Returns None in the case of INTEGER PRIMARY KEYs - /// This is because this column is an alias to rowid if the table does not use a compound - /// primary key. In this case the row is not nullable, and the output of - /// sqlite3_column_metadata may be incorrect. - pub(crate) fn column_nullable(&self, index: usize) -> Result, Error> { - unsafe { - let index = check_col_idx!(index); - // https://sqlite.org/c3ref/column_database_name.html - // - // ### Note - // The returned string is valid until the prepared statement is destroyed using - // sqlite3_finalize() or until the statement is automatically reprepared by the - // first call to sqlite3_step() for a particular run or until the same information - // is requested again in a different encoding. - let db_name = sqlite3_column_database_name(self.0.as_ptr(), index); - let table_name = sqlite3_column_table_name(self.0.as_ptr(), index); - let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index); - if db_name.is_null() || table_name.is_null() || origin_name.is_null() { - return Ok(None); - } - - let mut not_null: c_int = 0; - let mut datatype: *const c_char = ptr::null(); - let mut primary_key: c_int = 0; - - // https://sqlite.org/c3ref/table_column_metadata.html - let status = sqlite3_table_column_metadata( - self.db_handle(), - db_name, - table_name, - origin_name, - &mut datatype, - // function docs state to provide NULL for return values you don't care about - ptr::null_mut(), - &mut not_null, - &mut primary_key, - ptr::null_mut(), - ); - - if status != SQLITE_OK { - // implementation note: the docs for sqlite3_table_column_metadata() specify - // that an error can be returned if the column came from a view; however, - // experimentally we found that the above functions give us the true origin - // for columns in views that came from real tables and so we should never hit this - // error; for view columns that are expressions we are given NULL for their origins - // so we don't need special handling for that case either. - // - // this is confirmed in the `tests/sqlite-macros.rs` integration test - return Err(SqliteError::new(self.db_handle()).into()); - } - - let datatype = CStr::from_ptr(datatype); - - Ok( - if primary_key != 0 - && datatype - .to_bytes() - .eq_ignore_ascii_case("integer".as_bytes()) - { - None - } else { - Some(not_null == 0) - }, - ) - } - } - - // Number Of SQL Parameters - #[inline] - pub(crate) fn bind_parameter_count(&self) -> usize { - // https://www.sqlite.org/c3ref/bind_parameter_count.html - unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) } - } - - // Name Of A Host Parameter - // NOTE: The first host parameter has an index of 1, not 0. - #[inline] - pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> { - unsafe { - // https://www.sqlite.org/c3ref/bind_parameter_name.html - let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index)); - if name.is_null() { - return None; - } - - Some( - from_utf8(CStr::from_ptr(name).to_bytes()) - .expect("sqlite3_bind_parameter_name() returned non-UTF-8 string"), - ) - } - } - - // Binding Values To Prepared Statements - // https://www.sqlite.org/c3ref/bind_blob.html - - #[inline] - pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int { - unsafe { - sqlite3_bind_blob64( - self.0.as_ptr(), - check_col_idx!(index), - v.as_ptr() as *const c_void, - v.len() as u64, - SQLITE_TRANSIENT(), - ) - } - } - - #[inline] - pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int { - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let encoding = SQLITE_UTF8 as u8; - - unsafe { - sqlite3_bind_text64( - self.0.as_ptr(), - check_col_idx!(index), - v.as_ptr() as *const c_char, - v.len() as u64, - SQLITE_TRANSIENT(), - encoding, - ) - } - } - - #[inline] - pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int { - unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) } - } - - #[inline] - pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int { - unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) } - } - - #[inline] - pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int { - unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) } - } - - #[inline] - pub(crate) fn bind_null(&self, index: usize) -> c_int { - unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) } - } - - // result values from the query - // https://www.sqlite.org/c3ref/column_blob.html - - #[inline] - pub(crate) fn column_type(&self, index: usize) -> c_int { - unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) } - } - - #[inline] - pub(crate) fn column_int(&self, index: usize) -> i32 { - unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 } - } - - #[inline] - pub(crate) fn column_int64(&self, index: usize) -> i64 { - unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 } - } - - #[inline] - pub(crate) fn column_double(&self, index: usize) -> f64 { - unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) } - } - - #[inline] - pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value { - unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) } - } - - pub(crate) fn column_blob(&self, index: usize) -> &[u8] { - let len = unsafe { - expect_ret_valid!(sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index))) - }; - - if len == 0 { - // empty blobs are NULL so just return an empty slice - return &[]; - } - - let ptr = - unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8; - debug_assert!(!ptr.is_null()); - - unsafe { from_raw_parts(ptr, len) } - } - - pub(crate) fn column_text(&self, index: usize) -> Result<&str, BoxDynError> { - Ok(from_utf8(self.column_blob(index))?) - } - - pub(crate) fn clear_bindings(&self) { - unsafe { sqlite3_clear_bindings(self.0.as_ptr()) }; - } - - pub(crate) fn reset(&mut self) -> Result<(), SqliteError> { - // SAFETY: we have exclusive access to the handle - unsafe { - if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK { - return Err(SqliteError::new(self.db_handle())); - } - } - - Ok(()) - } - - pub(crate) fn step(&mut self) -> Result { - // SAFETY: we have exclusive access to the handle - unsafe { - #[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))] - loop { - match sqlite3_step(self.0.as_ptr()) { - SQLITE_ROW => return Ok(true), - SQLITE_DONE => return Ok(false), - SQLITE_MISUSE => panic!("misuse!"), - #[cfg(feature = "unlock-notify")] - libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => { - // The shared cache is locked by another connection. Wait for unlock - // notification and try again. - super::unlock_notify::wait(self.db_handle())?; - // Need to reset the handle after the unlock - // (https://www.sqlite.org/unlock_notify.html) - sqlite3_reset(self.0.as_ptr()); - } - _ => return Err(SqliteError::new(self.db_handle())), - } - } - } - } -} - -impl Drop for StatementHandle { - fn drop(&mut self) { - // SAFETY: we have exclusive access to the `StatementHandle` here - unsafe { - // https://sqlite.org/c3ref/finalize.html - let status = sqlite3_finalize(self.0.as_ptr()); - if status == SQLITE_MISUSE { - // Panic in case of detected misuse of SQLite API. - // - // sqlite3_finalize returns it at least in the - // case of detected double free, i.e. calling - // sqlite3_finalize on already finalized - // statement. - panic!("Detected sqlite3_finalize misuse."); - } - } - } -} diff --git a/vendor/sqlx-sqlite/src/statement/mod.rs b/vendor/sqlx-sqlite/src/statement/mod.rs deleted file mode 100644 index 93485397d3..0000000000 --- a/vendor/sqlx-sqlite/src/statement/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::column::ColumnIndex; -use crate::error::Error; -use crate::ext::ustr::UStr; -use crate::{Sqlite, SqliteArguments, SqliteColumn, SqliteTypeInfo}; -use sqlx_core::sql_str::SqlStr; -use sqlx_core::{Either, HashMap}; -use std::sync::Arc; - -pub(crate) use sqlx_core::statement::*; - -mod handle; - -#[cfg(feature = "unlock-notify")] -pub(super) mod unlock_notify; -mod r#virtual; - -pub(crate) use handle::StatementHandle; -pub(crate) use r#virtual::VirtualStatement; - -#[derive(Debug, Clone)] -#[allow(clippy::rc_buffer)] -pub struct SqliteStatement { - pub(crate) sql: SqlStr, - pub(crate) parameters: usize, - pub(crate) columns: Arc>, - pub(crate) column_names: Arc>, -} - -impl Statement for SqliteStatement { - type Database = Sqlite; - - fn into_sql(self) -> SqlStr { - self.sql - } - - fn sql(&self) -> &SqlStr { - &self.sql - } - - fn parameters(&self) -> Option> { - Some(Either::Right(self.parameters)) - } - - fn columns(&self) -> &[SqliteColumn] { - &self.columns - } - - impl_statement_query!(SqliteArguments); -} - -impl ColumnIndex for &'_ str { - fn index(&self, statement: &SqliteStatement) -> Result { - statement - .column_names - .get(*self) - .ok_or_else(|| Error::ColumnNotFound((*self).into())) - .copied() - } -} diff --git a/vendor/sqlx-sqlite/src/statement/unlock_notify.rs b/vendor/sqlx-sqlite/src/statement/unlock_notify.rs deleted file mode 100644 index 5821c23ae3..0000000000 --- a/vendor/sqlx-sqlite/src/statement/unlock_notify.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::ffi::c_void; -use std::os::raw::c_int; -use std::slice; -use std::sync::{Condvar, Mutex}; - -use libsqlite3_sys::{sqlite3, sqlite3_unlock_notify, SQLITE_OK}; - -use crate::SqliteError; - -// Wait for unlock notification (https://www.sqlite.org/unlock_notify.html) -pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> { - let notify = Notify::new(); - - if sqlite3_unlock_notify( - conn, - Some(unlock_notify_cb), - ¬ify as *const Notify as *mut Notify as *mut _, - ) != SQLITE_OK - { - return Err(SqliteError::new(conn)); - } - - notify.wait(); - - Ok(()) -} - -unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) { - let ptr = ptr as *mut &Notify; - // We don't have a choice; we can't panic and unwind into FFI here. - let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0)); - - for notify in slice { - notify.fire(); - } -} - -struct Notify { - mutex: Mutex, - condvar: Condvar, -} - -impl Notify { - fn new() -> Self { - Self { - mutex: Mutex::new(false), - condvar: Condvar::new(), - } - } - - fn wait(&self) { - // We only want to wait until the lock is available again. - #[allow(let_underscore_lock)] - let _ = self - .condvar - .wait_while(self.mutex.lock().unwrap(), |fired| !*fired) - .unwrap(); - } - - fn fire(&self) { - let mut lock = self.mutex.lock().unwrap(); - *lock = true; - self.condvar.notify_one(); - } -} diff --git a/vendor/sqlx-sqlite/src/statement/virtual.rs b/vendor/sqlx-sqlite/src/statement/virtual.rs deleted file mode 100644 index b25aa69e47..0000000000 --- a/vendor/sqlx-sqlite/src/statement/virtual.rs +++ /dev/null @@ -1,204 +0,0 @@ -#![allow(clippy::rc_buffer)] - -use std::cmp; -use std::os::raw::c_char; -use std::ptr::{null, null_mut, NonNull}; -use std::sync::Arc; - -use libsqlite3_sys::{ - sqlite3, sqlite3_prepare_v3, sqlite3_stmt, SQLITE_OK, SQLITE_PREPARE_PERSISTENT, -}; - -use sqlx_core::bytes::{Buf, Bytes}; -use sqlx_core::error::Error; -use sqlx_core::ext::ustr::UStr; -use sqlx_core::{HashMap, SmallVec}; - -use crate::connection::ConnectionHandle; -use crate::statement::StatementHandle; -use crate::{SqliteColumn, SqliteError}; - -// A virtual statement consists of *zero* or more raw SQLite3 statements. We chop up a SQL statement -// on `;` to support multiple statements in one query. - -#[derive(Debug)] -pub struct VirtualStatement { - persistent: bool, - - /// the current index of the actual statement that is executing - /// if `None`, no statement is executing and `prepare()` must be called; - /// if `Some(self.handles.len())` and `self.tail.is_empty()`, - /// there are no more statements to execute and `reset()` must be called - index: Option, - - /// tail of the most recently prepared SQL statement within this container - tail: Bytes, - - /// underlying sqlite handles for each inner statement - /// a SQL query string in SQLite is broken up into N statements - /// we use a [`SmallVec`] to optimize for the most likely case of a single statement - pub(crate) handles: SmallVec<[StatementHandle; 1]>, - - // each set of columns - pub(crate) columns: SmallVec<[Arc>; 1]>, - - // each set of column names - pub(crate) column_names: SmallVec<[Arc>; 1]>, -} - -pub struct PreparedStatement<'a> { - pub(crate) handle: &'a mut StatementHandle, - pub(crate) columns: &'a Arc>, - pub(crate) column_names: &'a Arc>, -} - -impl VirtualStatement { - pub(crate) fn new(mut query: &str, persistent: bool) -> Result { - query = query.trim(); - - if query.len() > i32::MAX as usize { - return Err(err_protocol!( - "query string must be smaller than {} bytes", - i32::MAX - )); - } - - Ok(Self { - persistent, - tail: Bytes::from(String::from(query)), - handles: SmallVec::with_capacity(1), - index: None, - columns: SmallVec::with_capacity(1), - column_names: SmallVec::with_capacity(1), - }) - } - - pub(crate) fn prepare_next( - &mut self, - conn: &mut ConnectionHandle, - ) -> Result>, Error> { - // increment `self.index` up to `self.handles.len()` - self.index = self - .index - .map(|idx| cmp::min(idx + 1, self.handles.len())) - .or(Some(0)); - - while self.handles.len() <= self.index.unwrap_or(0) { - if self.tail.is_empty() { - return Ok(None); - } - - if let Some(statement) = prepare(conn.as_ptr(), &mut self.tail, self.persistent)? { - let num = statement.column_count(); - - let mut columns = Vec::with_capacity(num); - let mut column_names = HashMap::with_capacity(num); - - for i in 0..num { - let name: UStr = statement.column_name(i).to_owned().into(); - let type_info = statement - .column_decltype(i) - .unwrap_or_else(|| statement.column_type_info(i)); - - columns.push(SqliteColumn { - ordinal: i, - name: name.clone(), - type_info, - origin: statement.column_origin(i), - }); - - column_names.insert(name, i); - } - - self.handles.push(statement); - self.columns.push(Arc::new(columns)); - self.column_names.push(Arc::new(column_names)); - } - } - - Ok(self.current()) - } - - pub fn current(&mut self) -> Option> { - self.index - .filter(|&idx| idx < self.handles.len()) - .map(move |idx| PreparedStatement { - handle: &mut self.handles[idx], - columns: &self.columns[idx], - column_names: &self.column_names[idx], - }) - } - - pub fn reset(&mut self) -> Result<(), Error> { - self.index = None; - - for handle in self.handles.iter_mut() { - handle.reset()?; - handle.clear_bindings(); - } - - Ok(()) - } -} - -fn prepare( - conn: *mut sqlite3, - query: &mut Bytes, - persistent: bool, -) -> Result, Error> { - let mut flags = 0; - - // For some reason, when building with the `sqlcipher` feature enabled - // `SQLITE_PREPARE_PERSISTENT` ends up being `i32` instead of `u32`. Crazy, right? - #[allow(trivial_casts, clippy::unnecessary_cast)] - if persistent { - // SQLITE_PREPARE_PERSISTENT - // The SQLITE_PREPARE_PERSISTENT flag is a hint to the query - // planner that the prepared statement will be retained for a long time - // and probably reused many times. - flags |= SQLITE_PREPARE_PERSISTENT as u32; - } - - while !query.is_empty() { - let mut statement_handle: *mut sqlite3_stmt = null_mut(); - let mut tail: *const c_char = null(); - - let query_ptr = query.as_ptr() as *const c_char; - let query_len = i32::try_from(query.len()).map_err(|_| { - err_protocol!( - "query string too large for SQLite3 API ({} bytes); \ - try breaking it into smaller chunks (< 2 GiB), executed separately", - query.len() - ) - })?; - - // - let status = unsafe { - sqlite3_prepare_v3( - conn, - query_ptr, - query_len, - flags, - &mut statement_handle, - &mut tail, - ) - }; - - if status != SQLITE_OK { - return Err(unsafe { SqliteError::new(conn).into() }); - } - - // tail should point to the first byte past the end of the first SQL - // statement in zSql. these routines only compile the first statement, - // so tail is left pointing to what remains un-compiled. - - let n = (tail as usize) - (query_ptr as usize); - query.advance(n); - - if let Some(handle) = NonNull::new(statement_handle) { - return Ok(Some(StatementHandle::new(handle))); - } - } - - Ok(None) -} diff --git a/vendor/sqlx-sqlite/src/testing/mod.rs b/vendor/sqlx-sqlite/src/testing/mod.rs deleted file mode 100644 index 058a24c52b..0000000000 --- a/vendor/sqlx-sqlite/src/testing/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::error::Error; -use crate::pool::PoolOptions; -use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport}; -use crate::{Sqlite, SqliteConnectOptions}; -use std::future::Future; -use std::path::{Path, PathBuf}; - -pub(crate) use sqlx_core::testing::*; - -const BASE_PATH: &str = "target/sqlx/test-dbs"; - -impl TestSupport for Sqlite { - fn test_context( - args: &TestArgs, - ) -> impl Future, Error>> + Send + '_ { - test_context(args) - } - - async fn cleanup_test(db_name: &str) -> Result<(), Error> { - crate::fs::remove_file(db_name).await?; - Ok(()) - } - - async fn cleanup_test_dbs() -> Result, Error> { - crate::fs::remove_dir_all(BASE_PATH).await?; - Ok(None) - } - - async fn snapshot(_conn: &mut Self::Connection) -> Result, Error> { - todo!() - } - - fn db_name(args: &TestArgs) -> String { - convert_path(args.test_path) - } -} - -async fn test_context(args: &TestArgs) -> Result, Error> { - let db_path = convert_path(args.test_path); - - if let Some(parent_path) = Path::parent(db_path.as_ref()) { - crate::fs::create_dir_all(parent_path) - .await - .expect("failed to create folders"); - } - - if Path::exists(db_path.as_ref()) { - crate::fs::remove_file(&db_path) - .await - .expect("failed to remove database from previous test run"); - } - - Ok(TestContext { - connect_opts: SqliteConnectOptions::new() - .filename(&db_path) - .create_if_missing(true), - // This doesn't really matter for SQLite as the databases are independent of each other. - // The main limitation is going to be the number of concurrent running tests. - pool_opts: PoolOptions::new().max_connections(1000), - db_name: db_path, - }) -} - -fn convert_path(test_path: &str) -> String { - let mut path = PathBuf::from(BASE_PATH); - - for segment in test_path.split("::") { - path.push(segment); - } - - path.set_extension("sqlite"); - - path.into_os_string() - .into_string() - .expect("path should be UTF-8") -} - -#[test] -fn test_convert_path() { - let path = convert_path("foo::bar::baz::quux"); - - assert_eq!(path, "target/sqlx/test-dbs/foo/bar/baz/quux.sqlite"); -} diff --git a/vendor/sqlx-sqlite/src/transaction.rs b/vendor/sqlx-sqlite/src/transaction.rs deleted file mode 100644 index 1c57d0163f..0000000000 --- a/vendor/sqlx-sqlite/src/transaction.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::future::Future; - -use sqlx_core::transaction::TransactionManager; -use sqlx_core::{error::Error, sql_str::SqlStr}; - -use crate::{Sqlite, SqliteConnection}; - -/// Implementation of [`TransactionManager`] for SQLite. -pub struct SqliteTransactionManager; - -impl TransactionManager for SqliteTransactionManager { - type Database = Sqlite; - - async fn begin(conn: &mut SqliteConnection, statement: Option) -> Result<(), Error> { - conn.worker.begin(statement).await - } - - fn commit(conn: &mut SqliteConnection) -> impl Future> + Send + '_ { - conn.worker.commit() - } - - fn rollback( - conn: &mut SqliteConnection, - ) -> impl Future> + Send + '_ { - conn.worker.rollback() - } - - fn start_rollback(conn: &mut SqliteConnection) { - conn.worker.start_rollback().ok(); - } - - fn get_transaction_depth(conn: &SqliteConnection) -> usize { - conn.worker.shared.get_transaction_depth() - } -} diff --git a/vendor/sqlx-sqlite/src/type_checking.rs b/vendor/sqlx-sqlite/src/type_checking.rs deleted file mode 100644 index 97af601c86..0000000000 --- a/vendor/sqlx-sqlite/src/type_checking.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::Sqlite; -#[allow(unused_imports)] -use sqlx_core as sqlx; - -// f32 is not included below as REAL represents a floating point value -// stored as an 8-byte IEEE floating point number (i.e. an f64) -// For more info see: https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes -impl_type_checking!( - Sqlite { - // Note that since the macro checks `column_type_info == ::type_info()` first, - // we can list `bool` without it being automatically picked for all integer types - // due to its `TypeInfo::compatible()` impl. - bool, - // Since it returns `DataType::Int4` for `type_info()`, - // `i32` should only be chosen IFF the column decltype is `INT4` - i32, - i64, - f64, - String, - Vec, - - #[cfg(feature = "uuid")] - sqlx::types::Uuid, - }, - ParamChecking::Weak, - // While there are type integrations that must be enabled via Cargo feature, - // SQLite's type system doesn't actually have any type that we cannot decode by default. - // - // The type integrations simply allow the user to skip some intermediate representation, - // which is usually TEXT. - feature-types: _info => None, - - // The expansion of the macro automatically applies the correct feature name - // and checks `[macros.preferred-crates]` - datetime-types: { - chrono: { - sqlx::types::chrono::NaiveDate, - - sqlx::types::chrono::NaiveDateTime, - - sqlx::types::chrono::DateTime - | sqlx::types::chrono::DateTime<_>, - }, - time: { - sqlx::types::time::OffsetDateTime, - - sqlx::types::time::PrimitiveDateTime, - - sqlx::types::time::Date, - }, - }, - numeric-types: { - bigdecimal: { }, - rust_decimal: { }, - }, -); diff --git a/vendor/sqlx-sqlite/src/type_info.rs b/vendor/sqlx-sqlite/src/type_info.rs deleted file mode 100644 index 38a0fc4169..0000000000 --- a/vendor/sqlx-sqlite/src/type_info.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::fmt::{self, Display, Formatter}; -use std::os::raw::c_int; -use std::str::FromStr; - -use libsqlite3_sys::{SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_TEXT}; - -use crate::error::BoxDynError; - -pub(crate) use sqlx_core::type_info::*; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub(crate) enum DataType { - // These variants should correspond to `SQLITE_*` type constants. - Null, - /// Note: SQLite's type system has no notion of integer widths. - /// The `INTEGER` type affinity can store up to 8 byte integers, - /// making `i64` the only safe choice when mapping integer types to Rust. - Integer, - Float, - Text, - Blob, - - // Explicitly not supported: see documentation in `types/mod.rs` - #[allow(dead_code)] - Numeric, - - // non-standard extensions (chosen based on the column's declared type) - /// Chosen if the column's declared type is `BOOLEAN`. - Bool, - /// Chosen if the column's declared type is `INT4`; - /// instructs the macros to use `i32` instead of `i64`. - /// Legacy feature; no idea if this is actually used anywhere. - Int4, - Date, - Time, - Datetime, -} - -/// Type information for a SQLite type. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub struct SqliteTypeInfo(pub(crate) DataType); - -impl Display for SqliteTypeInfo { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.pad(self.name()) - } -} - -impl TypeInfo for SqliteTypeInfo { - fn is_null(&self) -> bool { - matches!(self.0, DataType::Null) - } - - fn name(&self) -> &str { - match self.0 { - DataType::Null => "NULL", - DataType::Text => "TEXT", - DataType::Float => "REAL", - DataType::Blob => "BLOB", - DataType::Int4 | DataType::Integer => "INTEGER", - DataType::Numeric => "NUMERIC", - - // non-standard extensions - DataType::Bool => "BOOLEAN", - DataType::Date => "DATE", - DataType::Time => "TIME", - DataType::Datetime => "DATETIME", - } - } -} - -impl DataType { - pub(crate) fn from_code(code: c_int) -> Self { - match code { - SQLITE_INTEGER => DataType::Integer, - SQLITE_FLOAT => DataType::Float, - SQLITE_BLOB => DataType::Blob, - SQLITE_NULL => DataType::Null, - SQLITE_TEXT => DataType::Text, - - // https://sqlite.org/c3ref/c_blob.html - _ => panic!("unknown data type code {code}"), - } - } -} - -// note: this implementation is particularly important as this is how the macros determine -// what Rust type maps to what *declared* SQL type -// -impl FromStr for DataType { - type Err = BoxDynError; - - fn from_str(s: &str) -> Result { - let s = s.to_ascii_lowercase(); - Ok(match &*s { - "int4" => DataType::Int4, - "int8" => DataType::Integer, - "boolean" | "bool" => DataType::Bool, - - "date" => DataType::Date, - "time" => DataType::Time, - "datetime" | "timestamp" => DataType::Datetime, - - _ if s.contains("int") => DataType::Integer, - - _ if s.contains("char") || s.contains("clob") || s.contains("text") => DataType::Text, - - _ if s.contains("blob") => DataType::Blob, - - _ if s.contains("real") || s.contains("floa") || s.contains("doub") => DataType::Float, - - _ => { - return Err(format!("unknown type: `{s}`").into()); - } - }) - } -} - -// #[cfg(feature = "any")] -// impl From for crate::any::AnyTypeInfo { -// #[inline] -// fn from(ty: SqliteTypeInfo) -> Self { -// crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Sqlite(ty)) -// } -// } - -#[test] -fn test_data_type_from_str() -> Result<(), BoxDynError> { - assert_eq!(DataType::Int4, "INT4".parse()?); - - assert_eq!(DataType::Integer, "INT".parse()?); - assert_eq!(DataType::Integer, "INTEGER".parse()?); - assert_eq!(DataType::Integer, "INTBIG".parse()?); - assert_eq!(DataType::Integer, "MEDIUMINT".parse()?); - - assert_eq!(DataType::Integer, "BIGINT".parse()?); - assert_eq!(DataType::Integer, "UNSIGNED BIG INT".parse()?); - assert_eq!(DataType::Integer, "INT8".parse()?); - - assert_eq!(DataType::Text, "CHARACTER(20)".parse()?); - assert_eq!(DataType::Text, "NCHAR(55)".parse()?); - assert_eq!(DataType::Text, "TEXT".parse()?); - assert_eq!(DataType::Text, "CLOB".parse()?); - - assert_eq!(DataType::Blob, "BLOB".parse()?); - - assert_eq!(DataType::Float, "REAL".parse()?); - assert_eq!(DataType::Float, "FLOAT".parse()?); - assert_eq!(DataType::Float, "DOUBLE PRECISION".parse()?); - - assert_eq!(DataType::Bool, "BOOLEAN".parse()?); - assert_eq!(DataType::Bool, "BOOL".parse()?); - - assert_eq!(DataType::Datetime, "DATETIME".parse()?); - assert_eq!(DataType::Time, "TIME".parse()?); - assert_eq!(DataType::Date, "DATE".parse()?); - - Ok(()) -} diff --git a/vendor/sqlx-sqlite/src/types/bool.rs b/vendor/sqlx-sqlite/src/types/bool.rs deleted file mode 100644 index 9228eef236..0000000000 --- a/vendor/sqlx-sqlite/src/types/bool.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; - -impl Type for bool { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Bool) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Bool | DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for bool { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int((*self).into())); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for bool { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()? != 0) - } -} diff --git a/vendor/sqlx-sqlite/src/types/bytes.rs b/vendor/sqlx-sqlite/src/types/bytes.rs deleted file mode 100644 index 6453007bc9..0000000000 --- a/vendor/sqlx-sqlite/src/types/bytes.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::borrow::Cow; -use std::rc::Rc; -use std::sync::Arc; - -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; - -impl Type for [u8] { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Blob) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Blob | DataType::Text) - } -} - -impl Encode<'_, Sqlite> for &'_ [u8] { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec()))); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for &'r [u8] { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.blob_borrowed()) - } -} - -impl Encode<'_, Sqlite> for Box<[u8]> { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.into_vec()))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.clone().into_vec()))); - - Ok(IsNull::No) - } -} - -impl Type for Vec { - fn type_info() -> SqliteTypeInfo { - <&[u8] as Type>::type_info() - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - <&[u8] as Type>::compatible(ty) - } -} - -impl Encode<'_, Sqlite> for Vec { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.clone()))); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for Vec { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.blob_owned()) - } -} - -impl Encode<'_, Sqlite> for Cow<'_, [u8]> { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.into()))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec()))); - - Ok(IsNull::No) - } -} - -impl Encode<'_, Sqlite> for Arc<[u8]> { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - as Encode<'_, Sqlite>>::encode(self.to_vec(), args) - } -} - -impl Encode<'_, Sqlite> for Rc<[u8]> { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - as Encode<'_, Sqlite>>::encode(self.to_vec(), args) - } -} diff --git a/vendor/sqlx-sqlite/src/types/chrono.rs b/vendor/sqlx-sqlite/src/types/chrono.rs deleted file mode 100644 index 8d987538d0..0000000000 --- a/vendor/sqlx-sqlite/src/types/chrono.rs +++ /dev/null @@ -1,220 +0,0 @@ -use std::fmt::Display; - -use crate::value::ValueRef; -use crate::{ - decode::Decode, - encode::{Encode, IsNull}, - error::BoxDynError, - type_info::DataType, - types::Type, - Sqlite, SqliteArgumentsBuffer, SqliteTypeInfo, SqliteValueRef, -}; -use chrono::FixedOffset; -use chrono::{ - DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, Utc, -}; - -impl Type for DateTime { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Datetime) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - >::compatible(ty) - } -} - -impl Type for NaiveDateTime { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Datetime) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!( - ty.0, - DataType::Datetime - | DataType::Text - | DataType::Integer - | DataType::Int4 - | DataType::Float - ) - } -} - -impl Type for NaiveDate { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Date) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Date | DataType::Text) - } -} - -impl Type for NaiveTime { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Time) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Time | DataType::Text) - } -} - -impl Encode<'_, Sqlite> for DateTime -where - Tz::Offset: Display, -{ - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf) - } -} - -impl Encode<'_, Sqlite> for NaiveDateTime { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.format("%F %T%.f").to_string(), buf) - } -} - -impl Encode<'_, Sqlite> for NaiveDate { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.format("%F").to_string(), buf) - } -} - -impl Encode<'_, Sqlite> for NaiveTime { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.format("%T%.f").to_string(), buf) - } -} - -impl<'r> Decode<'r, Sqlite> for DateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Utc.from_utc_datetime(&decode_datetime(value)?.naive_utc())) - } -} - -impl<'r> Decode<'r, Sqlite> for DateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Local.from_utc_datetime(&decode_datetime(value)?.naive_utc())) - } -} - -impl<'r> Decode<'r, Sqlite> for DateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - decode_datetime(value) - } -} - -fn decode_datetime(value: SqliteValueRef<'_>) -> Result, BoxDynError> { - let dt = match value.type_info().0 { - DataType::Text => decode_datetime_from_text(value.text_borrowed()?), - DataType::Int4 | DataType::Integer => decode_datetime_from_int(value.int64()?), - DataType::Float => decode_datetime_from_float(value.double()?), - - _ => None, - }; - - if let Some(dt) = dt { - Ok(dt) - } else { - Err(format!("invalid datetime: {}", value.text_borrowed()?).into()) - } -} - -fn decode_datetime_from_text(value: &str) -> Option> { - if let Ok(dt) = DateTime::parse_from_rfc3339(value) { - return Some(dt); - } - - // Loop over common date time patterns, inspired by Diesel - // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L56-L97 - let sqlite_datetime_formats = &[ - // Most likely format - "%F %T%.f", - // Other formats in order of appearance in docs - "%F %R", - "%F %RZ", - "%F %R%:z", - "%F %T%.fZ", - "%F %T%.f%:z", - "%FT%R", - "%FT%RZ", - "%FT%R%:z", - "%FT%T%.f", - "%FT%T%.fZ", - "%FT%T%.f%:z", - ]; - - for format in sqlite_datetime_formats { - if let Ok(dt) = DateTime::parse_from_str(value, format) { - return Some(dt); - } - - if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) { - return Some(Utc.fix().from_utc_datetime(&dt)); - } - } - - None -} - -fn decode_datetime_from_int(value: i64) -> Option> { - Utc.fix().timestamp_opt(value, 0).single() -} - -fn decode_datetime_from_float(value: f64) -> Option> { - let epoch_in_julian_days = 2_440_587.5; - let seconds_in_day = 86400.0; - let timestamp = (value - epoch_in_julian_days) * seconds_in_day; - - if !timestamp.is_finite() { - return None; - } - - // We don't really have a choice but to do lossy casts for this conversion - // We checked above if the value is infinite or NaN which could otherwise cause problems - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - { - let seconds = timestamp.trunc() as i64; - let nanos = (timestamp.fract() * 1E9).abs() as u32; - - Utc.fix().timestamp_opt(seconds, nanos).single() - } -} - -impl<'r> Decode<'r, Sqlite> for NaiveDateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(decode_datetime(value)?.naive_local()) - } -} - -impl<'r> Decode<'r, Sqlite> for NaiveDate { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(NaiveDate::parse_from_str(value.text_borrowed()?, "%F")?) - } -} - -impl<'r> Decode<'r, Sqlite> for NaiveTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - let value = value.text_borrowed()?; - - // Loop over common time patterns, inspired by Diesel - // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L29-L47 - #[rustfmt::skip] // don't like how rustfmt mangles the comments - let sqlite_time_formats = &[ - // Most likely format - "%T.f", "%T%.f", - // Other formats in order of appearance in docs - "%R", "%RZ", "%T%.fZ", "%R%:z", "%T%.f%:z", - ]; - - for format in sqlite_time_formats { - if let Ok(dt) = NaiveTime::parse_from_str(value, format) { - return Ok(dt); - } - } - - Err(format!("invalid time: {value}").into()) - } -} diff --git a/vendor/sqlx-sqlite/src/types/float.rs b/vendor/sqlx-sqlite/src/types/float.rs deleted file mode 100644 index d23b3f3a08..0000000000 --- a/vendor/sqlx-sqlite/src/types/float.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; - -impl Type for f32 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Float) - } -} - -impl Encode<'_, Sqlite> for f32 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Double((*self).into())); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for f32 { - fn decode(value: SqliteValueRef<'r>) -> Result { - // Truncation is intentional - #[allow(clippy::cast_possible_truncation)] - Ok(value.double()? as f32) - } -} - -impl Type for f64 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Float) - } -} - -impl Encode<'_, Sqlite> for f64 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Double(*self)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for f64 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.double()?) - } -} diff --git a/vendor/sqlx-sqlite/src/types/int.rs b/vendor/sqlx-sqlite/src/types/int.rs deleted file mode 100644 index 2cf4209ad6..0000000000 --- a/vendor/sqlx-sqlite/src/types/int.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; - -impl Type for i8 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Int4) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for i8 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int(*self as i32)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for i8 { - fn decode(value: SqliteValueRef<'r>) -> Result { - // NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates - // which leads to bugs, e.g.: - // https://github.com/launchbadge/sqlx/issues/3179 - // Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161 - Ok(value.int64()?.try_into()?) - } -} - -impl Type for i16 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Int4) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for i16 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int(*self as i32)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for i16 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?.try_into()?) - } -} - -impl Type for i32 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Int4) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for i32 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int(*self)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for i32 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?.try_into()?) - } -} - -impl Type for i64 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Integer) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for i64 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int64(*self)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for i64 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?) - } -} diff --git a/vendor/sqlx-sqlite/src/types/json.rs b/vendor/sqlx-sqlite/src/types/json.rs deleted file mode 100644 index dafd483658..0000000000 --- a/vendor/sqlx-sqlite/src/types/json.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::types::{Json, Type}; -use crate::{type_info::DataType, Sqlite, SqliteTypeInfo, SqliteValueRef}; - -impl Type for Json { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Text) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - <&str as Type>::compatible(ty) - } -} - -impl Encode<'_, Sqlite> for Json -where - T: Serialize, -{ - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.encode_to_string()?, buf) - } -} - -impl<'r, T> Decode<'r, Sqlite> for Json -where - T: 'r + Deserialize<'r>, -{ - fn decode(value: SqliteValueRef<'r>) -> Result { - // Saves a pass over the data by making `serde_json` check UTF-8. - Self::decode_from_bytes(Decode::::decode(value)?) - } -} diff --git a/vendor/sqlx-sqlite/src/types/mod.rs b/vendor/sqlx-sqlite/src/types/mod.rs deleted file mode 100644 index d36af7ea14..0000000000 --- a/vendor/sqlx-sqlite/src/types/mod.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Conversions between Rust and **SQLite** types. -//! -//! # Types -//! -//! | Rust type | SQLite type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | `bool` | BOOLEAN | -//! | `i8` | INTEGER | -//! | `i16` | INTEGER | -//! | `i32` | INTEGER, INT4 | -//! | `i64` | BIGINT, INT8 | -//! | `u8` | INTEGER | -//! | `u16` | INTEGER | -//! | `u32` | INTEGER | -//! | `u64` | INTEGER (Decode only; see note) | -//! | `f32` | REAL | -//! | `f64` | REAL | -//! | `&str`, [`String`] | TEXT | -//! | `&[u8]`, `Vec` | BLOB | -//! -//! #### Note: Unsigned Integers -//! Decoding of unsigned integer types simply performs a checked conversion -//! to ensure that overflow does not occur. -//! -//! Encoding of the unsigned integer types `u8`, `u16` and `u32` is implemented by zero-extending to -//! the next-larger signed type. So `u8` becomes `i16`, `u16` becomes `i32`, and `u32` becomes `i64` -//! while still retaining their semantic values. -//! -//! SQLite stores integers in a variable-width encoding and always handles them in memory as 64-bit -//! signed values, so no space is wasted by this implicit widening. -//! -//! However, there is no corresponding larger type for `u64` in SQLite -//! (it would require a native 16-byte integer, i.e. the equivalent of `i128`), -//! and so encoding is not supported for this type. -//! -//! Bit-casting `u64` to `i64`, or storing it as `REAL`, `BLOB` or `TEXT`, -//! would change the semantics of the value in SQL and so violates the principle of least surprise. -//! -//! ### [`chrono`](https://crates.io/crates/chrono) -//! -//! Requires the `chrono` Cargo feature flag. -//! -//! | Rust type | Sqlite type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | `chrono::NaiveDateTime` | DATETIME (TEXT, INTEGER, REAL) | -//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | -//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | -//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | -//! | `chrono::NaiveDate` | DATE (TEXT only) | -//! | `chrono::NaiveTime` | TIME (TEXT only) | -//! -//! ##### NOTE: `DATETIME` conversions -//! SQLite may represent `DATETIME` values as one of three types: `TEXT`, `REAL`, or `INTEGER`. -//! Which one is used is entirely up to you and how you store timestamps in your database. -//! -//! The deserialization for `NaiveDateTime`, `DateTime` and `DateTime` infer the date -//! format from the type of the value they're being decoded from: -//! -//! * If `TEXT`, the format is assumed to be an ISO-8601 compatible datetime string. -//! A number of possible formats are tried; see `sqlx-sqlite/src/types/chrono.rs` for the current -//! set of formats. -//! * If `INTEGER`, it is expected to be the number of seconds since January 1, 1970 00:00 UTC, -//! as if returned from the `unixepoch()` function (without the `subsec` modifier). -//! * If `REAL`, it is expected to be the (possibly fractional) number of days since the Julian epoch, -//! November 24, 4714 BCE 12:00 UTC, as if returned from the `julianday()` function. -//! -//! These types will always encode to a datetime string, either -//! with a timezone offset (`DateTime` for any `Tz: TimeZone`) or without (`NaiveDateTime`). -//! -//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values -//! As stated previously, `DateTime` always encodes to a date-time string -//! _with_ a timezone offset, -//! in [RFC 3339 format][::chrono::DateTime::to_rfc3339_opts] (with `use_z: false`). -//! -//! However, most of SQLite's datetime functions -//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`) -//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset. -//! -//! This may cause problems with interoperability with other applications, and especially -//! when comparing datetime values, which compares the actual string values lexicographically. -//! -//! Date-time strings in the SQLite format will generally _not_ compare consistently -//! with date-time strings in the RFC 3339 format. -//! -//! We recommend that you decide up-front whether `DATETIME` values should be stored -//! with explicit time zones or not, and use the corresponding type -//! (and its corresponding offset, if applicable) _consistently_ throughout your -//! application: -//! -//! * RFC 3339 format: `DateTime` (e.g. `DateTime`, `DateTime`, `DateTime`) -//! * Changing or mixing and matching offsets may break comparisons with existing timestamps. -//! * `DateTime` is **not recommended** for portable applications. -//! * `DateTime` is only recommended if the offset is **constant**. -//! * SQLite format: `NaiveDateTime` -//! -//! Note that non-constant offsets may still cause issues when comparing timestamps, -//! as the comparison operators are not timezone-aware. -//! -//! ### [`time`](https://crates.io/crates/time) -//! -//! Requires the `time` Cargo feature flag. -//! -//! | Rust type | Sqlite type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | `time::PrimitiveDateTime` | DATETIME (TEXT, INTEGER) | -//! | `time::OffsetDateTime` | DATETIME (TEXT, INTEGER) | -//! | `time::Date` | DATE (TEXT only) | -//! | `time::Time` | TIME (TEXT only) | -//! -//! ##### NOTE: `DATETIME` conversions -//! The behavior here is identical to the corresponding `chrono` types, minus the support for `REAL` -//! values as Julian days (it's just not implemented). -//! -//! `PrimitiveDateTime` and `OffsetDateTime` will always encode to a datetime string, either -//! with a timezone offset (`OffsetDateTime`) or without (`PrimitiveDateTime`). -//! -//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values -//! As stated previously, `OffsetDateTime` always encodes to a datetime string _with_ a timezone offset, -//! in [RFC 3339 format][::time::format_description::well_known::Rfc3339] (using `Z` for UTC offsets). -//! -//! However, most of SQLite's datetime functions -//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`) -//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset. -//! -//! This may cause problems with interoperability with other applications, and especially -//! when comparing datetime values, which compares the actual string values lexicographically. -//! -//! Date-time strings in the SQLite format will generally _not_ compare consistently -//! with date-time strings in the RFC 3339 format. -//! -//! We recommend that you decide up-front whether `DATETIME` values should be stored -//! with explicit time zones or not, and use the corresponding type -//! (and its corresponding offset, if applicable) _consistently_ throughout your -//! application: -//! -//! * RFC 3339 format: `OffsetDateTime` with a **constant** offset. -//! * Changing or mixing and matching offsets may break comparisons with existing timestamps. -//! * `OffsetDateTime::now_local()` is **not recommended** for portable applications. -//! * Non-UTC offsets are only recommended if the offset is **constant**. -//! * SQLite format: `PrimitiveDateTime` -//! -//! Note that non-constant offsets may still cause issues when comparing timestamps, -//! as the comparison operators are not timezone-aware. -//! -//! ### [`uuid`](https://crates.io/crates/uuid) -//! -//! Requires the `uuid` Cargo feature flag. -//! -//! | Rust type | Sqlite type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | `uuid::Uuid` | BLOB, TEXT | -//! | `uuid::fmt::Hyphenated` | TEXT | -//! | `uuid::fmt::Simple` | TEXT | -//! -//! ### [`json`](https://crates.io/crates/serde_json) -//! -//! Requires the `json` Cargo feature flag. -//! -//! | Rust type | Sqlite type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | [`Json`] | TEXT | -//! | `serde_json::JsonValue` | TEXT | -//! | `&serde_json::value::RawValue` | TEXT | -//! -//! # Nullable -//! -//! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents -//! a potentially `NULL` value from SQLite. -//! -//! # Non-feature: `NUMERIC` / `rust_decimal` / `bigdecimal` Support -//! Support for mapping `rust_decimal::Decimal` and `bigdecimal::BigDecimal` to SQLite has been -//! deliberately omitted because SQLite does not have native support for high- -//! or arbitrary-precision decimal arithmetic, and to pretend so otherwise would be a -//! significant misstep in API design. -//! -//! The in-tree [`decimal.c`] extension is unfortunately not included in the [amalgamation], -//! which is used to build the bundled version of SQLite3 for `libsqlite3-sys` (which we have -//! enabled by default for the simpler setup experience), otherwise we could support that. -//! -//! The `NUMERIC` type affinity, while seemingly designed for storing decimal values, -//! stores non-integer real numbers as double-precision IEEE-754 floating point, -//! i.e. `REAL` in SQLite, `f64` in Rust, `double` in C/C++, etc. -//! -//! [Datatypes in SQLite: Type Affinity][type-affinity] (accessed 2023/11/20): -//! -//! > A column with NUMERIC affinity may contain values using all five storage classes. -//! > When text data is inserted into a NUMERIC column, the storage class of the text is converted to -//! > INTEGER or REAL (in order of preference) if the text is a well-formed integer or real literal, -//! > respectively. If the TEXT value is a well-formed integer literal that is too large to fit in a -//! > 64-bit signed integer, it is converted to REAL. For conversions between TEXT and REAL storage -//! > classes, only the first 15 significant decimal digits of the number are preserved. -//! -//! With the SQLite3 interactive CLI, we can see that a higher-precision value -//! (20 digits in this case) is rounded off: -//! -//! ```text -//! sqlite> CREATE TABLE foo(bar NUMERIC); -//! sqlite> INSERT INTO foo(bar) VALUES('1.2345678901234567890'); -//! sqlite> SELECT * FROM foo; -//! 1.23456789012346 -//! ``` -//! -//! It appears the `TEXT` storage class is only used if the value contains invalid characters -//! or extra whitespace. -//! -//! Thus, the `NUMERIC` type affinity is **unsuitable** for storage of high-precision decimal values -//! and should be **avoided at all costs**. -//! -//! Support for `rust_decimal` and `bigdecimal` would only be a trap because users will naturally -//! want to use the `NUMERIC` type affinity, and might otherwise encounter serious bugs caused by -//! rounding errors that they were deliberately avoiding when they chose an arbitrary-precision type -//! over a floating-point type in the first place. -//! -//! Instead, you should only use a type affinity that SQLite will not attempt to convert implicitly, -//! such as `TEXT` or `BLOB`, and map values to/from SQLite as strings. You can do this easily -//! using [the `Text` adapter]. -//! -//! -//! [`decimal.c`]: https://www.sqlite.org/floatingpoint.html#the_decimal_c_extension -//! [amalgamation]: https://www.sqlite.org/amalgamation.html -//! [type-affinity]: https://www.sqlite.org/datatype3.html#type_affinity -//! [the `Text` adapter]: Text - -pub(crate) use sqlx_core::types::*; - -mod bool; -mod bytes; -#[cfg(feature = "chrono")] -mod chrono; -mod float; -mod int; -#[cfg(feature = "json")] -mod json; -mod str; -mod text; -#[cfg(feature = "time")] -mod time; -mod uint; -#[cfg(feature = "uuid")] -mod uuid; diff --git a/vendor/sqlx-sqlite/src/types/str.rs b/vendor/sqlx-sqlite/src/types/str.rs deleted file mode 100644 index a28f778b63..0000000000 --- a/vendor/sqlx-sqlite/src/types/str.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; -use sqlx_core::database::Database; -use std::borrow::Cow; -use std::rc::Rc; -use std::sync::Arc; - -impl Type for str { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Text) - } -} - -impl Encode<'_, Sqlite> for &'_ str { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for &'r str { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.text_borrowed()?) - } -} - -impl Encode<'_, Sqlite> for Box { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::TextSlice(Arc::from(self))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); - - Ok(IsNull::No) - } -} - -impl Type for String { - fn type_info() -> SqliteTypeInfo { - <&str as Type>::type_info() - } -} - -impl Encode<'_, Sqlite> for String { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.clone()))); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for String { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.text_owned()?) - } -} - -impl Encode<'_, Sqlite> for Cow<'_, str> { - fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.into()))); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); - - Ok(IsNull::No) - } -} - -impl Encode<'_, Sqlite> for Arc { - fn encode(self, args: &mut ::ArgumentBuffer) -> Result - where - Self: Sized, - { - args.push(SqliteArgumentValue::TextSlice(self)); - - Ok(IsNull::No) - } - - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - >::encode(self.to_string(), args) - } -} - -impl Encode<'_, Sqlite> for Rc { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - >::encode(self.to_string(), args) - } -} diff --git a/vendor/sqlx-sqlite/src/types/text.rs b/vendor/sqlx-sqlite/src/types/text.rs deleted file mode 100644 index ef03caf5a0..0000000000 --- a/vendor/sqlx-sqlite/src/types/text.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::{Sqlite, SqliteTypeInfo, SqliteValueRef}; -use sqlx_core::decode::Decode; -use sqlx_core::encode::{Encode, IsNull}; -use sqlx_core::error::BoxDynError; -use sqlx_core::types::{Text, Type}; -use std::fmt::Display; -use std::str::FromStr; - -impl Type for Text { - fn type_info() -> SqliteTypeInfo { - >::type_info() - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - >::compatible(ty) - } -} - -impl Encode<'_, Sqlite> for Text -where - T: Display, -{ - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.0.to_string(), buf) - } -} - -impl<'r, T> Decode<'r, Sqlite> for Text -where - T: FromStr, - BoxDynError: From<::Err>, -{ - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Self(value.with_temp_text(|text| text.parse::())??)) - } -} diff --git a/vendor/sqlx-sqlite/src/types/time.rs b/vendor/sqlx-sqlite/src/types/time.rs deleted file mode 100644 index 0e0027882d..0000000000 --- a/vendor/sqlx-sqlite/src/types/time.rs +++ /dev/null @@ -1,319 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::value::ValueRef; -use crate::{ - decode::Decode, - encode::{Encode, IsNull}, - error::BoxDynError, - type_info::DataType, - types::Type, - Sqlite, SqliteTypeInfo, SqliteValueRef, -}; -use time::format_description::{well_known::Rfc3339, BorrowedFormatItem}; -use time::macros::format_description as fd; -use time::{Date, OffsetDateTime, PrimitiveDateTime, Time}; - -impl Type for OffsetDateTime { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Datetime) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - >::compatible(ty) - } -} - -impl Type for PrimitiveDateTime { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Datetime) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!( - ty.0, - DataType::Datetime | DataType::Text | DataType::Integer | DataType::Int4 - ) - } -} - -impl Type for Date { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Date) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Date | DataType::Text) - } -} - -impl Type for Time { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Time) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Time | DataType::Text) - } -} - -impl Encode<'_, Sqlite> for OffsetDateTime { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - Encode::::encode(self.format(&Rfc3339)?, buf) - } -} - -impl Encode<'_, Sqlite> for PrimitiveDateTime { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - let format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); - Encode::::encode(self.format(&format)?, buf) - } -} - -impl Encode<'_, Sqlite> for Date { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - let format = fd!("[year]-[month]-[day]"); - Encode::::encode(self.format(&format)?, buf) - } -} - -impl Encode<'_, Sqlite> for Time { - fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { - let format = fd!("[hour]:[minute]:[second].[subsecond]"); - Encode::::encode(self.format(&format)?, buf) - } -} - -impl<'r> Decode<'r, Sqlite> for OffsetDateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - decode_offset_datetime(value) - } -} - -impl<'r> Decode<'r, Sqlite> for PrimitiveDateTime { - fn decode(value: SqliteValueRef<'r>) -> Result { - decode_datetime(value) - } -} - -impl<'r> Decode<'r, Sqlite> for Date { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Date::parse( - value.text_borrowed()?, - &fd!("[year]-[month]-[day]"), - )?) - } -} - -impl<'r> Decode<'r, Sqlite> for Time { - fn decode(value: SqliteValueRef<'r>) -> Result { - let value = value.text_borrowed()?; - - let sqlite_time_formats = &[ - fd!("[hour]:[minute]:[second].[subsecond]"), - fd!("[hour]:[minute]:[second]"), - fd!("[hour]:[minute]"), - ]; - - for format in sqlite_time_formats { - if let Ok(dt) = Time::parse(value, &format) { - return Ok(dt); - } - } - - Err(format!("invalid time: {value}").into()) - } -} - -fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result { - let dt = match value.type_info().0 { - DataType::Text => decode_offset_datetime_from_text(value.text_borrowed()?), - DataType::Int4 | DataType::Integer => { - Some(OffsetDateTime::from_unix_timestamp(value.int64()?)?) - } - - _ => None, - }; - - if let Some(dt) = dt { - Ok(dt) - } else { - Err(format!("invalid offset datetime: {}", value.text_borrowed()?).into()) - } -} - -fn decode_offset_datetime_from_text(value: &str) -> Option { - if let Ok(dt) = OffsetDateTime::parse(value, &Rfc3339) { - return Some(dt); - } - - if let Ok(dt) = OffsetDateTime::parse(value, formats::OFFSET_DATE_TIME) { - return Some(dt); - } - - if let Some(dt) = decode_datetime_from_text(value) { - return Some(dt.assume_utc()); - } - - None -} - -fn decode_datetime(value: SqliteValueRef<'_>) -> Result { - let dt = match value.type_info().0 { - DataType::Text => decode_datetime_from_text(value.text_borrowed()?), - DataType::Int4 | DataType::Integer => { - let parsed = OffsetDateTime::from_unix_timestamp(value.int64()?).unwrap(); - Some(PrimitiveDateTime::new(parsed.date(), parsed.time())) - } - - _ => None, - }; - - if let Some(dt) = dt { - Ok(dt) - } else { - Err(format!("invalid datetime: {}", value.text_borrowed()?).into()) - } -} - -fn decode_datetime_from_text(value: &str) -> Option { - let default_format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); - if let Ok(dt) = PrimitiveDateTime::parse(value, &default_format) { - return Some(dt); - } - - let formats = [ - BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_SPACE_SEPARATED), - BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_T_SEPARATED), - ]; - - if let Ok(dt) = PrimitiveDateTime::parse(value, &BorrowedFormatItem::First(&formats)) { - return Some(dt); - } - - None -} - -mod formats { - use time::format_description::BorrowedFormatItem::{Component, Literal, Optional}; - use time::format_description::{modifier, BorrowedFormatItem, Component::*}; - - const YEAR: BorrowedFormatItem<'_> = Component(Year({ - let mut value = modifier::Year::default(); - value.padding = modifier::Padding::Zero; - value.repr = modifier::YearRepr::Full; - value.iso_week_based = false; - value.sign_is_mandatory = false; - value - })); - - const MONTH: BorrowedFormatItem<'_> = Component(Month({ - let mut value = modifier::Month::default(); - value.padding = modifier::Padding::Zero; - value.repr = modifier::MonthRepr::Numerical; - value.case_sensitive = true; - value - })); - - const DAY: BorrowedFormatItem<'_> = Component(Day({ - let mut value = modifier::Day::default(); - value.padding = modifier::Padding::Zero; - value - })); - - const HOUR: BorrowedFormatItem<'_> = Component(Hour({ - let mut value = modifier::Hour::default(); - value.padding = modifier::Padding::Zero; - value.is_12_hour_clock = false; - value - })); - - const MINUTE: BorrowedFormatItem<'_> = Component(Minute({ - let mut value = modifier::Minute::default(); - value.padding = modifier::Padding::Zero; - value - })); - - const SECOND: BorrowedFormatItem<'_> = Component(Second({ - let mut value = modifier::Second::default(); - value.padding = modifier::Padding::Zero; - value - })); - - const SUBSECOND: BorrowedFormatItem<'_> = Component(Subsecond({ - let mut value = modifier::Subsecond::default(); - value.digits = modifier::SubsecondDigits::OneOrMore; - value - })); - - const OFFSET_HOUR: BorrowedFormatItem<'_> = Component(OffsetHour({ - let mut value = modifier::OffsetHour::default(); - value.sign_is_mandatory = true; - value.padding = modifier::Padding::Zero; - value - })); - - const OFFSET_MINUTE: BorrowedFormatItem<'_> = Component(OffsetMinute({ - let mut value = modifier::OffsetMinute::default(); - value.padding = modifier::Padding::Zero; - value - })); - - pub(super) const OFFSET_DATE_TIME: &[BorrowedFormatItem<'_>] = { - &[ - YEAR, - Literal(b"-"), - MONTH, - Literal(b"-"), - DAY, - Optional(&Literal(b" ")), - Optional(&Literal(b"T")), - HOUR, - Literal(b":"), - MINUTE, - Optional(&Literal(b":")), - Optional(&SECOND), - Optional(&Literal(b".")), - Optional(&SUBSECOND), - Optional(&OFFSET_HOUR), - Optional(&Literal(b":")), - Optional(&OFFSET_MINUTE), - ] - }; - - pub(super) const PRIMITIVE_DATE_TIME_SPACE_SEPARATED: &[BorrowedFormatItem<'_>] = { - &[ - YEAR, - Literal(b"-"), - MONTH, - Literal(b"-"), - DAY, - Literal(b" "), - HOUR, - Literal(b":"), - MINUTE, - Optional(&Literal(b":")), - Optional(&SECOND), - Optional(&Literal(b".")), - Optional(&SUBSECOND), - Optional(&Literal(b"Z")), - ] - }; - - pub(super) const PRIMITIVE_DATE_TIME_T_SEPARATED: &[BorrowedFormatItem<'_>] = { - &[ - YEAR, - Literal(b"-"), - MONTH, - Literal(b"-"), - DAY, - Literal(b"T"), - HOUR, - Literal(b":"), - MINUTE, - Optional(&Literal(b":")), - Optional(&SECOND), - Optional(&Literal(b".")), - Optional(&SUBSECOND), - Optional(&Literal(b"Z")), - ] - }; -} diff --git a/vendor/sqlx-sqlite/src/types/uint.rs b/vendor/sqlx-sqlite/src/types/uint.rs deleted file mode 100644 index 1e8b1c5392..0000000000 --- a/vendor/sqlx-sqlite/src/types/uint.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; - -impl Type for u8 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Int4) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for u8 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int(*self as i32)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for u8 { - fn decode(value: SqliteValueRef<'r>) -> Result { - // NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates - // which leads to bugs, e.g.: - // https://github.com/launchbadge/sqlx/issues/3179 - // Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161 - Ok(value.int64()?.try_into()?) - } -} - -impl Type for u16 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Int4) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for u16 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int(*self as i32)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for u16 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?.try_into()?) - } -} - -impl Type for u32 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Integer) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl Encode<'_, Sqlite> for u32 { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Int64(*self as i64)); - - Ok(IsNull::No) - } -} - -impl<'r> Decode<'r, Sqlite> for u32 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?.try_into()?) - } -} - -impl Type for u64 { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Integer) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Int4 | DataType::Integer) - } -} - -impl<'r> Decode<'r, Sqlite> for u64 { - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(value.int64()?.try_into()?) - } -} diff --git a/vendor/sqlx-sqlite/src/types/uuid.rs b/vendor/sqlx-sqlite/src/types/uuid.rs deleted file mode 100644 index 718ca5fb5d..0000000000 --- a/vendor/sqlx-sqlite/src/types/uuid.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::arguments::SqliteArgumentsBuffer; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::type_info::DataType; -use crate::types::Type; -use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; -use std::sync::Arc; -use uuid::{ - fmt::{Hyphenated, Simple}, - Uuid, -}; - -impl Type for Uuid { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Blob) - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - matches!(ty.0, DataType::Blob | DataType::Text) - } -} - -impl Encode<'_, Sqlite> for Uuid { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Blob(Arc::new( - self.as_bytes().to_vec(), - ))); - - Ok(IsNull::No) - } -} - -impl Decode<'_, Sqlite> for Uuid { - fn decode(value: SqliteValueRef<'_>) -> Result { - // construct a Uuid from the returned bytes - Uuid::from_slice(value.blob_borrowed()).map_err(Into::into) - } -} - -impl Type for Hyphenated { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Text) - } -} - -impl Encode<'_, Sqlite> for Hyphenated { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); - - Ok(IsNull::No) - } -} - -impl Decode<'_, Sqlite> for Hyphenated { - fn decode(value: SqliteValueRef<'_>) -> Result { - let uuid: Result = - Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into); - - Ok(uuid?.hyphenated()) - } -} - -impl Type for Simple { - fn type_info() -> SqliteTypeInfo { - SqliteTypeInfo(DataType::Text) - } -} - -impl Encode<'_, Sqlite> for Simple { - fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { - args.push(SqliteArgumentValue::Text(Arc::new(self.to_string()))); - - Ok(IsNull::No) - } -} - -impl Decode<'_, Sqlite> for Simple { - fn decode(value: SqliteValueRef<'_>) -> Result { - let uuid: Result = - Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into); - - Ok(uuid?.simple()) - } -} diff --git a/vendor/sqlx-sqlite/src/value.rs b/vendor/sqlx-sqlite/src/value.rs deleted file mode 100644 index bdf376c187..0000000000 --- a/vendor/sqlx-sqlite/src/value.rs +++ /dev/null @@ -1,416 +0,0 @@ -use std::borrow::Cow; -use std::cell::OnceCell; -use std::ptr::NonNull; -use std::slice; -use std::str; - -use libsqlite3_sys::{ - sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double, - sqlite3_value_dup, sqlite3_value_free, sqlite3_value_int64, sqlite3_value_type, -}; -use sqlx_core::type_info::TypeInfo; -pub(crate) use sqlx_core::value::{Value, ValueRef}; - -use crate::type_info::DataType; -use crate::{Sqlite, SqliteError, SqliteTypeInfo}; - -/// An owned handle to a [`sqlite3_value`]. -/// -/// # Note: Decoding is Stateful -/// The [`sqlite3_value` interface][value-methods] reserves the right to be stateful: -/// -/// > Other interfaces might change the datatype for an sqlite3_value object. -/// > For example, if the datatype is initially SQLITE_INTEGER and sqlite3_value_text(V) is called -/// > to extract a text value for that integer, then subsequent calls to sqlite3_value_type(V) -/// > might return SQLITE_TEXT. Whether or not a persistent internal datatype conversion occurs is -/// > undefined and may change from one release of SQLite to the next. -/// -/// Thus, this type is `!Sync` and [`SqliteValueRef`] is `!Send` and `!Sync` to prevent data races. -/// -/// Additionally, this statefulness means that the return values of `sqlite3_value_bytes()` and -/// `sqlite3_value_blob()` could be invalidated by later calls to other `sqlite3_value*` methods. -/// -/// To prevent undefined behavior from accessing dangling pointers, this type (and any -/// [`SqliteValueRef`] instances created from it) remembers when it was used to decode a -/// borrowed `&[u8]` or `&str` and returns an error if it is used to decode any other type. -/// -/// To bypass this error, you must prove that no outstanding borrows exist. -/// -/// This may be done in one of a few ways: -/// * If you hold mutable access, call [`Self::reset_borrow()`] which resets the borrowed state. -/// * If you have an immutable reference, call [`Self::clone()`] to get a new instance -/// with no outstanding borrows. -/// * If you hold a [`SqliteValueRef`], call [`SqliteValueRef::to_owned()`] -/// to get a new `SqliteValue` with no outstanding borrows. -/// -/// This is *only* necessary if using the same `SqliteValue` or [`SqliteValueRef`] to decode -/// multiple different types. The vast majority of use-cases employing once-through decoding -/// should not have to worry about this. -/// -/// [`sqlite3_value`]: https://www.sqlite.org/c3ref/value.html -/// [value-methods]: https://www.sqlite.org/c3ref/value_blob.html -pub struct SqliteValue(ValueHandle); - -/// A borrowed reference to a [`sqlite3_value`]. -/// -/// Semantically, this behaves as a reference to [`SqliteValue`]. -/// -/// # Note: Decoding is Stateful -/// See [`SqliteValue`] for details. -pub struct SqliteValueRef<'r>(Cow<'r, ValueHandle>); - -impl SqliteValue { - // SAFETY: The sqlite3_value must be non-null and SQLite must not free it. It will be freed on drop. - pub(crate) unsafe fn dup( - value: *mut sqlite3_value, - column_type: Option, - ) -> Self { - debug_assert!(!value.is_null()); - let handle = ValueHandle::try_dup_of(value, column_type) - .expect("SQLite failed to allocate memory for duplicated value"); - Self(handle) - } - - /// Prove that there are no outstanding borrows of this instance. - /// - /// Call this after decoding a borrowed `&[u8]` or `&str` - /// to reset the internal borrowed state and allow decoding of other types. - pub fn reset_borrow(&mut self) { - self.0.reset_blob_borrow(); - } - - /// Call [`sqlite3_value_dup()`] to create a new instance of this type. - /// - /// Returns an error if the call returns a null pointer, indicating that - /// SQLite was unable to allocate the additional memory required. - /// - /// Non-panicking version of [`Self::clone()`]. - /// - /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html - pub fn try_clone(&self) -> Result { - self.0.try_dup().map(Self) - } -} - -impl Clone for SqliteValue { - /// Call [`sqlite3_value_dup()`] to create a new instance of this type. - /// - /// # Panics - /// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition. - /// - /// See [`Self::try_clone()`] for a non-panicking version. - /// - /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html - fn clone(&self) -> Self { - self.try_clone().expect("failed to clone `SqliteValue`") - } -} - -impl Value for SqliteValue { - type Database = Sqlite; - - fn as_ref(&self) -> SqliteValueRef<'_> { - SqliteValueRef::value(self) - } - - fn type_info(&self) -> Cow<'_, SqliteTypeInfo> { - Cow::Owned(self.0.type_info()) - } - - fn is_null(&self) -> bool { - self.0.is_null() - } -} - -impl<'r> SqliteValueRef<'r> { - /// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`]. - /// - /// Returns an error if the call returns a null pointer, indicating that - /// SQLite was unable to allocate the additional memory required. - /// - /// Non-panicking version of [`Self::try_to_owned()`]. - /// - /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html - pub fn try_to_owned(&self) -> Result { - self.0.try_dup().map(SqliteValue) - } - - pub(crate) fn value(value: &'r SqliteValue) -> Self { - Self(Cow::Borrowed(&value.0)) - } - - /// # Safety - /// The supplied sqlite3_value must not be null and SQLite must free it. - /// It will not be freed on drop. - /// The lifetime on this struct should tie it to whatever scope it's valid for before SQLite frees it. - #[allow(unused)] - pub(crate) unsafe fn borrowed(value: *mut sqlite3_value) -> Self { - debug_assert!(!value.is_null()); - let handle = ValueHandle::temporary(NonNull::new_unchecked(value)); - Self(Cow::Owned(handle)) - } - - // NOTE: `int()` is deliberately omitted because it will silently truncate a wider value, - // which is likely to cause bugs: - // https://github.com/launchbadge/sqlx/issues/3179 - // (Similar bug in Postgres): https://github.com/launchbadge/sqlx/issues/3161 - pub(super) fn int64(&self) -> Result { - self.0.int64() - } - - pub(super) fn double(&self) -> Result { - self.0.double() - } - - pub(super) fn blob_borrowed(&self) -> &'r [u8] { - // SAFETY: lifetime is matched to `'r` - unsafe { self.0.blob_borrowed() } - } - - pub(super) fn with_temp_blob(&self, op: impl FnOnce(&[u8]) -> R) -> R { - self.0.with_blob(op) - } - - pub(super) fn blob_owned(&self) -> Vec { - self.with_temp_blob(|blob| blob.to_vec()) - } - - pub(super) fn text_borrowed(&self) -> Result<&'r str, str::Utf8Error> { - // SAFETY: lifetime is matched to `'r` - unsafe { self.0.text_borrowed() } - } - - pub(super) fn with_temp_text( - &self, - op: impl FnOnce(&str) -> R, - ) -> Result { - self.0.with_blob(|blob| str::from_utf8(blob).map(op)) - } - - pub(super) fn text_owned(&self) -> Result { - self.with_temp_text(|text| text.to_string()) - } -} - -impl<'r> ValueRef<'r> for SqliteValueRef<'r> { - type Database = Sqlite; - - /// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`]. - /// - /// # Panics - /// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition. - /// - /// See [`Self::try_to_owned()`] for a non-panicking version. - /// - /// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html - fn to_owned(&self) -> SqliteValue { - SqliteValue( - self.0 - .try_dup() - .expect("failed to convert SqliteValueRef to owned SqliteValue"), - ) - } - - fn type_info(&self) -> Cow<'_, SqliteTypeInfo> { - Cow::Owned(self.0.type_info()) - } - - fn is_null(&self) -> bool { - self.0.is_null() - } -} - -pub(crate) struct ValueHandle { - value: NonNull, - column_type: Option, - // Note: `std::cell` version - borrowed_blob: OnceCell, - free_on_drop: bool, -} - -struct Blob { - ptr: *const u8, - len: usize, -} - -#[derive(Debug, thiserror::Error)] -#[error("given `SqliteValue` was previously decoded as BLOB or TEXT; `SqliteValue::reset_borrow()` must be called first")] -pub(crate) struct BorrowedBlobError; - -// SAFE: only protected value objects are stored in SqliteValue -unsafe impl Send for ValueHandle {} - -// SAFETY: the `sqlite3_value_*()` methods reserve the right to be stateful, -// which means method calls aren't thread-safe without mutual exclusion. -// -// impl !Sync for ValueHandle {} - -impl ValueHandle { - /// # Safety - /// The `sqlite3_value` must be valid and SQLite must not free it. It will be freed on drop. - unsafe fn try_dup_of( - value: *mut sqlite3_value, - column_type: Option, - ) -> Result { - // SAFETY: caller must ensure `value` is valid. - let value = - unsafe { NonNull::new(sqlite3_value_dup(value)).ok_or_else(SqliteError::nomem)? }; - - Ok(Self { - value, - column_type, - borrowed_blob: OnceCell::new(), - free_on_drop: true, - }) - } - - fn temporary(value: NonNull) -> Self { - Self { - value, - column_type: None, - borrowed_blob: OnceCell::new(), - free_on_drop: false, - } - } - - fn try_dup(&self) -> Result { - // SAFETY: `value` is initialized - unsafe { Self::try_dup_of(self.value.as_ptr(), self.column_type.clone()) } - } - - fn value_type_info(&self) -> SqliteTypeInfo { - SqliteTypeInfo(DataType::from_code(unsafe { - sqlite3_value_type(self.value.as_ptr()) - })) - } - - fn type_info(&self) -> SqliteTypeInfo { - let value_type = self.value_type_info(); - - // Assume the actual value type is more accurate, if it's not NULL. - match &self.column_type { - Some(column_type) if value_type.is_null() => column_type.clone(), - _ => value_type, - } - } - - fn int64(&self) -> Result { - // SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()` - self.assert_blob_not_borrowed()?; - - Ok(unsafe { sqlite3_value_int64(self.value.as_ptr()) }) - } - - fn double(&self) -> Result { - // SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()` - self.assert_blob_not_borrowed()?; - - Ok(unsafe { sqlite3_value_double(self.value.as_ptr()) }) - } - - fn is_null(&self) -> bool { - self.value_type_info().is_null() - } -} - -impl Clone for ValueHandle { - fn clone(&self) -> Self { - self.try_dup().unwrap() - } -} - -impl Drop for ValueHandle { - fn drop(&mut self) { - if self.free_on_drop { - unsafe { - sqlite3_value_free(self.value.as_ptr()); - } - } - } -} - -impl ValueHandle { - fn assert_blob_not_borrowed(&self) -> Result<(), BorrowedBlobError> { - if self.borrowed_blob.get().is_none() { - Ok(()) - } else { - Err(BorrowedBlobError) - } - } - - fn reset_blob_borrow(&mut self) { - self.borrowed_blob.take(); - } - - fn get_blob(&self) -> Option { - if let Some(blob) = self.borrowed_blob.get() { - return Some(Blob { ..*blob }); - } - - // SAFETY: calling `sqlite3_value_bytes` from multiple threads at once is a data race. - let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) }; - - // This likely means UB in SQLite itself or our usage of it; - // signed integer overflow is UB in the C standard. - let len = usize::try_from(len).unwrap_or_else(|_| { - panic!("sqlite3_value_bytes() returned value out of range for usize: {len}") - }); - - if len == 0 { - // empty blobs are NULL - return None; - } - - let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8; - debug_assert!(!ptr.is_null()); - - Some(Blob { ptr, len }) - } - - fn with_blob(&self, with_blob: impl FnOnce(&[u8]) -> R) -> R { - let Some(blob) = self.get_blob() else { - return with_blob(&[]); - }; - - // SAFETY: the slice cannot outlive the call - with_blob(unsafe { blob.as_slice() }) - } - - /// # Safety - /// Caller must ensure lifetime '`b` cannot outlive `self`. - unsafe fn blob_borrowed<'a>(&self) -> &'a [u8] { - let Some(blob) = self.get_blob() else { - return &[]; - }; - - // SAFETY: we need to store that the blob was borrowed - // to prevent - let blob = self.borrowed_blob.get_or_init(|| blob); - - unsafe { blob.as_slice() } - } - - /// # Safety - /// Caller must ensure lifetime '`b` cannot outlive `self`. - unsafe fn text_borrowed<'b>(&self) -> Result<&'b str, str::Utf8Error> { - let Some(blob) = self.get_blob() else { - return Ok(""); - }; - - // SAFETY: lifetime of `blob` will be tied to `'b`. - let s = str::from_utf8(unsafe { blob.as_slice() })?; - - // We only store the borrow after we ensure the string is valid. - self.borrowed_blob.set(blob).ok(); - - Ok(s) - } -} - -impl Blob { - /// # Safety - /// `'a` must not outlive the `sqlite3_value` this blob came from. - unsafe fn as_slice<'a>(&self) -> &'a [u8] { - slice::from_raw_parts(self.ptr, self.len) - } -}