diff --git a/Cargo.lock b/Cargo.lock index 2ae7e78..12d0f49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1048,6 +1048,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1307,7 +1313,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1315,14 +1321,17 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -1948,9 +1957,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -3320,6 +3329,16 @@ dependencies = [ "serde", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + [[package]] name = "ruma" version = "0.14.1" @@ -3497,9 +3516,7 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +version = "0.37.99" dependencies = [ "bitflags 2.11.0", "fallible-iterator", @@ -3507,6 +3524,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -3977,6 +3995,18 @@ dependencies = [ "der", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "sse-codec" version = "0.3.2" @@ -4018,6 +4048,7 @@ dependencies = [ "portable-pty", "pulldown-cmark", "reqwest 0.13.2", + "rusqlite", "rust-embed", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 00d6bd0..1a02d0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,9 @@ matrix-sdk = { version = "0.16.0", default-features = false, features = [ pulldown-cmark = { version = "0.13.1", default-features = false, features = [ "html", ] } + +[patch.crates-io] +# Patch rusqlite 0.37.x (used by matrix-sdk-sqlite) with a local fork that requires +# libsqlite3-sys 0.37.0 instead of 0.35.0, enabling a single unified libsqlite3-sys +# 0.37.0 in the dependency graph with the "bundled" feature for static builds. +rusqlite = { path = "vendor/rusqlite" } diff --git a/server/Cargo.toml b/server/Cargo.toml index 4ba1230..e82f327 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -32,7 +32,10 @@ matrix-sdk = { workspace = true } pulldown-cmark = { workspace = true } # Force bundled SQLite so static musl builds don't need a system libsqlite3 -libsqlite3-sys = { version = "0.35.0", features = ["bundled"] } +libsqlite3-sys = { version = "0.37.0", features = ["bundled"] } +# Enable fallible_uint feature to restore u64/usize ToSql/FromSql impls needed +# by matrix-sdk-sqlite (removed in rusqlite 0.38+ without this feature flag) +rusqlite = { version = "0.37.99", features = ["fallible_uint"] } wait-timeout = "0.2.1" [dev-dependencies] diff --git a/vendor/rusqlite/.cargo-ok b/vendor/rusqlite/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/vendor/rusqlite/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/vendor/rusqlite/.cargo_vcs_info.json b/vendor/rusqlite/.cargo_vcs_info.json new file mode 100644 index 0000000..9b66cf4 --- /dev/null +++ b/vendor/rusqlite/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "2a1790a69107cd03dae85d501dcbdb11c5b32ef3" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/vendor/rusqlite/.gitignore b/vendor/rusqlite/.gitignore new file mode 100644 index 0000000..5f0a3e1 --- /dev/null +++ b/vendor/rusqlite/.gitignore @@ -0,0 +1,3 @@ +/target/ +/doc/ +Cargo.lock diff --git a/vendor/rusqlite/Cargo.toml b/vendor/rusqlite/Cargo.toml new file mode 100644 index 0000000..3366c42 --- /dev/null +++ b/vendor/rusqlite/Cargo.toml @@ -0,0 +1,350 @@ +# 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" +name = "rusqlite" +version = "0.37.99" +authors = ["The rusqlite developers"] +build = false +exclude = [ + "/.github/*", + "/.gitattributes", + "/appveyor.yml", + "/Changelog.md", + "/clippy.toml", + "/codecov.yml", + "**/*.sh", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Ergonomic wrapper for SQLite" +documentation = "https://docs.rs/rusqlite/" +readme = "README.md" +keywords = [ + "sqlite", + "database", + "ffi", +] +categories = ["database"] +license = "MIT" +repository = "https://github.com/rusqlite/rusqlite" + +[package.metadata.docs.rs] +features = [ + "modern-full", + "rusqlite-macros", +] +all-features = false +no-default-features = false +default-target = "x86_64-unknown-linux-gnu" +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[package.metadata.playground] +features = ["bundled-full"] +all-features = false + +[badges.appveyor] +repository = "rusqlite/rusqlite" + +[badges.codecov] +repository = "rusqlite/rusqlite" + +[badges.maintenance] +status = "actively-developed" + +[features] +array = [ + "vtab", + "pointer", +] +backup = [] +blob = [] +buildtime_bindgen = [ + "libsqlite3-sys/buildtime_bindgen", + "sqlite-wasm-rs/bindgen", +] +bundled = [ + "libsqlite3-sys/bundled", + "modern_sqlite", +] +bundled-full = [ + "modern-full", + "bundled", +] +bundled-sqlcipher = [ + "libsqlite3-sys/bundled-sqlcipher", + "bundled", +] +bundled-sqlcipher-vendored-openssl = [ + "libsqlite3-sys/bundled-sqlcipher-vendored-openssl", + "bundled-sqlcipher", +] +bundled-windows = ["libsqlite3-sys/bundled-windows"] +cache = ["hashlink"] +collation = [] +column_decltype = [] +column_metadata = ["libsqlite3-sys/column_metadata"] +csvtab = [ + "csv", + "vtab", +] +default = ["cache"] +extra_check = [] +fallible_uint = [] +functions = [] +hooks = [] +i128_blob = [] +in_gecko = [ + "modern_sqlite", + "libsqlite3-sys/in_gecko", +] +limits = [] +load_extension = [] +loadable_extension = ["libsqlite3-sys/loadable_extension"] +modern-full = [ + "array", + "backup", + "blob", + "modern_sqlite", + "chrono", + "collation", + "column_metadata", + "column_decltype", + "csvtab", + "extra_check", + "functions", + "hooks", + "i128_blob", + "jiff", + "limits", + "load_extension", + "serde_json", + "serialize", + "series", + "time", + "trace", + "unlock_notify", + "url", + "uuid", + "vtab", + "window", +] +modern_sqlite = ["libsqlite3-sys/bundled_bindings"] +pointer = [] +preupdate_hook = [ + "libsqlite3-sys/preupdate_hook", + "hooks", +] +serialize = [] +series = ["vtab"] +session = [ + "libsqlite3-sys/session", + "hooks", +] +sqlcipher = ["libsqlite3-sys/sqlcipher"] +trace = [] +unlock_notify = ["libsqlite3-sys/unlock_notify"] +vtab = [] +wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] +window = ["functions"] +with-asan = ["libsqlite3-sys/with-asan"] + +[lib] +name = "rusqlite" +path = "src/lib.rs" + +[[example]] +name = "load_extension" +path = "examples/load_extension.rs" +required-features = [ + "load_extension", + "bundled", + "functions", + "trace", +] + +[[example]] +name = "loadable_extension" +crate-type = ["cdylib"] +path = "examples/loadable_extension.rs" +required-features = [ + "loadable_extension", + "functions", + "trace", +] + +[[example]] +name = "owning_rows" +path = "examples/owning_rows.rs" + +[[example]] +name = "owning_statement" +path = "examples/owning_statement.rs" + +[[example]] +name = "persons" +path = "examples/persons/main.rs" + +[[test]] +name = "auto_ext" +path = "tests/auto_ext.rs" + +[[test]] +name = "config_log" +path = "tests/config_log.rs" +harness = false + +[[test]] +name = "deny_single_threaded_sqlite_config" +path = "tests/deny_single_threaded_sqlite_config.rs" + +[[test]] +name = "vtab" +path = "tests/vtab.rs" + +[[bench]] +name = "cache" +path = "benches/cache.rs" +harness = false + +[[bench]] +name = "exec" +path = "benches/exec.rs" +harness = false + +[dependencies.bitflags] +version = "2.6.0" + +[dependencies.chrono] +version = "0.4.42" +features = ["clock"] +optional = true +default-features = false + +[dependencies.csv] +version = "1.1" +optional = true + +[dependencies.fallible-iterator] +version = "0.3" + +[dependencies.fallible-streaming-iterator] +version = "0.1" + +[dependencies.hashlink] +version = "0.11" +optional = true + +[dependencies.jiff] +version = "0.2" +features = ["std"] +optional = true +default-features = false + +[dependencies.rusqlite-macros] +version = "0.4.2" +optional = true + +[dependencies.serde_json] +version = "1.0" +optional = true + +[dependencies.smallvec] +version = "1.6.1" + +[dependencies.time] +version = "0.3.47" +features = [ + "formatting", + "macros", + "parsing", +] +optional = true + +[dependencies.url] +version = "2.1" +optional = true + +[dependencies.uuid] +version = "1.0" +optional = true + +[dev-dependencies.bencher] +version = "0.1" + +[dev-dependencies.doc-comment] +version = "0.3" + +[dev-dependencies.regex] +version = "1.5.5" + +[dev-dependencies.self_cell] +version = "1.1.0" + +[dev-dependencies.tempfile] +version = "3.1.0" + +[dev-dependencies.unicase] +version = "2.6.0" + +[dev-dependencies.uuid] +version = "1.0" +features = ["v4"] + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.chrono] +version = "0.4.42" +features = ["wasmbind"] +optional = true +default-features = false + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.jiff] +version = "0.2" +features = ["js"] +optional = true +default-features = false + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.sqlite-wasm-rs] +version = "0.5.1" +default-features = false + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.time] +version = "0.3.47" +features = ["wasm-bindgen"] +optional = true + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.uuid] +version = "1.0" +features = ["js"] +optional = true + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.getrandom] +version = "0.4" +features = ["wasm_js"] + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.uuid] +version = "1.0" +features = ["js"] + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.wasm-bindgen] +version = "0.2.104" + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.wasm-bindgen-test] +version = "0.3.54" + +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies.libsqlite3-sys] +version = "0.37.0" diff --git a/vendor/rusqlite/Cargo.toml.orig b/vendor/rusqlite/Cargo.toml.orig new file mode 100644 index 0000000..fadcd62 --- /dev/null +++ b/vendor/rusqlite/Cargo.toml.orig @@ -0,0 +1,242 @@ +[package] +name = "rusqlite" +# Note: Update version in README.md when you change this. +version = "0.37.99" +authors = ["The rusqlite developers"] +edition = "2021" +description = "Ergonomic wrapper for SQLite" +repository = "https://github.com/rusqlite/rusqlite" +documentation = "https://docs.rs/rusqlite/" +readme = "README.md" +keywords = ["sqlite", "database", "ffi"] +license = "MIT" +categories = ["database"] + +exclude = [ + "/.github/*", + "/.gitattributes", + "/appveyor.yml", + "/Changelog.md", + "/clippy.toml", + "/codecov.yml", + "**/*.sh", +] + +[badges] +appveyor = { repository = "rusqlite/rusqlite" } +codecov = { repository = "rusqlite/rusqlite" } +maintenance = { status = "actively-developed" } + +[lib] +name = "rusqlite" + +[workspace] +members = ["libsqlite3-sys"] + +[features] +# if not SQLITE_OMIT_LOAD_EXTENSION +load_extension = [] +# hot-backup interface +backup = [] +# if not SQLITE_OMIT_INCRBLOB +# sqlite3_blob +blob = [] +# Prepared statements cache by connection (like https://www.sqlite.org/tclsqlite.html#cache) +cache = ["hashlink"] +# sqlite3_create_collation_v2 +collation = [] +# sqlite3_create_function_v2 +functions = [] +# sqlite3_log / sqlite3_trace_v2 +trace = [] +# Use bundled SQLite sources (instead of the one provided by your OS / distribution) +bundled = ["libsqlite3-sys/bundled", "modern_sqlite"] +# Use SQLCipher instead of SQLite +bundled-sqlcipher = ["libsqlite3-sys/bundled-sqlcipher", "bundled"] +bundled-sqlcipher-vendored-openssl = [ + "libsqlite3-sys/bundled-sqlcipher-vendored-openssl", + "bundled-sqlcipher", +] +buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen", "sqlite-wasm-rs/bindgen"] +# sqlite3_limit +limits = [] +# Used to generate a cdylib +loadable_extension = ["libsqlite3-sys/loadable_extension"] +# sqlite3_commit_hook, sqlite3_rollback_hook, ... +hooks = [] +# if SQLITE_ENABLE_PREUPDATE_HOOK +preupdate_hook = ["libsqlite3-sys/preupdate_hook", "hooks"] +# u64, usize, NonZeroU64, NonZeroUsize +fallible_uint = [] +i128_blob = [] +sqlcipher = ["libsqlite3-sys/sqlcipher"] +# SQLITE_ENABLE_UNLOCK_NOTIFY +unlock_notify = ["libsqlite3-sys/unlock_notify"] +# if not SQLITE_OMIT_VIRTUALTABLE +# sqlite3_vtab +vtab = [] +csvtab = ["csv", "vtab"] +# Port of Carray() table-valued function +array = ["vtab", "pointer"] +# if SQLITE_ENABLE_SESSION +# session extension +session = ["libsqlite3-sys/session", "hooks"] +# if not SQLITE_OMIT_WINDOWFUNC +# sqlite3_create_window_function +window = ["functions"] +# Port of generate_series table-valued function +series = ["vtab"] +# check for invalid query. +extra_check = [] +# ]3.34.1, last] +modern_sqlite = ["libsqlite3-sys/bundled_bindings"] +in_gecko = ["modern_sqlite", "libsqlite3-sys/in_gecko"] +bundled-windows = ["libsqlite3-sys/bundled-windows"] +# Build bundled sqlite with -fsanitize=address +with-asan = ["libsqlite3-sys/with-asan"] +# if SQLITE_ENABLE_COLUMN_METADATA +column_metadata = ["libsqlite3-sys/column_metadata"] +# if not SQLITE_OMIT_DECLTYPE +column_decltype = [] +wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] +# if not SQLITE_OMIT_DESERIALIZE +serialize = [] +# pointer passing interfaces: 3.20.0 +pointer = [] + +# Helper feature for enabling most non-build-related optional features +# or dependencies (except `session`). This is useful for running tests / clippy +# / etc. New features and optional dependencies that don't conflict with anything +# else should be added here. +modern-full = [ + "array", + "backup", + "blob", + "modern_sqlite", + "chrono", + "collation", + "column_metadata", + "column_decltype", + "csvtab", + "extra_check", + "functions", + "hooks", + "i128_blob", + "jiff", + "limits", + "load_extension", + "serde_json", + "serialize", + "series", + "time", + "trace", + "unlock_notify", + "url", + "uuid", + "vtab", + "window", +] + +bundled-full = ["modern-full", "bundled"] +default = ["cache"] + +[dependencies] +# Jiff Date/Time/Timestamp persistence +jiff = { version = "0.2", optional = true, default-features = false, features = [ + "std", +] } +# Date/Time/Timestamp persistence +time = { version = "0.3.47", features = [ + "formatting", + "macros", + "parsing", +], optional = true } +bitflags = "2.6.0" +# LRU cache of statement +hashlink = { version = "0.11", optional = true } +# Chrono Date/Time/Timestamp persistence +chrono = { version = "0.4.42", optional = true, default-features = false, features = [ + "clock", +] } +# JSON persistence +serde_json = { version = "1.0", optional = true } +# Virtual table +csv = { version = "1.1", optional = true } +# Url persistence +url = { version = "2.1", optional = true } +fallible-iterator = "0.3" +fallible-streaming-iterator = "0.1" +# Uuid persistence +uuid = { version = "1.0", optional = true } +smallvec = "1.6.1" +# WIP comptime checks +rusqlite-macros = { path = "rusqlite-macros", version = "0.4.2", optional = true } + +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +libsqlite3-sys = { path = "libsqlite3-sys", version = "0.37.0" } + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] +sqlite-wasm-rs = { version = "0.5.1", default-features = false } +chrono = { version = "0.4.42", optional = true, default-features = false, features = ["wasmbind"] } +jiff = { version = "0.2", optional = true, default-features = false, features = ["js"] } +time = { version = "0.3.47", optional = true, features = ["wasm-bindgen"] } +uuid = { version = "1.0", optional = true, features = ["js"] } + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies] +# Something is dependent on them, we use feature to override it. +uuid = { version = "1.0", features = ["js"] } +getrandom = { version = "0.4", features = ["wasm_js"] } +wasm-bindgen-test = "0.3.54" +wasm-bindgen = "0.2.104" + +[dev-dependencies] +doc-comment = "0.3" +tempfile = "3.1.0" +regex = "1.5.5" +uuid = { version = "1.0", features = ["v4"] } +unicase = "2.6.0" +self_cell = "1.1.0" +# Use `bencher` over criterion because it builds much faster, +# and we don't have many benchmarks +bencher = "0.1" + +[[test]] +name = "auto_ext" + +[[test]] +name = "config_log" +harness = false + +[[test]] +name = "deny_single_threaded_sqlite_config" + +[[test]] +name = "vtab" + +[[bench]] +name = "cache" +harness = false + +[[bench]] +name = "exec" +harness = false + +[[example]] +name = "loadable_extension" +crate-type = ["cdylib"] +required-features = ["loadable_extension", "functions", "trace"] + +[[example]] +name = "load_extension" +required-features = ["load_extension", "bundled", "functions", "trace"] + +[package.metadata.docs.rs] +features = ["modern-full", "rusqlite-macros"] +all-features = false +no-default-features = false +default-target = "x86_64-unknown-linux-gnu" +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.playground] +features = ["bundled-full"] +all-features = false diff --git a/vendor/rusqlite/LICENSE b/vendor/rusqlite/LICENSE new file mode 100644 index 0000000..08c660f --- /dev/null +++ b/vendor/rusqlite/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 The rusqlite developers + +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/rusqlite/README.md b/vendor/rusqlite/README.md new file mode 100644 index 0000000..76822b3 --- /dev/null +++ b/vendor/rusqlite/README.md @@ -0,0 +1,259 @@ +# Rusqlite + +[![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite) +[![Documentation](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite) +[![Build Status (GitHub)](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions) +[![Build Status (AppVeyor)](https://ci.appveyor.com/api/projects/status/github/rusqlite/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/rusqlite/rusqlite) +[![Code Coverage](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite) +[![Dependency Status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite) +[![Discord Chat](https://img.shields.io/discord/927966344266256434.svg?logo=discord)](https://discord.gg/nFYfGPB8g4) + +Rusqlite is an ergonomic wrapper for using SQLite from Rust. + +Historically, the API was based on the one from [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the two have diverged in many ways, and no compatibility between the two is intended. + +## Usage + +In your Cargo.toml: + +```toml +[dependencies] +# `bundled` causes us to automatically compile and link in an up to date +# version of SQLite for you. This avoids many common build issues, and +# avoids depending on the version of SQLite on the users system (or your +# system), which may be old or missing. It's the right choice for most +# programs that control their own SQLite databases. +# +# That said, it's not ideal for all scenarios and in particular, generic +# libraries built around `rusqlite` should probably not enable it, which +# is why it is not a default feature -- it could become hard to disable. +rusqlite = { version = "0.39.0", features = ["bundled"] } +``` + +Simple example usage: + +```rust +use rusqlite::{Connection, Result}; + +#[derive(Debug)] +struct Person { + id: i32, + name: String, + data: Option>, +} + +fn main() -> Result<()> { + let conn = Connection::open_in_memory()?; + + conn.execute( + "CREATE TABLE person ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + data BLOB + )", + (), // empty list of parameters. + )?; + let me = Person { + id: 0, + name: "Steven".to_string(), + data: None, + }; + conn.execute( + "INSERT INTO person (name, data) VALUES (?1, ?2)", + (&me.name, &me.data), + )?; + + let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; + let person_iter = stmt.query_map([], |row| { + Ok(Person { + id: row.get(0)?, + name: row.get(1)?, + data: row.get(2)?, + }) + })?; + + for person in person_iter { + println!("Found person {:?}", person.unwrap()); + } + Ok(()) +} +``` + +### Supported SQLite Versions + +The base `rusqlite` package supports SQLite version 3.34.1 or newer. If you need +support for older versions, please file an issue. Some cargo features require a +newer SQLite version; see details below. + +### Optional Features + +Rusqlite provides several features that are behind [Cargo +features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: + +* [`load_extension`](https://docs.rs/rusqlite/~0/rusqlite/struct.LoadExtensionGuard.html) + allows loading dynamic library-based SQLite extensions. +* `loadable_extension` to program [loadable extension](https://sqlite.org/loadext.html) in Rust. +* [`backup`](https://docs.rs/rusqlite/~0/rusqlite/backup/index.html) + allows use of SQLite's online backup API. +* [`functions`](https://docs.rs/rusqlite/~0/rusqlite/functions/index.html) + allows you to load Rust closures into SQLite connections for use in queries. +* `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.) +* [`trace`](https://docs.rs/rusqlite/~0/rusqlite/trace/index.html) + allows hooks into SQLite's tracing and profiling APIs. +* [`blob`](https://docs.rs/rusqlite/~0/rusqlite/blob/index.html) + gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. +* [`limits`](https://docs.rs/rusqlite/~0/rusqlite/struct.Connection.html#method.limit) + allows you to set and retrieve SQLite's per connection limits. +* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the + `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). +* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various + types from the [`chrono` crate](https://crates.io/crates/chrono). +* `time` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various + types from the [`time` crate](https://crates.io/crates/time). +* `jiff` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the + `Value` type from the [`jiff` crate](https://crates.io/crates/jiff). +* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the + `Url` type from the [`url` crate](https://crates.io/crates/url). +* `bundled` uses a bundled version of SQLite. This is a good option for cases where linking to SQLite is complicated, such as Windows. +* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature overrides `bundled`. +* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation. +* `bundled-sqlcipher-vendored-openssl` allows using bundled-sqlcipher with a vendored version of OpenSSL (via the `openssl-sys` crate) as the crypto provider. + - As the name implies this depends on the `bundled-sqlcipher` feature, and automatically turns it on. + - If turned on, this uses the [`openssl-sys`](https://crates.io/crates/openssl-sys) crate, with the `vendored` feature enabled in order to build and bundle the OpenSSL crypto library. +* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks. +* `preupdate_hook` for [preupdate](https://sqlite.org/c3ref/preupdate_count.html) notification callbacks. (Implies `hooks`.) +* `unlock_notify` for [Unlock](https://sqlite.org/unlock_notify.html) notification. +* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implementations in Rust). Currently, only read-only virtual tables are supported. +* `series` exposes [`generate_series(...)`](https://www.sqlite.org/series.html) Table-Valued Function. (Implies `vtab`.) +* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. (Implies `vtab`.) +* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. (Implies `vtab`.) +* `fallible_uint` allows storing values of type `u64`, `usize`, `NonZeroU64`, `NonZeroUsize` but only if <= `i64::MAX`. +* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected. +* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs. +* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature. (Implies `hooks`.) +* `extra_check` fails when a query passed to `execute` is readonly and has a column count > 0. +* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`. +* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html). +* `serialize` exposes [`sqlite3_serialize`](http://sqlite.org/c3ref/serialize.html) (3.23.0). +* `rusqlite-macros` enables the use of the [`prepare_and_bind`](https://docs.rs/rusqlite/~0/rusqlite/macro.prepare_and_bind.html) + and [`prepare_cached_and_bind`](https://docs.rs/rusqlite/~0/rusqlite/macro.prepare_cached_and_bind.html) + procedural macros, which allow capturing identifiers in SQL statements. + + +## Notes on building rusqlite and libsqlite3-sys + +`libsqlite3-sys` is a separate crate from `rusqlite` that provides the Rust +declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a SQLite library that already exists on your system using pkg-config, or a +[Vcpkg](https://github.com/Microsoft/vcpkg) installation for MSVC ABI builds. + +You can adjust this behavior in a number of ways: + +* If you use the `bundled`, `bundled-sqlcipher`, or `bundled-sqlcipher-vendored-openssl` features, `libsqlite3-sys` will use the + [cc](https://crates.io/crates/cc) crate to compile SQLite or SQLCipher from source and + link against that. This source is embedded in the `libsqlite3-sys` crate and + is currently SQLite 3.51.3 (as of `rusqlite` 0.39.0 / `libsqlite3-sys` + 0.37.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: + ```toml + [dependencies.rusqlite] + version = "0.39.0" + features = ["bundled"] + ``` +* When using any of the `bundled` features, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE3_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.) +* When using `bundled-sqlcipher` (and not also using `bundled-sqlcipher-vendored-openssl`), `libsqlite3-sys` will need to + link against crypto libraries on the system. If the build script can find a `libcrypto` from OpenSSL or LibreSSL (it will consult `OPENSSL_LIB_DIR`/`OPENSSL_INCLUDE_DIR` and `OPENSSL_DIR` environment variables), it will use that. If building on and for Macs, and none of those variables are set, it will use the system's SecurityFramework instead. + +* When linking against a SQLite (or SQLCipher) library already on the system (so *not* using any of the `bundled` features), you can set the `SQLITE3_LIB_DIR` (or `SQLCIPHER_LIB_DIR`) environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` (or `SQLCIPHER_INCLUDE_DIR`) variable to point to the directory containing `sqlite3.h`. +* Installing the sqlite3 development packages will usually be all that is required, but + the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs) + and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration + options. The default when using vcpkg is to dynamically link, + which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build. + `vcpkg install sqlite3:x64-windows` will install the required library. +* When linking against a SQLite (or SQLCipher) library already on the system, you can set the `SQLITE3_STATIC` (or `SQLCIPHER_STATIC`) environment variable to 1 to request that the library be statically instead of dynamically linked. + + +### Binding generation + +We use [bindgen](https://crates.io/crates/bindgen) to generate the Rust +declarations from SQLite's C header file. `bindgen` +[recommends](https://github.com/servo/rust-bindgen#library-usage-with-buildrs) +running this as part of the build process of libraries that used this. We tried +this briefly (`rusqlite` 0.10.0, specifically), but it had some annoyances: + +* The build time for `libsqlite3-sys` (and therefore `rusqlite`) increased + dramatically. +* Running `bindgen` requires a relatively-recent version of Clang, which many + systems do not have installed by default. +* Running `bindgen` also requires the SQLite header file to be present. + +As of `rusqlite` 0.10.1, we avoid running `bindgen` at build-time by shipping +pregenerated bindings for several versions of SQLite. When compiling +`rusqlite`, we use your selected Cargo features to pick the bindings for the +minimum SQLite version that supports your chosen features. If you are using +`libsqlite3-sys` directly, you can use the same features to choose which +pregenerated bindings are chosen: + +* `min_sqlite_version_3_34_1` - SQLite 3.34.1 bindings (this is the default) + +If you use any of the `bundled` features, you will get pregenerated bindings for the +bundled version of SQLite/SQLCipher. If you need other specific pregenerated binding +versions, please file an issue. If you want to run `bindgen` at buildtime to +produce your own bindings, use the `buildtime_bindgen` Cargo feature. + +If you enable the `modern_sqlite` feature, we'll use the bindings we would have +included with the bundled build. You generally should have `buildtime_bindgen` +enabled if you turn this on, as otherwise you'll need to keep the version of +SQLite you link with in sync with what rusqlite would have bundled, (usually the +most recent release of SQLite). Failing to do this will cause a runtime error. + +## Contributing + +Rusqlite has many features, and many of them impact the build configuration in +incompatible ways. This is unfortunate, and makes testing changes hard. + +To help here: you generally should ensure that you run tests/lint for +`--features bundled`, and `--features "bundled-full session buildtime_bindgen"`. + +If running bindgen is problematic for you, `--features bundled-full` enables +bundled and all features which don't require binding generation, and can be used +instead. + +### Checklist + +- Run `cargo fmt` to ensure your Rust code is correctly formatted. +- Ensure `cargo clippy --workspace --features bundled` passes without warnings. +- Ensure `cargo clippy --workspace --features "bundled-full session buildtime_bindgen"` passes without warnings. +- Ensure `cargo test --workspace --features bundled` reports no failures. +- Ensure `cargo test --workspace --features "bundled-full session buildtime_bindgen"` reports no failures. + +## Author + +Rusqlite is the product of hard work by a number of people. A list is available +here: https://github.com/rusqlite/rusqlite/graphs/contributors + +## Community + +Feel free to join the [Rusqlite Discord Server](https://discord.gg/nFYfGPB8g4) to discuss or get help with `rusqlite` or `libsqlite3-sys`. + +## License + +Rusqlite and libsqlite3-sys are available under the MIT license. See the LICENSE file for more info. + +### Licenses of Bundled Software + +Depending on the set of enabled cargo `features`, rusqlite and libsqlite3-sys will also bundle other libraries, which have their own licensing terms: + +- If `--features=bundled-sqlcipher` is enabled, the vendored source of [SQLcipher](https://github.com/sqlcipher/sqlcipher) will be compiled and statically linked in. SQLcipher is distributed under a BSD-style license, as described [here](libsqlite3-sys/sqlcipher/LICENSE). + +- If `--features=bundled` is enabled, the vendored source of SQLite will be compiled and linked in. SQLite is in the public domain, as described [here](https://www.sqlite.org/copyright.html). + +Both of these are quite permissive, have no bearing on the license of the code in `rusqlite` or `libsqlite3-sys` themselves, and can be entirely ignored if you do not use the feature in question. + +## Minimum supported Rust version (MSRV) + +Latest stable Rust version at the time of release. It might compile with older versions. diff --git a/vendor/rusqlite/benches/cache.rs b/vendor/rusqlite/benches/cache.rs new file mode 100644 index 0000000..dd3683e --- /dev/null +++ b/vendor/rusqlite/benches/cache.rs @@ -0,0 +1,18 @@ +use bencher::{benchmark_group, benchmark_main, Bencher}; +use rusqlite::Connection; + +fn bench_no_cache(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + db.set_prepared_statement_cache_capacity(0); + let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71"; + b.iter(|| db.prepare(sql).unwrap()); +} + +fn bench_cache(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71"; + b.iter(|| db.prepare_cached(sql).unwrap()); +} + +benchmark_group!(cache_benches, bench_no_cache, bench_cache); +benchmark_main!(cache_benches); diff --git a/vendor/rusqlite/benches/exec.rs b/vendor/rusqlite/benches/exec.rs new file mode 100644 index 0000000..b95cb35 --- /dev/null +++ b/vendor/rusqlite/benches/exec.rs @@ -0,0 +1,17 @@ +use bencher::{benchmark_group, benchmark_main, Bencher}; +use rusqlite::Connection; + +fn bench_execute(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + let sql = "PRAGMA user_version=1"; + b.iter(|| db.execute(sql, []).unwrap()); +} + +fn bench_execute_batch(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + let sql = "PRAGMA user_version=1"; + b.iter(|| db.execute_batch(sql).unwrap()); +} + +benchmark_group!(exec_benches, bench_execute, bench_execute_batch); +benchmark_main!(exec_benches); diff --git a/vendor/rusqlite/bindings.md b/vendor/rusqlite/bindings.md new file mode 100644 index 0000000..3e03374 --- /dev/null +++ b/vendor/rusqlite/bindings.md @@ -0,0 +1,405 @@ +# List of SQLite functions supported + +- [ ] `sqlite3_version` +- [X] `sqlite3_libversion` +- [ ] `sqlite3_sourceid` +- [X] `sqlite3_libversion_number` + +- [ ] `sqlite3_compileoption_used` +- [ ] `sqlite3_compileoption_get` + +- [X] `sqlite3_threadsafe` (internal use only) + +- [X] `sqlite3_close` +- [ ] `sqlite3_close_v2` + +- [ ] `sqlite3_exec` + +- [ ] `sqlite3_initialize` +- [ ] `sqlite3_shutdown` +- [ ] `sqlite3_os_init` +- [ ] `sqlite3_os_end` + +- [ ] `sqlite3_config` (partially, `fn` callback for SQLITE_CONFIG_LOG) (cannot be used by a loadable extension) +- [X] `sqlite3_db_config` + +- [X] `sqlite3_extended_result_codes` (not public, internal use only) + +- [X] `sqlite3_last_insert_rowid` +- [ ] `sqlite3_set_last_insert_rowid` + +- [X] `sqlite3_changes` +- [X] `sqlite3_changes64` +- [X] `sqlite3_total_changes` +- [X] `sqlite3_total_changes64` + +- [X] `sqlite3_interrupt` +- [X] `sqlite3_is_interrupted` + +- [ ] `sqlite3_complete` + +- [X] `sqlite3_busy_handler` (`fn` callback) +- [X] `sqlite3_busy_timeout` + +- [ ] `sqlite3_get_table` + +- [ ] `sqlite3_mprintf` +- [ ] `sqlite3_vmprintf` +- [ ] `sqlite3_snprintf` +- [ ] `sqlite3_vsnprintf` + +- [ ] `sqlite3_malloc` +- [X] `sqlite3_malloc64` (not public, internal use only) +- [ ] `sqlite3_realloc` +- [ ] `sqlite3_realloc64` +- [X] `sqlite3_free` (not public, internal use only) +- [ ] `sqlite3_msize` + +- [ ] `sqlite3_memory_used` +- [ ] `sqlite3_memory_highwater` + +- [ ] `sqlite3_randomness` + +- [X] `sqlite3_set_authorizer` (`FnMut` callback, reference kept) +- [X] `sqlite3_trace` deprecated (`fn` callback) +- [X] `sqlite3_profile` deprecated (`fn` callback) +- [X] `sqlite3_trace_v2` (`fn` callback, no context data) +- [X] `sqlite3_progress_handler` (`FnMut` callback, reference kept) + +- [ ] `sqlite3_open` +- [X] `sqlite3_open_v2` +- [ ] `sqlite3_uri_parameter` +- [ ] `sqlite3_uri_boolean` +- [ ] `sqlite3_uri_int64` +- [ ] `sqlite3_uri_key` + +- [ ] `sqlite3_filename_database` +- [ ] `sqlite3_filename_journal` +- [ ] `sqlite3_filename_wal` +- [ ] `sqlite3_database_file_object` +- [ ] `sqlite3_create_filename` +- [ ] `sqlite3_free_filename` + +- [X] `sqlite3_errcode` +- [X] `sqlite3_extended_errcode` +- [X] `sqlite3_errmsg` (not public, internal use only) +- [X] `sqlite3_errstr` (not public, internal use only) +- [X] `sqlite3_error_offset` + +- [X] `sqlite3_limit` + +- [ ] `sqlite3_prepare` +- [X] `sqlite3_prepare_v2` +- [X] `sqlite3_prepare_v3` + +- [X] `sqlite3_sql` (not public, internal use only) +- [X] `sqlite3_expanded_sql` +- [ ] `sqlite3_normalized_sql` + +- [X] `sqlite3_stmt_readonly` +- [X] `sqlite3_stmt_isexplain` +- [ ] `sqlite3_stmt_explain` +- [X] `sqlite3_stmt_busy` + +- [ ] `sqlite3_bind_blob` +- [X] `sqlite3_bind_blob64` +- [X] `sqlite3_bind_double` +- [ ] `sqlite3_bind_int` +- [X] `sqlite3_bind_int64` +- [X] `sqlite3_bind_null` +- [ ] `sqlite3_bind_text` +- [X] `sqlite3_bind_text64` +- [ ] `sqlite3_bind_value` +- [X] `sqlite3_bind_pointer` +- [X] `sqlite3_bind_zeroblob` +- [ ] `sqlite3_bind_zeroblob64` + +- [X] `sqlite3_bind_parameter_count` +- [X] `sqlite3_bind_parameter_name` +- [X] `sqlite3_bind_parameter_index` +- [X] `sqlite3_clear_bindings` + +- [X] `sqlite3_column_count` +- [ ] `sqlite3_data_count` +- [X] `sqlite3_column_name` +- [X] `sqlite3_column_database_name` +- [X] `sqlite3_column_table_name` +- [X] `sqlite3_column_origin_name` +- [X] `sqlite3_column_decltype` + +- [X] `sqlite3_step` + +- [X] `sqlite3_column_blob` +- [X] `sqlite3_column_double` +- [ ] `sqlite3_column_int` +- [X] `sqlite3_column_int64` +- [X] `sqlite3_column_text` +- [X] `sqlite3_column_value` (not public, internal use only) +- [X] `sqlite3_column_bytes` (not public, internal use only) +- [X] `sqlite3_column_type` + +- [X] `sqlite3_finalize` +- [X] `sqlite3_reset` (not public, internal use only) + +- [ ] `sqlite3_create_function` +- [X] `sqlite3_create_function_v2` (Boxed callback, destroyed by SQLite) +- [X] `sqlite3_create_window_function` (Boxed callback, destroyed by SQLite) + +- [X] `sqlite3_value_blob` +- [X] `sqlite3_value_double` +- [ ] `sqlite3_value_int` +- [X] `sqlite3_value_int64` +- [X] `sqlite3_value_pointer` +- [X] `sqlite3_value_text` +- [X] `sqlite3_value_bytes` (not public, internal use only) +- [X] `sqlite3_value_type` +- [ ] `sqlite3_value_numeric_type` +- [X] `sqlite3_value_nochange` +- [ ] `sqlite3_value_frombind` +- [ ] `sqlite3_value_encoding` +- [X] `sqlite3_value_subtype` + +- [ ] `sqlite3_value_dup` +- [ ] `sqlite3_value_free` + +- [X] `sqlite3_aggregate_context` (not public, internal use only) +- [X] `sqlite3_user_data` (not public, internal use only) +- [X] `sqlite3_context_db_handle` (Connection ref) +- [X] `sqlite3_get_auxdata` +- [X] `sqlite3_set_auxdata` +- [ ] `sqlite3_get_clientdata` +- [ ] `sqlite3_set_clientdata` + +- [ ] `sqlite3_result_blob` +- [X] `sqlite3_result_blob64` +- [X] `sqlite3_result_double` +- [X] `sqlite3_result_error` +- [X] `sqlite3_result_error_toobig` +- [X] `sqlite3_result_error_nomem` +- [X] `sqlite3_result_error_code` +- [ ] `sqlite3_result_int` +- [X] `sqlite3_result_int64` +- [X] `sqlite3_result_null` +- [ ] `sqlite3_result_text` +- [X] `sqlite3_result_text64` +- [X] `sqlite3_result_value` +- [X] `sqlite3_result_pointer` +- [X] `sqlite3_result_zeroblob` +- [ ] `sqlite3_result_zeroblob64` +- [X] `sqlite3_result_subtype` + +- [ ] `sqlite3_create_collation` +- [X] `sqlite3_create_collation_v2` (Boxed callback, destroyed by SQLite) +- [X] `sqlite3_collation_needed` (`fn` callback) + +- [ ] `sqlite3_sleep` + +- [X] `sqlite3_get_autocommit` + +- [X] `sqlite3_db_handle` (not public, internal use only, Connection ref) +- [X] `sqlite3_db_name` +- [X] `sqlite3_db_filename` +- [X] `sqlite3_db_readonly` +- [X] `sqlite3_txn_state` +- [X] `sqlite3_next_stmt` (not public, internal use only) + +- [X] `sqlite3_commit_hook` (`FnMut` callback, reference kept) +- [X] `sqlite3_rollback_hook` (`FnMut` callback, reference kept) +- [ ] `sqlite3_autovacuum_pages` +- [X] `sqlite3_update_hook` (`FnMut` callback, reference kept) + +- [ ] `sqlite3_enable_shared_cache` +- [ ] `sqlite3_release_memory` +- [X] `sqlite3_db_release_memory` +- [ ] `sqlite3_soft_heap_limit64` +- [ ] `sqlite3_hard_heap_limit64` + +- [X] `sqlite3_table_column_metadata` + +- [X] `sqlite3_load_extension` +- [X] `sqlite3_enable_load_extension` (cannot be used by a loadable extension) +- [X] `sqlite3_auto_extension` (`fn` callbak with Connection ref) +- [X] `sqlite3_cancel_auto_extension` +- [X] `sqlite3_reset_auto_extension` + +- [ ] `sqlite3_create_module` +- [X] `sqlite3_create_module_v2` +- [ ] `sqlite3_drop_modules` +- [X] `sqlite3_declare_vtab` +- [ ] `sqlite3_overload_function` + +- [X] `sqlite3_blob_open` +- [X] `sqlite3_blob_reopen` +- [X] `sqlite3_blob_close` +- [X] `sqlite3_blob_bytes` +- [X] `sqlite3_blob_read` +- [X] `sqlite3_blob_write` + +- [ ] `sqlite3_vfs_find` +- [ ] `sqlite3_vfs_register` +- [ ] `sqlite3_vfs_unregister` + +- [ ] `sqlite3_mutex_alloc` +- [ ] `sqlite3_mutex_free` +- [ ] `sqlite3_mutex_enter` +- [ ] `sqlite3_mutex_try` +- [ ] `sqlite3_mutex_leave` +- [ ] `sqlite3_mutex_held` +- [ ] `sqlite3_mutex_notheld` +- [ ] `sqlite3_db_mutex` + +- [X] `sqlite3_file_control` (not public, internal use only) +- [ ] `sqlite3_test_control` + +- [ ] `sqlite3_keyword_count` +- [ ] `sqlite3_keyword_name` +- [ ] `sqlite3_keyword_check` + +- [ ] `sqlite3_str_new` +- [ ] `sqlite3_str_finish` +- [ ] `sqlite3_str_append` +- [ ] `sqlite3_str_reset` +- [ ] `sqlite3_str_errcode` +- [ ] `sqlite3_str_length` +- [ ] `sqlite3_str_value` + +- [ ] `sqlite3_status` +- [ ] `sqlite3_status64` +- [ ] `sqlite3_db_status` +- [X] `sqlite3_stmt_status` + +- [X] `sqlite3_backup_init` +- [X] `sqlite3_backup_step` +- [X] `sqlite3_backup_finish` +- [X] `sqlite3_backup_remaining` +- [X] `sqlite3_backup_pagecount` + +- [X] `sqlite3_unlock_notify` (`fn` callback, internal use only) + +- [ ] `sqlite3_stricmp` +- [ ] `sqlite3_strnicmp` +- [ ] `sqlite3_strglob` +- [ ] `sqlite3_strlike` + +- [X] `sqlite3_log` + +- [X] `sqlite3_wal_hook` (`fn` callback with Connection ref) +- [ ] `sqlite3_wal_autocheckpoint` +- [X] `sqlite3_wal_checkpoint` +- [X] `sqlite3_wal_checkpoint_v2` + +- [X] `sqlite3_vtab_config` +- [X] `sqlite3_vtab_on_conflict` +- [X] `sqlite3_vtab_nochange` +- [X] `sqlite3_vtab_collation` +- [X] `sqlite3_vtab_distinct` +- [X] `sqlite3_vtab_in` +- [X] `sqlite3_vtab_in_first` +- [X] `sqlite3_vtab_in_next` +- [X] `sqlite3_vtab_rhs_value` + +- [ ] `sqlite3_stmt_scanstatus` +- [ ] `sqlite3_stmt_scanstatus_v2` +- [ ] `sqlite3_stmt_scanstatus_reset` + +- [X] `sqlite3_db_cacheflush` + +- [X] `sqlite3_preupdate_hook` (`FnMut` callback with Connection ref, reference kept) (cannot be used by a loadable extension) +- [X] `sqlite3_preupdate_old` +- [X] `sqlite3_preupdate_count` +- [X] `sqlite3_preupdate_depth` +- [X] `sqlite3_preupdate_new` +- [ ] `sqlite3_preupdate_blobwrite` + +- [ ] `sqlite3_system_errno` + +- [ ] `sqlite3_snapshot_get` +- [ ] `sqlite3_snapshot_open` +- [ ] `sqlite3_snapshot_free` +- [ ] `sqlite3_snapshot_cmp` +- [ ] `sqlite3_snapshot_recover` + +- [X] `sqlite3_serialize` +- [X] `sqlite3_deserialize` + +- [ ] `sqlite3_rtree_geometry_callback` +- [ ] `sqlite3_rtree_query_callback` + +- [X] `sqlite3session_create` +- [X] `sqlite3session_delete` +- [ ] `sqlite3session_object_config` +- [X] `sqlite3session_enable` +- [X] `sqlite3session_indirect` +- [X] `sqlite3session_attach` +- [X] `sqlite3session_table_filter` (Boxed callback, reference kept) +- [X] `sqlite3session_changeset` +- [ ] `sqlite3session_changeset_size` +- [X] `sqlite3session_diff` +- [X] `sqlite3session_patchset` +- [X] `sqlite3session_isempty` +- [ ] `sqlite3session_memory_used` +- [X] `sqlite3changeset_start` +- [ ] `sqlite3changeset_start_v2` +- [X] `sqlite3changeset_next` +- [X] `sqlite3changeset_op` +- [X] `sqlite3changeset_pk` +- [X] `sqlite3changeset_old` +- [X] `sqlite3changeset_new` +- [X] `sqlite3changeset_conflict` +- [X] `sqlite3changeset_fk_conflicts` +- [X] `sqlite3changeset_finalize` +- [X] `sqlite3changeset_invert` +- [X] `sqlite3changeset_concat` +- [ ] `sqlite3changeset_upgrade` +- [X] `sqlite3changegroup_new` +- [ ] `sqlite3changegroup_schema` +- [X] `sqlite3changegroup_add` +- [ ] `sqlite3changegroup_add_change` +- [X] `sqlite3changegroup_output` +- [X] `sqlite3changegroup_delete` +- [X] `sqlite3changeset_apply` +- [ ] `sqlite3changeset_apply_v2` +- [ ] `sqlite3rebaser_create` +- [ ] `sqlite3rebaser_configure` +- [ ] `sqlite3rebaser_rebase` +- [ ] `sqlite3rebaser_delete` +- [X] `sqlite3changeset_apply_strm` +- [ ] `sqlite3changeset_apply_v2_strm` +- [X] `sqlite3changeset_concat_strm` +- [X] `sqlite3changeset_invert_strm` +- [X] `sqlite3changeset_start_strm` +- [ ] `sqlite3changeset_start_v2_strm` +- [X] `sqlite3session_changeset_strm` +- [X] `sqlite3session_patchset_strm` +- [X] `sqlite3changegroup_add_strm` +- [X] `sqlite3changegroup_add_strm` +- [X] `sqlite3changegroup_output_strm` +- [ ] `sqlite3rebaser_rebase_strm` +- [ ] `sqlite3session_config` + +## List of virtual table methods supported + +- [X] `xCreate` +- [X] `xConnect` +- [X] `xBestIndex` +- [X] `xDisconnect` +- [X] `xDestroy` +- [X] `xOpen` +- [X] `xClose` +- [X] `xFilter` +- [X] `xNext` +- [X] `xEof` +- [X] `xColumn` +- [X] `xRowid` +- [X] `xUpdate` +- [X] `xBegin` +- [X] `xSync` +- [X] `xCommit` +- [X] `xRollback` +- [ ] `xFindFunction` +- [ ] `xRename` +- [ ] `xSavepoint` +- [ ] `xRelease` +- [ ] `xRollbackTo` +- [ ] `xShadowName` +- [ ] `xIntegrity` diff --git a/vendor/rusqlite/examples/load_extension.rs b/vendor/rusqlite/examples/load_extension.rs new file mode 100644 index 0000000..eee07de --- /dev/null +++ b/vendor/rusqlite/examples/load_extension.rs @@ -0,0 +1,23 @@ +//! Ensure `loadable_extension.rs` works. + +use rusqlite::{Connection, Result}; +use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; + +fn main() -> Result<()> { + let db = Connection::open_in_memory()?; + + unsafe { + db.load_extension_enable()?; + db.load_extension( + format!("target/debug/examples/{DLL_PREFIX}loadable_extension{DLL_SUFFIX}"), + None::<&str>, + )?; + db.load_extension_disable()?; + } + + let str = db.query_row("SELECT rusqlite_test_function()", [], |row| { + row.get::<_, String>(0) + })?; + assert_eq!(&str, "Rusqlite extension loaded correctly!"); + Ok(()) +} diff --git a/vendor/rusqlite/examples/loadable_extension.rs b/vendor/rusqlite/examples/loadable_extension.rs new file mode 100644 index 0000000..84999a5 --- /dev/null +++ b/vendor/rusqlite/examples/loadable_extension.rs @@ -0,0 +1,49 @@ +//! Adaptation of https://sqlite.org/loadext.html#programming_loadable_extensions +//! +//! # build +//! ```sh +//! cargo build --example loadable_extension --features "loadable_extension functions trace" +//! ``` +//! +//! # test +//! ```sh +//! sqlite> .log on +//! sqlite> .load target/debug/examples/libloadable_extension.so +//! (28) Rusqlite extension initialized +//! sqlite> SELECT rusqlite_test_function(); +//! Rusqlite extension loaded correctly! +//! ``` +use std::os::raw::{c_char, c_int}; + +use rusqlite::ffi; +use rusqlite::functions::FunctionFlags; +use rusqlite::types::{ToSqlOutput, Value}; +use rusqlite::{Connection, Result}; + +/// Entry point for SQLite to load the extension. +/// See on this function's name and usage. +/// # Safety +/// This function is called by SQLite and must be safe to call. +#[no_mangle] +pub unsafe extern "C" fn sqlite3_extension_init( + db: *mut ffi::sqlite3, + pz_err_msg: *mut *mut c_char, + p_api: *mut ffi::sqlite3_api_routines, +) -> c_int { + Connection::extension_init2(db, pz_err_msg, p_api, extension_init) +} + +fn extension_init(db: Connection) -> Result { + db.create_scalar_function( + c"rusqlite_test_function", + 0, + FunctionFlags::SQLITE_DETERMINISTIC, + |_ctx| { + Ok(ToSqlOutput::Owned(Value::Text( + "Rusqlite extension loaded correctly!".to_string(), + ))) + }, + )?; + rusqlite::trace::log(ffi::SQLITE_WARNING, "Rusqlite extension initialized"); + Ok(false) +} diff --git a/vendor/rusqlite/examples/owning_rows.rs b/vendor/rusqlite/examples/owning_rows.rs new file mode 100644 index 0000000..1e42212 --- /dev/null +++ b/vendor/rusqlite/examples/owning_rows.rs @@ -0,0 +1,27 @@ +extern crate rusqlite; + +use rusqlite::{CachedStatement, Connection, Result, Rows}; +use self_cell::{self_cell, MutBorrow}; + +type RowsRef<'a> = Rows<'a>; + +self_cell!( + struct OwningRows<'conn> { + owner: MutBorrow>, + #[covariant] + dependent: RowsRef, + } +); + +fn main() -> Result<()> { + let conn = Connection::open_in_memory()?; + let stmt = conn.prepare_cached("SELECT 1")?; + let mut or = OwningRows::try_new(MutBorrow::new(stmt), |s| s.borrow_mut().query([]))?; + or.with_dependent_mut(|_stmt, rows| -> Result<()> { + while let Some(row) = rows.next()? { + assert_eq!(Ok(1), row.get(0)); + } + Ok(()) + })?; + Ok(()) +} diff --git a/vendor/rusqlite/examples/owning_statement.rs b/vendor/rusqlite/examples/owning_statement.rs new file mode 100644 index 0000000..21ee1d6 --- /dev/null +++ b/vendor/rusqlite/examples/owning_statement.rs @@ -0,0 +1,30 @@ +extern crate rusqlite; +use rusqlite::{CachedStatement, Connection, Result, Rows}; +use self_cell::{self_cell, MutBorrow}; + +type CachedStatementRef<'a> = CachedStatement<'a>; + +// Caveat: single statement at a time for one connection. +// But if you need multiple statements, you can still create your own struct +// with multiple fields (one for each statement). +self_cell!( + struct OwningStatement { + owner: MutBorrow, + #[covariant] + dependent: CachedStatementRef, + } +); + +fn main() -> Result<()> { + let conn = Connection::open_in_memory()?; + + let mut os = OwningStatement::try_new(MutBorrow::new(conn), |s| { + s.borrow_mut().prepare_cached("SELECT 1") + })?; + + let mut rows = os.with_dependent_mut(|_conn, stmt| -> Result> { stmt.query([]) })?; + while let Some(row) = rows.next()? { + assert_eq!(Ok(1), row.get(0)); + } + Ok(()) +} diff --git a/vendor/rusqlite/examples/persons/README.md b/vendor/rusqlite/examples/persons/README.md new file mode 100644 index 0000000..f649031 --- /dev/null +++ b/vendor/rusqlite/examples/persons/README.md @@ -0,0 +1,48 @@ +# Persons example + +## Run + +``` +$ cargo run --example persons +``` + +## Run (wasm32-wasi) + +### Requisites + +- [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) +- [wasmtime](https://wasmtime.dev/) + +``` +# Set to wasi-sdk directory +$ export WASI_SDK_PATH=`` +$ export CC_wasm32_wasi="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot" +# Build +$ cargo build --example persons --target wasm32-wasi --release --features bundled +# Run +$ wasmtime target/wasm32-wasi/release/examples/persons.wasm +Found persons: +ID: 1, Name: Steven +ID: 2, Name: John +ID: 3, Name: Alex +``` + +## Run (wasm32-unknown-unknown) + +### Requisites + +- [emscripten](https://emscripten.org/docs/getting_started/downloads.html) +- [wasm-bindgen-cli](https://github.com/wasm-bindgen/wasm-bindgen) + +``` +# Build +$ cargo build --example persons --target wasm32-unknown-unknown --release +# Bindgen +$ wasm-bindgen target/wasm32-unknown-unknown/release/examples/persons.wasm --out-dir target/pkg --nodejs +# Run +$ node target/pkg/persons.js +Found persons: +ID: 1, Name: Steven +ID: 2, Name: John +ID: 3, Name: Alex +``` diff --git a/vendor/rusqlite/examples/persons/main.rs b/vendor/rusqlite/examples/persons/main.rs new file mode 100644 index 0000000..6faeddd --- /dev/null +++ b/vendor/rusqlite/examples/persons/main.rs @@ -0,0 +1,57 @@ +use rusqlite::{Connection, Result}; +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +macro_rules! println { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +struct Person { + id: i32, + name: String, +} + +#[cfg_attr(all(target_family = "wasm", target_os = "unknown"), wasm_bindgen(main))] +fn main() -> Result<()> { + let conn = Connection::open_in_memory()?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS persons ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL + )", + (), // empty list of parameters. + )?; + + conn.execute( + "INSERT INTO persons (name) VALUES (?1), (?2), (?3)", + ["Steven", "John", "Alex"].map(|n| n.to_string()), + )?; + + let mut stmt = conn.prepare("SELECT id, name FROM persons")?; + let rows = stmt.query_map([], |row| { + Ok(Person { + id: row.get(0)?, + name: row.get(1)?, + }) + })?; + + println!("Found persons:"); + + for person in rows { + match person { + Ok(p) => println!("ID: {}, Name: {}", p.id, p.name), + Err(e) => eprintln!("Error: {e:?}"), + } + } + + Ok(()) +} diff --git a/vendor/rusqlite/src/auto_extension.rs b/vendor/rusqlite/src/auto_extension.rs new file mode 100644 index 0000000..e3b7e22 --- /dev/null +++ b/vendor/rusqlite/src/auto_extension.rs @@ -0,0 +1,62 @@ +//! Automatic extension loading +use super::ffi; +use crate::error::{check, to_sqlite_error}; +use crate::{Connection, Error, Result}; +use std::ffi::{c_char, c_int}; +use std::panic::catch_unwind; + +/// Automatic extension initialization routine +pub type AutoExtension = fn(Connection) -> Result<()>; + +/// Raw automatic extension initialization routine +pub type RawAutoExtension = unsafe extern "C" fn( + db: *mut ffi::sqlite3, + pz_err_msg: *mut *mut c_char, + _: *const ffi::sqlite3_api_routines, +) -> c_int; + +/// Bridge between `RawAutoExtension` and `AutoExtension` +/// +/// # Safety +/// * Opening a database from an auto-extension handler will lead to +/// an endless recursion of the auto-handler triggering itself +/// indirectly for each newly-opened database. +/// * Results are undefined if the given db is closed by an auto-extension. +/// * The list of auto-extensions should not be manipulated from an auto-extension. +pub unsafe fn init_auto_extension( + db: *mut ffi::sqlite3, + pz_err_msg: *mut *mut c_char, + ax: AutoExtension, +) -> c_int { + let r = catch_unwind(|| { + let c = Connection::from_handle(db); + c.and_then(ax) + }) + .unwrap_or_else(|_| Err(Error::UnwindingPanic)); + match r { + Err(e) => to_sqlite_error(&e, pz_err_msg), + _ => ffi::SQLITE_OK, + } +} + +/// Register au auto-extension +/// +/// # Safety +/// * Opening a database from an auto-extension handler will lead to +/// an endless recursion of the auto-handler triggering itself +/// indirectly for each newly-opened database. +/// * Results are undefined if the given db is closed by an auto-extension. +/// * The list of auto-extensions should not be manipulated from an auto-extension. +pub unsafe fn register_auto_extension(ax: RawAutoExtension) -> Result<()> { + check(ffi::sqlite3_auto_extension(Some(ax))) +} + +/// Unregister the initialization routine +pub fn cancel_auto_extension(ax: RawAutoExtension) -> bool { + unsafe { ffi::sqlite3_cancel_auto_extension(Some(ax)) == 1 } +} + +/// Disable all automatic extensions previously registered +pub fn reset_auto_extension() { + unsafe { ffi::sqlite3_reset_auto_extension() } +} diff --git a/vendor/rusqlite/src/backup.rs b/vendor/rusqlite/src/backup.rs new file mode 100644 index 0000000..7413e20 --- /dev/null +++ b/vendor/rusqlite/src/backup.rs @@ -0,0 +1,442 @@ +//! Online SQLite backup API. +//! +//! Alternatively, you can create a backup with a simple +//! [`VACUUM INTO `](https://sqlite.org/lang_vacuum.html#vacuuminto). +//! +//! To create a [`Backup`], you must have two distinct [`Connection`]s - one +//! for the source (which can be used while the backup is running) and one for +//! the destination (which cannot). A [`Backup`] handle exposes three methods: +//! [`step`](Backup::step) will attempt to back up a specified number of pages, +//! [`progress`](Backup::progress) gets the current progress of the backup as of +//! the last call to [`step`](Backup::step), and +//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the +//! entire source database, allowing you to specify how many pages are backed up +//! at a time and how long the thread should sleep between chunks of pages. +//! +//! The following example is equivalent to "Example 2: Online Backup of a +//! Running Database" from [SQLite's Online Backup API +//! documentation](https://www.sqlite.org/backup.html). +//! +//! ```rust,no_run +//! # use rusqlite::{backup, Connection, Result}; +//! # use std::path::Path; +//! # use std::time; +//! +//! fn backup_db>( +//! src: &Connection, +//! dst: P, +//! progress: fn(backup::Progress), +//! ) -> Result<()> { +//! let mut dst = Connection::open(dst)?; +//! let backup = backup::Backup::new(src, &mut dst)?; +//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress)) +//! } +//! ``` + +use std::marker::PhantomData; +use std::path::Path; +use std::ptr; + +use std::ffi::c_int; +use std::thread; +use std::time::Duration; + +use crate::ffi; + +use crate::error::error_from_handle; +use crate::{Connection, Name, Result, MAIN_DB}; + +impl Connection { + /// Back up the `name` database to the given + /// destination path. + /// + /// If `progress` is not `None`, it will be called periodically + /// until the backup completes. + /// + /// For more fine-grained control over the backup process (e.g., + /// to sleep periodically during the backup or to back up to an + /// already-open database connection), see the `backup` module. + /// + /// # Failure + /// + /// Will return `Err` if the destination path cannot be opened + /// or if the backup fails. + pub fn backup>( + &self, + name: N, + dst_path: P, + progress: Option, + ) -> Result<()> { + use self::StepResult::{Busy, Done, Locked, More}; + let mut dst = Self::open(dst_path)?; + let backup = Backup::new_with_names(self, name, &mut dst, MAIN_DB)?; + + let mut r = More; + while r == More { + r = backup.step(100)?; + if let Some(f) = progress { + f(backup.progress()); + } + } + + match r { + Done => Ok(()), + Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }), + Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }), + More => unreachable!(), + } + } + + /// Restore the given source path into the + /// `name` database. If `progress` is not `None`, it will be + /// called periodically until the restore completes. + /// + /// For more fine-grained control over the restore process (e.g., + /// to sleep periodically during the restore or to restore from an + /// already-open database connection), see the `backup` module. + /// + /// # Failure + /// + /// Will return `Err` if the destination path cannot be opened + /// or if the restore fails. + pub fn restore, F: Fn(Progress)>( + &mut self, + name: N, + src_path: P, + progress: Option, + ) -> Result<()> { + use self::StepResult::{Busy, Done, Locked, More}; + let src = Self::open(src_path)?; + let restore = Backup::new_with_names(&src, MAIN_DB, self, name)?; + + let mut r = More; + let mut busy_count = 0_i32; + 'restore_loop: while r == More || r == Busy { + r = restore.step(100)?; + if let Some(ref f) = progress { + f(restore.progress()); + } + if r == Busy { + busy_count += 1; + if busy_count >= 3 { + break 'restore_loop; + } + thread::sleep(Duration::from_millis(100)); + } + } + + match r { + Done => Ok(()), + Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }), + Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }), + More => unreachable!(), + } + } +} + +/// Possible successful results of calling +/// [`Backup::step`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum StepResult { + /// The backup is complete. + Done, + + /// The step was successful but there are still more pages that need to be + /// backed up. + More, + + /// The step failed because appropriate locks could not be acquired. This is + /// not a fatal error - the step can be retried. + Busy, + + /// The step failed because the source connection was writing to the + /// database. This is not a fatal error - the step can be retried. + Locked, +} + +/// Struct specifying the progress of a backup. +/// +/// The percentage completion can be calculated as `(pagecount - remaining) / +/// pagecount`. The progress of a backup is as of the last call to +/// [`step`](Backup::step) - if the source database is modified after a call to +/// [`step`](Backup::step), the progress value will become outdated and +/// potentially incorrect. +#[derive(Copy, Clone, Debug)] +pub struct Progress { + /// Number of pages in the source database that still need to be backed up. + pub remaining: c_int, + /// Total number of pages in the source database. + pub pagecount: c_int, +} + +/// A handle to an online backup. +pub struct Backup<'a, 'b> { + phantom_from: PhantomData<&'a Connection>, + to: &'b Connection, + b: *mut ffi::sqlite3_backup, +} + +impl Backup<'_, '_> { + /// Attempt to create a new handle that will allow backups from `from` to + /// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any + /// API calls on the destination of a backup while the backup is taking + /// place. + /// + /// # Failure + /// + /// Will return `Err` if the underlying `sqlite3_backup_init` call returns + /// `NULL`. + #[inline] + pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result> { + Backup::new_with_names(from, MAIN_DB, to, MAIN_DB) + } + + /// Attempt to create a new handle that will allow backups from the + /// `from_name` database of `from` to the `to_name` database of `to`. Note + /// that `to` is a `&mut` - this is because SQLite forbids any API calls on + /// the destination of a backup while the backup is taking place. + /// + /// # Failure + /// + /// Will return `Err` if the underlying `sqlite3_backup_init` call returns + /// `NULL`. + pub fn new_with_names<'a, 'b, F: Name, T: Name>( + from: &'a Connection, + from_name: F, + to: &'b mut Connection, + to_name: T, + ) -> Result> { + let to_name = to_name.as_cstr()?; + let from_name = from_name.as_cstr()?; + + let to_db = to.db.borrow_mut().db; + + let b = unsafe { + let b = ffi::sqlite3_backup_init( + to_db, + to_name.as_ptr(), + from.db.borrow_mut().db, + from_name.as_ptr(), + ); + if b.is_null() { + return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db))); + } + b + }; + + Ok(Backup { + phantom_from: PhantomData, + to, + b, + }) + } + + /// Gets the progress of the backup as of the last call to + /// [`step`](Backup::step). + #[inline] + #[must_use] + pub fn progress(&self) -> Progress { + unsafe { + Progress { + remaining: ffi::sqlite3_backup_remaining(self.b), + pagecount: ffi::sqlite3_backup_pagecount(self.b), + } + } + } + + /// Attempts to back up the given number of pages. If `num_pages` is + /// negative, will attempt to back up all remaining pages. This will hold a + /// lock on the source database for the duration, so it is probably not + /// what you want for databases that are currently active (see + /// [`run_to_completion`](Backup::run_to_completion) for a better + /// alternative). + /// + /// # Failure + /// + /// Will return `Err` if the underlying `sqlite3_backup_step` call returns + /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and + /// `LOCKED` are transient errors and are therefore returned as possible + /// `Ok` values. + #[inline] + pub fn step(&self, num_pages: c_int) -> Result { + use self::StepResult::{Busy, Done, Locked, More}; + + let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) }; + match rc { + ffi::SQLITE_DONE => Ok(Done), + ffi::SQLITE_OK => Ok(More), + ffi::SQLITE_BUSY => Ok(Busy), + ffi::SQLITE_LOCKED => Ok(Locked), + _ => self.to.decode_result(rc).map(|_| More), + } + } + + /// Attempts to run the entire backup. Will call + /// [`step(pages_per_step)`](Backup::step) as many times as necessary, + /// sleeping for `pause_between_pages` between each call to give the + /// source database time to process any pending queries. This is a + /// direct implementation of "Example 2: Online Backup of a Running + /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html). + /// + /// If `progress` is not `None`, it will be called after each step with the + /// current progress of the backup. Note that is possible the progress may + /// not change if the step returns `Busy` or `Locked` even though the + /// backup is still running. + /// + /// # Failure + /// + /// Will return `Err` if any of the calls to [`step`](Backup::step) return + /// `Err`. + pub fn run_to_completion( + &self, + pages_per_step: c_int, + pause_between_pages: Duration, + progress: Option, + ) -> Result<()> { + use self::StepResult::{Busy, Done, Locked, More}; + + assert!(pages_per_step > 0, "pages_per_step must be positive"); + + loop { + let r = self.step(pages_per_step)?; + if let Some(progress) = progress { + progress(self.progress()); + } + match r { + More | Busy | Locked => thread::sleep(pause_between_pages), + Done => return Ok(()), + } + } + } +} + +impl Drop for Backup<'_, '_> { + #[inline] + fn drop(&mut self) { + unsafe { ffi::sqlite3_backup_finish(self.b) }; + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::{Backup, Progress}; + use crate::{Connection, Result, MAIN_DB, TEMP_DB}; + use std::time::Duration; + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn backup_to_path() -> Result<()> { + let src = Connection::open_in_memory()?; + src.execute_batch("CREATE TABLE foo AS SELECT 42 AS x")?; + + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("test.db3"); + + fn progress(_: Progress) {} + + src.backup(MAIN_DB, path.as_path(), Some(progress))?; + + let mut dst = Connection::open_in_memory()?; + dst.restore(MAIN_DB, path, Some(progress))?; + + Ok(()) + } + + #[test] + fn test_backup() -> Result<()> { + let src = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(42); + END;"; + src.execute_batch(sql)?; + + let mut dst = Connection::open_in_memory()?; + + { + let backup = Backup::new(&src, &mut dst)?; + backup.step(-1)?; + } + + assert_eq!(42, dst.one_column::("SELECT x FROM foo", [])?); + + src.execute_batch("INSERT INTO foo VALUES(43)")?; + + { + let backup = Backup::new(&src, &mut dst)?; + backup.run_to_completion(5, Duration::from_millis(250), None)?; + } + + let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?; + assert_eq!(42 + 43, the_answer); + Ok(()) + } + + #[test] + fn test_backup_temp() -> Result<()> { + let src = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TEMPORARY TABLE foo(x INTEGER); + INSERT INTO foo VALUES(42); + END;"; + src.execute_batch(sql)?; + + let mut dst = Connection::open_in_memory()?; + + { + let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?; + backup.step(-1)?; + } + + assert_eq!(42, dst.one_column::("SELECT x FROM foo", [])?); + + src.execute_batch("INSERT INTO foo VALUES(43)")?; + + { + let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?; + backup.run_to_completion(5, Duration::from_millis(250), None)?; + } + + let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?; + assert_eq!(42 + 43, the_answer); + Ok(()) + } + + #[test] + fn test_backup_attached() -> Result<()> { + let src = Connection::open_in_memory()?; + let sql = "ATTACH DATABASE ':memory:' AS my_attached; + BEGIN; + CREATE TABLE my_attached.foo(x INTEGER); + INSERT INTO my_attached.foo VALUES(42); + END;"; + src.execute_batch(sql)?; + + let mut dst = Connection::open_in_memory()?; + + { + let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?; + backup.step(-1)?; + } + + assert_eq!(42, dst.one_column::("SELECT x FROM foo", [])?); + + src.execute_batch("INSERT INTO foo VALUES(43)")?; + + { + let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?; + backup.run_to_completion(5, Duration::from_millis(250), None)?; + } + + let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?; + assert_eq!(42 + 43, the_answer); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/bind.rs b/vendor/rusqlite/src/bind.rs new file mode 100644 index 0000000..f307a7a --- /dev/null +++ b/vendor/rusqlite/src/bind.rs @@ -0,0 +1,71 @@ +use crate::{ffi, Error, Result, Statement}; +use std::ffi::CStr; + +mod sealed { + use std::ffi::CStr; + /// This trait exists just to ensure that the only impls of `trait BindIndex` + /// that are allowed are ones in this crate. + pub trait Sealed {} + impl Sealed for usize {} + impl Sealed for &str {} + impl Sealed for &CStr {} +} + +/// A trait implemented by types that can index into parameters of a statement. +/// +/// It is only implemented for `usize` and `&str` and `&CStr`. +pub trait BindIndex: sealed::Sealed { + /// Returns the index of the associated parameter, or `Error` if no such + /// parameter exists. + fn idx(&self, stmt: &Statement<'_>) -> Result; +} + +impl BindIndex for usize { + #[inline] + fn idx(&self, _: &Statement<'_>) -> Result { + // No validation + Ok(*self) + } +} + +impl BindIndex for &'_ str { + fn idx(&self, stmt: &Statement<'_>) -> Result { + match stmt.parameter_index(self)? { + Some(idx) => Ok(idx), + None => Err(Error::InvalidParameterName(self.to_string())), + } + } +} +/// C-string literal to avoid alloc +impl BindIndex for &CStr { + fn idx(&self, stmt: &Statement<'_>) -> Result { + let r = unsafe { ffi::sqlite3_bind_parameter_index(stmt.ptr(), self.as_ptr()) }; + match r { + 0 => Err(Error::InvalidParameterName( + self.to_string_lossy().to_string(), + )), + i => Ok(i as usize), + } + } +} + +#[cfg(test)] +mod test { + use crate::{ffi, Connection, Error, Result}; + + #[test] + fn invalid_name() -> Result<()> { + let db = Connection::open_in_memory()?; + let mut stmt = db.prepare("SELECT 1")?; + let err = stmt.raw_bind_parameter(1, 1).unwrap_err(); + assert_eq!( + err.sqlite_error_code(), + Some(ffi::ErrorCode::ParameterOutOfRange), + ); + let err = stmt.raw_bind_parameter(":p1", 1).unwrap_err(); + assert_eq!(err, Error::InvalidParameterName(":p1".to_owned())); + let err = stmt.raw_bind_parameter(c"x", 1).unwrap_err(); + assert_eq!(err, Error::InvalidParameterName("x".to_owned())); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/blob/mod.rs b/vendor/rusqlite/src/blob/mod.rs new file mode 100644 index 0000000..6e3e020 --- /dev/null +++ b/vendor/rusqlite/src/blob/mod.rs @@ -0,0 +1,564 @@ +//! Incremental BLOB I/O. +//! +//! Note that SQLite does not provide API-level access to change the size of a +//! BLOB; that must be performed through SQL statements. +//! +//! There are two choices for how to perform IO on a [`Blob`]. +//! +//! 1. The implementations it provides of the `std::io::Read`, `std::io::Write`, +//! and `std::io::Seek` traits. +//! +//! 2. A positional IO API, e.g. [`Blob::read_at`], [`Blob::write_at`] and +//! similar. +//! +//! Documenting these in order: +//! +//! ## 1. `std::io` trait implementations. +//! +//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`, +//! so it plays nicely with other types that build on these (such as +//! `std::io::BufReader` and `std::io::BufWriter`). However, you must be careful +//! with the size of the blob. For example, when using a `BufWriter`, the +//! `BufWriter` will accept more data than the `Blob` will allow, so make sure +//! to call `flush` and check for errors. (See the unit tests in this module for +//! an example.) +//! +//! ## 2. Positional IO +//! +//! `Blob`s also offer a `pread` / `pwrite`-style positional IO api in the form +//! of [`Blob::read_at`], [`Blob::write_at`], [`Blob::raw_read_at`], +//! [`Blob::read_at_exact`], and [`Blob::raw_read_at_exact`]. +//! +//! These APIs all take the position to read from or write to from as a +//! parameter, instead of using an internal `pos` value. +//! +//! ### Positional IO Read Variants +//! +//! For the `read` functions, there are several functions provided: +//! +//! - [`Blob::read_at`] +//! - [`Blob::raw_read_at`] +//! - [`Blob::read_at_exact`] +//! - [`Blob::raw_read_at_exact`] +//! +//! These can be divided along two axes: raw/not raw, and exact/inexact: +//! +//! 1. Raw/not raw refers to the type of the destination buffer. The raw +//! functions take a `&mut [MaybeUninit]` as the destination buffer, +//! where the "normal" functions take a `&mut [u8]`. +//! +//! Using `MaybeUninit` here can be more efficient in some cases, but is +//! often inconvenient, so both are provided. +//! +//! 2. Exact/inexact refers to whether or not the entire buffer must be +//! filled in order for the call to be considered a success. +//! +//! The "exact" functions require the provided buffer be entirely filled, or +//! they return an error, whereas the "inexact" functions read as much out of +//! the blob as is available, and return how much they were able to read. +//! +//! The inexact functions are preferable if you do not know the size of the +//! blob already, and the exact functions are preferable if you do. +//! +//! ### Comparison to using the `std::io` traits: +//! +//! In general, the positional methods offer the following Pro/Cons compared to +//! using the implementation `std::io::{Read, Write, Seek}` we provide for +//! `Blob`: +//! +//! 1. (Pro) There is no need to first seek to a position in order to perform IO +//! on it as the position is a parameter. +//! +//! 2. (Pro) `Blob`'s positional read functions don't mutate the blob in any +//! way, and take `&self`. No `&mut` access required. +//! +//! 3. (Pro) Positional IO functions return `Err(rusqlite::Error)` on failure, +//! rather than `Err(std::io::Error)`. Returning `rusqlite::Error` is more +//! accurate and convenient. +//! +//! Note that for the `std::io` API, no data is lost however, and it can be +//! recovered with `io_err.downcast::()` (this can be easy +//! to forget, though). +//! +//! 4. (Pro, for now). A `raw` version of the read API exists which can allow +//! reading into a `&mut [MaybeUninit]` buffer, which avoids a potential +//! costly initialization step. (However, `std::io` traits will certainly +//! gain this someday, which is why this is only a "Pro, for now"). +//! +//! 5. (Con) The set of functions is more bare-bones than what is offered in +//! `std::io`, which has a number of adapters, handy algorithms, further +//! traits. +//! +//! 6. (Con) No meaningful interoperability with other crates, so if you need +//! that you must use `std::io`. +//! +//! To generalize: the `std::io` traits are useful because they conform to a +//! standard interface that a lot of code knows how to handle, however that +//! interface is not a perfect fit for [`Blob`], so another small set of +//! functions is provided as well. +//! +//! # Example (`std::io`) +//! +//! ```rust +//! # use rusqlite::blob::ZeroBlob; +//! # use rusqlite::{Connection, MAIN_DB}; +//! # use std::error::Error; +//! # use std::io::{Read, Seek, SeekFrom, Write}; +//! # fn main() -> Result<(), Box> { +//! let db = Connection::open_in_memory()?; +//! db.execute_batch("CREATE TABLE test_table (content BLOB);")?; +//! +//! // Insert a BLOB into the `content` column of `test_table`. Note that the Blob +//! // I/O API provides no way of inserting or resizing BLOBs in the DB -- this +//! // must be done via SQL. +//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?; +//! +//! // Get the row id off the BLOB we just inserted. +//! let rowid = db.last_insert_rowid(); +//! // Open the BLOB we just inserted for IO. +//! let mut blob = db.blob_open(MAIN_DB, "test_table", "content", rowid, false)?; +//! +//! // Write some data into the blob. Make sure to test that the number of bytes +//! // written matches what you expect; if you try to write too much, the data +//! // will be truncated to the size of the BLOB. +//! let bytes_written = blob.write(b"01234567")?; +//! assert_eq!(bytes_written, 8); +//! +//! // Move back to the start and read into a local buffer. +//! // Same guidance - make sure you check the number of bytes read! +//! blob.seek(SeekFrom::Start(0))?; +//! let mut buf = [0u8; 20]; +//! let bytes_read = blob.read(&mut buf[..])?; +//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10 +//! +//! // Insert another BLOB, this time using a parameter passed in from +//! // rust (potentially with a dynamic size). +//! db.execute( +//! "INSERT INTO test_table (content) VALUES (?1)", +//! [ZeroBlob(64)], +//! )?; +//! +//! // given a new row ID, we can reopen the blob on that row +//! let rowid = db.last_insert_rowid(); +//! blob.reopen(rowid)?; +//! // Just check that the size is right. +//! assert_eq!(blob.len(), 64); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Example (Positional) +//! +//! ```rust +//! # use rusqlite::blob::ZeroBlob; +//! # use rusqlite::{Connection, MAIN_DB}; +//! # use std::error::Error; +//! # fn main() -> Result<(), Box> { +//! let db = Connection::open_in_memory()?; +//! db.execute_batch("CREATE TABLE test_table (content BLOB);")?; +//! // Insert a blob into the `content` column of `test_table`. Note that the Blob +//! // I/O API provides no way of inserting or resizing blobs in the DB -- this +//! // must be done via SQL. +//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?; +//! // Get the row id off the blob we just inserted. +//! let rowid = db.last_insert_rowid(); +//! // Open the blob we just inserted for IO. +//! let mut blob = db.blob_open(MAIN_DB, "test_table", "content", rowid, false)?; +//! // Write some data into the blob. +//! blob.write_at(b"ABCDEF", 2)?; +//! +//! // Read the whole blob into a local buffer. +//! let mut buf = [0u8; 10]; +//! blob.read_at_exact(&mut buf, 0)?; +//! assert_eq!(&buf, b"\0\0ABCDEF\0\0"); +//! +//! // Insert another blob, this time using a parameter passed in from +//! // rust (potentially with a dynamic size). +//! db.execute( +//! "INSERT INTO test_table (content) VALUES (?1)", +//! [ZeroBlob(64)], +//! )?; +//! +//! // given a new row ID, we can reopen the blob on that row +//! let rowid = db.last_insert_rowid(); +//! blob.reopen(rowid)?; +//! assert_eq!(blob.len(), 64); +//! # Ok(()) +//! # } +//! ``` +use std::cmp::min; +use std::io; +use std::ptr; + +use super::ffi; +use super::types::{ToSql, ToSqlOutput}; +use crate::{Connection, Name, Result}; + +mod pos_io; + +/// Handle to an open BLOB. See +/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion. +pub struct Blob<'conn> { + conn: &'conn Connection, + blob: *mut ffi::sqlite3_blob, + // used by std::io implementations, + pos: i32, +} + +impl Connection { + /// Open a handle to the BLOB located in `row_id`, + /// `column`, `table` in database `db`. + /// + /// # Failure + /// + /// Will return `Err` if `db`/`table`/`column` cannot be converted to a + /// C-compatible string or if the underlying SQLite BLOB open call + /// fails. + #[inline] + pub fn blob_open( + &self, + db: D, + table: N, + column: N, + row_id: i64, + read_only: bool, + ) -> Result> { + let c = self.db.borrow_mut(); + let mut blob = ptr::null_mut(); + let db = db.as_cstr()?; + let table = table.as_cstr()?; + let column = column.as_cstr()?; + let rc = unsafe { + ffi::sqlite3_blob_open( + c.db(), + db.as_ptr(), + table.as_ptr(), + column.as_ptr(), + row_id, + !read_only as std::ffi::c_int, + &mut blob, + ) + }; + c.decode_result(rc).map(|_| Blob { + conn: self, + blob, + pos: 0, + }) + } +} + +impl Blob<'_> { + /// Move a BLOB handle to a new row. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite BLOB reopen call fails. + #[inline] + pub fn reopen(&mut self, row: i64) -> Result<()> { + let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) }; + if rc != ffi::SQLITE_OK { + return self.conn.decode_result(rc); + } + self.pos = 0; + Ok(()) + } + + /// Return the size in bytes of the BLOB. + #[inline] + #[must_use] + pub fn size(&self) -> i32 { + unsafe { ffi::sqlite3_blob_bytes(self.blob) } + } + + /// Return the current size in bytes of the BLOB. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.size().try_into().unwrap() + } + + /// Return true if the BLOB is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.size() == 0 + } + + /// Close a BLOB handle. + /// + /// Calling `close` explicitly is not required (the BLOB will be closed + /// when the `Blob` is dropped), but it is available, so you can get any + /// errors that occur. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite close call fails. + #[inline] + pub fn close(mut self) -> Result<()> { + self.close_() + } + + #[inline] + fn close_(&mut self) -> Result<()> { + let rc = unsafe { ffi::sqlite3_blob_close(self.blob) }; + self.blob = ptr::null_mut(); + self.conn.decode_result(rc) + } +} + +impl io::Read for Blob<'_> { + /// Read data from a BLOB incrementally. Will return Ok(0) if the end of + /// the blob has been reached. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite read call fails. + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let max_allowed_len = (self.size() - self.pos) as usize; + let n = min(buf.len(), max_allowed_len) as i32; + if n <= 0 { + return Ok(0); + } + let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr().cast(), n, self.pos) }; + self.conn + .decode_result(rc) + .map(|_| { + self.pos += n; + n as usize + }) + .map_err(io::Error::other) + } +} + +impl io::Write for Blob<'_> { + /// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of + /// the blob has been reached; consider using `Write::write_all(buf)` + /// if you want to get an error if the entirety of the buffer cannot be + /// written. + /// + /// This function may only modify the contents of the BLOB; it is not + /// possible to increase the size of a BLOB using this API. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite write call fails. + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let max_allowed_len = (self.size() - self.pos) as usize; + let n = min(buf.len(), max_allowed_len) as i32; + if n <= 0 { + return Ok(0); + } + let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) }; + self.conn + .decode_result(rc) + .map(|_| { + self.pos += n; + n as usize + }) + .map_err(io::Error::other) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl io::Seek for Blob<'_> { + /// Seek to an offset, in bytes, in BLOB. + #[inline] + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + let pos = match pos { + io::SeekFrom::Start(offset) => offset as i64, + io::SeekFrom::Current(offset) => i64::from(self.pos) + offset, + io::SeekFrom::End(offset) => i64::from(self.size()) + offset, + }; + + if pos < 0 { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to negative position", + )) + } else if pos > i64::from(self.size()) { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to position past end of blob", + )) + } else { + self.pos = pos as i32; + Ok(pos as u64) + } + } +} + +#[expect(unused_must_use)] +impl Drop for Blob<'_> { + #[inline] + fn drop(&mut self) { + self.close_(); + } +} + +/// BLOB of length N that is filled with zeroes. +/// +/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is +/// later written using incremental BLOB I/O routines. +/// +/// A negative value for the zeroblob results in a zero-length BLOB. +#[derive(Copy, Clone)] +pub struct ZeroBlob(pub i32); + +impl ToSql for ZeroBlob { + #[inline] + fn to_sql(&self) -> Result> { + let Self(length) = *self; + Ok(ToSqlOutput::ZeroBlob(length)) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result, MAIN_DB}; + use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; + + fn db_with_test_blob() -> Result<(Connection, i64)> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE test (content BLOB); + INSERT INTO test VALUES (ZEROBLOB(10)); + END;"; + db.execute_batch(sql)?; + let rowid = db.last_insert_rowid(); + Ok((db, rowid)) + } + + #[test] + fn test_blob() -> Result<()> { + let (db, rowid) = db_with_test_blob()?; + + let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + assert!(!blob.is_empty()); + assert_eq!(10, blob.len()); + assert_eq!(4, blob.write(b"Clob").unwrap()); + assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10 + assert_eq!(0, blob.write(b"5678").unwrap()); // still cannot write past 10 + blob.flush().unwrap(); + + blob.reopen(rowid)?; + blob.close()?; + + blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, true)?; + let mut bytes = [0u8; 5]; + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"Clob5"); + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"67890"); + assert_eq!(0, blob.read(&mut bytes[..]).unwrap()); + + blob.seek(SeekFrom::Start(2)).unwrap(); + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"ob567"); + + // only first 4 bytes of `bytes` should be read into + blob.seek(SeekFrom::Current(-1)).unwrap(); + assert_eq!(4, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"78907"); + + blob.seek(SeekFrom::End(-6)).unwrap(); + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"56789"); + + blob.reopen(rowid)?; + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(&bytes, b"Clob5"); + + // should not be able to seek negative or past end + blob.seek(SeekFrom::Current(-20)).unwrap_err(); + blob.seek(SeekFrom::End(0)).unwrap(); + blob.seek(SeekFrom::Current(1)).unwrap_err(); + + // write_all should detect when we return Ok(0) because there is no space left, + // and return a write error + blob.reopen(rowid)?; + blob.write_all(b"0123456789x").unwrap_err(); + Ok(()) + } + + #[test] + fn test_blob_in_bufreader() -> Result<()> { + let (db, rowid) = db_with_test_blob()?; + + let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + assert_eq!(8, blob.write(b"one\ntwo\n").unwrap()); + + blob.reopen(rowid)?; + let mut reader = BufReader::new(blob); + + let mut line = String::new(); + assert_eq!(4, reader.read_line(&mut line).unwrap()); + assert_eq!("one\n", line); + + line.truncate(0); + assert_eq!(4, reader.read_line(&mut line).unwrap()); + assert_eq!("two\n", line); + + line.truncate(0); + assert_eq!(2, reader.read_line(&mut line).unwrap()); + assert_eq!("\0\0", line); + Ok(()) + } + + #[test] + fn test_blob_in_bufwriter() -> Result<()> { + let (db, rowid) = db_with_test_blob()?; + + { + let blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + let mut writer = BufWriter::new(blob); + + // trying to write too much and then flush should fail + assert_eq!(8, writer.write(b"01234567").unwrap()); + assert_eq!(8, writer.write(b"01234567").unwrap()); + writer.flush().unwrap_err(); + } + + { + // ... but it should've written the first 10 bytes + let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + let mut bytes = [0u8; 10]; + assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(b"0123456701", &bytes); + } + + { + let blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + let mut writer = BufWriter::new(blob); + + // trying to write_all too much should fail + writer.write_all(b"aaaaaaaaaabbbbb").unwrap(); + writer.flush().unwrap_err(); + } + + { + // ... but it should've written the first 10 bytes + let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?; + let mut bytes = [0u8; 10]; + assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(b"aaaaaaaaaa", &bytes); + Ok(()) + } + } + + #[test] + fn zero_blob() -> Result<()> { + use crate::types::ToSql; + let zb = super::ZeroBlob(1); + assert!(zb.to_sql().is_ok()); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/blob/pos_io.rs b/vendor/rusqlite/src/blob/pos_io.rs new file mode 100644 index 0000000..5fe89a5 --- /dev/null +++ b/vendor/rusqlite/src/blob/pos_io.rs @@ -0,0 +1,274 @@ +use super::Blob; + +use std::mem::MaybeUninit; +use std::slice::from_raw_parts_mut; + +use crate::ffi; +use crate::{Error, Result}; + +impl Blob<'_> { + /// Write `buf` to `self` starting at `write_start`, returning an error if + /// `write_start + buf.len()` is past the end of the blob. + /// + /// If an error is returned, no data is written. + /// + /// Note: the blob cannot be resized using this function -- that must be + /// done using SQL (for example, an `UPDATE` statement). + /// + /// Note: This is part of the positional I/O API, and thus takes an absolute + /// position write to, instead of using the internal position that can be + /// manipulated by the `std::io` traits. + /// + /// Unlike the similarly named [`FileExt::write_at`][fext_write_at] function + /// (from `std::os::unix`), it's always an error to perform a "short write". + /// + /// [fext_write_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at + #[inline] + pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> { + let len = self.len(); + + if buf.len().saturating_add(write_start) > len { + return Err(Error::BlobSizeError); + } + // We know `len` fits in an `i32`, so either: + // + // 1. `buf.len() + write_start` overflows, in which case we'd hit the + // return above (courtesy of `saturating_add`). + // + // 2. `buf.len() + write_start` doesn't overflow but is larger than len, + // in which case ditto. + // + // 3. `buf.len() + write_start` doesn't overflow but is less than len. + // This means that both `buf.len()` and `write_start` can also be + // losslessly converted to i32, since `len` came from an i32. + // Sanity check the above. + debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok()); + self.conn.decode_result(unsafe { + ffi::sqlite3_blob_write( + self.blob, + buf.as_ptr().cast(), + buf.len() as i32, + write_start as i32, + ) + }) + } + + /// An alias for `write_at` provided for compatibility with the conceptually + /// equivalent [`std::os::unix::FileExt::write_all_at`][write_all_at] + /// function from libstd: + /// + /// [write_all_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#method.write_all_at + #[inline] + pub fn write_all_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> { + self.write_at(buf, write_start) + } + + /// Read as much as possible from `offset` to `offset + buf.len()` out of + /// `self`, writing into `buf`. On success, returns the number of bytes + /// written. + /// + /// If there's insufficient data in `self`, then the returned value will be + /// less than `buf.len()`. + /// + /// See also [`Blob::raw_read_at`], which can take an uninitialized buffer, + /// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is + /// not read. + /// + /// Note: This is part of the positional I/O API, and thus takes an absolute + /// position to read from, instead of using the internal position that can + /// be manipulated by the `std::io` traits. Consequently, it does not change + /// that value either. + #[inline] + pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result { + // Safety: this is safe because `raw_read_at` never stores uninitialized + // data into `as_uninit`. + let as_uninit: &mut [MaybeUninit] = + unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) }; + self.raw_read_at(as_uninit, read_start).map(|s| s.len()) + } + + /// Read as much as possible from `offset` to `offset + buf.len()` out of + /// `self`, writing into `buf`. On success, returns the portion of `buf` + /// which was initialized by this call. + /// + /// If there's insufficient data in `self`, then the returned value will be + /// shorter than `buf`. + /// + /// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of + /// a slice of `MaybeUninit`. + /// + /// Note: This is part of the positional I/O API, and thus takes an absolute + /// position to read from, instead of using the internal position that can + /// be manipulated by the `std::io` traits. Consequently, it does not change + /// that value either. + #[inline] + pub fn raw_read_at<'a>( + &self, + buf: &'a mut [MaybeUninit], + read_start: usize, + ) -> Result<&'a mut [u8]> { + let len = self.len(); + + let read_len = match len.checked_sub(read_start) { + None | Some(0) => 0, + Some(v) => v.min(buf.len()), + }; + + if read_len == 0 { + // We could return `Ok(&mut [])`, but it seems confusing that the + // pointers don't match, so fabricate an empty slice of u8 with the + // same base pointer as `buf`. + let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast::(), 0) }; + return Ok(empty); + } + + // At this point we believe `read_start as i32` is lossless because: + // + // 1. `len as i32` is known to be lossless, since it comes from a SQLite + // api returning an i32. + // + // 2. If we got here, `len.checked_sub(read_start)` was Some (or else + // we'd have hit the `if read_len == 0` early return), so `len` must + // be larger than `read_start`, and so it must fit in i32 as well. + debug_assert!(i32::try_from(read_start).is_ok()); + + // We also believe that `read_start + read_len <= len` because: + // + // 1. This is equivalent to `read_len <= len - read_start` via algebra. + // 2. We know that `read_len` is `min(len - read_start, buf.len())` + // 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`, + // or `min(A, B) <= A` which is clearly true. + // + // Note that this stuff is in debug_assert so no need to use checked_add + // and such -- we'll always panic on overflow in debug builds. + debug_assert!(read_start + read_len <= len); + + // These follow naturally. + debug_assert!(buf.len() >= read_len); + debug_assert!(i32::try_from(buf.len()).is_ok()); + debug_assert!(i32::try_from(read_len).is_ok()); + + unsafe { + self.conn.decode_result(ffi::sqlite3_blob_read( + self.blob, + buf.as_mut_ptr().cast(), + read_len as i32, + read_start as i32, + ))?; + + Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::(), read_len)) + } + } + + /// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf` + /// is not fully initialized. + #[inline] + pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> { + let n = self.read_at(buf, read_start)?; + if n != buf.len() { + Err(Error::BlobSizeError) + } else { + Ok(()) + } + } + + /// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if + /// `buf` is not fully initialized. + #[inline] + pub fn raw_read_at_exact<'a>( + &self, + buf: &'a mut [MaybeUninit], + read_start: usize, + ) -> Result<&'a mut [u8]> { + let buflen = buf.len(); + let initted = self.raw_read_at(buf, read_start)?; + if initted.len() != buflen { + Err(Error::BlobSizeError) + } else { + Ok(initted) + } + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result, MAIN_DB}; + // to ensure we don't modify seek pos + use std::io::Seek as _; + + #[test] + fn test_pos_io() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE test_table(content BLOB);")?; + db.execute("INSERT INTO test_table(content) VALUES (ZEROBLOB(10))", [])?; + + let rowid = db.last_insert_rowid(); + let mut blob = db.blob_open(MAIN_DB, c"test_table", c"content", rowid, false)?; + // modify the seek pos to ensure we aren't using it or modifying it. + blob.seek(std::io::SeekFrom::Start(1)).unwrap(); + + let one2ten: [u8; 10] = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + blob.write_at(&one2ten, 0)?; + + let mut s = [0u8; 10]; + blob.read_at_exact(&mut s, 0)?; + assert_eq!(&s, &one2ten, "write should go through"); + blob.read_at_exact(&mut s, 1).unwrap_err(); + + blob.read_at_exact(&mut s, 0)?; + assert_eq!(&s, &one2ten, "should be unchanged"); + + let mut fives = [0u8; 5]; + blob.read_at_exact(&mut fives, 0)?; + assert_eq!(&fives, &[1u8, 2, 3, 4, 5]); + + blob.read_at_exact(&mut fives, 5)?; + assert_eq!(&fives, &[6u8, 7, 8, 9, 10]); + blob.read_at_exact(&mut fives, 7).unwrap_err(); + blob.read_at_exact(&mut fives, 12).unwrap_err(); + blob.read_at_exact(&mut fives, 10).unwrap_err(); + blob.read_at_exact(&mut fives, i32::MAX as usize) + .unwrap_err(); + blob.read_at_exact(&mut fives, i32::MAX as usize + 1) + .unwrap_err(); + + // zero length writes are fine if in bounds + blob.read_at_exact(&mut [], 10)?; + blob.read_at_exact(&mut [], 0)?; + blob.read_at_exact(&mut [], 5)?; + + blob.write_all_at(&[16, 17, 18, 19, 20], 5)?; + blob.read_at_exact(&mut s, 0)?; + assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]); + + blob.write_at(&[100, 99, 98, 97, 96], 6).unwrap_err(); + blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize) + .unwrap_err(); + blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1) + .unwrap_err(); + + blob.read_at_exact(&mut s, 0)?; + assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]); + + let mut s2: [std::mem::MaybeUninit; 10] = [std::mem::MaybeUninit::uninit(); 10]; + { + let read = blob.raw_read_at_exact(&mut s2, 0)?; + assert_eq!(read, &s); + assert!(std::ptr::eq(read.as_ptr(), s2.as_ptr().cast())); + } + + let mut empty = []; + assert!(std::ptr::eq( + blob.raw_read_at_exact(&mut empty, 0)?.as_ptr(), + empty.as_ptr().cast(), + )); + blob.raw_read_at_exact(&mut s2, 5).unwrap_err(); + + let end_pos = blob.stream_position().unwrap(); + assert_eq!(end_pos, 1); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/busy.rs b/vendor/rusqlite/src/busy.rs new file mode 100644 index 0000000..639efa6 --- /dev/null +++ b/vendor/rusqlite/src/busy.rs @@ -0,0 +1,138 @@ +//! Busy handler (when the database is locked) +use std::ffi::{c_int, c_void}; +use std::mem; +use std::panic::catch_unwind; +use std::ptr; +use std::time::Duration; + +use crate::ffi; +use crate::{Connection, InnerConnection, Result}; + +impl Connection { + /// Set a busy handler that sleeps for a specified amount of time when a + /// table is locked. The handler will sleep multiple times until at + /// least "ms" milliseconds of sleeping have accumulated. + /// + /// Calling this routine with an argument equal to zero turns off all busy + /// handlers. + /// + /// There can only be a single busy handler for a particular database + /// connection at any given moment. If another busy handler was defined + /// (using [`busy_handler`](Connection::busy_handler)) prior to calling this + /// routine, that other busy handler is cleared. + /// + /// Newly created connections currently have a default busy timeout of + /// 5000ms, but this may be subject to change. + pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { + let ms: i32 = timeout + .as_secs() + .checked_mul(1000) + .and_then(|t| t.checked_add(timeout.subsec_millis().into())) + .and_then(|t| t.try_into().ok()) + .expect("too big"); + self.db.borrow_mut().busy_timeout(ms) + } + + /// Register a callback to handle `SQLITE_BUSY` errors. + /// + /// If the busy callback is `None`, then `SQLITE_BUSY` is returned + /// immediately upon encountering the lock. The argument to the busy + /// handler callback is the number of times that the + /// busy handler has been invoked previously for the + /// same locking event. If the busy callback returns `false`, then no + /// additional attempts are made to access the + /// database and `SQLITE_BUSY` is returned to the + /// application. If the callback returns `true`, then another attempt + /// is made to access the database and the cycle repeats. + /// + /// There can only be a single busy handler defined for each database + /// connection. Setting a new busy handler clears any previously set + /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout) + /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler + /// and thus clear any previously set busy handler. + /// + /// Newly created connections default to a + /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout + /// of 5000ms, although this is subject to change. + pub fn busy_handler(&self, callback: Option bool>) -> Result<()> { + unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int { + let handler_fn: fn(i32) -> bool = mem::transmute(p_arg); + c_int::from(catch_unwind(|| handler_fn(count)).unwrap_or_default()) + } + let c = self.db.borrow_mut(); + c.decode_result(unsafe { + ffi::sqlite3_busy_handler( + c.db(), + callback.as_ref().map(|_| busy_handler_callback as _), + callback.map_or_else(ptr::null_mut, |f| f as *mut c_void), + ) + }) + } +} + +impl InnerConnection { + #[inline] + fn busy_timeout(&mut self, timeout: c_int) -> Result<()> { + let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) }; + self.decode_result(r) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, ErrorCode, Result, TransactionBehavior}; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn test_default_busy() -> Result<()> { + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("test.db3"); + + let mut db1 = Connection::open(&path)?; + let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?; + let db2 = Connection::open(&path)?; + let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!()); + assert_eq!( + r.unwrap_err().sqlite_error_code(), + Some(ErrorCode::DatabaseBusy) + ); + tx1.rollback() + } + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn test_busy_handler() -> Result<()> { + static CALLED: AtomicBool = AtomicBool::new(false); + fn busy_handler(n: i32) -> bool { + if n > 2 { + false + } else { + CALLED.swap(true, Ordering::Relaxed) + } + } + + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("busy-handler.db3"); + + let db1 = Connection::open(&path)?; + db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?; + let db2 = Connection::open(&path)?; + db2.busy_handler(Some(busy_handler))?; + db1.execute_batch("BEGIN EXCLUSIVE")?; + let err = db2.prepare("SELECT * FROM t").unwrap_err(); + assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy)); + assert!(CALLED.load(Ordering::Relaxed)); + db1.busy_handler(None)?; + Ok(()) + } +} diff --git a/vendor/rusqlite/src/cache.rs b/vendor/rusqlite/src/cache.rs new file mode 100644 index 0000000..15c08ea --- /dev/null +++ b/vendor/rusqlite/src/cache.rs @@ -0,0 +1,351 @@ +//! Prepared statements cache for faster execution. + +use crate::raw_statement::RawStatement; +use crate::{Connection, PrepFlags, Result, Statement}; +use hashlink::LruCache; +use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +impl Connection { + /// Prepare a SQL statement for execution, returning a previously prepared + /// (but not currently in-use) statement if one is available. The + /// returned statement will be cached for reuse by future calls to + /// [`prepare_cached`](Connection::prepare_cached) once it is dropped. + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn insert_new_people(conn: &Connection) -> Result<()> { + /// { + /// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?; + /// stmt.execute(["Joe Smith"])?; + /// } + /// { + /// // This will return the same underlying SQLite statement handle without + /// // having to prepare it again. + /// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?; + /// stmt.execute(["Bob Jones"])?; + /// } + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn prepare_cached(&self, sql: &str) -> Result> { + self.cache.get(self, sql) + } + + /// Set the maximum number of cached prepared statements this connection + /// will hold. By default, a connection will hold a relatively small + /// number of cached statements. If you need more, or know that you + /// will not use cached statements, you + /// can set the capacity manually using this method. + #[inline] + pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) { + self.cache.set_capacity(capacity); + } + + /// Remove/finalize all prepared statements currently in the cache. + #[inline] + pub fn flush_prepared_statement_cache(&self) { + self.cache.flush(); + } +} + +/// Prepared statements LRU cache. +#[derive(Debug)] +pub struct StatementCache(RefCell, RawStatement>>); + +unsafe impl Send for StatementCache {} + +/// Cacheable statement. +/// +/// Statement will return automatically to the cache by default. +/// If you want the statement to be discarded, call +/// [`discard()`](CachedStatement::discard) on it. +pub struct CachedStatement<'conn> { + stmt: Option>, + cache: &'conn StatementCache, +} + +impl<'conn> Deref for CachedStatement<'conn> { + type Target = Statement<'conn>; + + #[inline] + fn deref(&self) -> &Statement<'conn> { + self.stmt.as_ref().unwrap() + } +} + +impl<'conn> DerefMut for CachedStatement<'conn> { + #[inline] + fn deref_mut(&mut self) -> &mut Statement<'conn> { + self.stmt.as_mut().unwrap() + } +} + +impl Drop for CachedStatement<'_> { + #[inline] + fn drop(&mut self) { + if let Some(stmt) = self.stmt.take() { + self.cache.cache_stmt(unsafe { stmt.into_raw() }); + } + } +} + +impl CachedStatement<'_> { + #[inline] + fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> { + CachedStatement { + stmt: Some(stmt), + cache, + } + } + + /// Discard the statement, preventing it from being returned to its + /// [`Connection`]'s collection of cached statements. + #[inline] + pub fn discard(mut self) { + self.stmt = None; + } +} + +impl StatementCache { + /// Create a statement cache. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self(RefCell::new(LruCache::new(capacity))) + } + + #[inline] + fn set_capacity(&self, capacity: usize) { + self.0.borrow_mut().set_capacity(capacity); + } + + // Search the cache for a prepared-statement object that implements `sql`. + // If no such prepared-statement can be found, allocate and prepare a new one. + // + // # Failure + // + // Will return `Err` if no cached statement can be found and the underlying + // SQLite prepare call fails. + fn get<'conn>( + &'conn self, + conn: &'conn Connection, + sql: &str, + ) -> Result> { + let trimmed = sql.trim(); + let mut cache = self.0.borrow_mut(); + let stmt = match cache.remove(trimmed) { + Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)), + None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT), + }; + stmt.map(|mut stmt| { + stmt.stmt.set_statement_cache_key(trimmed); + CachedStatement::new(stmt, self) + }) + } + + // Return a statement to the cache. + fn cache_stmt(&self, mut stmt: RawStatement) { + if stmt.is_null() { + return; + } + let mut cache = self.0.borrow_mut(); + stmt.clear_bindings(); + if let Some(sql) = stmt.statement_cache_key() { + cache.insert(sql, stmt); + } else { + debug_assert!( + false, + "bug in statement cache code, statement returned to cache that without key" + ); + } + } + + #[inline] + fn flush(&self) { + let mut cache = self.0.borrow_mut(); + cache.clear(); + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::StatementCache; + use crate::{Connection, Result}; + use fallible_iterator::FallibleIterator; + + impl StatementCache { + fn clear(&self) { + self.0.borrow_mut().clear(); + } + + fn len(&self) -> usize { + self.0.borrow().len() + } + + fn capacity(&self) -> usize { + self.0.borrow().capacity() + } + } + + #[test] + fn test_cache() -> Result<()> { + let db = Connection::open_in_memory()?; + let cache = &db.cache; + let initial_capacity = cache.capacity(); + assert_eq!(0, cache.len()); + assert!(initial_capacity > 0); + + let sql = "PRAGMA schema_version"; + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + + cache.clear(); + assert_eq!(0, cache.len()); + assert_eq!(initial_capacity, cache.capacity()); + Ok(()) + } + + #[test] + fn test_set_capacity() -> Result<()> { + let db = Connection::open_in_memory()?; + let cache = &db.cache; + + let sql = "PRAGMA schema_version"; + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + + db.set_prepared_statement_cache_capacity(0); + assert_eq!(0, cache.len()); + + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(0, cache.len()); + + db.set_prepared_statement_cache_capacity(8); + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + Ok(()) + } + + #[test] + fn test_discard() -> Result<()> { + let db = Connection::open_in_memory()?; + let cache = &db.cache; + + let sql = "PRAGMA schema_version"; + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + stmt.discard(); + } + assert_eq!(0, cache.len()); + Ok(()) + } + + #[test] + fn test_ddl() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch( + r" + CREATE TABLE foo (x INT); + INSERT INTO foo VALUES (1); + ", + )?; + + let sql = "SELECT * FROM foo"; + + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next()); + } + + db.execute_batch( + r" + ALTER TABLE foo ADD COLUMN y INT; + UPDATE foo SET y = 2; + ", + )?; + + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!( + Ok(Some((1i32, 2i32))), + stmt.query([])?.map(|r| Ok((r.get(0)?, r.get(1)?))).next() + ); + } + Ok(()) + } + + #[test] + fn test_connection_close() -> Result<()> { + let conn = Connection::open_in_memory()?; + conn.prepare_cached("SELECT * FROM sqlite_master;")?; + + conn.close().expect("connection not closed"); + Ok(()) + } + + #[test] + fn test_cache_key() -> Result<()> { + let db = Connection::open_in_memory()?; + let cache = &db.cache; + assert_eq!(0, cache.len()); + + //let sql = " PRAGMA schema_version; -- comment"; + let sql = "PRAGMA schema_version; "; + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + + { + let mut stmt = db.prepare_cached(sql)?; + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?); + } + assert_eq!(1, cache.len()); + Ok(()) + } + + #[test] + fn test_empty_stmt() -> Result<()> { + let conn = Connection::open_in_memory()?; + conn.prepare_cached("")?; + Ok(()) + } +} diff --git a/vendor/rusqlite/src/collation.rs b/vendor/rusqlite/src/collation.rs new file mode 100644 index 0000000..6189e78 --- /dev/null +++ b/vendor/rusqlite/src/collation.rs @@ -0,0 +1,236 @@ +//! Add, remove, or modify a collation +use std::cmp::Ordering; +use std::ffi::{c_char, c_int, c_void, CStr}; +use std::panic::catch_unwind; +use std::ptr; +use std::slice; + +use crate::ffi; +use crate::util::free_boxed_value; +use crate::{Connection, InnerConnection, Name, Result}; + +impl Connection { + /// Add or modify a collation. + #[inline] + pub fn create_collation(&self, collation_name: N, x_compare: C) -> Result<()> + where + C: Fn(&str, &str) -> Ordering + Send + 'static, + { + self.db + .borrow_mut() + .create_collation(collation_name, x_compare) + } + + /// Collation needed callback + #[inline] + pub fn collation_needed(&self, x_coll_needed: fn(&Self, &str) -> Result<()>) -> Result<()> { + self.db.borrow_mut().collation_needed(x_coll_needed) + } + + /// Remove collation. + #[inline] + pub fn remove_collation(&self, collation_name: N) -> Result<()> { + self.db.borrow_mut().remove_collation(collation_name) + } +} + +impl InnerConnection { + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.create_collation("foo", |_, _| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// std::cmp::Ordering::Equal + /// })?; + /// } + /// let value: String = db.query_row( + /// "WITH cte(bar) AS + /// (VALUES ('v1'),('v2'),('v3'),('v4'),('v5')) + /// SELECT DISTINCT bar COLLATE foo FROM cte;", + /// [], + /// |row| row.get(0), + /// )?; + /// assert_eq!(value, "v1"); + /// Ok(()) + /// } + /// ``` + fn create_collation(&mut self, collation_name: N, x_compare: C) -> Result<()> + where + C: Fn(&str, &str) -> Ordering + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure( + arg1: *mut c_void, + arg2: c_int, + arg3: *const c_void, + arg4: c_int, + arg5: *const c_void, + ) -> c_int + where + C: Fn(&str, &str) -> Ordering, + { + let r = catch_unwind(|| { + let boxed_f: *mut C = arg1.cast::(); + assert!(!boxed_f.is_null(), "Internal error - null function pointer"); + let s1 = { + let c_slice = slice::from_raw_parts(arg3.cast::(), arg2 as usize); + String::from_utf8_lossy(c_slice) + }; + let s2 = { + let c_slice = slice::from_raw_parts(arg5.cast::(), arg4 as usize); + String::from_utf8_lossy(c_slice) + }; + (*boxed_f)(s1.as_ref(), s2.as_ref()) + }); + let t = match r { + Err(_) => { + return -1; // FIXME How ? + } + Ok(r) => r, + }; + + match t { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + } + } + + let boxed_f: *mut C = Box::into_raw(Box::new(x_compare)); + let c_name = collation_name.as_cstr()?; + let flags = ffi::SQLITE_UTF8; + let r = unsafe { + ffi::sqlite3_create_collation_v2( + self.db(), + c_name.as_ptr(), + flags, + boxed_f.cast::(), + Some(call_boxed_closure::), + Some(free_boxed_value::), + ) + }; + let res = self.decode_result(r); + // The xDestroy callback is not called if the sqlite3_create_collation_v2() + // function fails. + if res.is_err() { + drop(unsafe { Box::from_raw(boxed_f) }); + } + res + } + + fn collation_needed( + &mut self, + x_coll_needed: fn(&Connection, &str) -> Result<()>, + ) -> Result<()> { + use std::mem; + #[expect(clippy::needless_return)] + unsafe extern "C" fn collation_needed_callback( + arg1: *mut c_void, + arg2: *mut ffi::sqlite3, + e_text_rep: c_int, + arg3: *const c_char, + ) { + use std::str; + + if e_text_rep != ffi::SQLITE_UTF8 { + // TODO: validate + return; + } + + let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1); + let res = catch_unwind(|| { + let conn = Connection::from_handle(arg2).unwrap(); + let collation_name = CStr::from_ptr(arg3) + .to_str() + .expect("illegal collation sequence name"); + callback(&conn, collation_name) + }); + if res.is_err() { + return; // FIXME How ? + } + } + + let r = unsafe { + ffi::sqlite3_collation_needed( + self.db(), + x_coll_needed as *mut c_void, + Some(collation_needed_callback), + ) + }; + self.decode_result(r) + } + + #[inline] + fn remove_collation(&mut self, collation_name: N) -> Result<()> { + let c_name = collation_name.as_cstr()?; + let r = unsafe { + ffi::sqlite3_create_collation_v2( + self.db(), + c_name.as_ptr(), + ffi::SQLITE_UTF8, + ptr::null_mut(), + None, + None, + ) + }; + self.decode_result(r) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result}; + use fallible_streaming_iterator::FallibleStreamingIterator; + use std::cmp::Ordering; + use unicase::UniCase; + + fn unicase_compare(s1: &str, s2: &str) -> Ordering { + UniCase::new(s1).cmp(&UniCase::new(s2)) + } + + #[test] + fn test_unicase() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_collation(c"unicase", unicase_compare)?; + collate(db) + } + + fn collate(db: Connection) -> Result<()> { + db.execute_batch( + "CREATE TABLE foo (bar); + INSERT INTO foo (bar) VALUES ('Maße'); + INSERT INTO foo (bar) VALUES ('MASSE');", + )?; + let mut stmt = db.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")?; + let rows = stmt.query([])?; + assert_eq!(rows.count()?, 1); + Ok(()) + } + + fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> { + if "unicase" == collation_name { + db.create_collation(collation_name, unicase_compare) + } else { + Ok(()) + } + } + + #[test] + fn test_collation_needed() -> Result<()> { + let db = Connection::open_in_memory()?; + db.collation_needed(collation_needed)?; + collate(db) + } + + #[test] + fn remove_collation() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_collation(c"unicase", unicase_compare)?; + db.remove_collation(c"unicase") + } +} diff --git a/vendor/rusqlite/src/column.rs b/vendor/rusqlite/src/column.rs new file mode 100644 index 0000000..2ca11cb --- /dev/null +++ b/vendor/rusqlite/src/column.rs @@ -0,0 +1,576 @@ +use std::ffi::{c_char, CStr}; +use std::ptr; +use std::str; + +use crate::ffi; +use crate::{Connection, Error, Name, Result, Statement}; + +/// Information about a column of a SQLite query. +#[cfg(feature = "column_decltype")] +#[derive(Debug)] +pub struct Column<'stmt> { + name: &'stmt str, + decl_type: Option<&'stmt str>, +} + +#[cfg(feature = "column_decltype")] +impl Column<'_> { + /// Returns the name of the column. + #[inline] + #[must_use] + pub fn name(&self) -> &str { + self.name + } + + /// Returns the type of the column (`None` for expression). + #[inline] + #[must_use] + pub fn decl_type(&self) -> Option<&str> { + self.decl_type + } +} + +/// Metadata about the origin of a column of a SQLite query +#[cfg(feature = "column_metadata")] +#[derive(Debug)] +pub struct ColumnMetadata<'stmt> { + name: &'stmt str, + database_name: Option<&'stmt str>, + table_name: Option<&'stmt str>, + origin_name: Option<&'stmt str>, +} + +#[cfg(feature = "column_metadata")] +impl ColumnMetadata<'_> { + #[inline] + #[must_use] + /// Returns the name of the column in the query results + pub fn name(&self) -> &str { + self.name + } + + #[inline] + #[must_use] + /// Returns the database name from which the column originates + pub fn database_name(&self) -> Option<&str> { + self.database_name + } + + #[inline] + #[must_use] + /// Returns the table name from which the column originates + pub fn table_name(&self) -> Option<&str> { + self.table_name + } + + #[inline] + #[must_use] + /// Returns the column name from which the column originates + pub fn origin_name(&self) -> Option<&str> { + self.origin_name + } +} + +impl Statement<'_> { + /// Get all the column names in the result set of the prepared statement. + /// + /// If associated DB schema can be altered concurrently, you should make + /// sure that current statement has already been stepped once before + /// calling this method. + pub fn column_names(&self) -> Vec<&str> { + let n = self.column_count(); + let mut cols = Vec::with_capacity(n); + for i in 0..n { + let s = self.column_name_unwrap(i); + cols.push(s); + } + cols + } + + /// Return the number of columns in the result set returned by the prepared + /// statement. + /// + /// If associated DB schema can be altered concurrently, you should make + /// sure that current statement has already been stepped once before + /// calling this method. + #[inline] + pub fn column_count(&self) -> usize { + self.stmt.column_count() + } + + /// Check that column name reference lifetime is limited: + /// + /// > The returned string pointer is valid... + /// + /// `column_name` reference can become invalid if `stmt` is reprepared + /// (because of schema change) when `query_row` is called. So we assert + /// that a compilation error happens if this reference is kept alive: + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// let mut stmt = db.prepare("SELECT 1 as x")?; + /// let column_name = stmt.column_name(0)?; + /// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502 + /// assert_eq!(1, x); + /// assert_eq!("x", column_name); + /// Ok(()) + /// } + /// ``` + #[inline] + pub(super) fn column_name_unwrap(&self, col: usize) -> &str { + // Just panic if the bounds are wrong for now, we never call this + // without checking first. + self.column_name(col).expect("Column out of bounds") + } + + /// Returns the name assigned to a particular column in the result set + /// returned by the prepared statement. + /// + /// If associated DB schema can be altered concurrently, you should make + /// sure that current statement has already been stepped once before + /// calling this method. + /// + /// ## Failure + /// + /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid + /// column range for this row. + /// + /// # Panics + /// + /// Panics when column name is not valid UTF-8. + #[inline] + pub fn column_name(&self, col: usize) -> Result<&str> { + self.stmt + .column_name(col) + // clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable) + .ok_or(Error::InvalidColumnIndex(col)) + .map(|slice| { + slice + .to_str() + .expect("Invalid UTF-8 sequence in column name") + }) + } + + /// Returns the column index in the result set for a given column name. + /// + /// If there is no AS clause then the name of the column is unspecified and + /// may change from one release of SQLite to the next. + /// + /// If associated DB schema can be altered concurrently, you should make + /// sure that current statement has already been stepped once before + /// calling this method. + /// + /// # Failure + /// + /// Will return an `Error::InvalidColumnName` when there is no column with + /// the specified `name`. + #[inline] + pub fn column_index(&self, name: &str) -> Result { + let bytes = name.as_bytes(); + let n = self.column_count(); + for i in 0..n { + // Note: `column_name` is only fallible if `i` is out of bounds, + // which we've already checked. + if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) { + return Ok(i); + } + } + Err(Error::InvalidColumnName(String::from(name))) + } + + /// Returns a slice describing the columns of the result of the query. + /// + /// If associated DB schema can be altered concurrently, you should make + /// sure that current statement has already been stepped once before + /// calling this method. + #[cfg(feature = "column_decltype")] + pub fn columns(&self) -> Vec> { + let n = self.column_count(); + let mut cols = Vec::with_capacity(n); + for i in 0..n { + let name = self.column_name_unwrap(i); + let slice = self.stmt.column_decltype(i); + let decl_type = slice.map(|s| { + s.to_str() + .expect("Invalid UTF-8 sequence in column declaration") + }); + cols.push(Column { name, decl_type }); + } + cols + } + + /// Returns the names of the database, table, and row from which + /// each column of this query's results originate. + /// + /// Computed or otherwise derived columns will have None values for these fields. + #[cfg(feature = "column_metadata")] + pub fn columns_with_metadata(&self) -> Vec> { + let n = self.column_count(); + let mut col_mets = Vec::with_capacity(n); + for i in 0..n { + let name = self.column_name_unwrap(i); + let db_slice = self.stmt.column_database_name(i); + let tbl_slice = self.stmt.column_table_name(i); + let origin_slice = self.stmt.column_origin_name(i); + col_mets.push(ColumnMetadata { + name, + database_name: db_slice.map(|s| { + s.to_str() + .expect("Invalid UTF-8 sequence in column db name") + }), + table_name: tbl_slice.map(|s| { + s.to_str() + .expect("Invalid UTF-8 sequence in column table name") + }), + origin_name: origin_slice.map(|s| { + s.to_str() + .expect("Invalid UTF-8 sequence in column origin name") + }), + }) + } + col_mets + } + + /// Extract metadata of column at specified index + /// + /// Returns: + /// - database name + /// - table name + /// - original column name + /// - declared data type + /// - name of default collation sequence + /// - True if column has a NOT NULL constraint + /// - True if column is part of the PRIMARY KEY + /// - True if column is AUTOINCREMENT + /// + /// See [Connection::column_metadata] + #[cfg(feature = "column_metadata")] + #[expect(clippy::type_complexity)] + pub fn column_metadata( + &self, + col: usize, + ) -> Result< + Option<( + &CStr, + &CStr, + &CStr, + Option<&CStr>, + Option<&CStr>, + bool, + bool, + bool, + )>, + > { + let db_name = self.stmt.column_database_name(col); + let table_name = self.stmt.column_table_name(col); + let origin_name = self.stmt.column_origin_name(col); + if db_name.is_none() || table_name.is_none() || origin_name.is_none() { + return Ok(None); + } + let (data_type, coll_seq, not_null, primary_key, auto_inc) = + self.conn + .column_metadata(db_name, table_name.unwrap(), origin_name.unwrap())?; + Ok(Some(( + db_name.unwrap(), + table_name.unwrap(), + origin_name.unwrap(), + data_type, + coll_seq, + not_null, + primary_key, + auto_inc, + ))) + } +} + +impl Connection { + /// Check if `table_name`.`column_name` exists. + /// + /// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases. + pub fn column_exists( + &self, + db_name: Option, + table_name: N, + column_name: N, + ) -> Result { + self.exists(db_name, table_name, Some(column_name)) + } + + /// Check if `table_name` exists. + /// + /// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases. + pub fn table_exists(&self, db_name: Option, table_name: N) -> Result { + self.exists(db_name, table_name, None) + } + + /// Extract metadata of column at specified index + /// + /// Returns: + /// - declared data type + /// - name of default collation sequence + /// - True if column has a NOT NULL constraint + /// - True if column is part of the PRIMARY KEY + /// - True if column is AUTOINCREMENT + #[expect(clippy::type_complexity)] + pub fn column_metadata( + &self, + db_name: Option, + table_name: N, + column_name: N, + ) -> Result<(Option<&CStr>, Option<&CStr>, bool, bool, bool)> { + let cs = db_name.as_ref().map(N::as_cstr).transpose()?; + let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let table_name = table_name.as_cstr()?; + let column_name = column_name.as_cstr()?; + + let mut data_type: *const c_char = ptr::null_mut(); + let mut coll_seq: *const c_char = ptr::null_mut(); + let mut not_null = 0; + let mut primary_key = 0; + let mut auto_inc = 0; + + self.decode_result(unsafe { + ffi::sqlite3_table_column_metadata( + self.handle(), + db_name, + table_name.as_ptr(), + column_name.as_ptr(), + &mut data_type, + &mut coll_seq, + &mut not_null, + &mut primary_key, + &mut auto_inc, + ) + })?; + + Ok(( + if data_type.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(data_type) }) + }, + if coll_seq.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(coll_seq) }) + }, + not_null != 0, + primary_key != 0, + auto_inc != 0, + )) + } + + fn exists( + &self, + db_name: Option, + table_name: N, + column_name: Option, + ) -> Result { + let cs = db_name.as_ref().map(N::as_cstr).transpose()?; + let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let table_name = table_name.as_cstr()?; + let cn = column_name.as_ref().map(N::as_cstr).transpose()?; + let column_name = cn.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let r = unsafe { + ffi::sqlite3_table_column_metadata( + self.handle(), + db_name, + table_name.as_ptr(), + column_name, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + }; + match r { + ffi::SQLITE_OK => Ok(true), + ffi::SQLITE_ERROR => Ok(false), + _ => self.db.borrow().decode_result(r).map(|_| false), + } + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result}; + + #[test] + #[cfg(feature = "column_decltype")] + fn test_columns() -> Result<()> { + use super::Column; + + let db = Connection::open_in_memory()?; + let query = db.prepare("SELECT * FROM sqlite_master")?; + let columns = query.columns(); + let column_names: Vec<&str> = columns.iter().map(Column::name).collect(); + assert_eq!( + column_names.as_slice(), + &["type", "name", "tbl_name", "rootpage", "sql"] + ); + let column_types: Vec> = columns + .iter() + .map(|col| col.decl_type().map(str::to_lowercase)) + .collect(); + assert_eq!( + &column_types[..3], + &[ + Some("text".to_owned()), + Some("text".to_owned()), + Some("text".to_owned()), + ] + ); + Ok(()) + } + + #[test] + #[cfg(feature = "column_metadata")] + fn test_columns_with_metadata() -> Result<()> { + let db = Connection::open_in_memory()?; + let query = db.prepare("SELECT *, 1 FROM sqlite_master")?; + + let col_mets = query.columns_with_metadata(); + + assert_eq!(col_mets.len(), 6); + + for col in col_mets.iter().take(5) { + assert_eq!(&col.database_name(), &Some("main")); + assert_eq!(&col.table_name(), &Some("sqlite_master")); + } + + assert!(col_mets[5].database_name().is_none()); + assert!(col_mets[5].table_name().is_none()); + assert!(col_mets[5].origin_name().is_none()); + + let col_origins: Vec> = col_mets.iter().map(|col| col.origin_name()).collect(); + + assert_eq!( + &col_origins[..5], + &[ + Some("type"), + Some("name"), + Some("tbl_name"), + Some("rootpage"), + Some("sql"), + ] + ); + + Ok(()) + } + + #[test] + fn test_column_name_in_error() -> Result<()> { + use crate::{types::Type, Error}; + let db = Connection::open_in_memory()?; + db.execute_batch( + "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, NULL); + END;", + )?; + let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?; + let mut rows = stmt.query([])?; + let row = rows.next()?.unwrap(); + match row.get::<_, String>(0).unwrap_err() { + Error::InvalidColumnType(idx, name, ty) => { + assert_eq!(idx, 0); + assert_eq!(name, "renamed"); + assert_eq!(ty, Type::Integer); + } + e => { + panic!("Unexpected error type: {e:?}"); + } + } + match row.get::<_, String>("y").unwrap_err() { + Error::InvalidColumnType(idx, name, ty) => { + assert_eq!(idx, 1); + assert_eq!(name, "y"); + assert_eq!(ty, Type::Null); + } + e => { + panic!("Unexpected error type: {e:?}"); + } + } + Ok(()) + } + + /// `column_name` reference should stay valid until `stmt` is reprepared (or + /// reset) even if DB schema is altered (SQLite documentation is + /// ambiguous here because it says reference "is valid until (...) the next + /// call to `sqlite3_column_name()` or `sqlite3_column_name16()` on the same + /// column.". We assume that reference is valid if only + /// `sqlite3_column_name()` is used): + #[test] + #[cfg(feature = "modern_sqlite")] + fn test_column_name_reference() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE y (x);")?; + let stmt = db.prepare("SELECT x FROM y;")?; + let column_name = stmt.column_name(0)?; + assert_eq!("x", column_name); + db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?; + // column name is not refreshed until statement is re-prepared + let same_column_name = stmt.column_name(0)?; + assert_eq!(same_column_name, column_name); + Ok(()) + } + + #[test] + #[cfg(feature = "column_metadata")] + fn stmt_column_metadata() -> Result<()> { + let db = Connection::open_in_memory()?; + let query = db.prepare("SELECT *, 1 FROM sqlite_master")?; + let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) = + query.column_metadata(0)?.unwrap(); + assert_eq!(db_name, crate::MAIN_DB); + assert_eq!(table_name, c"sqlite_master"); + assert_eq!(col_name, c"type"); + assert_eq!(data_type, Some(c"TEXT")); + assert_eq!(coll_seq, Some(c"BINARY")); + assert!(!not_null); + assert!(!primary_key); + assert!(!auto_inc); + assert!(query.column_metadata(5)?.is_none()); + Ok(()) + } + + #[test] + fn column_exists() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(db.column_exists(None, c"sqlite_master", c"type")?); + assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_master", c"type")?); + assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_master", c"type")?); + Ok(()) + } + + #[test] + fn table_exists() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(db.table_exists(None, c"sqlite_master")?); + assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_master")?); + assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_master")?); + Ok(()) + } + + #[test] + fn column_metadata() -> Result<()> { + let db = Connection::open_in_memory()?; + let (data_type, coll_seq, not_null, primary_key, auto_inc) = + db.column_metadata(None, c"sqlite_master", c"type")?; + assert_eq!( + data_type.map(|cs| cs.to_str().unwrap().to_ascii_uppercase()), + Some("TEXT".to_owned()) + ); + assert_eq!(coll_seq, Some(c"BINARY")); + assert!(!not_null); + assert!(!primary_key); + assert!(!auto_inc); + assert!(db.column_metadata(None, c"sqlite_master", c"foo").is_err()); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/config.rs b/vendor/rusqlite/src/config.rs new file mode 100644 index 0000000..b7905f0 --- /dev/null +++ b/vendor/rusqlite/src/config.rs @@ -0,0 +1,169 @@ +//! Configure database connections + +use std::ffi::c_int; + +use crate::error::check; +use crate::ffi; +use crate::{Connection, Result}; + +/// Database Connection Configuration Options +/// See [Database Connection Configuration Options](https://sqlite.org/c3ref/c_dbconfig_enable_fkey.html) for details. +#[repr(i32)] +#[derive(Copy, Clone, Debug)] +#[expect(non_camel_case_types)] +#[non_exhaustive] +pub enum DbConfig { + //SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */ + //SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */ + /// Enable or disable the enforcement of foreign key constraints. + SQLITE_DBCONFIG_ENABLE_FKEY = ffi::SQLITE_DBCONFIG_ENABLE_FKEY, + /// Enable or disable triggers. + SQLITE_DBCONFIG_ENABLE_TRIGGER = ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER, + /// Enable or disable the `fts3_tokenizer()` function which is part of the + /// FTS3 full-text search engine extension. + SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, // 3.12.0 + //SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005, + /// In WAL mode, enable or disable the checkpoint operation before closing + /// the connection. + SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2 + /// Activates or deactivates the query planner stability guarantee (QPSG). + SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0 + /// Includes or excludes output for any operations performed by trigger + /// programs from the output of EXPLAIN QUERY PLAN commands. + SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0 + /// Activates or deactivates the "reset" flag for a database connection. + /// Run VACUUM with this flag set to reset the database. + SQLITE_DBCONFIG_RESET_DATABASE = 1009, // 3.24.0 + /// Activates or deactivates the "defensive" flag for a database connection. + SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0 + /// Activates or deactivates the `writable_schema` flag. + SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011, // 3.28.0 + /// Activates or deactivates the legacy behavior of the ALTER TABLE RENAME + /// command. + SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012, // 3.29 + /// Activates or deactivates the legacy double-quoted string literal + /// misfeature for DML statements only. + SQLITE_DBCONFIG_DQS_DML = 1013, // 3.29.0 + /// Activates or deactivates the legacy double-quoted string literal + /// misfeature for DDL statements. + SQLITE_DBCONFIG_DQS_DDL = 1014, // 3.29.0 + /// Enable or disable views. + SQLITE_DBCONFIG_ENABLE_VIEW = 1015, // 3.30.0 + /// Activates or deactivates the legacy file format flag. + SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016, // 3.31.0 + /// Tells SQLite to assume that database schemas (the contents of the + /// `sqlite_master` tables) are untainted by malicious content. + SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0 + /// Sets or clears a flag that enables collection of the + /// `sqlite3_stmt_scanstatus_v2()` statistics + #[cfg(feature = "modern_sqlite")] + SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018, // 3.42.0 + /// Changes the default order in which tables and indexes are scanned + #[cfg(feature = "modern_sqlite")] + SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019, // 3.42.0 + /// Enables or disables the ability of the ATTACH DATABASE SQL command + /// to create a new database file if the database filed named in the ATTACH command does not already exist. + #[cfg(feature = "modern_sqlite")] + SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE = 1020, // 3.49.0 + /// Enables or disables the ability of the ATTACH DATABASE SQL command to open a database for writing. + #[cfg(feature = "modern_sqlite")] + SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE = 1021, // 3.49.0 + /// Enables or disables the ability to include comments in SQL text. + #[cfg(feature = "modern_sqlite")] + SQLITE_DBCONFIG_ENABLE_COMMENTS = 1022, // 3.49.0 +} + +impl Connection { + /// Returns the current value of a `config`. + /// + /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: return `false` or `true` to indicate + /// whether FK enforcement is off or on + /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: return `false` or `true` to indicate + /// whether triggers are disabled or enabled + /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: return `false` or `true` to + /// indicate whether `fts3_tokenizer` are disabled or enabled + /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: return `false` to indicate + /// checkpoints-on-close are not disabled or `true` if they are + /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: return `false` or `true` to indicate + /// whether the QPSG is disabled or enabled + /// - `SQLITE_DBCONFIG_TRIGGER_EQP`: return `false` to indicate + /// output-for-trigger are not disabled or `true` if it is + #[inline] + pub fn db_config(&self, config: DbConfig) -> Result { + let c = self.db.borrow(); + unsafe { + let mut val = 0; + check(ffi::sqlite3_db_config( + c.db(), + config as c_int, + -1, + &mut val, + ))?; + Ok(val != 0) + } + } + + /// Make configuration changes to a database connection + /// + /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: `false` to disable FK enforcement, + /// `true` to enable FK enforcement + /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: `false` to disable triggers, `true` + /// to enable triggers + /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: `false` to disable + /// `fts3_tokenizer()`, `true` to enable `fts3_tokenizer()` + /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: `false` (the default) to enable + /// checkpoints-on-close, `true` to disable them + /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: `false` to disable the QPSG, `true` to + /// enable QPSG + /// - `SQLITE_DBCONFIG_TRIGGER_EQP`: `false` to disable output for trigger + /// programs, `true` to enable it + #[inline] + pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result { + let c = self.db.borrow_mut(); + unsafe { + let mut val = 0; + check(ffi::sqlite3_db_config( + c.db(), + config as c_int, + new_val as c_int, + &mut val, + ))?; + Ok(val != 0) + } + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::DbConfig; + use crate::{Connection, Result}; + + #[test] + fn test_db_config() -> Result<()> { + let db = Connection::open_in_memory()?; + + let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY)?; + assert_eq!( + db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite), + Ok(opposite) + ); + assert_eq!( + db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY), + Ok(opposite) + ); + + let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)?; + assert_eq!( + db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite), + Ok(opposite) + ); + assert_eq!( + db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER), + Ok(opposite) + ); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/context.rs b/vendor/rusqlite/src/context.rs new file mode 100644 index 0000000..ad57f50 --- /dev/null +++ b/vendor/rusqlite/src/context.rs @@ -0,0 +1,62 @@ +//! Code related to `sqlite3_context` common to `functions` and `vtab` modules. + +use crate::ffi::sqlite3_value; +use std::ffi::c_void; + +use crate::ffi; +use crate::ffi::sqlite3_context; + +use crate::str_for_sqlite; +use crate::types::{ToSqlOutput, ValueRef}; + +// This function is inline despite it's size because what's in the ToSqlOutput +// is often known to the compiler, and thus const prop/DCE can substantially +// simplify the function. +#[inline] +pub(super) unsafe fn set_result( + ctx: *mut sqlite3_context, + #[allow(unused_variables)] args: &[*mut sqlite3_value], + result: &ToSqlOutput<'_>, +) { + let value = match *result { + ToSqlOutput::Borrowed(v) => v, + ToSqlOutput::Owned(ref v) => ValueRef::from(v), + + #[cfg(feature = "blob")] + ToSqlOutput::ZeroBlob(len) => { + // TODO sqlite3_result_zeroblob64 // 3.8.11 + return ffi::sqlite3_result_zeroblob(ctx, len); + } + #[cfg(feature = "functions")] + ToSqlOutput::Arg(i) => { + return ffi::sqlite3_result_value(ctx, args[i]); + } + #[cfg(feature = "pointer")] + ToSqlOutput::Pointer(ref p) => { + return ffi::sqlite3_result_pointer(ctx, p.0 as _, p.1.as_ptr(), p.2); + } + }; + + match value { + ValueRef::Null => ffi::sqlite3_result_null(ctx), + ValueRef::Integer(i) => ffi::sqlite3_result_int64(ctx, i), + ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r), + ValueRef::Text(s) => { + let (c_str, len, destructor) = str_for_sqlite(s); + ffi::sqlite3_result_text64(ctx, c_str, len, destructor, ffi::SQLITE_UTF8 as _); + } + ValueRef::Blob(b) => { + let length = b.len(); + if length == 0 { + ffi::sqlite3_result_zeroblob(ctx, 0); + } else { + ffi::sqlite3_result_blob64( + ctx, + b.as_ptr().cast::(), + length as ffi::sqlite3_uint64, + ffi::SQLITE_TRANSIENT(), + ); + } + } + } +} diff --git a/vendor/rusqlite/src/error.rs b/vendor/rusqlite/src/error.rs new file mode 100644 index 0000000..944d203 --- /dev/null +++ b/vendor/rusqlite/src/error.rs @@ -0,0 +1,514 @@ +use crate::types::FromSqlError; +use crate::types::Type; +use crate::{errmsg_to_string, ffi, Result}; +use std::error; +use std::ffi::{c_char, c_int, NulError}; +use std::fmt; +use std::path::PathBuf; +use std::str; + +/// Enum listing possible errors from rusqlite. +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + /// An error from an underlying SQLite call. + SqliteFailure(ffi::Error, Option), + + /// Error reported when attempting to open a connection when SQLite was + /// configured to allow single-threaded use only. + SqliteSingleThreadedMode, + + /// Error when the value of a particular column is requested, but it cannot + /// be converted to the requested Rust type. + FromSqlConversionFailure(usize, Type, Box), + + /// Error when SQLite gives us an integral value outside the range of the + /// requested type (e.g., trying to get the value 1000 into a `u8`). + /// The associated `usize` is the column index, + /// and the associated `i64` is the value returned by SQLite. + IntegralValueOutOfRange(usize, i64), + + /// Error converting a string to UTF-8. + Utf8Error(usize, str::Utf8Error), + + /// Error converting a string to a C-compatible string because it contained + /// an embedded nul. + NulError(NulError), + + /// Error when using SQL named parameters and passing a parameter name not + /// present in the SQL. + InvalidParameterName(String), + + /// Error converting a file path to a string. + InvalidPath(PathBuf), + + /// Error returned when an [`execute`](crate::Connection::execute) call + /// returns rows. + ExecuteReturnedResults, + + /// Error when a query that was expected to return at least one row (e.g., + /// for [`query_row`](crate::Connection::query_row)) did not return any. + QueryReturnedNoRows, + + /// Error when a query that was expected to return only one row (e.g., + /// for [`query_one`](crate::Connection::query_one)) did return more than one. + QueryReturnedMoreThanOneRow, + + /// Error when the value of a particular column is requested, but the index + /// is out of range for the statement. + InvalidColumnIndex(usize), + + /// Error when the value of a named column is requested, but no column + /// matches the name for the statement. + InvalidColumnName(String), + + /// Error when the value of a particular column is requested, but the type + /// of the result in that column cannot be converted to the requested + /// Rust type. + InvalidColumnType(usize, String, Type), + + /// Error when a query that was expected to insert one row did not insert + /// any or insert many. + StatementChangedRows(usize), + + /// Error returned by + /// [`functions::Context::get`](crate::functions::Context::get) when the + /// function argument cannot be converted to the requested type. + #[cfg(feature = "functions")] + InvalidFunctionParameterType(usize, Type), + /// Error returned by [`vtab::Values::get`](crate::vtab::Values::get) when + /// the filter argument cannot be converted to the requested type. + #[cfg(feature = "vtab")] + InvalidFilterParameterType(usize, Type), + + /// An error case available for implementors of custom user functions (e.g., + /// [`create_scalar_function`](crate::Connection::create_scalar_function)). + #[cfg(feature = "functions")] + UserFunctionError(Box), + + /// Error available for the implementors of the + /// [`ToSql`](crate::types::ToSql) trait. + ToSqlConversionFailure(Box), + + /// Error when the SQL is not a `SELECT`, is not read-only. + InvalidQuery, + + /// An error case available for implementors of custom modules (e.g., + /// [`create_module`](crate::Connection::create_module)). + #[cfg(feature = "vtab")] + ModuleError(String), + + /// An unwinding panic occurs in a UDF (user-defined function). + UnwindingPanic, + + /// An error returned when + /// [`Context::get_aux`](crate::functions::Context::get_aux) attempts to + /// retrieve data of a different type than what had been stored using + /// [`Context::set_aux`](crate::functions::Context::set_aux). + #[cfg(feature = "functions")] + GetAuxWrongType, + + /// Error when the SQL contains multiple statements. + MultipleStatement, + /// Error when the number of bound parameters does not match the number of + /// parameters in the query. The first `usize` is how many parameters were + /// given, the 2nd is how many were expected. + InvalidParameterCount(usize, usize), + + /// Returned from various functions in the Blob IO positional API. For + /// example, + /// [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) will + /// return it if the blob has insufficient data. + #[cfg(feature = "blob")] + BlobSizeError, + /// Error referencing a specific token in the input SQL + #[cfg(feature = "modern_sqlite")] // 3.38.0 + SqlInputError { + /// error code + error: ffi::Error, + /// error message + msg: String, + /// SQL input + sql: String, + /// byte offset of the start of invalid token + offset: c_int, + }, + /// Loadable extension initialization error + #[cfg(feature = "loadable_extension")] + InitError(ffi::InitError), + /// Error when the schema of a particular database is requested, but the index + /// is out of range. + #[cfg(feature = "modern_sqlite")] // 3.39.0 + InvalidDatabaseIndex(usize), +} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::SqliteFailure(e1, s1), Self::SqliteFailure(e2, s2)) => e1 == e2 && s1 == s2, + (Self::SqliteSingleThreadedMode, Self::SqliteSingleThreadedMode) => true, + (Self::IntegralValueOutOfRange(i1, n1), Self::IntegralValueOutOfRange(i2, n2)) => { + i1 == i2 && n1 == n2 + } + (Self::Utf8Error(i1, e1), Self::Utf8Error(i2, e2)) => i1 == i2 && e1 == e2, + (Self::NulError(e1), Self::NulError(e2)) => e1 == e2, + (Self::InvalidParameterName(n1), Self::InvalidParameterName(n2)) => n1 == n2, + (Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2, + (Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true, + (Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true, + (Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true, + (Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2, + (Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2, + (Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => { + i1 == i2 && t1 == t2 && n1 == n2 + } + (Self::StatementChangedRows(n1), Self::StatementChangedRows(n2)) => n1 == n2, + #[cfg(feature = "functions")] + ( + Self::InvalidFunctionParameterType(i1, t1), + Self::InvalidFunctionParameterType(i2, t2), + ) => i1 == i2 && t1 == t2, + #[cfg(feature = "vtab")] + ( + Self::InvalidFilterParameterType(i1, t1), + Self::InvalidFilterParameterType(i2, t2), + ) => i1 == i2 && t1 == t2, + (Self::InvalidQuery, Self::InvalidQuery) => true, + #[cfg(feature = "vtab")] + (Self::ModuleError(s1), Self::ModuleError(s2)) => s1 == s2, + (Self::UnwindingPanic, Self::UnwindingPanic) => true, + #[cfg(feature = "functions")] + (Self::GetAuxWrongType, Self::GetAuxWrongType) => true, + (Self::InvalidParameterCount(i1, n1), Self::InvalidParameterCount(i2, n2)) => { + i1 == i2 && n1 == n2 + } + #[cfg(feature = "blob")] + (Self::BlobSizeError, Self::BlobSizeError) => true, + #[cfg(feature = "modern_sqlite")] + ( + Self::SqlInputError { + error: e1, + msg: m1, + sql: s1, + offset: o1, + }, + Self::SqlInputError { + error: e2, + msg: m2, + sql: s2, + offset: o2, + }, + ) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2, + #[cfg(feature = "loadable_extension")] + (Self::InitError(e1), Self::InitError(e2)) => e1 == e2, + #[cfg(feature = "modern_sqlite")] + (Self::InvalidDatabaseIndex(i1), Self::InvalidDatabaseIndex(i2)) => i1 == i2, + (..) => false, + } + } +} + +impl From for Error { + #[cold] + fn from(err: str::Utf8Error) -> Self { + Self::Utf8Error(UNKNOWN_COLUMN, err) + } +} + +impl From for Error { + #[cold] + fn from(err: NulError) -> Self { + Self::NulError(err) + } +} + +const UNKNOWN_COLUMN: usize = usize::MAX; + +/// The conversion isn't precise, but it's convenient to have it +/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`. +impl From for Error { + #[cold] + fn from(err: FromSqlError) -> Self { + // The error type requires index and type fields, but they aren't known in this + // context. + match err { + FromSqlError::OutOfRange(val) => Self::IntegralValueOutOfRange(UNKNOWN_COLUMN, val), + FromSqlError::InvalidBlobSize { .. } => { + Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err)) + } + FromSqlError::Other(source) => { + Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source) + } + _ => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)), + } + } +} + +#[cfg(feature = "loadable_extension")] +impl From for Error { + #[cold] + fn from(err: ffi::InitError) -> Self { + Self::InitError(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::SqliteFailure(ref err, None) => err.fmt(f), + Self::SqliteFailure(_, Some(ref s)) => write!(f, "{s}"), + Self::SqliteSingleThreadedMode => write!( + f, + "SQLite was compiled or configured for single-threaded use only" + ), + Self::FromSqlConversionFailure(i, ref t, ref err) => { + if i != UNKNOWN_COLUMN { + write!(f, "Conversion error from type {t} at index: {i}, {err}") + } else { + err.fmt(f) + } + } + Self::IntegralValueOutOfRange(col, val) => { + if col != UNKNOWN_COLUMN { + write!(f, "Integer {val} out of range at index {col}") + } else { + write!(f, "Integer {val} out of range") + } + } + Self::Utf8Error(col, ref err) => { + if col != UNKNOWN_COLUMN { + write!(f, "{err} at index {col}") + } else { + err.fmt(f) + } + } + Self::NulError(ref err) => err.fmt(f), + Self::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"), + Self::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()), + Self::ExecuteReturnedResults => { + write!(f, "Execute returned results - did you mean to call query?") + } + Self::QueryReturnedNoRows => write!(f, "Query returned no rows"), + Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"), + Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"), + Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"), + Self::InvalidColumnType(i, ref name, ref t) => { + write!(f, "Invalid column type {t} at index: {i}, name: {name}") + } + Self::InvalidParameterCount(i1, n1) => write!( + f, + "Wrong number of parameters passed to query. Got {i1}, needed {n1}" + ), + Self::StatementChangedRows(i) => write!(f, "Query changed {i} rows"), + + #[cfg(feature = "functions")] + Self::InvalidFunctionParameterType(i, ref t) => { + write!(f, "Invalid function parameter type {t} at index {i}") + } + #[cfg(feature = "vtab")] + Self::InvalidFilterParameterType(i, ref t) => { + write!(f, "Invalid filter parameter type {t} at index {i}") + } + #[cfg(feature = "functions")] + Self::UserFunctionError(ref err) => err.fmt(f), + Self::ToSqlConversionFailure(ref err) => err.fmt(f), + Self::InvalidQuery => write!(f, "Query is not read-only"), + #[cfg(feature = "vtab")] + Self::ModuleError(ref desc) => write!(f, "{desc}"), + Self::UnwindingPanic => write!(f, "unwinding panic"), + #[cfg(feature = "functions")] + Self::GetAuxWrongType => write!(f, "get_aux called with wrong type"), + Self::MultipleStatement => write!(f, "Multiple statements provided"), + #[cfg(feature = "blob")] + Self::BlobSizeError => "Blob size is insufficient".fmt(f), + #[cfg(feature = "modern_sqlite")] + Self::SqlInputError { + ref msg, + offset, + ref sql, + .. + } => write!(f, "{msg} in {sql} at offset {offset}"), + #[cfg(feature = "loadable_extension")] + Self::InitError(ref err) => err.fmt(f), + #[cfg(feature = "modern_sqlite")] + Self::InvalidDatabaseIndex(i) => write!(f, "Invalid database index: {i}"), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + Self::SqliteFailure(ref err, _) => Some(err), + Self::Utf8Error(_, ref err) => Some(err), + Self::NulError(ref err) => Some(err), + + Self::IntegralValueOutOfRange(..) + | Self::SqliteSingleThreadedMode + | Self::InvalidParameterName(_) + | Self::ExecuteReturnedResults + | Self::QueryReturnedNoRows + | Self::QueryReturnedMoreThanOneRow + | Self::InvalidColumnIndex(_) + | Self::InvalidColumnName(_) + | Self::InvalidColumnType(..) + | Self::InvalidPath(_) + | Self::InvalidParameterCount(..) + | Self::StatementChangedRows(_) + | Self::InvalidQuery + | Self::MultipleStatement => None, + + #[cfg(feature = "functions")] + Self::InvalidFunctionParameterType(..) => None, + #[cfg(feature = "vtab")] + Self::InvalidFilterParameterType(..) => None, + + #[cfg(feature = "functions")] + Self::UserFunctionError(ref err) => Some(&**err), + + Self::FromSqlConversionFailure(_, _, ref err) + | Self::ToSqlConversionFailure(ref err) => Some(&**err), + + #[cfg(feature = "vtab")] + Self::ModuleError(_) => None, + + Self::UnwindingPanic => None, + + #[cfg(feature = "functions")] + Self::GetAuxWrongType => None, + + #[cfg(feature = "blob")] + Self::BlobSizeError => None, + #[cfg(feature = "modern_sqlite")] + Self::SqlInputError { ref error, .. } => Some(error), + #[cfg(feature = "loadable_extension")] + Self::InitError(ref err) => Some(err), + #[cfg(feature = "modern_sqlite")] + Self::InvalidDatabaseIndex(_) => None, + } + } +} + +impl Error { + /// Returns the underlying SQLite error if this is [`Error::SqliteFailure`]. + #[inline] + #[must_use] + pub fn sqlite_error(&self) -> Option<&ffi::Error> { + match self { + Self::SqliteFailure(error, _) => Some(error), + _ => None, + } + } + + /// Returns the underlying SQLite error code if this is + /// [`Error::SqliteFailure`]. + #[inline] + #[must_use] + pub fn sqlite_error_code(&self) -> Option { + self.sqlite_error().map(|error| error.code) + } +} + +// These are public but not re-exported by lib.rs, so only visible within crate. + +#[cold] +pub fn error_from_sqlite_code(code: c_int, message: Option) -> Error { + Error::SqliteFailure(ffi::Error::new(code), message) +} + +macro_rules! err { + ($code:expr $(,)?) => { + $crate::error::error_from_sqlite_code($code, None) + }; + ($code:expr, $msg:literal $(,)?) => { + $crate::error::error_from_sqlite_code($code, Some(format!($msg))) + }; + ($code:expr, $err:expr $(,)?) => { + $crate::error::error_from_sqlite_code($code, Some(format!($err))) + }; + ($code:expr, $fmt:expr, $($arg:tt)*) => { + $crate::error::error_from_sqlite_code($code, Some(format!($fmt, $($arg)*))) + }; +} + +#[cold] +pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error { + error_from_sqlite_code(code, error_msg(db, code)) +} + +unsafe fn error_msg(db: *mut ffi::sqlite3, code: c_int) -> Option { + if db.is_null() || ffi::sqlite3_errcode(db) != code { + let err_str = ffi::sqlite3_errstr(code); + if err_str.is_null() { + None + } else { + Some(errmsg_to_string(err_str)) + } + } else { + Some(errmsg_to_string(ffi::sqlite3_errmsg(db))) + } +} + +pub unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> { + if code == ffi::SQLITE_OK { + Ok(()) + } else { + Err(error_from_handle(db, code)) + } +} + +#[cold] +#[cfg(not(feature = "modern_sqlite"))] // SQLite >= 3.38.0 +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, _sql: &str) -> Error { + error_from_handle(db, code) +} + +#[cold] +#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error { + if db.is_null() { + error_from_sqlite_code(code, None) + } else { + let error = ffi::Error::new(code); + let msg = error_msg(db, code); + if ffi::ErrorCode::Unknown == error.code { + let offset = ffi::sqlite3_error_offset(db); + if offset >= 0 { + return Error::SqlInputError { + error, + msg: msg.unwrap_or("error".to_owned()), + sql: sql.to_owned(), + offset, + }; + } + } + Error::SqliteFailure(error, msg) + } +} + +pub fn check(code: c_int) -> Result<()> { + if code != ffi::SQLITE_OK { + Err(error_from_sqlite_code(code, None)) + } else { + Ok(()) + } +} + +/// Transform Rust error to SQLite error (message and code). +/// # Safety +/// This function is unsafe because it uses raw pointer +pub unsafe fn to_sqlite_error(e: &Error, err_msg: *mut *mut c_char) -> c_int { + use crate::util::alloc; + match e { + Error::SqliteFailure(err, s) => { + if let Some(s) = s { + *err_msg = alloc(s); + } + err.extended_code + } + err => { + *err_msg = alloc(&err.to_string()); + ffi::SQLITE_ERROR + } + } +} diff --git a/vendor/rusqlite/src/functions.rs b/vendor/rusqlite/src/functions.rs new file mode 100644 index 0000000..94fdf58 --- /dev/null +++ b/vendor/rusqlite/src/functions.rs @@ -0,0 +1,1271 @@ +//! Create or redefine SQL functions. +//! +//! # Example +//! +//! Adding a `regexp` function to a connection in which compiled regular +//! expressions are cached in a `HashMap`. For an alternative implementation +//! that uses SQLite's [Function Auxiliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface +//! to avoid recompiling regular expressions, see the unit tests for this +//! module. +//! +//! ```rust +//! use regex::Regex; +//! use rusqlite::functions::FunctionFlags; +//! use rusqlite::{Connection, Error, Result}; +//! use std::sync::Arc; +//! type BoxError = Box; +//! +//! fn add_regexp_function(db: &Connection) -> Result<()> { +//! db.create_scalar_function( +//! "regexp", +//! 2, +//! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, +//! move |ctx| { +//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); +//! let regexp: Arc = ctx.get_or_create_aux(0, |vr| -> Result<_, BoxError> { +//! Ok(Regex::new(vr.as_str()?)?) +//! })?; +//! let is_match = { +//! let text = ctx +//! .get_raw(1) +//! .as_str() +//! .map_err(|e| Error::UserFunctionError(e.into()))?; +//! +//! regexp.is_match(text) +//! }; +//! +//! Ok(is_match) +//! }, +//! ) +//! } +//! +//! fn main() -> Result<()> { +//! let db = Connection::open_in_memory()?; +//! add_regexp_function(&db)?; +//! +//! let is_match: bool = +//! db.query_row("SELECT regexp('[aeiou]*', 'aaaaeeeiii')", [], |row| { +//! row.get(0) +//! })?; +//! +//! assert!(is_match); +//! Ok(()) +//! } +//! ``` +use std::any::Any; +use std::ffi::{c_int, c_uint, c_void}; +use std::marker::PhantomData; +use std::ops::Deref; +use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe}; +use std::ptr; +use std::slice; +use std::sync::Arc; + +use crate::ffi; +use crate::ffi::sqlite3_context; +use crate::ffi::sqlite3_value; + +use crate::context::set_result; +use crate::types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef}; +use crate::util::free_boxed_value; +use crate::{str_to_cstring, Connection, Error, InnerConnection, Name, Result}; + +unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) { + if let Error::SqliteFailure(ref err, ref s) = *err { + ffi::sqlite3_result_error_code(ctx, err.extended_code); + if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + } else { + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + if let Ok(cstr) = str_to_cstring(&err.to_string()) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + } +} + +/// Context is a wrapper for the SQLite function +/// evaluation context. +pub struct Context<'a> { + ctx: *mut sqlite3_context, + args: &'a [*mut sqlite3_value], +} + +impl Context<'_> { + /// Returns the number of arguments to the function. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.args.len() + } + + /// Returns `true` when there is no argument. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.args.is_empty() + } + + /// Returns the `idx`th argument as a `T`. + /// + /// # Failure + /// + /// Will panic if `idx` is greater than or equal to + /// [`self.len()`](Context::len). + /// + /// Will return Err if the underlying SQLite type cannot be converted to a + /// `T`. + pub fn get(&self, idx: usize) -> Result { + let arg = self.args[idx]; + let value = unsafe { ValueRef::from_value(arg) }; + FromSql::column_result(value).map_err(|err| match err { + FromSqlError::InvalidType => { + Error::InvalidFunctionParameterType(idx, value.data_type()) + } + FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), + FromSqlError::Utf8Error(err) => Error::Utf8Error(idx, err), + FromSqlError::Other(err) => { + Error::FromSqlConversionFailure(idx, value.data_type(), err) + } + FromSqlError::InvalidBlobSize { .. } => { + Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) + } + }) + } + + /// Return raw pointer at `idx` + /// # Safety + /// This function is unsafe because it uses raw pointer and cast + #[cfg(feature = "pointer")] + pub unsafe fn get_pointer( + &self, + idx: usize, + ptr_type: &'static std::ffi::CStr, + ) -> Option<&T> { + let arg = self.args[idx]; + debug_assert_eq!(unsafe { ffi::sqlite3_value_type(arg) }, ffi::SQLITE_NULL); + unsafe { + ffi::sqlite3_value_pointer(arg, ptr_type.as_ptr()) + .cast::() + .as_ref() + } + } + + /// Returns the `idx`th argument as a `ValueRef`. + /// + /// # Failure + /// + /// Will panic if `idx` is greater than or equal to + /// [`self.len()`](Context::len). + #[inline] + #[must_use] + pub fn get_raw(&self, idx: usize) -> ValueRef<'_> { + let arg = self.args[idx]; + unsafe { ValueRef::from_value(arg) } + } + + /// Returns the `idx`th argument as a `SqlFnArg`. + /// To be used when the SQL function result is one of its arguments. + #[inline] + #[must_use] + pub fn get_arg(&self, idx: usize) -> SqlFnArg { + assert!(idx < self.len()); + SqlFnArg { idx } + } + + /// Returns the subtype of `idx`th argument. + /// + /// # Failure + /// + /// Will panic if `idx` is greater than or equal to + /// [`self.len()`](Context::len). + pub fn get_subtype(&self, idx: usize) -> c_uint { + let arg = self.args[idx]; + unsafe { ffi::sqlite3_value_subtype(arg) } + } + + /// Fetch or insert the auxiliary data associated with a particular + /// parameter. This is intended to be an easier-to-use way of fetching it + /// compared to calling [`get_aux`](Context::get_aux) and + /// [`set_aux`](Context::set_aux) separately. + /// + /// See `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of + /// this feature, or the unit tests of this module for an example. + /// + /// # Failure + /// + /// Will panic if `arg` is greater than or equal to + /// [`self.len()`](Context::len). + pub fn get_or_create_aux(&self, arg: c_int, func: F) -> Result> + where + T: Send + Sync + 'static, + E: Into>, + F: FnOnce(ValueRef<'_>) -> Result, + { + if let Some(v) = self.get_aux(arg)? { + Ok(v) + } else { + let vr = self.get_raw(arg as usize); + self.set_aux( + arg, + func(vr).map_err(|e| Error::UserFunctionError(e.into()))?, + ) + } + } + + /// Sets the auxiliary data associated with a particular parameter. See + /// `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of + /// this feature, or the unit tests of this module for an example. + /// + /// # Failure + /// + /// Will panic if `arg` is greater than or equal to + /// [`self.len()`](Context::len). + pub fn set_aux(&self, arg: c_int, value: T) -> Result> { + assert!(arg < self.len() as i32); + let orig: Arc = Arc::new(value); + let inner: AuxInner = orig.clone(); + let outer = Box::new(inner); + let raw: *mut AuxInner = Box::into_raw(outer); + unsafe { + ffi::sqlite3_set_auxdata( + self.ctx, + arg, + raw.cast(), + Some(free_boxed_value::), + ); + }; + Ok(orig) + } + + /// Gets the auxiliary data that was associated with a given parameter via + /// [`set_aux`](Context::set_aux). Returns `Ok(None)` if no data has been + /// associated, and Ok(Some(v)) if it has. Returns an error if the + /// requested type does not match. + /// + /// # Failure + /// + /// Will panic if `arg` is greater than or equal to + /// [`self.len()`](Context::len). + pub fn get_aux(&self, arg: c_int) -> Result>> { + assert!(arg < self.len() as i32); + let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *const AuxInner }; + if p.is_null() { + Ok(None) + } else { + let v: AuxInner = AuxInner::clone(unsafe { &*p }); + v.downcast::() + .map(Some) + .map_err(|_| Error::GetAuxWrongType) + } + } + + /// Get the db connection handle via [sqlite3_context_db_handle](https://www.sqlite.org/c3ref/context_db_handle.html) + /// + /// # Safety + /// + /// This function is marked unsafe because there is a potential for other + /// references to the connection to be sent across threads, [see this comment](https://github.com/rusqlite/rusqlite/issues/643#issuecomment-640181213). + pub unsafe fn get_connection(&self) -> Result> { + let handle = ffi::sqlite3_context_db_handle(self.ctx); + Ok(ConnectionRef { + conn: Connection::from_handle(handle)?, + phantom: PhantomData, + }) + } +} + +/// A reference to a connection handle with a lifetime bound to something. +pub struct ConnectionRef<'ctx> { + // comes from Connection::from_handle(sqlite3_context_db_handle(...)) + // and is non-owning + conn: Connection, + phantom: PhantomData<&'ctx Context<'ctx>>, +} + +impl Deref for ConnectionRef<'_> { + type Target = Connection; + + #[inline] + fn deref(&self) -> &Connection { + &self.conn + } +} + +type AuxInner = Arc; + +/// Subtype of an SQL function +pub type SubType = Option; + +/// Result of an SQL function +pub trait SqlFnOutput { + /// Converts Rust value to SQLite value with an optional subtype + fn to_sql(&self) -> Result<(ToSqlOutput<'_>, SubType)>; +} + +impl SqlFnOutput for T { + #[inline] + fn to_sql(&self) -> Result<(ToSqlOutput<'_>, SubType)> { + ToSql::to_sql(self).map(|o| (o, None)) + } +} + +impl SqlFnOutput for (T, SubType) { + fn to_sql(&self) -> Result<(ToSqlOutput<'_>, SubType)> { + ToSql::to_sql(&self.0).map(|o| (o, self.1)) + } +} + +/// n-th arg of an SQL scalar function +pub struct SqlFnArg { + idx: usize, +} +impl ToSql for SqlFnArg { + fn to_sql(&self) -> Result> { + Ok(ToSqlOutput::Arg(self.idx)) + } +} + +unsafe fn sql_result( + ctx: *mut sqlite3_context, + args: &[*mut sqlite3_value], + r: Result, +) { + let t = r.as_ref().map(SqlFnOutput::to_sql); + + match t { + Ok(Ok((ref value, sub_type))) => { + set_result(ctx, args, value); + if let Some(sub_type) = sub_type { + ffi::sqlite3_result_subtype(ctx, sub_type); + } + } + Ok(Err(err)) => report_error(ctx, &err), + Err(err) => report_error(ctx, err), + }; +} + +/// Aggregate is the callback interface for user-defined +/// aggregate function. +/// +/// `A` is the type of the aggregation context and `T` is the type of the final +/// result. Implementations should be stateless. +pub trait Aggregate +where + A: RefUnwindSafe + UnwindSafe, + T: SqlFnOutput, +{ + /// Initializes the aggregation context. Will be called prior to the first + /// call to [`step()`](Aggregate::step) to set up the context for an + /// invocation of the function. (Note: `init()` will not be called if + /// there are no rows.) + fn init(&self, ctx: &mut Context<'_>) -> Result; + + /// "step" function called once for each row in an aggregate group. May be + /// called 0 times if there are no rows. + fn step(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>; + + /// Computes and returns the final result. Will be called exactly once for + /// each invocation of the function. If [`step()`](Aggregate::step) was + /// called at least once, will be given `Some(A)` (the same `A` as was + /// created by [`init`](Aggregate::init) and given to + /// [`step`](Aggregate::step)); if [`step()`](Aggregate::step) was not + /// called (because the function is running against 0 rows), will be + /// given `None`. + /// + /// The passed context will have no arguments. + fn finalize(&self, ctx: &mut Context<'_>, acc: Option) -> Result; +} + +/// `WindowAggregate` is the callback interface for +/// user-defined aggregate window function. +#[cfg(feature = "window")] +pub trait WindowAggregate: Aggregate +where + A: RefUnwindSafe + UnwindSafe, + T: SqlFnOutput, +{ + /// Returns the current value of the aggregate. Unlike xFinal, the + /// implementation should not delete any context. + fn value(&self, acc: Option<&mut A>) -> Result; + + /// Removes a row from the current window. + fn inverse(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>; +} + +bitflags::bitflags! { + /// Function Flags. + /// See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html) + /// and [Function Flags](https://sqlite.org/c3ref/c_deterministic.html) for details. + #[derive(Clone, Copy, Debug)] + #[repr(C)] + pub struct FunctionFlags: c_int { + /// Specifies UTF-8 as the text encoding this SQL function prefers for its parameters. + const SQLITE_UTF8 = ffi::SQLITE_UTF8; + /// Specifies UTF-16 using little-endian byte order as the text encoding this SQL function prefers for its parameters. + const SQLITE_UTF16LE = ffi::SQLITE_UTF16LE; + /// Specifies UTF-16 using big-endian byte order as the text encoding this SQL function prefers for its parameters. + const SQLITE_UTF16BE = ffi::SQLITE_UTF16BE; + /// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters. + const SQLITE_UTF16 = ffi::SQLITE_UTF16; + /// Means that the function always gives the same output when the input parameters are the same. + const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC; // 3.8.3 + /// Means that the function may only be invoked from top-level SQL. + const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0 + /// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the subtypes of its arguments. + const SQLITE_SUBTYPE = 0x0000_0010_0000; // 3.30.0 + /// Means that the function is unlikely to cause problems even if misused. + const SQLITE_INNOCUOUS = 0x0000_0020_0000; // 3.31.0 + /// Indicates to SQLite that a function might call `sqlite3_result_subtype()` to cause a subtype to be associated with its result. + const SQLITE_RESULT_SUBTYPE = 0x0000_0100_0000; // 3.45.0 + /// Indicates that the function is an aggregate that internally orders the values provided to the first argument. + const SQLITE_SELFORDER1 = 0x0000_0200_0000; // 3.47.0 + } +} + +impl Default for FunctionFlags { + #[inline] + fn default() -> Self { + Self::SQLITE_UTF8 + } +} + +impl Connection { + /// Attach a user-defined scalar function to + /// this database connection. + /// + /// `fn_name` is the name the function will be accessible from SQL. + /// `n_arg` is the number of arguments to the function. Use `-1` for a + /// variable number. If the function always returns the same value + /// given the same input, `deterministic` should be `true`. + /// + /// The function will remain available until the connection is closed or + /// until it is explicitly removed via + /// [`remove_function`](Connection::remove_function). + /// + /// # Example + /// + /// ```rust + /// # use rusqlite::{Connection, Result}; + /// # use rusqlite::functions::FunctionFlags; + /// fn scalar_function_example(db: Connection) -> Result<()> { + /// db.create_scalar_function( + /// "halve", + /// 1, + /// FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + /// |ctx| { + /// let value = ctx.get::(0)?; + /// Ok(value / 2f64) + /// }, + /// )?; + /// + /// let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?; + /// assert_eq!(six_halved, 3f64); + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return Err if the function could not be attached to the connection. + #[inline] + pub fn create_scalar_function( + &self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + x_func: F, + ) -> Result<()> + where + F: Fn(&Context<'_>) -> Result + Send + 'static, + T: SqlFnOutput, + { + self.db + .borrow_mut() + .create_scalar_function(fn_name, n_arg, flags, x_func) + } + + /// Attach a user-defined aggregate function to this + /// database connection. + /// + /// # Failure + /// + /// Will return Err if the function could not be attached to the connection. + #[inline] + pub fn create_aggregate_function( + &self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + aggr: D, + ) -> Result<()> + where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate + 'static, + T: SqlFnOutput, + { + self.db + .borrow_mut() + .create_aggregate_function(fn_name, n_arg, flags, aggr) + } + + /// Attach a user-defined aggregate window function to + /// this database connection. + /// + /// See `https://sqlite.org/windowfunctions.html#udfwinfunc` for more + /// information. + #[cfg(feature = "window")] + #[inline] + pub fn create_window_function( + &self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + aggr: W, + ) -> Result<()> + where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate + 'static, + T: SqlFnOutput, + { + self.db + .borrow_mut() + .create_window_function(fn_name, n_arg, flags, aggr) + } + + /// Removes a user-defined function from this + /// database connection. + /// + /// `fn_name` and `n_arg` should match the name and number of arguments + /// given to [`create_scalar_function`](Connection::create_scalar_function) + /// or [`create_aggregate_function`](Connection::create_aggregate_function). + /// + /// # Failure + /// + /// Will return Err if the function could not be removed. + #[inline] + pub fn remove_function(&self, fn_name: N, n_arg: c_int) -> Result<()> { + self.db.borrow_mut().remove_function(fn_name, n_arg) + } +} + +impl InnerConnection { + /// ```compile_fail + /// use rusqlite::{functions::FunctionFlags, Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.create_scalar_function( + /// "test", + /// 0, + /// FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + /// |_| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// Ok(true) + /// }, + /// ); + /// } + /// let result: Result = db.query_row("SELECT test()", [], |r| r.get(0)); + /// assert!(result?); + /// Ok(()) + /// } + /// ``` + fn create_scalar_function( + &mut self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + x_func: F, + ) -> Result<()> + where + F: Fn(&Context<'_>) -> Result + Send + 'static, + T: SqlFnOutput, + { + unsafe extern "C" fn call_boxed_closure( + ctx: *mut sqlite3_context, + argc: c_int, + argv: *mut *mut sqlite3_value, + ) where + F: Fn(&Context<'_>) -> Result, + T: SqlFnOutput, + { + let args = slice::from_raw_parts(argv, argc as usize); + let r = catch_unwind(|| { + let boxed_f: *const F = ffi::sqlite3_user_data(ctx).cast::(); + assert!(!boxed_f.is_null(), "Internal error - null function pointer"); + let ctx = Context { ctx, args }; + (*boxed_f)(&ctx) + }); + let t = match r { + Err(_) => { + report_error(ctx, &Error::UnwindingPanic); + return; + } + Ok(r) => r, + }; + sql_result(ctx, args, t); + } + + let boxed_f: *mut F = Box::into_raw(Box::new(x_func)); + let c_name = fn_name.as_cstr()?; + let r = unsafe { + ffi::sqlite3_create_function_v2( + self.db(), + c_name.as_ptr(), + n_arg, + flags.bits(), + boxed_f.cast::(), + Some(call_boxed_closure::), + None, + None, + Some(free_boxed_value::), + ) + }; + self.decode_result(r) + } + + fn create_aggregate_function( + &mut self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + aggr: D, + ) -> Result<()> + where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate + 'static, + T: SqlFnOutput, + { + let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr)); + let c_name = fn_name.as_cstr()?; + let r = unsafe { + ffi::sqlite3_create_function_v2( + self.db(), + c_name.as_ptr(), + n_arg, + flags.bits(), + boxed_aggr.cast::(), + None, + Some(call_boxed_step::), + Some(call_boxed_final::), + Some(free_boxed_value::), + ) + }; + self.decode_result(r) + } + + #[cfg(feature = "window")] + fn create_window_function( + &mut self, + fn_name: N, + n_arg: c_int, + flags: FunctionFlags, + aggr: W, + ) -> Result<()> + where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate + 'static, + T: SqlFnOutput, + { + let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr)); + let c_name = fn_name.as_cstr()?; + let r = unsafe { + ffi::sqlite3_create_window_function( + self.db(), + c_name.as_ptr(), + n_arg, + flags.bits(), + boxed_aggr.cast::(), + Some(call_boxed_step::), + Some(call_boxed_final::), + Some(call_boxed_value::), + Some(call_boxed_inverse::), + Some(free_boxed_value::), + ) + }; + self.decode_result(r) + } + + fn remove_function(&mut self, fn_name: N, n_arg: c_int) -> Result<()> { + let c_name = fn_name.as_cstr()?; + let r = unsafe { + ffi::sqlite3_create_function_v2( + self.db(), + c_name.as_ptr(), + n_arg, + ffi::SQLITE_UTF8, + ptr::null_mut(), + None, + None, + None, + None, + ) + }; + self.decode_result(r) + } +} + +unsafe fn aggregate_context(ctx: *mut sqlite3_context, bytes: usize) -> Option<*mut *mut A> { + let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A; + if pac.is_null() { + return None; + } + Some(pac) +} + +unsafe extern "C" fn call_boxed_step( + ctx: *mut sqlite3_context, + argc: c_int, + argv: *mut *mut sqlite3_value, +) where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate, + T: SqlFnOutput, +{ + let Some(pac) = aggregate_context(ctx, size_of::<*mut A>()) else { + ffi::sqlite3_result_error_nomem(ctx); + return; + }; + + let r = catch_unwind(|| { + let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::(); + assert!( + !boxed_aggr.is_null(), + "Internal error - null aggregate pointer" + ); + let mut ctx = Context { + ctx, + args: slice::from_raw_parts(argv, argc as usize), + }; + + #[expect(clippy::unnecessary_cast)] + if (*pac as *mut A).is_null() { + *pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?)); + } + + (*boxed_aggr).step(&mut ctx, &mut **pac) + }); + let r = match r { + Err(_) => { + report_error(ctx, &Error::UnwindingPanic); + return; + } + Ok(r) => r, + }; + match r { + Ok(_) => {} + Err(err) => report_error(ctx, &err), + }; +} + +#[cfg(feature = "window")] +unsafe extern "C" fn call_boxed_inverse( + ctx: *mut sqlite3_context, + argc: c_int, + argv: *mut *mut sqlite3_value, +) where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate, + T: SqlFnOutput, +{ + let Some(pac) = aggregate_context(ctx, size_of::<*mut A>()) else { + ffi::sqlite3_result_error_nomem(ctx); + return; + }; + + let r = catch_unwind(|| { + let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::(); + assert!( + !boxed_aggr.is_null(), + "Internal error - null aggregate pointer" + ); + let mut ctx = Context { + ctx, + args: slice::from_raw_parts(argv, argc as usize), + }; + (*boxed_aggr).inverse(&mut ctx, &mut **pac) + }); + let r = match r { + Err(_) => { + report_error(ctx, &Error::UnwindingPanic); + return; + } + Ok(r) => r, + }; + match r { + Ok(_) => {} + Err(err) => report_error(ctx, &err), + }; +} + +unsafe extern "C" fn call_boxed_final(ctx: *mut sqlite3_context) +where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate, + T: SqlFnOutput, +{ + // Within the xFinal callback, it is customary to set N=0 in calls to + // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. + let a: Option = match aggregate_context(ctx, 0) { + Some(pac) => + { + #[expect(clippy::unnecessary_cast)] + if (*pac as *mut A).is_null() { + None + } else { + let a = Box::from_raw(*pac); + Some(*a) + } + } + None => None, + }; + + let r = catch_unwind(|| { + let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::(); + assert!( + !boxed_aggr.is_null(), + "Internal error - null aggregate pointer" + ); + let mut ctx = Context { ctx, args: &mut [] }; + (*boxed_aggr).finalize(&mut ctx, a) + }); + let t = match r { + Err(_) => { + report_error(ctx, &Error::UnwindingPanic); + return; + } + Ok(r) => r, + }; + sql_result(ctx, &[], t); +} + +#[cfg(feature = "window")] +unsafe extern "C" fn call_boxed_value(ctx: *mut sqlite3_context) +where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate, + T: SqlFnOutput, +{ + // Within the xValue callback, it is customary to set N=0 in calls to + // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. + let pac = aggregate_context(ctx, 0).filter(|&pac| { + #[expect(clippy::unnecessary_cast)] + !(*pac as *mut A).is_null() + }); + + let r = catch_unwind(|| { + let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::(); + assert!( + !boxed_aggr.is_null(), + "Internal error - null aggregate pointer" + ); + (*boxed_aggr).value(pac.map(|pac| &mut **pac)) + }); + let t = match r { + Err(_) => { + report_error(ctx, &Error::UnwindingPanic); + return; + } + Ok(r) => r, + }; + sql_result(ctx, &[], t); +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + #[cfg(feature = "window")] + use crate::functions::WindowAggregate; + use crate::functions::{Aggregate, Context, FunctionFlags, SqlFnArg, SubType}; + use crate::{Connection, Error, Result}; + use regex::Regex; + use std::ffi::c_double; + + fn half(ctx: &Context<'_>) -> Result { + assert!(!ctx.is_empty()); + assert_eq!(ctx.len(), 1, "called with unexpected number of arguments"); + assert!(unsafe { + ctx.get_connection() + .as_ref() + .map(::std::ops::Deref::deref) + .is_ok() + }); + let value = ctx.get::(0)?; + Ok(value / 2f64) + } + + #[test] + fn test_function_half() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_scalar_function( + c"half", + 1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + half, + )?; + let result: f64 = db.one_column("SELECT half(6)", [])?; + + assert!((3f64 - result).abs() < f64::EPSILON); + Ok(()) + } + + #[test] + fn test_remove_function() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_scalar_function( + c"half", + 1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + half, + )?; + assert!((3f64 - db.one_column::("SELECT half(6)", [])?).abs() < f64::EPSILON); + + db.remove_function(c"half", 1)?; + db.one_column::("SELECT half(6)", []).unwrap_err(); + Ok(()) + } + + // This implementation of a regexp scalar function uses SQLite's auxiliary data + // (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular + // expression multiple times within one query. + fn regexp_with_auxiliary(ctx: &Context<'_>) -> Result { + assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); + type BoxError = Box; + let regexp: std::sync::Arc = ctx + .get_or_create_aux(0, |vr| -> Result<_, BoxError> { + Ok(Regex::new(vr.as_str()?)?) + })?; + + let is_match = { + let text = ctx + .get_raw(1) + .as_str() + .map_err(|e| Error::UserFunctionError(e.into()))?; + + regexp.is_match(text) + }; + + Ok(is_match) + } + + #[test] + fn test_function_regexp_with_auxiliary() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch( + "BEGIN; + CREATE TABLE foo (x string); + INSERT INTO foo VALUES ('lisa'); + INSERT INTO foo VALUES ('lXsi'); + INSERT INTO foo VALUES ('lisX'); + END;", + )?; + db.create_scalar_function( + c"regexp", + 2, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + regexp_with_auxiliary, + )?; + + assert!(db.one_column::("SELECT regexp('l.s[aeiouy]', 'lisa')", [])?); + + assert_eq!( + 2, + db.one_column::( + "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1", + [], + )? + ); + Ok(()) + } + + #[test] + fn test_varargs_function() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_scalar_function( + c"my_concat", + -1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + |ctx| { + let mut ret = String::new(); + + for idx in 0..ctx.len() { + let s = ctx.get::(idx)?; + ret.push_str(&s); + } + + Ok(ret) + }, + )?; + + for &(expected, query) in &[ + ("", "SELECT my_concat()"), + ("onetwo", "SELECT my_concat('one', 'two')"), + ("abc", "SELECT my_concat('a', 'b', 'c')"), + ] { + assert_eq!(expected, db.one_column::(query, [])?); + } + Ok(()) + } + + #[test] + fn test_get_aux_type_checking() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_scalar_function(c"example", 2, FunctionFlags::default(), |ctx| { + if !ctx.get::(1)? { + ctx.set_aux::(0, 100)?; + } else { + assert_eq!(ctx.get_aux::(0), Err(Error::GetAuxWrongType)); + assert_eq!(*ctx.get_aux::(0)?.unwrap(), 100); + } + Ok(true) + })?; + + let res: bool = db.query_row( + "SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)", + [], + |r| r.get(0), + )?; + // Doesn't actually matter, we'll assert in the function if there's a problem. + assert!(res); + Ok(()) + } + + struct Sum; + struct Count; + + impl Aggregate> for Sum { + fn init(&self, _: &mut Context<'_>) -> Result { + Ok(0) + } + + fn step(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> { + *sum += ctx.get::(0)?; + Ok(()) + } + + fn finalize(&self, _: &mut Context<'_>, sum: Option) -> Result> { + Ok(sum) + } + } + + impl Aggregate for Count { + fn init(&self, _: &mut Context<'_>) -> Result { + Ok(0) + } + + fn step(&self, _ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> { + *sum += 1; + Ok(()) + } + + fn finalize(&self, _: &mut Context<'_>, sum: Option) -> Result { + Ok(sum.unwrap_or(0)) + } + } + + #[test] + fn test_sum() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_aggregate_function( + c"my_sum", + 1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + Sum, + )?; + + // sum should return NULL when given no columns (contrast with count below) + let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)"; + assert!(db.one_column::, _>(no_result, [])?.is_none()); + + let single_sum = "SELECT my_sum(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)"; + assert_eq!(4, db.one_column::(single_sum, [])?); + + let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \ + 2, 1)"; + let result: (i64, i64) = db.query_row(dual_sum, [], |r| Ok((r.get(0)?, r.get(1)?)))?; + assert_eq!((4, 2), result); + Ok(()) + } + + #[test] + fn test_count() -> Result<()> { + let db = Connection::open_in_memory()?; + db.create_aggregate_function( + c"my_count", + -1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + Count, + )?; + + // count should return 0 when given no columns (contrast with sum above) + let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)"; + assert_eq!(db.one_column::(no_result, [])?, 0); + + let single_sum = "SELECT my_count(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)"; + assert_eq!(2, db.one_column::(single_sum, [])?); + Ok(()) + } + + #[cfg(feature = "window")] + impl WindowAggregate> for Sum { + fn inverse(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> { + *sum -= ctx.get::(0)?; + Ok(()) + } + + fn value(&self, sum: Option<&mut i64>) -> Result> { + Ok(sum.copied()) + } + } + + #[test] + #[cfg(feature = "window")] + fn test_window() -> Result<()> { + use fallible_iterator::FallibleIterator; + + let db = Connection::open_in_memory()?; + db.create_window_function( + c"sumint", + 1, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, + Sum, + )?; + db.execute_batch( + "CREATE TABLE t3(x, y); + INSERT INTO t3 VALUES('a', 4), + ('b', 5), + ('c', 3), + ('d', 8), + ('e', 1);", + )?; + + let mut stmt = db.prepare( + "SELECT x, sumint(y) OVER ( + ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING + ) AS sum_y + FROM t3 ORDER BY x;", + )?; + + let results: Vec<(String, i64)> = stmt + .query([])? + .map(|row| Ok((row.get("x")?, row.get("sum_y")?))) + .collect()?; + let expected = vec![ + ("a".to_owned(), 9), + ("b".to_owned(), 12), + ("c".to_owned(), 16), + ("d".to_owned(), 12), + ("e".to_owned(), 9), + ]; + assert_eq!(expected, results); + Ok(()) + } + + #[test] + fn test_sub_type() -> Result<()> { + fn test_getsubtype(ctx: &Context<'_>) -> Result { + Ok(ctx.get_subtype(0) as i32) + } + fn test_setsubtype(ctx: &Context<'_>) -> Result<(SqlFnArg, SubType)> { + use std::ffi::c_uint; + let value = ctx.get_arg(0); + let sub_type = ctx.get::(1)?; + Ok((value, Some(sub_type))) + } + let db = Connection::open_in_memory()?; + db.create_scalar_function( + c"test_getsubtype", + 1, + FunctionFlags::SQLITE_UTF8, + test_getsubtype, + )?; + db.create_scalar_function( + c"test_setsubtype", + 2, + FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_RESULT_SUBTYPE, + test_setsubtype, + )?; + let result: i32 = db.one_column("SELECT test_getsubtype('hello');", [])?; + assert_eq!(0, result); + + let result: i32 = + db.one_column("SELECT test_getsubtype(test_setsubtype('hello',123));", [])?; + assert_eq!(123, result); + + Ok(()) + } + + #[test] + fn test_blob() -> Result<()> { + fn test_len(ctx: &Context<'_>) -> Result { + let blob = ctx.get_raw(0); + Ok(blob + .as_bytes_or_null()? + .map_or(0, |b| b.len().try_into().unwrap())) + } + let db = Connection::open_in_memory()?; + db.create_scalar_function("test_len", 1, FunctionFlags::SQLITE_DETERMINISTIC, test_len)?; + assert_eq!( + 6, + db.one_column::("SELECT test_len(X'53514C697465');", [])? + ); + assert_eq!(0, db.one_column::("SELECT test_len(X'');", [])?); + assert_eq!(0, db.one_column::("SELECT test_len(NULL);", [])?); + Ok(()) + } + + #[test] + #[cfg(feature = "pointer")] + fn test_rc_pointer() -> Result<()> { + use crate::types::ToSqlOutput; + use std::ops::Deref; + use std::rc::Rc; + + const PTR_TYPE: &std::ffi::CStr = c"my_rust_ptr"; + let rc = Rc::new(1); + { + let ptr = ToSqlOutput::from_rc(rc.clone(), PTR_TYPE); + assert_eq!(2, Rc::strong_count(&rc)); + fn myfunc(ctx: &Context<'_>) -> Result> { + let x = unsafe { ctx.get_pointer(0, PTR_TYPE) }; + assert_eq!(x, Some(&1)); + Ok(ToSqlOutput::from_rc(Rc::new(*x.unwrap()), PTR_TYPE)) + } + let db = Connection::open_in_memory()?; + db.create_scalar_function("myfunc", 1, FunctionFlags::SQLITE_DETERMINISTIC, myfunc)?; + let mut stmt = db.prepare("SELECT myfunc(?)")?; + let result = stmt.query_one([ptr], |r| { + unsafe { r.get_pointer::<_, i32>(0, PTR_TYPE) }.map(|opt| opt.cloned()) + })?; + assert_eq!(result.unwrap(), *rc.deref()); + } + assert_eq!(1, Rc::strong_count(&rc)); + Ok(()) + } + + #[test] + #[cfg(feature = "pointer")] + fn test_box_pointer() -> Result<()> { + use crate::types::ToSqlOutput; + + const PTR_TYPE: &std::ffi::CStr = c"my_rust_ptr"; + let value = 1; + let ptr = ToSqlOutput::new_boxed(value, PTR_TYPE); + fn myfunc(ctx: &Context<'_>) -> Result> { + let x = unsafe { ctx.get_pointer(0, PTR_TYPE) }; + assert_eq!(x, Some(&1)); + Ok(ToSqlOutput::new_boxed(*x.unwrap(), PTR_TYPE)) + } + let db = Connection::open_in_memory()?; + db.create_scalar_function("myfunc", 1, FunctionFlags::SQLITE_DETERMINISTIC, myfunc)?; + let mut stmt = db.prepare("SELECT myfunc(?)")?; + let result = stmt.query_one([ptr], |r| { + unsafe { r.get_pointer::<_, i32>(0, PTR_TYPE) }.map(|opt| opt.cloned()) + })?; + assert_eq!(result.unwrap(), value); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/hooks/mod.rs b/vendor/rusqlite/src/hooks/mod.rs new file mode 100644 index 0000000..efa7bce --- /dev/null +++ b/vendor/rusqlite/src/hooks/mod.rs @@ -0,0 +1,1002 @@ +//! Commit, Data Change and Rollback Notification Callbacks +#![expect(non_camel_case_types)] + +use std::ffi::{c_char, c_int, c_void, CStr}; +use std::panic::catch_unwind; +use std::ptr; + +use crate::ffi; + +use crate::{error::decode_result_raw, Connection, InnerConnection, Result}; + +#[cfg(feature = "preupdate_hook")] +pub use preupdate_hook::*; + +#[cfg(feature = "preupdate_hook")] +mod preupdate_hook; + +/// Action Codes +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum Action { + /// Unsupported / unexpected action + UNKNOWN = -1, + /// DELETE command + SQLITE_DELETE = ffi::SQLITE_DELETE, + /// INSERT command + SQLITE_INSERT = ffi::SQLITE_INSERT, + /// UPDATE command + SQLITE_UPDATE = ffi::SQLITE_UPDATE, +} + +impl From for Action { + #[inline] + fn from(code: i32) -> Self { + match code { + ffi::SQLITE_DELETE => Self::SQLITE_DELETE, + ffi::SQLITE_INSERT => Self::SQLITE_INSERT, + ffi::SQLITE_UPDATE => Self::SQLITE_UPDATE, + _ => Self::UNKNOWN, + } + } +} + +/// The context received by an authorizer hook. +/// +/// See for more info. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct AuthContext<'c> { + /// The action to be authorized. + pub action: AuthAction<'c>, + + /// The database name, if applicable. + pub database_name: Option<&'c str>, + + /// The inner-most trigger or view responsible for the access attempt. + /// `None` if the access attempt was made by top-level SQL code. + pub accessor: Option<&'c str>, +} + +/// Actions and arguments found within a statement during +/// preparation. +/// +/// See for more info. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum AuthAction<'c> { + /// This variant is not normally produced by SQLite. You may encounter it + // if you're using a different version than what's supported by this library. + Unknown { + /// The unknown authorization action code. + code: i32, + /// The third arg to the authorizer callback. + arg1: Option<&'c str>, + /// The fourth arg to the authorizer callback. + arg2: Option<&'c str>, + }, + CreateIndex { + index_name: &'c str, + table_name: &'c str, + }, + CreateTable { + table_name: &'c str, + }, + CreateTempIndex { + index_name: &'c str, + table_name: &'c str, + }, + CreateTempTable { + table_name: &'c str, + }, + CreateTempTrigger { + trigger_name: &'c str, + table_name: &'c str, + }, + CreateTempView { + view_name: &'c str, + }, + CreateTrigger { + trigger_name: &'c str, + table_name: &'c str, + }, + CreateView { + view_name: &'c str, + }, + Delete { + table_name: &'c str, + }, + DropIndex { + index_name: &'c str, + table_name: &'c str, + }, + DropTable { + table_name: &'c str, + }, + DropTempIndex { + index_name: &'c str, + table_name: &'c str, + }, + DropTempTable { + table_name: &'c str, + }, + DropTempTrigger { + trigger_name: &'c str, + table_name: &'c str, + }, + DropTempView { + view_name: &'c str, + }, + DropTrigger { + trigger_name: &'c str, + table_name: &'c str, + }, + DropView { + view_name: &'c str, + }, + Insert { + table_name: &'c str, + }, + Pragma { + pragma_name: &'c str, + /// The pragma value, if present (e.g., `PRAGMA name = value;`). + pragma_value: Option<&'c str>, + }, + Read { + table_name: &'c str, + column_name: &'c str, + }, + Select, + Transaction { + operation: TransactionOperation, + }, + Update { + table_name: &'c str, + column_name: &'c str, + }, + Attach { + filename: &'c str, + }, + Detach { + database_name: &'c str, + }, + AlterTable { + database_name: &'c str, + table_name: &'c str, + }, + Reindex { + index_name: &'c str, + }, + Analyze { + table_name: &'c str, + }, + CreateVtable { + table_name: &'c str, + module_name: &'c str, + }, + DropVtable { + table_name: &'c str, + module_name: &'c str, + }, + Function { + function_name: &'c str, + }, + Savepoint { + operation: TransactionOperation, + savepoint_name: &'c str, + }, + Recursive, +} + +impl<'c> AuthAction<'c> { + fn from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self { + match (code, arg1, arg2) { + (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex { + index_name, + table_name, + }, + (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name }, + (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => { + Self::CreateTempIndex { + index_name, + table_name, + } + } + (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => { + Self::CreateTempTable { table_name } + } + (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => { + Self::CreateTempTrigger { + trigger_name, + table_name, + } + } + (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => { + Self::CreateTempView { view_name } + } + (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => { + Self::CreateTrigger { + trigger_name, + table_name, + } + } + (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name }, + (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name }, + (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex { + index_name, + table_name, + }, + (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name }, + (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => { + Self::DropTempIndex { + index_name, + table_name, + } + } + (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => { + Self::DropTempTable { table_name } + } + (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => { + Self::DropTempTrigger { + trigger_name, + table_name, + } + } + (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name }, + (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger { + trigger_name, + table_name, + }, + (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name }, + (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name }, + (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma { + pragma_name, + pragma_value, + }, + (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read { + table_name, + column_name, + }, + (ffi::SQLITE_SELECT, ..) => Self::Select, + (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction { + operation: TransactionOperation::from_str(operation_str), + }, + (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update { + table_name, + column_name, + }, + (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename }, + (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name }, + (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable { + database_name, + table_name, + }, + (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name }, + (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name }, + (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => { + Self::CreateVtable { + table_name, + module_name, + } + } + (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable { + table_name, + module_name, + }, + (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name }, + (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint { + operation: TransactionOperation::from_str(operation_str), + savepoint_name, + }, + (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive, + (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 }, + } + } +} + +pub(crate) type BoxedAuthorizer = + Box FnMut(AuthContext<'c>) -> Authorization + Send + 'static>; + +/// A transaction operation. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum TransactionOperation { + Unknown, + Begin, + Release, + Rollback, +} + +impl TransactionOperation { + fn from_str(op_str: &str) -> Self { + match op_str { + "BEGIN" => Self::Begin, + "RELEASE" => Self::Release, + "ROLLBACK" => Self::Rollback, + _ => Self::Unknown, + } + } +} + +/// [`authorizer`](Connection::authorizer) return code +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Authorization { + /// Authorize the action. + Allow, + /// Don't allow access, but don't trigger an error either. + Ignore, + /// Trigger an error. + Deny, +} + +impl Authorization { + fn into_raw(self) -> c_int { + match self { + Self::Allow => ffi::SQLITE_OK, + Self::Ignore => ffi::SQLITE_IGNORE, + Self::Deny => ffi::SQLITE_DENY, + } + } +} + +impl Connection { + /// Register a callback function to be invoked whenever + /// a transaction is committed. + /// + /// The callback returns `true` to rollback. + #[inline] + pub fn commit_hook(&self, hook: Option) -> Result<()> + where + F: FnMut() -> bool + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().commit_hook(hook); + Ok(()) + } + + /// Register a callback function to be invoked whenever + /// a transaction is rolled back. + #[inline] + pub fn rollback_hook(&self, hook: Option) -> Result<()> + where + F: FnMut() + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().rollback_hook(hook); + Ok(()) + } + + /// Register a callback function to be invoked whenever + /// a row is updated, inserted or deleted in a rowid table. + /// + /// The callback parameters are: + /// + /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or + /// `SQLITE_DELETE`), + /// - the name of the database ("main", "temp", ...), + /// - the name of the table that is updated, + /// - the ROWID of the row that is updated. + #[inline] + pub fn update_hook(&self, hook: Option) -> Result<()> + where + F: FnMut(Action, &str, &str, i64) + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().update_hook(hook); + Ok(()) + } + + /// Register a callback that is invoked each time data is committed to a database in wal mode. + /// + /// A single database handle may have at most a single write-ahead log callback registered at one time. + /// Calling `wal_hook` replaces any previously registered write-ahead log callback. + /// Note that the `sqlite3_wal_autocheckpoint()` interface and the `wal_autocheckpoint` pragma + /// both invoke `sqlite3_wal_hook()` and will overwrite any prior `sqlite3_wal_hook()` settings. + pub fn wal_hook(&self, hook: Option Result<()>>) { + unsafe extern "C" fn wal_hook_callback( + client_data: *mut c_void, + db: *mut ffi::sqlite3, + db_name: *const c_char, + pages: c_int, + ) -> c_int { + let hook_fn: fn(&Wal, c_int) -> Result<()> = std::mem::transmute(client_data); + let wal = Wal { db, db_name }; + catch_unwind(|| match hook_fn(&wal, pages) { + Ok(_) => ffi::SQLITE_OK, + Err(e) => e + .sqlite_error() + .map_or(ffi::SQLITE_ERROR, |x| x.extended_code), + }) + .unwrap_or_default() + } + let c = self.db.borrow_mut(); + unsafe { + ffi::sqlite3_wal_hook( + c.db(), + hook.as_ref().map(|_| wal_hook_callback as _), + hook.map_or_else(ptr::null_mut, |f| f as *mut c_void), + ); + } + } + + /// Register a query progress callback. + /// + /// The parameter `num_ops` is the approximate number of virtual machine + /// instructions that are evaluated between successive invocations of the + /// `handler`. If `num_ops` is less than one then the progress handler + /// is disabled. + /// + /// If the progress callback returns `true`, the operation is interrupted. + pub fn progress_handler(&self, num_ops: c_int, handler: Option) -> Result<()> + where + F: FnMut() -> bool + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().progress_handler(num_ops, handler); + Ok(()) + } + + /// Register an authorizer callback that's invoked + /// as a statement is being prepared. + #[inline] + pub fn authorizer<'c, F>(&self, hook: Option) -> Result<()> + where + F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().authorizer(hook); + Ok(()) + } +} + +/// Checkpoint mode +#[derive(Clone, Copy)] +#[repr(i32)] +#[non_exhaustive] +pub enum CheckpointMode { + /// Do as much as possible w/o blocking + PASSIVE = ffi::SQLITE_CHECKPOINT_PASSIVE, + /// Wait for writers, then checkpoint + FULL = ffi::SQLITE_CHECKPOINT_FULL, + /// Like FULL but wait for readers + RESTART = ffi::SQLITE_CHECKPOINT_RESTART, + /// Like RESTART but also truncate WAL + TRUNCATE = ffi::SQLITE_CHECKPOINT_TRUNCATE, + /// Do no work at all + #[cfg(feature = "modern_sqlite")] // 3.51.0 + NOOP = -1, //ffi::SQLITE_CHECKPOINT_NOOP, +} + +/// Write-Ahead Log +pub struct Wal { + db: *mut ffi::sqlite3, + db_name: *const c_char, +} + +impl Wal { + /// Checkpoint a database + pub fn checkpoint(&self) -> Result<()> { + unsafe { decode_result_raw(self.db, ffi::sqlite3_wal_checkpoint(self.db, self.db_name)) } + } + /// Checkpoint a database + pub fn checkpoint_v2(&self, mode: CheckpointMode) -> Result<(c_int, c_int)> { + let mut n_log = 0; + let mut n_ckpt = 0; + unsafe { + decode_result_raw( + self.db, + ffi::sqlite3_wal_checkpoint_v2( + self.db, + self.db_name, + mode as c_int, + &mut n_log, + &mut n_ckpt, + ), + )? + }; + Ok((n_log, n_ckpt)) + } + + /// Name of the database that was written to + pub fn name(&self) -> &CStr { + unsafe { CStr::from_ptr(self.db_name) } + } +} + +impl InnerConnection { + #[inline] + pub fn remove_hooks(&mut self) { + self.update_hook(None::); + self.commit_hook(None:: bool>); + self.rollback_hook(None::); + self.progress_handler(0, None:: bool>); + self.authorizer(None::) -> Authorization>); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.commit_hook(Some(|| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// true + /// })); + /// } + /// assert!(db + /// .execute_batch( + /// "BEGIN; + /// CREATE TABLE foo (t TEXT); + /// COMMIT;", + /// ) + /// .is_err()); + /// Ok(()) + /// } + /// ``` + fn commit_hook(&mut self, hook: Option) + where + F: FnMut() -> bool + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) -> c_int + where + F: FnMut() -> bool, + { + let r = catch_unwind(|| { + let boxed_hook: *mut F = p_arg.cast::(); + (*boxed_hook)() + }); + c_int::from(r.unwrap_or_default()) + } + let boxed_hook = hook.map(Box::new); + unsafe { + ffi::sqlite3_commit_hook( + self.db(), + boxed_hook.as_ref().map(|_| call_boxed_closure:: as _), + boxed_hook + .as_ref() + .map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _), + ) + }; + self.commit_hook = boxed_hook.map(|bh| bh as _); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.rollback_hook(Some(|| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// })); + /// } + /// assert!(db + /// .execute_batch( + /// "BEGIN; + /// CREATE TABLE foo (t TEXT); + /// ROLLBACK;", + /// ) + /// .is_err()); + /// Ok(()) + /// } + /// ``` + fn rollback_hook(&mut self, hook: Option) + where + F: FnMut() + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) + where + F: FnMut(), + { + drop(catch_unwind(|| { + let boxed_hook: *mut F = p_arg.cast::(); + (*boxed_hook)(); + })); + } + + let boxed_hook = hook.map(Box::new); + unsafe { + ffi::sqlite3_rollback_hook( + self.db(), + boxed_hook.as_ref().map(|_| call_boxed_closure:: as _), + boxed_hook + .as_ref() + .map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _), + ) + }; + self.rollback_hook = boxed_hook.map(|bh| bh as _); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.update_hook(Some(|_, _: &str, _: &str, _| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// })); + /// } + /// db.execute_batch("CREATE TABLE foo AS SELECT 1 AS bar;") + /// } + /// ``` + fn update_hook(&mut self, hook: Option) + where + F: FnMut(Action, &str, &str, i64) + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure( + p_arg: *mut c_void, + action_code: c_int, + p_db_name: *const c_char, + p_table_name: *const c_char, + row_id: i64, + ) where + F: FnMut(Action, &str, &str, i64), + { + let action = Action::from(action_code); + drop(catch_unwind(|| { + let boxed_hook: *mut F = p_arg.cast::(); + (*boxed_hook)( + action, + expect_utf8(p_db_name, "database name"), + expect_utf8(p_table_name, "table name"), + row_id, + ); + })); + } + + let boxed_hook = hook.map(Box::new); + unsafe { + ffi::sqlite3_update_hook( + self.db(), + boxed_hook.as_ref().map(|_| call_boxed_closure:: as _), + boxed_hook + .as_ref() + .map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _), + ) + }; + self.update_hook = boxed_hook.map(|bh| bh as _); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.progress_handler( + /// 1, + /// Some(|| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// true + /// }), + /// ); + /// } + /// assert!(db + /// .execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") + /// .is_err()); + /// Ok(()) + /// } + /// ``` + fn progress_handler(&mut self, num_ops: c_int, handler: Option) + where + F: FnMut() -> bool + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) -> c_int + where + F: FnMut() -> bool, + { + let r = catch_unwind(|| { + let boxed_handler: *mut F = p_arg.cast::(); + (*boxed_handler)() + }); + c_int::from(r.unwrap_or_default()) + } + + let boxed_handler = handler.map(Box::new); + unsafe { + ffi::sqlite3_progress_handler( + self.db(), + num_ops, + boxed_handler.as_ref().map(|_| call_boxed_closure:: as _), + boxed_handler + .as_ref() + .map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _), + ) + }; + self.progress_handler = boxed_handler.map(|bh| bh as _); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.authorizer(Some(|_: rusqlite::hooks::AuthContext<'_>| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// rusqlite::hooks::Authorization::Deny + /// })); + /// } + /// assert!(db + /// .execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") + /// .is_err()); + /// Ok(()) + /// } + /// ``` + fn authorizer<'c, F>(&'c mut self, authorizer: Option) + where + F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure<'c, F>( + p_arg: *mut c_void, + action_code: c_int, + param1: *const c_char, + param2: *const c_char, + db_name: *const c_char, + trigger_or_view_name: *const c_char, + ) -> c_int + where + F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static, + { + catch_unwind(|| { + let action = AuthAction::from_raw( + action_code, + expect_optional_utf8(param1, "authorizer param 1"), + expect_optional_utf8(param2, "authorizer param 2"), + ); + let auth_ctx = AuthContext { + action, + database_name: expect_optional_utf8(db_name, "database name"), + accessor: expect_optional_utf8( + trigger_or_view_name, + "accessor (inner-most trigger or view)", + ), + }; + let boxed_hook: *mut F = p_arg.cast::(); + (*boxed_hook)(auth_ctx) + }) + .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw) + } + + let boxed_authorizer = authorizer.map(Box::new); + + match unsafe { + ffi::sqlite3_set_authorizer( + self.db(), + boxed_authorizer + .as_ref() + .map(|_| call_boxed_closure::<'c, F> as _), + boxed_authorizer + .as_ref() + .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _), + ) + } { + ffi::SQLITE_OK => { + self.authorizer = boxed_authorizer.map(|ba| ba as _); + } + err_code => { + // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE` + // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid. + // This library does not allow constructing a null db ptr, so if this branch + // is hit, something very bad has happened. Panicking instead of returning + // `Result` keeps this hook's API consistent with the others. + panic!("unexpectedly failed to set_authorizer: {}", unsafe { + crate::error::error_from_handle(self.db(), err_code) + }); + } + } + } +} + +unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str { + expect_optional_utf8(p_str, description) + .unwrap_or_else(|| panic!("received empty {description}")) +} + +unsafe fn expect_optional_utf8<'a>( + p_str: *const c_char, + description: &'static str, +) -> Option<&'a str> { + if p_str.is_null() { + return None; + } + CStr::from_ptr(p_str) + .to_str() + .unwrap_or_else(|_| panic!("received non-utf8 string as {description}")) + .into() +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::Action; + use crate::{Connection, Result, MAIN_DB}; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[test] + fn test_commit_hook() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + db.commit_hook(Some(|| { + CALLED.store(true, Ordering::Relaxed); + false + }))?; + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_fn_commit_hook() -> Result<()> { + let db = Connection::open_in_memory()?; + + fn hook() -> bool { + true + } + + db.commit_hook(Some(hook))?; + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") + .unwrap_err(); + Ok(()) + } + + #[test] + fn test_rollback_hook() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + db.rollback_hook(Some(|| { + CALLED.store(true, Ordering::Relaxed); + }))?; + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_update_hook() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + db.update_hook(Some(|action, db: &str, tbl: &str, row_id| { + assert_eq!(Action::SQLITE_INSERT, action); + assert_eq!("main", db); + assert_eq!("foo", tbl); + assert_eq!(1, row_id); + CALLED.store(true, Ordering::Relaxed); + }))?; + db.execute_batch("CREATE TABLE foo (t TEXT)")?; + db.execute_batch("INSERT INTO foo VALUES ('lisa')")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_progress_handler() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + db.progress_handler( + 1, + Some(|| { + CALLED.store(true, Ordering::Relaxed); + false + }), + )?; + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_progress_handler_interrupt() -> Result<()> { + let db = Connection::open_in_memory()?; + + fn handler() -> bool { + true + } + + db.progress_handler(1, Some(handler))?; + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") + .unwrap_err(); + Ok(()) + } + + #[test] + fn test_authorizer() -> Result<()> { + use super::{AuthAction, AuthContext, Authorization}; + + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")?; + + let authorizer = move |ctx: AuthContext<'_>| match ctx.action { + AuthAction::Read { + column_name: "private", + .. + } => Authorization::Ignore, + AuthAction::DropTable { .. } => Authorization::Deny, + AuthAction::Pragma { .. } => panic!("shouldn't be called"), + _ => Authorization::Allow, + }; + + db.authorizer(Some(authorizer))?; + db.execute_batch( + "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;", + )?; + db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> { + assert_eq!(row.get::<_, String>("public")?, "pub txt"); + assert!(row.get::<_, Option>("private")?.is_none()); + Ok(()) + })?; + db.execute_batch("DROP TABLE foo").unwrap_err(); + + db.authorizer(None::) -> Authorization>)?; + db.execute_batch("PRAGMA user_version=1")?; // Disallowed by first authorizer, but it's now removed. + + Ok(()) + } + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn wal_hook() -> Result<()> { + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("wal-hook.db3"); + + let db = Connection::open(&path)?; + let journal_mode: String = + db.pragma_update_and_check(None, "journal_mode", "wal", |row| row.get(0))?; + assert_eq!(journal_mode, "wal"); + + static CALLED: AtomicBool = AtomicBool::new(false); + db.wal_hook(Some(|wal, pages| { + assert_eq!(wal.name(), MAIN_DB); + assert!(pages > 0); + CALLED.swap(true, Ordering::Relaxed); + wal.checkpoint() + })); + db.execute_batch("CREATE TABLE x(c);")?; + assert!(CALLED.load(Ordering::Relaxed)); + + db.wal_hook(Some(|wal, pages| { + assert!(pages > 0); + let (log, ckpt) = wal.checkpoint_v2(super::CheckpointMode::TRUNCATE)?; + assert_eq!(log, 0); + assert_eq!(ckpt, 0); + Ok(()) + })); + db.execute_batch("CREATE TABLE y(c);")?; + + db.wal_hook(None); + Ok(()) + } + + #[test] + fn test_non_owning_hooks_cleanup() -> Result<()> { + let conn = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + CALLED.store(false, Ordering::Relaxed); + conn.commit_hook(Some(|| { + CALLED.store(true, Ordering::Relaxed); + false + }))?; + + let non_owning_conn = unsafe { Connection::from_handle(conn.handle()) }?; + drop(non_owning_conn); + + conn.execute_batch("CREATE TABLE test(value)")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/hooks/preupdate_hook.rs b/vendor/rusqlite/src/hooks/preupdate_hook.rs new file mode 100644 index 0000000..d5f946b --- /dev/null +++ b/vendor/rusqlite/src/hooks/preupdate_hook.rs @@ -0,0 +1,362 @@ +use std::ffi::{c_char, c_int, c_void}; +use std::fmt::Debug; +use std::panic::catch_unwind; +use std::ptr; + +use super::expect_utf8; +use super::Action; +use crate::error::check; +use crate::ffi; +use crate::inner_connection::InnerConnection; +use crate::types::ValueRef; +use crate::Connection; +use crate::Result; + +/// The possible cases for when a PreUpdateHook gets triggered. Allows access to the relevant +/// functions for each case through the contained values. +#[derive(Debug)] +pub enum PreUpdateCase { + /// Pre-update hook was triggered by an insert. + Insert(PreUpdateNewValueAccessor), + /// Pre-update hook was triggered by a delete. + Delete(PreUpdateOldValueAccessor), + /// Pre-update hook was triggered by an update. + Update { + #[allow(missing_docs)] + old_value_accessor: PreUpdateOldValueAccessor, + #[allow(missing_docs)] + new_value_accessor: PreUpdateNewValueAccessor, + }, + /// This variant is not normally produced by SQLite. You may encounter it + /// if you're using a different version than what's supported by this library. + Unknown, +} + +impl From for Action { + fn from(puc: PreUpdateCase) -> Action { + match puc { + PreUpdateCase::Insert(_) => Action::SQLITE_INSERT, + PreUpdateCase::Delete(_) => Action::SQLITE_DELETE, + PreUpdateCase::Update { .. } => Action::SQLITE_UPDATE, + PreUpdateCase::Unknown => Action::UNKNOWN, + } + } +} + +/// An accessor to access the old values of the row being deleted/updated during the preupdate callback. +#[derive(Debug)] +pub struct PreUpdateOldValueAccessor { + db: *mut ffi::sqlite3, + old_row_id: i64, +} + +impl PreUpdateOldValueAccessor { + /// Get the amount of columns in the row being deleted/updated. + pub fn get_column_count(&self) -> i32 { + unsafe { ffi::sqlite3_preupdate_count(self.db) } + } + + /// Get 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 { ffi::sqlite3_preupdate_depth(self.db) } + } + + /// Get the row id of the row being updated/deleted. + pub fn get_old_row_id(&self) -> i64 { + self.old_row_id + } + + /// Get the value of the row being updated/deleted at the specified index. + pub fn get_old_column_value(&self, i: i32) -> Result> { + let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut(); + unsafe { + check(ffi::sqlite3_preupdate_old(self.db, i, &mut p_value))?; + Ok(ValueRef::from_value(p_value)) + } + } +} + +/// An accessor to access the new values of the row being inserted/updated +/// during the preupdate callback. +#[derive(Debug)] +pub struct PreUpdateNewValueAccessor { + db: *mut ffi::sqlite3, + new_row_id: i64, +} + +impl PreUpdateNewValueAccessor { + /// Get the amount of columns in the row being inserted/updated. + pub fn get_column_count(&self) -> i32 { + unsafe { ffi::sqlite3_preupdate_count(self.db) } + } + + /// Get 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 { ffi::sqlite3_preupdate_depth(self.db) } + } + + /// Get the row id of the row being inserted/updated. + pub fn get_new_row_id(&self) -> i64 { + self.new_row_id + } + + /// Get the value of the row being updated/deleted at the specified index. + pub fn get_new_column_value(&self, i: i32) -> Result> { + let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut(); + unsafe { + check(ffi::sqlite3_preupdate_new(self.db, i, &mut p_value))?; + Ok(ValueRef::from_value(p_value)) + } + } +} + +impl Connection { + /// Register a callback function to be invoked before + /// a row is updated, inserted or deleted. + /// + /// The callback parameters are: + /// + /// - the name of the database ("main", "temp", ...), + /// - the name of the table that is updated, + /// - a variant of the PreUpdateCase enum which allows access to extra functions depending + /// on whether it's an update, delete or insert. + #[inline] + pub fn preupdate_hook(&self, hook: Option) -> Result<()> + where + F: FnMut(Action, &str, &str, &PreUpdateCase) + Send + 'static, + { + self.db.borrow().check_owned()?; + self.db.borrow_mut().preupdate_hook(hook); + Ok(()) + } +} + +impl InnerConnection { + #[inline] + pub fn remove_preupdate_hook(&mut self) { + self.preupdate_hook(None::); + } + + /// ```compile_fail + /// use rusqlite::{Connection, Result, hooks::PreUpdateCase}; + /// fn main() -> Result<()> { + /// let db = Connection::open_in_memory()?; + /// { + /// let mut called = std::sync::atomic::AtomicBool::new(false); + /// db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| { + /// called.store(true, std::sync::atomic::Ordering::Relaxed); + /// })); + /// } + /// db.execute_batch("CREATE TABLE foo AS SELECT 1 AS bar;") + /// } + /// ``` + fn preupdate_hook(&mut self, hook: Option) + where + F: FnMut(Action, &str, &str, &PreUpdateCase) + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure( + p_arg: *mut c_void, + sqlite: *mut ffi::sqlite3, + action_code: c_int, + db_name: *const c_char, + tbl_name: *const c_char, + old_row_id: i64, + new_row_id: i64, + ) where + F: FnMut(Action, &str, &str, &PreUpdateCase), + { + let action = Action::from(action_code); + + let preupdate_case = match action { + Action::SQLITE_INSERT => PreUpdateCase::Insert(PreUpdateNewValueAccessor { + db: sqlite, + new_row_id, + }), + Action::SQLITE_DELETE => PreUpdateCase::Delete(PreUpdateOldValueAccessor { + db: sqlite, + old_row_id, + }), + Action::SQLITE_UPDATE => PreUpdateCase::Update { + old_value_accessor: PreUpdateOldValueAccessor { + db: sqlite, + old_row_id, + }, + new_value_accessor: PreUpdateNewValueAccessor { + db: sqlite, + new_row_id, + }, + }, + Action::UNKNOWN => PreUpdateCase::Unknown, + }; + + drop(catch_unwind(|| { + let boxed_hook: *mut F = p_arg.cast::(); + (*boxed_hook)( + action, + expect_utf8(db_name, "database name"), + expect_utf8(tbl_name, "table name"), + &preupdate_case, + ); + })); + } + + let boxed_hook = hook.map(Box::new); + unsafe { + ffi::sqlite3_preupdate_hook( + self.db(), + boxed_hook.as_ref().map(|_| call_boxed_closure:: as _), + boxed_hook + .as_ref() + .map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _), + ) + }; + self.preupdate_hook = boxed_hook.map(|bh| bh as _); + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use std::sync::atomic::{AtomicBool, Ordering}; + + use super::super::Action; + use super::PreUpdateCase; + use crate::{Connection, Result}; + + #[test] + fn test_preupdate_hook_insert() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + + db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| { + assert_eq!(Action::SQLITE_INSERT, action); + assert_eq!("main", db); + assert_eq!("foo", tbl); + match case { + PreUpdateCase::Insert(accessor) => { + assert_eq!(1, accessor.get_column_count()); + assert_eq!(1, accessor.get_new_row_id()); + assert_eq!(0, accessor.get_query_depth()); + // out of bounds access should return an error + assert!(accessor.get_new_column_value(1).is_err()); + assert_eq!( + "lisa", + accessor.get_new_column_value(0).unwrap().as_str().unwrap() + ); + assert_eq!(0, accessor.get_query_depth()); + } + _ => panic!("wrong preupdate case"), + } + CALLED.store(true, Ordering::Relaxed); + }))?; + db.execute_batch("CREATE TABLE foo (t TEXT)")?; + db.execute_batch("INSERT INTO foo VALUES ('lisa')")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_preupdate_hook_delete() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + + db.execute_batch("CREATE TABLE foo (t TEXT)")?; + db.execute_batch("INSERT INTO foo VALUES ('lisa')")?; + + db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| { + assert_eq!(Action::SQLITE_DELETE, action); + assert_eq!("main", db); + assert_eq!("foo", tbl); + match case { + PreUpdateCase::Delete(accessor) => { + assert_eq!(1, accessor.get_column_count()); + assert_eq!(1, accessor.get_old_row_id()); + assert_eq!(0, accessor.get_query_depth()); + // out of bounds access should return an error + assert!(accessor.get_old_column_value(1).is_err()); + assert_eq!( + "lisa", + accessor.get_old_column_value(0).unwrap().as_str().unwrap() + ); + assert_eq!(0, accessor.get_query_depth()); + } + _ => panic!("wrong preupdate case"), + } + CALLED.store(true, Ordering::Relaxed); + }))?; + + db.execute_batch("DELETE from foo")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_preupdate_hook_update() -> Result<()> { + let db = Connection::open_in_memory()?; + + static CALLED: AtomicBool = AtomicBool::new(false); + + db.execute_batch("CREATE TABLE foo (t TEXT)")?; + db.execute_batch("INSERT INTO foo VALUES ('lisa')")?; + + db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| { + assert_eq!(Action::SQLITE_UPDATE, action); + assert_eq!("main", db); + assert_eq!("foo", tbl); + match case { + PreUpdateCase::Update { + old_value_accessor, + new_value_accessor, + } => { + assert_eq!(1, old_value_accessor.get_column_count()); + assert_eq!(1, old_value_accessor.get_old_row_id()); + assert_eq!(0, old_value_accessor.get_query_depth()); + // out of bounds access should return an error + assert!(old_value_accessor.get_old_column_value(1).is_err()); + assert_eq!( + "lisa", + old_value_accessor + .get_old_column_value(0) + .unwrap() + .as_str() + .unwrap() + ); + assert_eq!(0, old_value_accessor.get_query_depth()); + + assert_eq!(1, new_value_accessor.get_column_count()); + assert_eq!(1, new_value_accessor.get_new_row_id()); + assert_eq!(0, new_value_accessor.get_query_depth()); + // out of bounds access should return an error + assert!(new_value_accessor.get_new_column_value(1).is_err()); + assert_eq!( + "janice", + new_value_accessor + .get_new_column_value(0) + .unwrap() + .as_str() + .unwrap() + ); + assert_eq!(0, new_value_accessor.get_query_depth()); + } + _ => panic!("wrong preupdate case"), + } + CALLED.store(true, Ordering::Relaxed); + }))?; + + db.execute_batch("UPDATE foo SET t = 'janice'")?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/inner_connection.rs b/vendor/rusqlite/src/inner_connection.rs new file mode 100644 index 0000000..0751cfc --- /dev/null +++ b/vendor/rusqlite/src/inner_connection.rs @@ -0,0 +1,446 @@ +use std::ffi::{c_char, c_int, CStr}; +#[cfg(feature = "load_extension")] +use std::path::Path; +use std::ptr; +use std::str; +use std::sync::{Arc, Mutex}; + +use super::ffi; +use super::{Connection, InterruptHandle, Name, OpenFlags, PrepFlags, Result}; +use crate::error::{decode_result_raw, error_from_handle, error_with_offset, Error}; +use crate::raw_statement::RawStatement; +use crate::statement::Statement; +use crate::version_number; + +pub struct InnerConnection { + pub db: *mut ffi::sqlite3, + // It's unsafe to call `sqlite3_close` while another thread is performing + // a `sqlite3_interrupt`, and vice versa, so we take this mutex during + // those functions. This protects a copy of the `db` pointer (which is + // cleared on closing), however the main copy, `db`, is unprotected. + // Otherwise, a long-running query would prevent calling interrupt, as + // interrupt would only acquire the lock after the query's completion. + interrupt_lock: Arc>, + #[cfg(feature = "hooks")] + pub commit_hook: Option bool + Send>>, + #[cfg(feature = "hooks")] + pub rollback_hook: Option>, + #[cfg(feature = "hooks")] + #[expect(clippy::type_complexity)] + pub update_hook: Option>, + #[cfg(feature = "hooks")] + pub progress_handler: Option bool + Send>>, + #[cfg(feature = "hooks")] + pub authorizer: Option, + #[cfg(feature = "preupdate_hook")] + #[expect(clippy::type_complexity)] + pub preupdate_hook: Option< + Box, + >, + owned: bool, +} + +unsafe impl Send for InnerConnection {} + +impl InnerConnection { + #[expect(clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle + #[inline] + pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> Self { + Self { + db, + interrupt_lock: Arc::new(Mutex::new(if owned { db } else { ptr::null_mut() })), + #[cfg(feature = "hooks")] + commit_hook: None, + #[cfg(feature = "hooks")] + rollback_hook: None, + #[cfg(feature = "hooks")] + update_hook: None, + #[cfg(feature = "hooks")] + progress_handler: None, + #[cfg(feature = "hooks")] + authorizer: None, + #[cfg(feature = "preupdate_hook")] + preupdate_hook: None, + owned, + } + } + + pub fn open_with_flags( + c_path: &CStr, + mut flags: OpenFlags, + vfs: Option<&CStr>, + ) -> Result { + ensure_safe_sqlite_threading_mode()?; + + let z_vfs = match vfs { + Some(c_vfs) => c_vfs.as_ptr(), + None => ptr::null(), + }; + + // turn on extended results code before opening database to have a better diagnostic if a failure happens + let exrescode = if version_number() >= 3_037_000 { + flags |= OpenFlags::SQLITE_OPEN_EXRESCODE; + true + } else { + false // flag SQLITE_OPEN_EXRESCODE is ignored by SQLite version < 3.37.0 + }; + + unsafe { + let mut db: *mut ffi::sqlite3 = ptr::null_mut(); + let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs); + if r != ffi::SQLITE_OK { + let e = if db.is_null() { + err!(r, "{}", c_path.to_string_lossy()) + } else { + let mut e = error_from_handle(db, r); + if let Error::SqliteFailure( + ffi::Error { + code: ffi::ErrorCode::CannotOpen, + .. + }, + Some(msg), + ) = e + { + e = err!(r, "{msg}: {}", c_path.to_string_lossy()); + } + ffi::sqlite3_close(db); + e + }; + + return Err(e); + } + + // attempt to turn on extended results code; don't fail if we can't. + if !exrescode { + ffi::sqlite3_extended_result_codes(db, 1); + } + + let r = ffi::sqlite3_busy_timeout(db, 5000); + if r != ffi::SQLITE_OK { + let e = error_from_handle(db, r); + ffi::sqlite3_close(db); + return Err(e); + } + + Ok(Self::new(db, true)) + } + } + + #[inline] + pub fn db(&self) -> *mut ffi::sqlite3 { + self.db + } + + #[inline] + pub fn decode_result(&self, code: c_int) -> Result<()> { + unsafe { decode_result_raw(self.db(), code) } + } + + pub fn close(&mut self) -> Result<()> { + if self.db.is_null() { + return Ok(()); + } + if self.owned { + self.remove_hooks(); + self.remove_preupdate_hook(); + } + let mut shared_handle = self.interrupt_lock.lock().unwrap(); + assert!( + !self.owned || !shared_handle.is_null(), + "Bug: Somehow interrupt_lock was cleared before the DB was closed" + ); + if !self.owned { + self.db = ptr::null_mut(); + return Ok(()); + } + unsafe { + let r = ffi::sqlite3_close(self.db); + // Need to use _raw because _guard has a reference out, and + // decode_result takes &mut self. + let r = decode_result_raw(self.db, r); + if r.is_ok() { + *shared_handle = ptr::null_mut(); + self.db = ptr::null_mut(); + } + r + } + } + + #[inline] + pub fn get_interrupt_handle(&self) -> InterruptHandle { + InterruptHandle { + db_lock: Arc::clone(&self.interrupt_lock), + } + } + + #[inline] + #[cfg(feature = "load_extension")] + pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> { + let r = ffi::sqlite3_enable_load_extension(self.db, onoff); + self.decode_result(r) + } + + #[cfg(feature = "load_extension")] + pub unsafe fn load_extension( + &self, + dylib_path: &Path, + entry_point: Option, + ) -> Result<()> { + let dylib_str = super::path_to_cstring(dylib_path)?; + let mut errmsg: *mut c_char = ptr::null_mut(); + let cs = entry_point.as_ref().map(N::as_cstr).transpose()?; + let c_entry = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let r = ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry, &mut errmsg); + if r == ffi::SQLITE_OK { + Ok(()) + } else { + let message = super::errmsg_to_string(errmsg); + ffi::sqlite3_free(errmsg.cast::()); + Err(crate::error::error_from_sqlite_code(r, Some(message))) + } + } + + #[inline] + pub fn last_insert_rowid(&self) -> i64 { + unsafe { ffi::sqlite3_last_insert_rowid(self.db()) } + } + + pub fn prepare<'a>( + &mut self, + conn: &'a Connection, + sql: &str, + flags: PrepFlags, + ) -> Result<(Statement<'a>, usize)> { + let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut(); + let Ok(len) = c_int::try_from(sql.len()) else { + return Err(err!(ffi::SQLITE_TOOBIG)); + }; + let c_sql = sql.as_bytes().as_ptr().cast::(); + let mut c_tail: *const c_char = ptr::null(); + #[cfg(not(feature = "unlock_notify"))] + let r = unsafe { + ffi::sqlite3_prepare_v3( + self.db(), + c_sql, + len, + flags.bits(), + &mut c_stmt, + &mut c_tail, + ) + }; + #[cfg(feature = "unlock_notify")] + let r = unsafe { + use crate::unlock_notify; + let mut rc; + loop { + rc = ffi::sqlite3_prepare_v3( + self.db(), + c_sql, + len, + flags.bits(), + &mut c_stmt, + &mut c_tail, + ); + if !unlock_notify::is_locked(self.db, rc) { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.db); + if rc != ffi::SQLITE_OK { + break; + } + } + rc + }; + // If there is an error, *ppStmt is set to NULL. + if r != ffi::SQLITE_OK { + return Err(unsafe { error_with_offset(self.db, r, sql) }); + } + // If the input text contains no SQL (if the input is an empty string or a + // comment) then *ppStmt is set to NULL. + let tail = if c_tail.is_null() { + 0 + } else { + let n = (c_tail as isize) - (c_sql as isize); + if n <= 0 || n >= len as isize { + 0 + } else { + n as usize + } + }; + Ok(( + Statement::new(conn, unsafe { RawStatement::new(c_stmt) }), + tail, + )) + } + + #[inline] + pub fn changes(&self) -> u64 { + #[cfg(not(feature = "modern_sqlite"))] + unsafe { + ffi::sqlite3_changes(self.db()) as u64 + } + #[cfg(feature = "modern_sqlite")] // 3.37.0 + unsafe { + ffi::sqlite3_changes64(self.db()) as u64 + } + } + + #[inline] + pub fn total_changes(&self) -> u64 { + #[cfg(not(feature = "modern_sqlite"))] + unsafe { + ffi::sqlite3_total_changes(self.db()) as u64 + } + #[cfg(feature = "modern_sqlite")] // 3.37.0 + unsafe { + ffi::sqlite3_total_changes64(self.db()) as u64 + } + } + + #[inline] + pub fn is_autocommit(&self) -> bool { + unsafe { get_autocommit(self.db()) } + } + + pub fn is_busy(&self) -> bool { + let db = self.db(); + unsafe { + let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut()); + while !stmt.is_null() { + if ffi::sqlite3_stmt_busy(stmt) != 0 { + return true; + } + stmt = ffi::sqlite3_next_stmt(db, stmt); + } + } + false + } + + pub fn cache_flush(&mut self) -> Result<()> { + crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) }) + } + + #[cfg(not(feature = "hooks"))] + #[inline] + fn remove_hooks(&mut self) {} + + #[cfg(not(feature = "preupdate_hook"))] + #[inline] + fn remove_preupdate_hook(&mut self) {} + + pub fn db_readonly(&self, db_name: N) -> Result { + let name = db_name.as_cstr()?; + let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) }; + match r { + 0 => Ok(false), + 1 => Ok(true), + -1 => Err(err!( + ffi::SQLITE_MISUSE, + "{db_name:?} is not the name of a database" + )), + _ => Err(err!(r, "Unexpected result")), + } + } + + #[cfg(feature = "modern_sqlite")] // 3.37.0 + pub fn txn_state( + &self, + db_name: Option, + ) -> Result { + let cs = db_name.as_ref().map(N::as_cstr).transpose()?; + let name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let r = unsafe { ffi::sqlite3_txn_state(self.db, name) }; + match r { + 0 => Ok(super::transaction::TransactionState::None), + 1 => Ok(super::transaction::TransactionState::Read), + 2 => Ok(super::transaction::TransactionState::Write), + -1 => Err(err!( + ffi::SQLITE_MISUSE, + "{db_name:?} is not the name of a valid schema" + )), + _ => Err(err!(r, "Unexpected result")), + } + } + + #[inline] + pub fn release_memory(&self) -> Result<()> { + self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) }) + } + + #[cfg(feature = "modern_sqlite")] // 3.41.0 + pub fn is_interrupted(&self) -> bool { + unsafe { ffi::sqlite3_is_interrupted(self.db) == 1 } + } + + #[cfg(any(feature = "hooks", feature = "preupdate_hook"))] + pub fn check_owned(&self) -> Result<()> { + if !self.owned { + return Err(err!(ffi::SQLITE_MISUSE, "Connection is not owned")); + } + Ok(()) + } +} + +#[inline] +pub(crate) unsafe fn get_autocommit(ptr: *mut ffi::sqlite3) -> bool { + ffi::sqlite3_get_autocommit(ptr) != 0 +} + +#[inline] +pub(crate) unsafe fn db_filename( + _: std::marker::PhantomData<&()>, + ptr: *mut ffi::sqlite3, + db_name: N, +) -> Option<&str> { + let db_name = db_name.as_cstr().unwrap(); + let db_filename = ffi::sqlite3_db_filename(ptr, db_name.as_ptr()); + if db_filename.is_null() { + None + } else { + CStr::from_ptr(db_filename).to_str().ok() + } +} + +impl Drop for InnerConnection { + #[expect(unused_must_use)] + #[inline] + fn drop(&mut self) { + self.close(); + } +} + +// threading mode checks are not necessary (and do not work) on target +// platforms that do not have threading (such as webassembly) +#[cfg(target_arch = "wasm32")] +fn ensure_safe_sqlite_threading_mode() -> Result<()> { + Ok(()) +} + +#[cfg(not(any(target_arch = "wasm32")))] +fn ensure_safe_sqlite_threading_mode() -> Result<()> { + // Ensure SQLite was compiled in threadsafe mode. + if unsafe { ffi::sqlite3_threadsafe() == 0 } { + return Err(Error::SqliteSingleThreadedMode); + } + + // Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode, + // but it's possible someone configured it to be in Single-thread mode + // before calling into us. That would mean we're exposing an unsafe API via + // a safe one (in Rust terminology). + // + // We can ask SQLite for a mutex and check for + // the magic value 8. This isn't documented, but it's what SQLite + // returns for its mutex allocation function in Single-thread mode. + const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; + let is_singlethreaded = unsafe { + let mutex_ptr = ffi::sqlite3_mutex_alloc(0); + let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC; + ffi::sqlite3_mutex_free(mutex_ptr); + is_singlethreaded + }; + if is_singlethreaded { + Err(Error::SqliteSingleThreadedMode) + } else { + Ok(()) + } +} diff --git a/vendor/rusqlite/src/lib.rs b/vendor/rusqlite/src/lib.rs new file mode 100644 index 0000000..ef599d3 --- /dev/null +++ b/vendor/rusqlite/src/lib.rs @@ -0,0 +1,2330 @@ +//! Rusqlite is an ergonomic wrapper for using SQLite from Rust. +//! +//! Historically, the API was based on the one from +//! [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the +//! two have diverged in many ways, and no compatibility between the two is +//! intended. +//! +//! ```rust +//! use rusqlite::{params, Connection, Result}; +//! +//! #[derive(Debug)] +//! struct Person { +//! id: i32, +//! name: String, +//! data: Option>, +//! } +//! +//! fn main() -> Result<()> { +//! let conn = Connection::open_in_memory()?; +//! +//! conn.execute( +//! "CREATE TABLE person ( +//! id INTEGER PRIMARY KEY, +//! name TEXT NOT NULL, +//! data BLOB +//! )", +//! (), // empty list of parameters. +//! )?; +//! let me = Person { +//! id: 0, +//! name: "Steven".to_string(), +//! data: None, +//! }; +//! conn.execute( +//! "INSERT INTO person (name, data) VALUES (?1, ?2)", +//! (&me.name, &me.data), +//! )?; +//! +//! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; +//! let person_iter = stmt.query_map([], |row| { +//! Ok(Person { +//! id: row.get(0)?, +//! name: row.get(1)?, +//! data: row.get(2)?, +//! }) +//! })?; +//! +//! for person in person_iter { +//! println!("Found person {:?}", person?); +//! } +//! Ok(()) +//! } +//! ``` +#![warn(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub use fallible_iterator; +pub use fallible_streaming_iterator; + +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub use libsqlite3_sys as ffi; +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +pub use sqlite_wasm_rs as ffi; + +use std::cell::RefCell; +use std::default::Default; +use std::ffi::{c_char, c_int, c_uint, CStr, CString}; +use std::fmt; + +use std::path::Path; +use std::result; +use std::str; +use std::sync::{Arc, Mutex}; + +#[cfg(feature = "cache")] +use crate::cache::StatementCache; +use crate::inner_connection::InnerConnection; +use crate::raw_statement::RawStatement; +use crate::types::ValueRef; + +pub use crate::bind::BindIndex; +#[cfg(feature = "cache")] +pub use crate::cache::CachedStatement; +#[cfg(feature = "column_decltype")] +pub use crate::column::Column; +#[cfg(feature = "column_metadata")] +pub use crate::column::ColumnMetadata; +pub use crate::error::{to_sqlite_error, Error}; +pub use crate::ffi::ErrorCode; +#[cfg(feature = "load_extension")] +pub use crate::load_extension_guard::LoadExtensionGuard; +pub use crate::params::{params_from_iter, Params, ParamsFromIter}; +pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows}; +pub use crate::statement::{Statement, StatementStatus}; +#[cfg(feature = "modern_sqlite")] +pub use crate::transaction::TransactionState; +pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; +pub use crate::types::ToSql; +pub use crate::util::Name; +pub use crate::version::*; +#[cfg(feature = "rusqlite-macros")] +#[doc(hidden)] +pub use rusqlite_macros::__bind; + +#[macro_use] +mod error; + +#[cfg(not(feature = "loadable_extension"))] +pub mod auto_extension; +#[cfg(feature = "backup")] +pub mod backup; +mod bind; +#[cfg(feature = "blob")] +pub mod blob; +mod busy; +#[cfg(feature = "cache")] +mod cache; +#[cfg(feature = "collation")] +mod collation; +mod column; +pub mod config; +#[cfg(any(feature = "functions", feature = "vtab"))] +mod context; +#[cfg(feature = "functions")] +pub mod functions; +#[cfg(feature = "hooks")] +pub mod hooks; +mod inner_connection; +#[cfg(feature = "limits")] +pub mod limits; +#[cfg(feature = "load_extension")] +mod load_extension_guard; +mod params; +mod pragma; +mod raw_statement; +mod row; +#[cfg(feature = "serialize")] +pub mod serialize; +#[cfg(feature = "session")] +pub mod session; +mod statement; +#[cfg(feature = "trace")] +pub mod trace; +mod transaction; +pub mod types; +#[cfg(feature = "unlock_notify")] +mod unlock_notify; +mod version; +#[cfg(feature = "vtab")] +pub mod vtab; + +pub(crate) mod util; + +// Actually, only sqlite3_enable_load_extension is disabled (not sqlite3_load_extension) +#[cfg(all(feature = "loadable_extension", feature = "load_extension"))] +compile_error!("feature \"loadable_extension\" and feature \"load_extension\" cannot be enabled at the same time"); + +// Number of cached prepared statements we'll hold on to. +#[cfg(feature = "cache")] +const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; + +/// A macro making it more convenient to pass longer lists of +/// parameters as a `&[&dyn ToSql]`. +/// +/// # Example +/// +/// ```rust,no_run +/// # use rusqlite::{Result, Connection, params}; +/// +/// struct Person { +/// name: String, +/// age_in_years: u8, +/// data: Option>, +/// } +/// +/// fn add_person(conn: &Connection, person: &Person) -> Result<()> { +/// conn.execute( +/// "INSERT INTO person(name, age_in_years, data) VALUES (?1, ?2, ?3)", +/// params![person.name, person.age_in_years, person.data], +/// )?; +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! params { + () => { + &[] as &[&dyn $crate::ToSql] + }; + ($($param:expr),+ $(,)?) => { + &[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql] + }; +} + +/// A macro making it more convenient to pass lists of named parameters +/// as a `&[(&str, &dyn ToSql)]`. +/// +/// # Example +/// +/// ```rust,no_run +/// # use rusqlite::{Result, Connection, named_params}; +/// +/// struct Person { +/// name: String, +/// age_in_years: u8, +/// data: Option>, +/// } +/// +/// fn add_person(conn: &Connection, person: &Person) -> Result<()> { +/// conn.execute( +/// "INSERT INTO person (name, age_in_years, data) +/// VALUES (:name, :age, :data)", +/// named_params! { +/// ":name": person.name, +/// ":age": person.age_in_years, +/// ":data": person.data, +/// }, +/// )?; +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! named_params { + () => { + &[] as &[(&str, &dyn $crate::ToSql)] + }; + // Note: It's a lot more work to support this as part of the same macro as + // `params!`, unfortunately. + ($($param_name:literal: $param_val:expr),+ $(,)?) => { + &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)] + }; +} + +/// Captured identifiers in SQL +/// +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). +/// * `$x.y` expression does not work. +/// +/// # Example +/// +/// ```rust, no_run +/// # use rusqlite::{prepare_and_bind, Connection, Result, Statement}; +/// +/// fn misc(db: &Connection) -> Result { +/// let name = "Lisa"; +/// let age = 8; +/// let smart = true; +/// Ok(prepare_and_bind!(db, "SELECT $name, @age, :smart;")) +/// } +/// ``` +#[cfg(feature = "rusqlite-macros")] +#[macro_export] +macro_rules! prepare_and_bind { + ($conn:expr, $sql:literal) => {{ + let mut stmt = $conn.prepare($sql)?; + $crate::__bind!(stmt $sql); + stmt + }}; +} + +/// Captured identifiers in SQL +/// +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). +/// * `$x.y` expression does not work. +#[cfg(feature = "rusqlite-macros")] +#[macro_export] +macro_rules! prepare_cached_and_bind { + ($conn:expr, $sql:literal) => {{ + let mut stmt = $conn.prepare_cached($sql)?; + $crate::__bind!(stmt $sql); + stmt + }}; +} + +/// A typedef of the result returned by many methods. +pub type Result = result::Result; + +/// See the [method documentation](#tymethod.optional). +pub trait OptionalExtension { + /// Converts a `Result` into a `Result>`. + /// + /// By default, Rusqlite treats 0 rows being returned from a query that is + /// expected to return 1 row as an error. This method will + /// handle that error, and give you back an `Option` instead. + fn optional(self) -> Result>; +} + +impl OptionalExtension for Result { + fn optional(self) -> Result> { + match self { + Ok(value) => Ok(Some(value)), + Err(Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e), + } + } +} + +unsafe fn errmsg_to_string(errmsg: *const c_char) -> String { + CStr::from_ptr(errmsg).to_string_lossy().into_owned() +} + +#[cfg(any(feature = "functions", feature = "vtab", test))] +fn str_to_cstring(s: &str) -> Result { + Ok(util::SmallCString::new(s)?) +} + +/// Returns `(string ptr, len as c_int, SQLITE_STATIC | SQLITE_TRANSIENT)` +/// normally. +/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless +/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is +/// static). +fn str_for_sqlite( + s: &[u8], +) -> ( + *const c_char, + ffi::sqlite3_uint64, + ffi::sqlite3_destructor_type, +) { + let len = s.len(); + let (ptr, dtor_info) = if len != 0 { + (s.as_ptr().cast::(), ffi::SQLITE_TRANSIENT()) + } else { + // Return a pointer guaranteed to live forever + ("".as_ptr().cast::(), ffi::SQLITE_STATIC()) + }; + (ptr, len as ffi::sqlite3_uint64, dtor_info) +} + +#[cfg(unix)] +fn path_to_cstring(p: &Path) -> Result { + use std::os::unix::ffi::OsStrExt; + Ok(CString::new(p.as_os_str().as_bytes())?) +} + +#[cfg(not(unix))] +fn path_to_cstring(p: &Path) -> Result { + let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?; + Ok(CString::new(s)?) +} + +/// Shorthand for `Main` database. +pub const MAIN_DB: &CStr = c"main"; +/// Shorthand for `Temp` database. +pub const TEMP_DB: &CStr = c"temp"; + +/// A connection to a SQLite database. +pub struct Connection { + db: RefCell, + #[cfg(feature = "cache")] + cache: StatementCache, + transaction_behavior: TransactionBehavior, +} + +unsafe impl Send for Connection {} + +impl Drop for Connection { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "cache")] + self.flush_prepared_statement_cache(); + } +} + +impl Connection { + /// Open a new connection to a SQLite database. If a database does not exist + /// at the path, one is created. + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn open_my_db() -> Result<()> { + /// let path = "./my_db.db3"; + /// let db = Connection::open(path)?; + /// // Use the database somehow... + /// println!("{}", db.is_autocommit()); + /// Ok(()) + /// } + /// ``` + /// + /// # Flags + /// + /// `Connection::open(path)` is equivalent to using + /// [`Connection::open_with_flags`] with the default [`OpenFlags`]. That is, + /// it's equivalent to: + /// + /// ```ignore + /// Connection::open_with_flags( + /// path, + /// OpenFlags::SQLITE_OPEN_READ_WRITE + /// | OpenFlags::SQLITE_OPEN_CREATE + /// | OpenFlags::SQLITE_OPEN_URI + /// | OpenFlags::SQLITE_OPEN_NO_MUTEX, + /// ) + /// ``` + /// + /// These flags have the following effects: + /// + /// - Open the database for both reading or writing. + /// - Create the database if one does not exist at the path. + /// - Allow the filename to be interpreted as a URI (see + /// for details). + /// - Disables the use of a per-connection mutex. + /// + /// Rusqlite enforces thread-safety at compile time, so additional + /// locking is not needed and provides no benefit. (See the + /// documentation on [`OpenFlags::SQLITE_OPEN_FULL_MUTEX`] for some + /// additional discussion about this). + /// + /// Most of these are also the default settings for the C API, although + /// technically the default locking behavior is controlled by the flags used + /// when compiling SQLite -- rather than let it vary, we choose `NO_MUTEX` + /// because it's a fairly clearly the best choice for users of this library. + /// + /// # Failure + /// + /// Will return `Err` if `path` cannot be converted to a C-compatible string + /// or if the underlying SQLite open call fails. + /// + /// # WASM support + /// + /// If you plan to use this connection type on the `wasm32-unknown-unknown` target please + /// make sure to read the following notes: + /// + /// - The database is stored in memory by default. + /// - Persistent VFS (Virtual File Systems) is optional, + /// see for details + #[inline] + pub fn open>(path: P) -> Result { + let flags = OpenFlags::default(); + Self::open_with_flags(path, flags) + } + + /// Open a new connection to an in-memory SQLite database. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite open call fails. + #[inline] + pub fn open_in_memory() -> Result { + let flags = OpenFlags::default(); + Self::open_in_memory_with_flags(flags) + } + + /// Open a new connection to a SQLite database. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid + /// flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if `path` cannot be converted to a C-compatible + /// string or if the underlying SQLite open call fails. + #[inline] + pub fn open_with_flags>(path: P, flags: OpenFlags) -> Result { + let c_path = path_to_cstring(path.as_ref())?; + InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Self { + db: RefCell::new(db), + #[cfg(feature = "cache")] + cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), + transaction_behavior: TransactionBehavior::Deferred, + }) + } + + /// Open a new connection to a SQLite database using the specific flags and + /// vfs name. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid + /// flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if either `path` or `vfs` cannot be converted to a + /// C-compatible string or if the underlying SQLite open call fails. + #[inline] + pub fn open_with_flags_and_vfs, V: Name>( + path: P, + flags: OpenFlags, + vfs: V, + ) -> Result { + let c_path = path_to_cstring(path.as_ref())?; + let c_vfs = vfs.as_cstr()?; + InnerConnection::open_with_flags(&c_path, flags, Some(&c_vfs)).map(|db| Self { + db: RefCell::new(db), + #[cfg(feature = "cache")] + cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), + transaction_behavior: TransactionBehavior::Deferred, + }) + } + + /// Open a new connection to an in-memory SQLite database. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid + /// flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite open call fails. + #[inline] + pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result { + Self::open_with_flags(":memory:", flags) + } + + /// Open a new connection to an in-memory SQLite database using the specific + /// flags and vfs name. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid + /// flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if `vfs` cannot be converted to a C-compatible + /// string or if the underlying SQLite open call fails. + #[inline] + pub fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: V) -> Result { + Self::open_with_flags_and_vfs(":memory:", flags, vfs) + } + + /// Convenience method to run multiple SQL statements (that cannot take any + /// parameters). + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn create_tables(conn: &Connection) -> Result<()> { + /// conn.execute_batch( + /// "BEGIN; + /// CREATE TABLE foo(x INTEGER); + /// CREATE TABLE bar(y TEXT); + /// COMMIT;", + /// ) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + pub fn execute_batch(&self, sql: &str) -> Result<()> { + let mut sql = sql; + while !sql.is_empty() { + let (stmt, tail) = self + .db + .borrow_mut() + .prepare(self, sql, PrepFlags::default())?; + if !stmt.stmt.is_null() && stmt.step()? { + // Some PRAGMA may return rows + if false { + return Err(Error::ExecuteReturnedResults); + } + } + if tail == 0 || tail >= sql.len() { + break; + } + sql = &sql[tail..]; + } + Ok(()) + } + + /// Convenience method to prepare and execute a single SQL statement. + /// + /// On success, returns the number of rows that were changed or inserted or + /// deleted (via `sqlite3_changes`). + /// + /// ## Example + /// + /// ### With positional params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection}; + /// fn update_rows(conn: &Connection) { + /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) { + /// Ok(updated) => println!("{} rows were updated", updated), + /// Err(err) => println!("update failed: {}", err), + /// } + /// } + /// ``` + /// + /// ### With positional params of varying types + /// + /// ```rust,no_run + /// # use rusqlite::{params, Connection}; + /// fn update_rows(conn: &Connection) { + /// match conn.execute( + /// "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2", + /// params![1i32, 1.5f64], + /// ) { + /// Ok(updated) => println!("{} rows were updated", updated), + /// Err(err) => println!("update failed: {}", err), + /// } + /// } + /// ``` + /// + /// ### With named params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn insert(conn: &Connection) -> Result { + /// conn.execute( + /// "INSERT INTO test (name) VALUES (:name)", + /// &[(":name", "one")], + /// ) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn execute(&self, sql: &str, params: P) -> Result { + self.prepare(sql).and_then(|mut stmt| stmt.execute(params)) + } + + /// Returns the path to the database file, if one exists and is known. + /// + /// Returns `Some("")` for a temporary or in-memory database. + /// + /// Note that in some cases [PRAGMA + /// database_list](https://sqlite.org/pragma.html#pragma_database_list) is + /// likely to be more robust. + #[inline] + pub fn path(&self) -> Option<&str> { + unsafe { inner_connection::db_filename(std::marker::PhantomData, self.handle(), MAIN_DB) } + } + + /// Attempts to free as much heap memory as possible from the database + /// connection. + /// + /// This calls [`sqlite3_db_release_memory`](https://www.sqlite.org/c3ref/db_release_memory.html). + #[inline] + pub fn release_memory(&self) -> Result<()> { + self.db.borrow_mut().release_memory() + } + + /// Get the SQLite rowid of the most recent successful INSERT. + /// + /// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under + /// the hood. + #[inline] + pub fn last_insert_rowid(&self) -> i64 { + self.db.borrow_mut().last_insert_rowid() + } + + /// Convenience method to execute a query that is expected to return a + /// single row. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Result, Connection}; + /// fn preferred_locale(conn: &Connection) -> Result { + /// conn.query_row( + /// "SELECT value FROM preferences WHERE name='locale'", + /// [], + /// |row| row.get(0), + /// ) + /// } + /// ``` + /// + /// If the query returns more than one row, all rows except the first are + /// ignored. + /// + /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the + /// query truly is optional, you can call `.optional()` on the result of + /// this to get a `Result>`. + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn query_row(&self, sql: &str, params: P, f: F) -> Result + where + P: Params, + F: FnOnce(&Row<'_>) -> Result, + { + let mut stmt = self.prepare(sql)?; + stmt.query_row(params, f) + } + + /// Convenience method to execute a query that is expected to return exactly + /// one row. + /// + /// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row. + /// + /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the + /// query truly is optional, you can call + /// [`.optional()`](crate::OptionalExtension::optional) on the result of + /// this to get a `Result>` (requires that the trait + /// `rusqlite::OptionalExtension` is imported). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + pub fn query_one(&self, sql: &str, params: P, f: F) -> Result + where + P: Params, + F: FnOnce(&Row<'_>) -> Result, + { + let mut stmt = self.prepare(sql)?; + stmt.query_one(params, f) + } + + // https://sqlite.org/tclsqlite.html#onecolumn + #[cfg(test)] + pub(crate) fn one_column(&self, sql: &str, params: P) -> Result + where + T: types::FromSql, + P: Params, + { + self.query_one(sql, params, |r| r.get(0)) + } + + /// Convenience method to execute a query that is expected to return a + /// single row, and execute a mapping via `f` on that returned row with + /// the possibility of failure. The `Result` type of `f` must implement + /// `std::convert::From`. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Result, Connection}; + /// fn preferred_locale(conn: &Connection) -> Result { + /// conn.query_row_and_then( + /// "SELECT value FROM preferences WHERE name='locale'", + /// [], + /// |row| row.get(0), + /// ) + /// } + /// ``` + /// + /// If the query returns more than one row, all rows except the first are + /// ignored. + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn query_row_and_then(&self, sql: &str, params: P, f: F) -> Result + where + P: Params, + F: FnOnce(&Row<'_>) -> Result, + E: From, + { + let mut stmt = self.prepare(sql)?; + let mut rows = stmt.query(params)?; + + rows.get_expected_row().map_err(E::from).and_then(f) + } + + /// Prepare a SQL statement for execution. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn insert_new_people(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?; + /// stmt.execute(["Joe Smith"])?; + /// stmt.execute(["Bob Jones"])?; + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn prepare(&self, sql: &str) -> Result> { + self.prepare_with_flags(sql, PrepFlags::default()) + } + + /// Prepare a SQL statement for execution. + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string + /// or if the underlying SQLite call fails. + #[inline] + pub fn prepare_with_flags(&self, sql: &str, flags: PrepFlags) -> Result> { + let (stmt, tail) = self.db.borrow_mut().prepare(self, sql, flags)?; + if tail != 0 && !self.prepare(&sql[tail..])?.stmt.is_null() { + Err(Error::MultipleStatement) + } else { + Ok(stmt) + } + } + + /// Close the SQLite connection. + /// + /// This is functionally equivalent to the `Drop` implementation for + /// `Connection` except that on failure, it returns an error and the + /// connection itself (presumably so closing can be attempted again). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[expect(clippy::result_large_err)] + #[inline] + pub fn close(self) -> Result<(), (Self, Error)> { + #[cfg(feature = "cache")] + self.flush_prepared_statement_cache(); + let r = self.db.borrow_mut().close(); + r.map_err(move |err| (self, err)) + } + + /// Enable loading of SQLite extensions from both SQL queries and Rust. + /// + /// You must call [`Connection::load_extension_disable`] when you're + /// finished loading extensions (failure to call it can lead to bad things, + /// see "Safety"), so you should strongly consider using + /// [`LoadExtensionGuard`] instead of this function, automatically disables + /// extension loading when it goes out of scope. + /// + /// # Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn load_my_extension(conn: &Connection) -> Result<()> { + /// // Safety: We fully trust the loaded extension and execute no untrusted SQL + /// // while extension loading is enabled. + /// unsafe { + /// conn.load_extension_enable()?; + /// let r = conn.load_extension("my/trusted/extension", None::<&str>); + /// conn.load_extension_disable()?; + /// r + /// } + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + /// + /// # Safety + /// + /// TLDR: Don't execute any untrusted queries between this call and + /// [`Connection::load_extension_disable`]. + /// + /// Perhaps surprisingly, this function does not only allow the use of + /// [`Connection::load_extension`] from Rust, but it also allows SQL queries + /// to perform [the same operation][loadext]. For example, in the period + /// between `load_extension_enable` and `load_extension_disable`, the + /// following operation will load and call some function in some dynamic + /// library: + /// + /// ```sql + /// SELECT load_extension('why_is_this_possible.dll', 'dubious_func'); + /// ``` + /// + /// This means that while this is enabled a carefully crafted SQL query can + /// be used to escalate a SQL injection attack into code execution. + /// + /// Safely using this function requires that you trust all SQL queries run + /// between when it is called, and when loading is disabled (by + /// [`Connection::load_extension_disable`]). + /// + /// [loadext]: https://www.sqlite.org/lang_corefunc.html#load_extension + #[cfg(feature = "load_extension")] + #[inline] + pub unsafe fn load_extension_enable(&self) -> Result<()> { + self.db.borrow_mut().enable_load_extension(1) + } + + /// Disable loading of SQLite extensions. + /// + /// See [`Connection::load_extension_enable`] for an example. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[cfg(feature = "load_extension")] + #[inline] + pub fn load_extension_disable(&self) -> Result<()> { + // It's always safe to turn off extension loading. + unsafe { self.db.borrow_mut().enable_load_extension(0) } + } + + /// Load the SQLite extension at `dylib_path`. `dylib_path` is passed + /// through to `sqlite3_load_extension`, which may attempt OS-specific + /// modifications if the file cannot be loaded directly (for example + /// converting `"some/ext"` to `"some/ext.so"`, `"some\\ext.dll"`, ...). + /// + /// If `entry_point` is `None`, SQLite will attempt to find the entry point. + /// If it is not `None`, the entry point will be passed through to + /// `sqlite3_load_extension`. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, LoadExtensionGuard}; + /// fn load_my_extension(conn: &Connection) -> Result<()> { + /// // Safety: we don't execute any SQL statements while + /// // extension loading is enabled. + /// let _guard = unsafe { LoadExtensionGuard::new(conn)? }; + /// // Safety: `my_sqlite_extension` is highly trustworthy. + /// unsafe { conn.load_extension("my_sqlite_extension", None::<&str>) } + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + /// + /// # Safety + /// + /// This is equivalent to performing a `dlopen`/`LoadLibrary` on a shared + /// library, and calling a function inside, and thus requires that you trust + /// the library that you're loading. + /// + /// That is to say: to safely use this, the code in the extension must be + /// sound, trusted, correctly use the SQLite APIs, and not contain any + /// memory or thread safety errors. + #[cfg(feature = "load_extension")] + #[inline] + pub unsafe fn load_extension, N: Name>( + &self, + dylib_path: P, + entry_point: Option, + ) -> Result<()> { + self.db + .borrow_mut() + .load_extension(dylib_path.as_ref(), entry_point) + } + + /// Get access to the underlying SQLite database connection handle. + /// + /// # Warning + /// + /// You should not need to use this function. If you do need to, please + /// [open an issue on the rusqlite repository](https://github.com/rusqlite/rusqlite/issues) and describe + /// your use case. + /// + /// # Safety + /// + /// This function is unsafe because it gives you raw access + /// to the SQLite connection, and what you do with it could impact the + /// safety of this `Connection`. + #[inline] + pub unsafe fn handle(&self) -> *mut ffi::sqlite3 { + self.db.borrow().db() + } + + /// Create a `Connection` from a raw handle. + /// + /// The underlying SQLite database connection handle will not be closed when + /// the returned connection is dropped/closed. + /// + /// # Safety + /// + /// This function is unsafe because improper use may impact the Connection. + #[inline] + pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result { + let db = InnerConnection::new(db, false); + Ok(Self { + db: RefCell::new(db), + #[cfg(feature = "cache")] + cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), + transaction_behavior: TransactionBehavior::Deferred, + }) + } + + /// Helper to register an SQLite extension written in Rust. + /// For [persistent](https://sqlite.org/loadext.html#persistent_loadable_extensions) extension, + /// `init` should return `Ok(true)`. + /// # Safety + /// * Results are undefined if `init` does not just register features. + #[cfg(feature = "loadable_extension")] + pub unsafe fn extension_init2( + db: *mut ffi::sqlite3, + pz_err_msg: *mut *mut c_char, + p_api: *mut ffi::sqlite3_api_routines, + init: fn(Self) -> Result, + ) -> c_int { + if p_api.is_null() { + return ffi::SQLITE_ERROR; + } + match ffi::rusqlite_extension_init2(p_api) + .map_err(Error::from) + .and(Self::from_handle(db)) + .and_then(init) + { + Err(err) => to_sqlite_error(&err, pz_err_msg), + Ok(true) => ffi::SQLITE_OK_LOAD_PERMANENTLY, + _ => ffi::SQLITE_OK, + } + } + + /// Create a `Connection` from a raw owned handle. + /// + /// The returned connection will attempt to close the inner connection + /// when dropped/closed. This function should only be called on connections + /// owned by the caller. + /// + /// # Safety + /// + /// This function is unsafe because improper use may impact the Connection. + /// In particular, it should only be called on connections created + /// and owned by the caller, e.g. as a result of calling + /// `ffi::sqlite3_open`(). + #[inline] + pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result { + let db = InnerConnection::new(db, true); + Ok(Self { + db: RefCell::new(db), + #[cfg(feature = "cache")] + cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), + transaction_behavior: TransactionBehavior::Deferred, + }) + } + + /// Get access to a handle that can be used to interrupt long-running + /// queries from another thread. + #[inline] + pub fn get_interrupt_handle(&self) -> InterruptHandle { + self.db.borrow().get_interrupt_handle() + } + + #[inline] + fn decode_result(&self, code: c_int) -> Result<()> { + self.db.borrow().decode_result(code) + } + + /// Return the number of rows modified, inserted or deleted by the most + /// recently completed INSERT, UPDATE or DELETE statement on the database + /// connection. + /// + /// See + #[inline] + pub fn changes(&self) -> u64 { + self.db.borrow().changes() + } + + /// Return the total number of rows modified, inserted or deleted by all + /// completed INSERT, UPDATE or DELETE statements since the database + /// connection was opened, including those executed as part of trigger programs. + /// + /// See + #[inline] + pub fn total_changes(&self) -> u64 { + self.db.borrow().total_changes() + } + + /// Test for auto-commit mode. + /// Autocommit mode is on by default. + #[inline] + pub fn is_autocommit(&self) -> bool { + self.db.borrow().is_autocommit() + } + + /// Determine if all associated prepared statements have been reset. + #[inline] + pub fn is_busy(&self) -> bool { + self.db.borrow().is_busy() + } + + /// Flush caches to disk mid-transaction + pub fn cache_flush(&self) -> Result<()> { + self.db.borrow_mut().cache_flush() + } + + /// Determine if a database is read-only + pub fn is_readonly(&self, db_name: N) -> Result { + self.db.borrow().db_readonly(db_name) + } + + /// Return the schema name for a database connection + /// + /// ## Failure + /// + /// Return an `Error::InvalidDatabaseIndex` if `index` is out of range. + #[cfg(feature = "modern_sqlite")] // 3.39.0 + pub fn db_name(&self, index: usize) -> Result { + unsafe { + let db = self.handle(); + let name = ffi::sqlite3_db_name(db, index as c_int); + if name.is_null() { + Err(Error::InvalidDatabaseIndex(index)) + } else { + Ok(CStr::from_ptr(name).to_str()?.to_owned()) + } + } + } + + /// Determine whether an interrupt is currently in effect + #[cfg(feature = "modern_sqlite")] // 3.41.0 + pub fn is_interrupted(&self) -> bool { + self.db.borrow().is_interrupted() + } +} + +impl fmt::Debug for Connection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Connection") + .field("path", &self.path()) + .finish() + } +} + +/// Batch fallible iterator +/// +/// # Warning +/// +/// There is no recovery on parsing error, when an invalid statement is found in `sql`, SQLite cannot jump to the next statement. +/// So you should break the loop when an error is raised by the `next` method. +/// +/// ```rust +/// use fallible_iterator::FallibleIterator; +/// use rusqlite::{Batch, Connection, Result}; +/// +/// fn main() -> Result<()> { +/// let conn = Connection::open_in_memory()?; +/// let sql = r" +/// CREATE TABLE tbl1 (col); +/// CREATE TABLE tbl2 (col); +/// "; +/// let mut batch = Batch::new(&conn, sql); +/// while let Some(mut stmt) = batch.next()? { +/// stmt.execute([])?; +/// } +/// Ok(()) +/// } +/// ``` +#[derive(Debug)] +pub struct Batch<'conn, 'sql> { + conn: &'conn Connection, + sql: &'sql str, + tail: usize, +} + +impl<'conn, 'sql> Batch<'conn, 'sql> { + /// Constructor + pub fn new(conn: &'conn Connection, sql: &'sql str) -> Self { + Batch { conn, sql, tail: 0 } + } +} +impl<'conn> fallible_iterator::FallibleIterator for Batch<'conn, '_> { + type Item = Statement<'conn>; + type Error = Error; + + /// Iterates on each batch statements. + /// + /// Returns `Ok(None)` when batch is completed. + fn next(&mut self) -> Result>> { + while self.tail < self.sql.len() { + let sql = &self.sql[self.tail..]; + let (next, tail) = + self.conn + .db + .borrow_mut() + .prepare(self.conn, sql, PrepFlags::default())?; + if tail == 0 { + self.tail = self.sql.len(); + } else { + self.tail += tail; + } + if next.stmt.is_null() { + continue; + } + return Ok(Some(next)); + } + Ok(None) + } +} + +bitflags::bitflags! { + /// Flags for opening SQLite database connections. See + /// [sqlite3_open_v2](https://www.sqlite.org/c3ref/open.html) for details. + /// + /// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE + /// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for + /// some discussion about these flags. + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(C)] + pub struct OpenFlags: c_int { + /// The database is opened in read-only mode. + /// If the database does not already exist, an error is returned. + const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY; + /// The database is opened for reading and writing if possible, + /// or reading only if the file is write-protected by the operating system. + /// In either case the database must already exist, otherwise an error is returned. + const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE; + /// The database is created if it does not already exist + const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE; + /// The filename can be interpreted as a URI if this flag is set. + const SQLITE_OPEN_URI = ffi::SQLITE_OPEN_URI; + /// The database will be opened as an in-memory database. + const SQLITE_OPEN_MEMORY = ffi::SQLITE_OPEN_MEMORY; + /// The new database connection will not use a per-connection mutex (the + /// connection will use the "multi-thread" threading mode, in SQLite + /// parlance). + /// + /// This is used by default, as proper `Send`/`Sync` usage (in + /// particular, the fact that [`Connection`] does not implement `Sync`) + /// ensures thread-safety without the need to perform locking around all + /// calls. + const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX; + /// The new database connection will use a per-connection mutex -- the + /// "serialized" threading mode, in SQLite parlance. + /// + /// # Caveats + /// + /// This flag should probably never be used with `rusqlite`, as we + /// ensure thread-safety statically (we implement [`Send`] and not + /// [`Sync`]). + /// + /// Critically, even if this flag is used, the [`Connection`] is not + /// safe to use across multiple threads simultaneously. To access a + /// database from multiple threads, you should either create multiple + /// connections, one for each thread (if you have very many threads, + /// wrapping the `rusqlite::Connection` in a mutex is also reasonable). + /// + /// This is both because of the additional per-connection state stored + /// by `rusqlite` (for example, the prepared statement cache), and + /// because not all of SQLites functions are fully thread safe, even in + /// serialized/`SQLITE_OPEN_FULLMUTEX` mode. + /// + /// All that said, it's fairly harmless to enable this flag with + /// `rusqlite`, it will just slow things down while providing no + /// benefit. + const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX; + /// The database is opened with shared cache enabled. + /// + /// This is frequently useful for in-memory connections, but note that + /// broadly speaking it's discouraged by SQLite itself, which states + /// "Any use of shared cache is discouraged" in the official + /// [documentation](https://www.sqlite.org/c3ref/enable_shared_cache.html). + const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000; + /// The database is opened shared cache disabled. + const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000; + /// The database filename is not allowed to be a symbolic link. (3.31.0) + const SQLITE_OPEN_NOFOLLOW = 0x0100_0000; + /// Extended result codes. (3.37.0) + const SQLITE_OPEN_EXRESCODE = 0x0200_0000; + } +} + +impl Default for OpenFlags { + #[inline] + fn default() -> Self { + // Note: update the `Connection::open` and top-level `OpenFlags` docs if + // you change these. + Self::SQLITE_OPEN_READ_WRITE + | Self::SQLITE_OPEN_CREATE + | Self::SQLITE_OPEN_NO_MUTEX + | Self::SQLITE_OPEN_URI + } +} + +bitflags::bitflags! { + /// Prepare flags. See + /// [sqlite3_prepare_v3](https://sqlite.org/c3ref/c_prepare_dont_log.html) for details. + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] + #[repr(C)] + pub struct PrepFlags: c_uint { + /// A hint to the query planner that the prepared statement will be retained for a long time and probably reused many times. + const SQLITE_PREPARE_PERSISTENT = 0x01; + /// Causes the SQL compiler to return an error (error code SQLITE_ERROR) if the statement uses any virtual tables. + const SQLITE_PREPARE_NO_VTAB = 0x04; + /// Prevents SQL compiler errors from being sent to the error log. + const SQLITE_PREPARE_DONT_LOG = 0x10; + } +} + +/// Allows interrupting a long-running computation. +pub struct InterruptHandle { + db_lock: Arc>, +} + +unsafe impl Send for InterruptHandle {} +unsafe impl Sync for InterruptHandle {} + +impl InterruptHandle { + /// Interrupt the query currently executing on another thread. This will + /// cause that query to fail with a `SQLITE3_INTERRUPT` error. + pub fn interrupt(&self) { + let db_handle = self.db_lock.lock().unwrap(); + if !db_handle.is_null() { + unsafe { ffi::sqlite3_interrupt(*db_handle) } + } + } +} + +#[cfg(doctest)] +doc_comment::doctest!("../README.md"); + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::*; + use fallible_iterator::FallibleIterator; + use std::error::Error as StdError; + use std::fmt; + + // this function is never called, but is still type checked; in + // particular, calls with specific instantiations will require + // that those types are `Send`. + #[allow(dead_code)] + #[expect(unconditional_recursion, clippy::extra_unused_type_parameters)] + fn ensure_send() { + ensure_send::(); + ensure_send::(); + } + + #[allow(dead_code)] + #[expect(unconditional_recursion, clippy::extra_unused_type_parameters)] + fn ensure_sync() { + ensure_sync::(); + } + + fn checked_memory_handle() -> Connection { + Connection::open_in_memory().unwrap() + } + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn test_concurrent_transactions_busy_commit() -> Result<()> { + use std::time::Duration; + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join("transactions.db3"); + + Connection::open(&path)?.execute_batch( + " + BEGIN; CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(42); END;", + )?; + + let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE)?; + let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)?; + + db1.busy_timeout(Duration::from_millis(0))?; + db2.busy_timeout(Duration::from_millis(0))?; + + { + let tx1 = db1.transaction()?; + let tx2 = db2.transaction()?; + + // SELECT first makes sqlite lock with a shared lock + tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?; + tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?; + + tx1.execute("INSERT INTO foo VALUES(?1)", [1])?; + let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]); + + let _ = tx1.commit(); + let _ = tx2.commit(); + } + + let _ = db1 + .transaction() + .expect("commit should have closed transaction"); + let _ = db2 + .transaction() + .expect("commit should have closed transaction"); + Ok(()) + } + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn test_persistence() -> Result<()> { + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("test.db3"); + + { + let db = Connection::open(&path)?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(42); + END;"; + db.execute_batch(sql)?; + } + + let path_string = path.to_str().unwrap(); + let db = Connection::open(path_string)?; + + assert_eq!(42, db.one_column::("SELECT x FROM foo", [])?); + Ok(()) + } + + #[test] + fn test_open() { + Connection::open_in_memory().unwrap(); + + let db = checked_memory_handle(); + db.close().unwrap(); + } + + #[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + ignore = "no filesystem on this platform" + )] + #[test] + fn test_path() -> Result<()> { + let tmp = tempfile::tempdir().unwrap(); + let db = Connection::open("")?; + assert_eq!(Some(""), db.path()); + let db = Connection::open_in_memory()?; + assert_eq!(Some(""), db.path()); + let db = Connection::open("file:dummy.db?mode=memory&cache=shared")?; + assert_eq!(Some(""), db.path()); + let path = tmp.path().join("file.db"); + let db = Connection::open(path)?; + assert!(db.path().is_some_and(|p| p.ends_with("file.db"))); + Ok(()) + } + + #[test] + fn test_open_failure() { + let filename = "no_such_file.db"; + let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY); + let err = result.unwrap_err(); + if let Error::SqliteFailure(e, Some(msg)) = err { + assert_eq!(ErrorCode::CannotOpen, e.code); + assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code); + assert!( + msg.contains(filename), + "error message '{msg}' does not contain '{filename}'" + ); + } else { + panic!("SqliteFailure expected"); + } + } + + #[cfg(unix)] + #[test] + fn test_invalid_unicode_file_names() -> Result<()> { + use std::ffi::OsStr; + use std::fs::File; + use std::os::unix::ffi::OsStrExt; + let temp_dir = tempfile::tempdir().unwrap(); + + let path = temp_dir.path(); + if File::create(path.join(OsStr::from_bytes(&[0xFE]))).is_err() { + // Skip test, filesystem doesn't support invalid Unicode + return Ok(()); + } + let db_path = path.join(OsStr::from_bytes(&[0xFF])); + { + let db = Connection::open(&db_path)?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(42); + END;"; + db.execute_batch(sql)?; + } + + let db = Connection::open(&db_path)?; + + assert_eq!(42, db.one_column::("SELECT x FROM foo", [])?); + Ok(()) + } + + #[test] + fn test_close_retry() -> Result<()> { + let db = Connection::open_in_memory()?; + + // force the DB to be busy by preparing a statement; this must be done at the + // FFI level to allow us to call .close() without dropping the prepared + // statement first. + let raw_stmt = { + use super::str_to_cstring; + use std::ffi::c_int; + use std::ptr; + + let raw_db = db.db.borrow_mut().db; + let sql = "SELECT 1"; + let mut raw_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut(); + let cstring = str_to_cstring(sql)?; + let rc = unsafe { + ffi::sqlite3_prepare_v2( + raw_db, + cstring.as_ptr(), + (sql.len() + 1) as c_int, + &mut raw_stmt, + ptr::null_mut(), + ) + }; + assert_eq!(rc, ffi::SQLITE_OK); + raw_stmt + }; + + // now that we have an open statement, trying (and retrying) to close should + // fail. + let (db, _) = db.close().unwrap_err(); + let (db, _) = db.close().unwrap_err(); + let (db, _) = db.close().unwrap_err(); + + // finalize the open statement so a final close will succeed + assert_eq!(ffi::SQLITE_OK, unsafe { ffi::sqlite3_finalize(raw_stmt) }); + + db.close().unwrap(); + Ok(()) + } + + #[test] + fn test_open_with_flags() { + for bad_flags in &[ + OpenFlags::empty(), + OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE, + OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE, + ] { + Connection::open_in_memory_with_flags(*bad_flags).unwrap_err(); + } + } + + #[test] + fn test_execute_batch() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(1); + INSERT INTO foo VALUES(2); + INSERT INTO foo VALUES(3); + INSERT INTO foo VALUES(4); + END;"; + db.execute_batch(sql)?; + + db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?; + + db.execute_batch("INVALID SQL").unwrap_err(); + + db.execute_batch("PRAGMA locking_mode = EXCLUSIVE")?; + Ok(()) + } + + #[test] + fn test_execute() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER)")?; + + assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [1i32])?); + assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [2i32])?); + + assert_eq!(3, db.one_column::("SELECT SUM(x) FROM foo", [])?); + Ok(()) + } + + #[test] + #[cfg(feature = "extra_check")] + fn test_execute_select_with_no_row() { + let db = checked_memory_handle(); + let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err(); + assert_eq!( + err, + Error::ExecuteReturnedResults, + "Unexpected error: {err}" + ); + } + + #[test] + fn test_execute_select_with_row() { + let db = checked_memory_handle(); + let err = db.execute("SELECT 1", []).unwrap_err(); + assert_eq!(err, Error::ExecuteReturnedResults); + } + + #[test] + fn test_execute_multiple() { + let db = checked_memory_handle(); + let err = db + .execute( + "CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)", + [], + ) + .unwrap_err(); + match err { + Error::MultipleStatement => (), + _ => panic!("Unexpected error: {err}"), + } + db.execute("CREATE TABLE t(c); -- bim", []) + .expect("Tail comment should be ignored"); + } + + #[test] + fn test_prepare_column_names() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER);")?; + + let stmt = db.prepare("SELECT * FROM foo")?; + assert_eq!(stmt.column_count(), 1); + assert_eq!(stmt.column_names(), vec!["x"]); + + let stmt = db.prepare("SELECT x AS a, x AS b FROM foo")?; + assert_eq!(stmt.column_count(), 2); + assert_eq!(stmt.column_names(), vec!["a", "b"]); + Ok(()) + } + + #[test] + fn test_prepare_execute() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER);")?; + + let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?; + assert_eq!(insert_stmt.execute([1i32])?, 1); + assert_eq!(insert_stmt.execute([2i32])?, 1); + assert_eq!(insert_stmt.execute([3i32])?, 1); + + assert_eq!(insert_stmt.execute(["hello"])?, 1); + assert_eq!(insert_stmt.execute(["goodbye"])?, 1); + assert_eq!(insert_stmt.execute([types::Null])?, 1); + + let mut update_stmt = db.prepare("UPDATE foo SET x=?1 WHERE x Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER);")?; + + let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?; + assert_eq!(insert_stmt.execute([1i32])?, 1); + assert_eq!(insert_stmt.execute([2i32])?, 1); + assert_eq!(insert_stmt.execute([3i32])?, 1); + + let mut query = db.prepare("SELECT x FROM foo WHERE x < ?1 ORDER BY x DESC")?; + { + let mut rows = query.query([4i32])?; + let mut v = Vec::::new(); + + while let Some(row) = rows.next()? { + v.push(row.get(0)?); + } + + assert_eq!(v, [3i32, 2, 1]); + } + + { + let mut rows = query.query([3i32])?; + let mut v = Vec::::new(); + + while let Some(row) = rows.next()? { + v.push(row.get(0)?); + } + + assert_eq!(v, [2i32, 1]); + } + Ok(()) + } + + #[test] + fn test_query_map() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql)?; + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?; + let results: Result> = query.query([])?.map(|row| row.get(1)).collect(); + + assert_eq!(results?.concat(), "hello, world!"); + Ok(()) + } + + #[test] + fn test_query_row() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(1); + INSERT INTO foo VALUES(2); + INSERT INTO foo VALUES(3); + INSERT INTO foo VALUES(4); + END;"; + db.execute_batch(sql)?; + + assert_eq!(10, db.one_column::("SELECT SUM(x) FROM foo", [])?); + + let result: Result = db.one_column("SELECT x FROM foo WHERE x > 5", []); + match result.unwrap_err() { + Error::QueryReturnedNoRows => (), + err => panic!("Unexpected error {err}"), + } + + db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(())) + .unwrap_err(); + + db.query_row("SELECT 1; SELECT 2;", [], |_| Ok(())) + .unwrap_err(); + + Ok(()) + } + + #[test] + fn test_optional() -> Result<()> { + let db = Connection::open_in_memory()?; + + let result: Result = db.one_column("SELECT 1 WHERE 0 <> 0", []); + let result = result.optional(); + match result? { + None => (), + _ => panic!("Unexpected result"), + } + + let result: Result = db.one_column("SELECT 1 WHERE 0 == 0", []); + let result = result.optional(); + match result? { + Some(1) => (), + _ => panic!("Unexpected result"), + } + + let bad_query_result: Result = db.one_column("NOT A PROPER QUERY", []); + let bad_query_result = bad_query_result.optional(); + bad_query_result.unwrap_err(); + Ok(()) + } + + #[test] + fn test_pragma_query_row() -> Result<()> { + let db = Connection::open_in_memory()?; + assert_eq!( + "memory", + db.one_column::("PRAGMA journal_mode", [])? + ); + let mode = db.one_column::("PRAGMA journal_mode=off", [])?; + if cfg!(feature = "bundled") { + assert_eq!(mode, "off"); + } else { + // Note: system SQLite on macOS defaults to "off" rather than + // "memory" for the journal mode (which cannot be changed for + // in-memory connections). This seems like it's *probably* legal + // according to the docs below, so we relax this test when not + // bundling: + // + // From https://www.sqlite.org/pragma.html#pragma_journal_mode + // > Note that the journal_mode for an in-memory database is either + // > MEMORY or OFF and can not be changed to a different value. An + // > attempt to change the journal_mode of an in-memory database to + // > any setting other than MEMORY or OFF is ignored. + assert!(mode == "memory" || mode == "off", "Got mode {mode:?}"); + } + + Ok(()) + } + + #[test] + fn test_prepare_failures() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER);")?; + + let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err(); + assert!(format!("{err}").contains("does_not_exist")); + Ok(()) + } + + #[test] + fn test_last_insert_rowid() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?; + db.execute_batch("INSERT INTO foo DEFAULT VALUES")?; + + assert_eq!(db.last_insert_rowid(), 1); + + let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES")?; + for _ in 0i32..9 { + stmt.execute([])?; + } + assert_eq!(db.last_insert_rowid(), 10); + Ok(()) + } + + #[test] + fn test_total_changes() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "CREATE TABLE foo(x INTEGER PRIMARY KEY, value TEXT default '' NOT NULL, + desc TEXT default ''); + CREATE VIEW foo_bar AS SELECT x, desc FROM foo WHERE value = 'bar'; + CREATE TRIGGER INSERT_FOOBAR + INSTEAD OF INSERT + ON foo_bar + BEGIN + INSERT INTO foo VALUES(new.x, 'bar', new.desc); + END;"; + db.execute_batch(sql)?; + let total_changes_before = db.total_changes(); + let changes = db + .prepare("INSERT INTO foo_bar VALUES(null, 'baz');")? + .execute([])?; + let total_changes_after = db.total_changes(); + assert_eq!(changes, 0); + assert_eq!(total_changes_after - total_changes_before, 1); + Ok(()) + } + + #[test] + fn test_is_autocommit() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!( + db.is_autocommit(), + "autocommit expected to be active by default" + ); + Ok(()) + } + + #[test] + fn test_is_busy() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(!db.is_busy()); + let mut stmt = db.prepare("PRAGMA schema_version")?; + assert!(!db.is_busy()); + { + let mut rows = stmt.query([])?; + assert!(!db.is_busy()); + let row = rows.next()?; + assert!(db.is_busy()); + assert!(row.is_some()); + } + assert!(!db.is_busy()); + Ok(()) + } + + #[test] + fn test_statement_debugging() -> Result<()> { + let db = Connection::open_in_memory()?; + let query = "SELECT 12345"; + let stmt = db.prepare(query)?; + + assert!(format!("{stmt:?}").contains(query)); + Ok(()) + } + + #[test] + fn test_notnull_constraint_error() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x NOT NULL)")?; + + let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []); + + match result.unwrap_err() { + Error::SqliteFailure(err, _) => { + assert_eq!(err.code, ErrorCode::ConstraintViolation); + assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL); + } + err => panic!("Unexpected error {err}"), + } + Ok(()) + } + + #[test] + fn test_version_string() { + let n = version_number(); + let major = n / 1_000_000; + let minor = (n % 1_000_000) / 1_000; + let patch = n % 1_000; + + assert!(version().contains(&format!("{major}.{minor}.{patch}"))); + } + + #[test] + #[cfg(feature = "functions")] + fn test_interrupt() -> Result<()> { + let db = Connection::open_in_memory()?; + + let interrupt_handle = db.get_interrupt_handle(); + + db.create_scalar_function( + "interrupt", + 0, + functions::FunctionFlags::default(), + move |_| { + interrupt_handle.interrupt(); + Ok(0) + }, + )?; + + let mut stmt = + db.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")?; + + let result: Result> = stmt.query([])?.map(|r| r.get(0)).collect(); + + assert_eq!( + result.unwrap_err().sqlite_error_code(), + Some(ErrorCode::OperationInterrupted) + ); + Ok(()) + } + + #[test] + fn test_interrupt_close() { + let db = checked_memory_handle(); + let handle = db.get_interrupt_handle(); + handle.interrupt(); + db.close().unwrap(); + handle.interrupt(); + + // Look at its internals to see if we cleared it out properly. + let db_guard = handle.db_lock.lock().unwrap(); + assert!(db_guard.is_null()); + // It would be nice to test that we properly handle close/interrupt + // running at the same time, but it seems impossible to do with any + // degree of reliability. + } + + #[test] + fn test_get_raw() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(i, x);")?; + let vals = ["foobar", "1234", "qwerty"]; + let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?1, ?2)")?; + for (i, v) in vals.iter().enumerate() { + let i_to_insert = i as i64; + assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1); + } + + let mut query = db.prepare("SELECT i, x FROM foo")?; + let mut rows = query.query([])?; + + while let Some(row) = rows.next()? { + let i = row.get_ref(0)?.as_i64()?; + let expect = vals[i as usize]; + let x = row.get_ref("x")?.as_str()?; + assert_eq!(x, expect); + } + + let mut query = db.prepare("SELECT x FROM foo")?; + let rows = query.query_map([], |row| { + let x = row.get_ref(0)?.as_str()?; // check From for Error + Ok(x[..].to_owned()) + })?; + + for (i, row) in rows.enumerate() { + assert_eq!(row?, vals[i]); + } + Ok(()) + } + + #[test] + fn test_from_handle() -> Result<()> { + let db = Connection::open_in_memory()?; + let handle = unsafe { db.handle() }; + { + let db = unsafe { Connection::from_handle(handle) }?; + db.execute_batch("PRAGMA VACUUM")?; + } + db.close().unwrap(); + Ok(()) + } + + #[test] + fn test_from_handle_owned() -> Result<()> { + let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut(); + let r = unsafe { ffi::sqlite3_open(c":memory:".as_ptr(), &mut handle) }; + assert_eq!(r, ffi::SQLITE_OK); + let db = unsafe { Connection::from_handle_owned(handle) }?; + db.execute_batch("PRAGMA VACUUM")?; + Ok(()) + } + + mod query_and_then_tests { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::*; + + #[derive(Debug)] + enum CustomError { + SomeError, + Sqlite(Error), + } + + impl fmt::Display for CustomError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + Self::SomeError => write!(f, "my custom error"), + Self::Sqlite(ref se) => write!(f, "my custom error: {se}"), + } + } + } + + impl StdError for CustomError { + fn description(&self) -> &str { + "my custom error" + } + + fn cause(&self) -> Option<&dyn StdError> { + match *self { + Self::SomeError => None, + Self::Sqlite(ref se) => Some(se), + } + } + } + + impl From for CustomError { + fn from(se: Error) -> Self { + Self::Sqlite(se) + } + } + + type CustomResult = Result; + + #[test] + fn test_query_and_then() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql)?; + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?; + let results: Result> = + query.query_and_then([], |row| row.get(1))?.collect(); + + assert_eq!(results?.concat(), "hello, world!"); + Ok(()) + } + + #[test] + fn test_query_and_then_fails() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql)?; + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?; + let bad_type: Result> = query.query_and_then([], |row| row.get(1))?.collect(); + + match bad_type.unwrap_err() { + Error::InvalidColumnType(..) => (), + err => panic!("Unexpected error {err}"), + } + + let bad_idx: Result> = + query.query_and_then([], |row| row.get(3))?.collect(); + + match bad_idx.unwrap_err() { + Error::InvalidColumnIndex(_) => (), + err => panic!("Unexpected error {err}"), + } + Ok(()) + } + + #[test] + fn test_query_and_then_custom_error() -> CustomResult<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql)?; + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?; + let results: CustomResult> = query + .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))? + .collect(); + + assert_eq!(results?.concat(), "hello, world!"); + Ok(()) + } + + #[test] + fn test_query_and_then_custom_error_fails() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql)?; + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?; + let bad_type: CustomResult> = query + .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))? + .collect(); + + match bad_type.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnType(..)) => (), + err => panic!("Unexpected error {err}"), + } + + let bad_idx: CustomResult> = query + .query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite))? + .collect(); + + match bad_idx.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (), + err => panic!("Unexpected error {err}"), + } + + let non_sqlite_err: CustomResult> = query + .query_and_then([], |_| Err(CustomError::SomeError))? + .collect(); + + match non_sqlite_err.unwrap_err() { + CustomError::SomeError => (), + err => panic!("Unexpected error {err}"), + } + Ok(()) + } + + #[test] + fn test_query_row_and_then_custom_error() -> CustomResult<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + END;"; + db.execute_batch(sql)?; + + let query = "SELECT x, y FROM foo ORDER BY x DESC"; + let results: CustomResult = + db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite)); + + assert_eq!(results?, "hello"); + Ok(()) + } + + #[test] + fn test_query_row_and_then_custom_error_fails() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + END;"; + db.execute_batch(sql)?; + + let query = "SELECT x, y FROM foo ORDER BY x DESC"; + let bad_type: CustomResult = + db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite)); + + match bad_type.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnType(..)) => (), + err => panic!("Unexpected error {err}"), + } + + let bad_idx: CustomResult = + db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite)); + + match bad_idx.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (), + err => panic!("Unexpected error {err}"), + } + + let non_sqlite_err: CustomResult = + db.query_row_and_then(query, [], |_| Err(CustomError::SomeError)); + + match non_sqlite_err.unwrap_err() { + CustomError::SomeError => (), + err => panic!("Unexpected error {err}"), + } + Ok(()) + } + } + + #[test] + fn test_dynamic() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + END;"; + db.execute_batch(sql)?; + + db.query_row("SELECT * FROM foo", [], |r| { + assert_eq!(2, r.as_ref().column_count()); + Ok(()) + }) + } + #[test] + fn test_dyn_box() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER);")?; + let b: Box = Box::new(5); + db.execute("INSERT INTO foo VALUES(?1)", [b])?; + db.query_row("SELECT x FROM foo", [], |r| { + assert_eq!(5, r.get_unwrap::<_, i32>(0)); + Ok(()) + }) + } + + #[test] + fn test_params() -> Result<()> { + let db = Connection::open_in_memory()?; + db.query_row( + "SELECT + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, + ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, + ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, + ?31, ?32, ?33, ?34;", + params![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + ], + |r| { + assert_eq!(1, r.get_unwrap::<_, i32>(0)); + Ok(()) + }, + ) + } + + #[test] + fn test_alter_table() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE x(t);")?; + // `execute_batch` should be used but `execute` should also work + db.execute("ALTER TABLE x RENAME TO y;", [])?; + Ok(()) + } + + #[test] + fn test_batch() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = r" + CREATE TABLE tbl1 (col); + CREATE TABLE tbl2 (col); + "; + let mut batch = Batch::new(&db, sql); + while let Some(mut stmt) = batch.next()? { + stmt.execute([])?; + } + Ok(()) + } + + #[test] + fn test_invalid_batch() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = r" + PRAGMA test1; + PRAGMA test2=?; + PRAGMA test3; + "; + let mut batch = Batch::new(&db, sql); + assert!(batch.next().is_ok()); + assert!(batch.next().is_err()); + assert!(batch.next().is_err()); + assert!(Batch::new(&db, sql).count().is_err()); + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn test_returning() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?; + let row_id = + db.one_column::("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [])?; + assert_eq!(row_id, 1); + Ok(()) + } + + #[test] + fn test_cache_flush() -> Result<()> { + let db = Connection::open_in_memory()?; + db.cache_flush() + } + + #[test] + fn db_readonly() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(!db.is_readonly(MAIN_DB)?); + Ok(()) + } + + #[test] + #[cfg(feature = "rusqlite-macros")] + fn prepare_and_bind() -> Result<()> { + let db = Connection::open_in_memory()?; + let name = "Lisa"; + let age = 8; + let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;"); + let (v1, v2) = stmt + .raw_query() + .next() + .and_then(|o| o.ok_or(Error::QueryReturnedNoRows)) + .and_then(|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?; + assert_eq!((v1.as_str(), v2), (name, age)); + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn test_db_name() -> Result<()> { + let db = Connection::open_in_memory()?; + assert_eq!(db.db_name(0)?, "main"); + assert_eq!(db.db_name(1)?, "temp"); + assert_eq!(db.db_name(2), Err(Error::InvalidDatabaseIndex(2))); + db.execute_batch("ATTACH DATABASE ':memory:' AS xyz;")?; + assert_eq!(db.db_name(2)?, "xyz"); + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn test_is_interrupted() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(!db.is_interrupted()); + db.get_interrupt_handle().interrupt(); + assert!(db.is_interrupted()); + Ok(()) + } + + #[test] + fn release_memory() -> Result<()> { + let db = Connection::open_in_memory()?; + db.release_memory() + } +} diff --git a/vendor/rusqlite/src/limits.rs b/vendor/rusqlite/src/limits.rs new file mode 100644 index 0000000..562d76e --- /dev/null +++ b/vendor/rusqlite/src/limits.rs @@ -0,0 +1,182 @@ +//! Run-Time Limits + +use crate::{ffi, Connection, Result}; +use std::ffi::c_int; + +/// Run-Time limit categories, for use with [`Connection::limit`] and +/// [`Connection::set_limit`]. +/// +/// See the official documentation for more information: +/// - +/// - +#[derive(Copy, Clone, Debug)] +#[repr(i32)] +#[non_exhaustive] +#[expect(non_camel_case_types)] +pub enum Limit { + /// The maximum size of any string or BLOB or table row, in bytes. + SQLITE_LIMIT_LENGTH = ffi::SQLITE_LIMIT_LENGTH, + /// The maximum length of an SQL statement, in bytes. + SQLITE_LIMIT_SQL_LENGTH = ffi::SQLITE_LIMIT_SQL_LENGTH, + /// The maximum number of columns in a table definition or in the result set + /// of a SELECT or the maximum number of columns in an index or in an + /// ORDER BY or GROUP BY clause. + SQLITE_LIMIT_COLUMN = ffi::SQLITE_LIMIT_COLUMN, + /// The maximum depth of the parse tree on any expression. + SQLITE_LIMIT_EXPR_DEPTH = ffi::SQLITE_LIMIT_EXPR_DEPTH, + /// The maximum number of terms in a compound SELECT statement. + SQLITE_LIMIT_COMPOUND_SELECT = ffi::SQLITE_LIMIT_COMPOUND_SELECT, + /// The maximum number of instructions in a virtual machine program used to + /// implement an SQL statement. + SQLITE_LIMIT_VDBE_OP = ffi::SQLITE_LIMIT_VDBE_OP, + /// The maximum number of arguments on a function. + SQLITE_LIMIT_FUNCTION_ARG = ffi::SQLITE_LIMIT_FUNCTION_ARG, + /// The maximum number of attached databases. + SQLITE_LIMIT_ATTACHED = ffi::SQLITE_LIMIT_ATTACHED, + /// The maximum length of the pattern argument to the LIKE or GLOB + /// operators. + SQLITE_LIMIT_LIKE_PATTERN_LENGTH = ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH, + /// The maximum index number of any parameter in an SQL statement. + SQLITE_LIMIT_VARIABLE_NUMBER = ffi::SQLITE_LIMIT_VARIABLE_NUMBER, + /// The maximum depth of recursion for triggers. + SQLITE_LIMIT_TRIGGER_DEPTH = ffi::SQLITE_LIMIT_TRIGGER_DEPTH, + /// The maximum number of auxiliary worker threads that a single prepared + /// statement may start. + SQLITE_LIMIT_WORKER_THREADS = ffi::SQLITE_LIMIT_WORKER_THREADS, + /// Only used for testing + #[cfg(test)] + INVALID = -1, +} + +impl Connection { + /// Returns the current value of a [`Limit`]. + #[inline] + pub fn limit(&self, limit: Limit) -> Result { + let c = self.db.borrow(); + let rc = unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) }; + if rc < 0 { + return Err(err!(ffi::SQLITE_RANGE, "{limit:?} is invalid")); + } + Ok(rc) + } + + /// Changes the [`Limit`] to `new_val`, returning the prior + /// value of the limit. + #[inline] + pub fn set_limit(&self, limit: Limit, new_val: i32) -> Result { + if new_val < 0 { + return Err(err!(ffi::SQLITE_RANGE, "{new_val} is invalid")); + } + let c = self.db.borrow_mut(); + let rc = unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, new_val) }; + if rc < 0 { + return Err(err!(ffi::SQLITE_RANGE, "{limit:?} is invalid")); + } + Ok(rc) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::*; + use crate::Result; + + #[test] + fn test_limit_values() { + assert_eq!(Limit::SQLITE_LIMIT_LENGTH as i32, ffi::SQLITE_LIMIT_LENGTH,); + assert_eq!( + Limit::SQLITE_LIMIT_SQL_LENGTH as i32, + ffi::SQLITE_LIMIT_SQL_LENGTH, + ); + assert_eq!(Limit::SQLITE_LIMIT_COLUMN as i32, ffi::SQLITE_LIMIT_COLUMN,); + assert_eq!( + Limit::SQLITE_LIMIT_EXPR_DEPTH as i32, + ffi::SQLITE_LIMIT_EXPR_DEPTH, + ); + assert_eq!( + Limit::SQLITE_LIMIT_COMPOUND_SELECT as i32, + ffi::SQLITE_LIMIT_COMPOUND_SELECT, + ); + assert_eq!( + Limit::SQLITE_LIMIT_VDBE_OP as i32, + ffi::SQLITE_LIMIT_VDBE_OP, + ); + assert_eq!( + Limit::SQLITE_LIMIT_FUNCTION_ARG as i32, + ffi::SQLITE_LIMIT_FUNCTION_ARG, + ); + assert_eq!( + Limit::SQLITE_LIMIT_ATTACHED as i32, + ffi::SQLITE_LIMIT_ATTACHED, + ); + assert_eq!( + Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32, + ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH, + ); + assert_eq!( + Limit::SQLITE_LIMIT_VARIABLE_NUMBER as i32, + ffi::SQLITE_LIMIT_VARIABLE_NUMBER, + ); + assert_eq!( + Limit::SQLITE_LIMIT_TRIGGER_DEPTH as i32, + ffi::SQLITE_LIMIT_TRIGGER_DEPTH, + ); + assert_eq!( + Limit::SQLITE_LIMIT_WORKER_THREADS as i32, + ffi::SQLITE_LIMIT_WORKER_THREADS, + ); + } + + #[test] + fn test_limit() -> Result<()> { + let db = Connection::open_in_memory()?; + db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024)?; + assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_LENGTH)?); + + db.set_limit(Limit::SQLITE_LIMIT_SQL_LENGTH, 1024)?; + assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_SQL_LENGTH)?); + + db.set_limit(Limit::SQLITE_LIMIT_COLUMN, 64)?; + assert_eq!(64, db.limit(Limit::SQLITE_LIMIT_COLUMN)?); + + db.set_limit(Limit::SQLITE_LIMIT_EXPR_DEPTH, 256)?; + assert_eq!(256, db.limit(Limit::SQLITE_LIMIT_EXPR_DEPTH)?); + + db.set_limit(Limit::SQLITE_LIMIT_COMPOUND_SELECT, 32)?; + assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_COMPOUND_SELECT)?); + + db.set_limit(Limit::SQLITE_LIMIT_FUNCTION_ARG, 32)?; + assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_FUNCTION_ARG)?); + + db.set_limit(Limit::SQLITE_LIMIT_ATTACHED, 2)?; + assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_ATTACHED)?); + + db.set_limit(Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 128)?; + assert_eq!(128, db.limit(Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH)?); + + db.set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, 99)?; + assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)?); + + db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32)?; + assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)?); + + db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2)?; + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS)?); + + // wasm build with DSQLITE_THREADSAFE=0, so limit not working + // see + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + assert_eq!(0, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS)?); + + assert!(db + .set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, -1) + .is_err()); + assert!(db.set_limit(Limit::INVALID, 0).is_err()); + assert!(db.limit(Limit::INVALID).is_err()); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/load_extension_guard.rs b/vendor/rusqlite/src/load_extension_guard.rs new file mode 100644 index 0000000..25a7973 --- /dev/null +++ b/vendor/rusqlite/src/load_extension_guard.rs @@ -0,0 +1,45 @@ +use crate::{Connection, Result}; + +/// RAII guard temporarily enabling SQLite extensions to be loaded. +/// +/// ## Example +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result, LoadExtensionGuard}; +/// # use std::path::{Path}; +/// fn load_my_extension(conn: &Connection) -> Result<()> { +/// unsafe { +/// let _guard = LoadExtensionGuard::new(conn)?; +/// conn.load_extension("trusted/sqlite/extension", None::<&str>) +/// } +/// } +/// ``` +pub struct LoadExtensionGuard<'conn> { + conn: &'conn Connection, +} + +impl LoadExtensionGuard<'_> { + /// Attempt to enable loading extensions. Loading extensions will be + /// disabled when this guard goes out of scope. Cannot be meaningfully + /// nested. + /// + /// # Safety + /// + /// You must not run untrusted queries while extension loading is enabled. + /// + /// See the safety comment on [`Connection::load_extension_enable`] for more + /// details. + #[inline] + pub unsafe fn new(conn: &Connection) -> Result> { + conn.load_extension_enable() + .map(|_| LoadExtensionGuard { conn }) + } +} + +#[expect(unused_must_use)] +impl Drop for LoadExtensionGuard<'_> { + #[inline] + fn drop(&mut self) { + self.conn.load_extension_disable(); + } +} diff --git a/vendor/rusqlite/src/params.rs b/vendor/rusqlite/src/params.rs new file mode 100644 index 0000000..e85793b --- /dev/null +++ b/vendor/rusqlite/src/params.rs @@ -0,0 +1,453 @@ +use crate::{BindIndex, Result, Statement, ToSql}; + +mod sealed { + /// This trait exists just to ensure that the only impls of `trait Params` + /// that are allowed are ones in this crate. + pub trait Sealed {} +} +use sealed::Sealed; + +/// Trait used for [sets of parameter][params] passed into SQL +/// statements/queries. +/// +/// [params]: https://www.sqlite.org/c3ref/bind_blob.html +/// +/// Note: Currently, this trait can only be implemented inside this crate. +/// Additionally, it's methods (which are `doc(hidden)`) should currently not be +/// considered part of the stable API, although it's possible they will +/// stabilize in the future. +/// +/// # Passing parameters to SQLite +/// +/// Many functions in this library let you pass parameters to SQLite. Doing this +/// lets you avoid any risk of SQL injection, and is simpler than escaping +/// things manually. Aside from deprecated functions and a few helpers, this is +/// indicated by the function taking a generic argument that implements `Params` +/// (this trait). +/// +/// ## Positional parameters +/// +/// For cases where you want to pass a list of parameters where the number of +/// parameters is known at compile time, this can be done in one of the +/// following ways: +/// +/// - For small lists of parameters up to 16 items, they may alternatively be +/// passed as a tuple, as in `thing.query((1, "foo"))`. +/// This is somewhat inconvenient for a single item, since you need a +/// weird-looking trailing comma: `thing.query(("example",))`. That case is +/// perhaps more cleanly expressed as `thing.query(["example"])`. +/// +/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g. +/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for +/// heterogeneous lists where the number of parameters greater than 16, or +/// homogeneous lists of parameters where the number of parameters exceeds 32. +/// +/// - For small homogeneous lists of parameters, they can either be passed as: +/// +/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo", +/// "bar", "baz"])`. +/// +/// - a reference to an array of references, as in `thing.query(&["foo", +/// "bar", "baz"])` or `thing.query(&[&1i32, &2, &3])`. +/// (Note: in this case we don't implement this for slices for coherence +/// reasons, so it really is only for the "reference to array" types — +/// hence why the number of parameters must be <= 32, or you need to +/// reach for `rusqlite::params!`) +/// +/// Unfortunately, in the current design it's not possible to allow this for +/// references to arrays of non-references (e.g. `&[1i32, 2, 3]`). Code like +/// this should instead either use `params!`, an array literal, a `&[&dyn +/// ToSql]` or if none of those work, [`ParamsFromIter`]. +/// +/// - As a slice of `ToSql` trait object references, e.g. `&[&dyn ToSql]`. This +/// is mostly useful for passing parameter lists around as arguments without +/// having every function take a generic `P: Params`. +/// +/// ### Example (positional) +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result, params}; +/// fn update_rows(conn: &Connection) -> Result<()> { +/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?; +/// +/// // Using a tuple: +/// stmt.execute((0, "foobar"))?; +/// +/// // Using `rusqlite::params!`: +/// stmt.execute(params![1i32, "blah"])?; +/// +/// // array literal — non-references +/// stmt.execute([2i32, 3i32])?; +/// +/// // array literal — references +/// stmt.execute(["foo", "bar"])?; +/// +/// // Slice literal, references: +/// stmt.execute(&[&2i32, &3i32])?; +/// +/// // Note: The types behind the references don't have to be `Sized` +/// stmt.execute(&["foo", "bar"])?; +/// +/// // However, this doesn't work (see above): +/// // stmt.execute(&[1i32, 2i32])?; +/// Ok(()) +/// } +/// ``` +/// +/// ## Named parameters +/// +/// SQLite lets you name parameters using a number of conventions (":foo", +/// "@foo", "$foo"). You can pass named parameters in to SQLite using rusqlite +/// in a few ways: +/// +/// - Using the [`rusqlite::named_params!`](crate::named_params!) macro, as in +/// `stmt.execute(named_params!{ ":name": "foo", ":age": 99 })`. Similar to +/// the `params` macro, this is most useful for heterogeneous lists of +/// parameters, or lists where the number of parameters exceeds 32. +/// +/// - As a slice of `&[(&str, &dyn ToSql)]`. This is what essentially all of +/// these boil down to in the end, conceptually at least. In theory, you can +/// pass this as `stmt`. +/// +/// - As array references, similar to the positional params. This looks like +/// `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or +/// `thing.query(&[(":foo", "abc"), (":bar", "def")])`. +/// +/// Note: Unbound named parameters will be left to the value they previously +/// were bound with, falling back to `NULL` for parameters which have never been +/// bound. +/// +/// ### Example (named) +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result, named_params}; +/// fn insert(conn: &Connection) -> Result<()> { +/// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :val)")?; +/// // Using `rusqlite::params!`: +/// stmt.execute(named_params! { ":key": "one", ":val": 2 })?; +/// // Alternatively: +/// stmt.execute(&[(":key", "three"), (":val", "four")])?; +/// // Or: +/// stmt.execute(&[(":key", &100), (":val", &200)])?; +/// Ok(()) +/// } +/// ``` +/// +/// ## No parameters +/// +/// You can just use an empty tuple or the empty array literal to run a query +/// that accepts no parameters. +/// +/// ### Example (no parameters) +/// +/// The empty tuple: +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result, params}; +/// fn delete_all_users(conn: &Connection) -> Result<()> { +/// // You may also use `()`. +/// conn.execute("DELETE FROM users", ())?; +/// Ok(()) +/// } +/// ``` +/// +/// The empty array: +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result, params}; +/// fn delete_all_users(conn: &Connection) -> Result<()> { +/// // Just use an empty array (e.g. `[]`) for no params. +/// conn.execute("DELETE FROM users", [])?; +/// Ok(()) +/// } +/// ``` +/// +/// ## Dynamic parameter list +/// +/// If you have a number of parameters which is unknown at compile time (for +/// example, building a dynamic query at runtime), you have two choices: +/// +/// - Use a `&[&dyn ToSql]`. This is often annoying to construct if you don't +/// already have this type on-hand. +/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an +/// iterator some `T: ToSql` with something that implements `Params`. The +/// usage of this looks like `rusqlite::params_from_iter(something)`. +/// +/// A lot of the considerations here are similar either way, so you should see +/// the [`ParamsFromIter`] documentation for more info / examples. +pub trait Params: Sealed { + // XXX not public api, might not need to expose. + // + // Binds the parameters to the statement. It is unlikely calling this + // explicitly will do what you want. Please use `Statement::query` or + // similar directly. + // + // For now, just hide the function in the docs... + #[doc(hidden)] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()>; +} + +// Explicitly impl for empty array. Critically, for `conn.execute([])` to be +// unambiguous, this must be the *only* implementation for an empty array. +// +// This sadly prevents `impl Params for [T; N]`, which +// forces people to use `params![...]` or `rusqlite::params_from_iter` for long +// homogeneous lists of parameters. This is not that big of a deal, but is +// unfortunate, especially because I mostly did it because I wanted a simple +// syntax for no-params that didn't require importing -- the empty tuple fits +// that nicely, but I didn't think of it until much later. +// +// Admittedly, if we did have the generic impl, then we *wouldn't* support the +// empty array literal as a parameter, since the `T` there would fail to be +// inferred. The error message here would probably be quite bad, and so on +// further thought, probably would end up causing *more* surprises, not less. +impl Sealed for [&(dyn ToSql + Send + Sync); 0] {} +impl Params for [&(dyn ToSql + Send + Sync); 0] { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.ensure_parameter_count(0) + } +} + +impl Sealed for &[&dyn ToSql] {} +impl Params for &[&dyn ToSql] { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self) + } +} + +impl Sealed for &[(S, T)] {} +impl Params for &[(S, T)] { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters_named(self) + } +} + +// Manual impls for the empty and singleton tuple, although the rest are covered +// by macros. +impl Sealed for () {} +impl Params for () { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.ensure_parameter_count(0) + } +} + +// I'm pretty sure you could tweak the `single_tuple_impl` to accept this. +impl Sealed for (T,) {} +impl Params for (T,) { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.ensure_parameter_count(1)?; + stmt.raw_bind_parameter(1, self.0)?; + Ok(()) + } +} + +macro_rules! single_tuple_impl { + ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => { + impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: ToSql,)* {} + impl<$($ftype,)*> Params for ($($ftype,)*) where $($ftype: ToSql,)* { + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.ensure_parameter_count($count)?; + $({ + debug_assert!($field < $count); + stmt.raw_bind_parameter($field + 1, self.$field)?; + })+ + Ok(()) + } + } + } +} + +// We use a macro for the rest, but don't bother with trying to implement it +// in a single invocation (it's possible to do, but my attempts were almost the +// same amount of code as just writing it out this way, and much more dense -- +// it is a more complicated case than the TryFrom macro we have for row->tuple). +// +// Note that going up to 16 (rather than the 12 that the impls in the stdlib +// usually support) is just because we did the same in the `TryFrom` impl. +// I didn't catch that then, but there's no reason to remove it, and it seems +// nice to be consistent here; this way putting data in the database and getting +// data out of the database are more symmetric in a (mostly superficial) sense. +single_tuple_impl!(2: (0 A), (1 B)); +single_tuple_impl!(3: (0 A), (1 B), (2 C)); +single_tuple_impl!(4: (0 A), (1 B), (2 C), (3 D)); +single_tuple_impl!(5: (0 A), (1 B), (2 C), (3 D), (4 E)); +single_tuple_impl!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F)); +single_tuple_impl!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G)); +single_tuple_impl!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H)); +single_tuple_impl!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I)); +single_tuple_impl!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J)); +single_tuple_impl!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K)); +single_tuple_impl!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L)); +single_tuple_impl!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M)); +single_tuple_impl!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N)); +single_tuple_impl!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O)); +single_tuple_impl!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P)); + +macro_rules! impl_for_array_ref { + ($($N:literal)+) => {$( + // These are already generic, and there's a shedload of them, so lets + // avoid the compile time hit from making them all inline for now. + impl Sealed for &[&T; $N] {} + impl Params for &[&T; $N] { + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self) + } + } + impl Sealed for &[(S, &T); $N] {} + impl Params for &[(S, &T); $N] { + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters_named(self) + } + } + impl Sealed for [T; $N] {} + impl Params for [T; $N] { + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(&self) + } + } + )+}; +} + +// Following libstd/libcore's (old) lead, implement this for arrays up to `[_; +// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the +// note above the impl of `[&dyn ToSql; 0]` for more information. +// +// Note that this unfortunately means we can't use const generics here, but I +// don't really think it matters -- users who hit that can use `params!` anyway. +impl_for_array_ref!( + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 +); + +/// Adapter type which allows any iterator over [`ToSql`] values to implement +/// [`Params`]. +/// +/// This struct is created by the [`params_from_iter`] function. +/// +/// This can be useful if you have something like an `&[String]` (of unknown +/// length), and you want to use them with an API that wants something +/// implementing `Params`. This way, you can avoid having to allocate storage +/// for something like a `&[&dyn ToSql]`. +/// +/// This essentially is only ever actually needed when dynamically generating +/// SQL — static SQL (by definition) has the number of parameters known +/// statically. As dynamically generating SQL is itself pretty advanced, this +/// API is itself for advanced use cases (See "Realistic use case" in the +/// examples). +/// +/// # Example +/// +/// ## Basic usage +/// +/// ```rust,no_run +/// use rusqlite::{params_from_iter, Connection, Result}; +/// use std::collections::BTreeSet; +/// +/// fn query(conn: &Connection, ids: &BTreeSet) -> Result<()> { +/// assert_eq!(ids.len(), 3, "Unrealistic sample code"); +/// +/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?1, ?2, ?3)")?; +/// let _rows = stmt.query(params_from_iter(ids.iter()))?; +/// +/// // use _rows... +/// Ok(()) +/// } +/// ``` +/// +/// ## Realistic use case +/// +/// Here's how you'd use `ParamsFromIter` to call [`Statement::exists`] with a +/// dynamic number of parameters. +/// +/// ```rust,no_run +/// use rusqlite::{Connection, Result}; +/// +/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result { +/// if usernames.is_empty() { +/// return Ok(false); +/// } +/// +/// // Note: `repeat_vars` never returns anything attacker-controlled, so +/// // it's fine to use it in a dynamically-built SQL string. +/// let vars = repeat_vars(usernames.len()); +/// +/// let sql = format!( +/// // In practice this would probably be better as an `EXISTS` query. +/// "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1", +/// vars, +/// ); +/// let mut stmt = conn.prepare(&sql)?; +/// stmt.exists(rusqlite::params_from_iter(usernames)) +/// } +/// +/// // Helper function to return a comma-separated sequence of `?`. +/// // - `repeat_vars(0) => panic!(...)` +/// // - `repeat_vars(1) => "?"` +/// // - `repeat_vars(2) => "?,?"` +/// // - `repeat_vars(3) => "?,?,?"` +/// // - ... +/// fn repeat_vars(count: usize) -> String { +/// assert_ne!(count, 0); +/// let mut s = "?,".repeat(count); +/// // Remove trailing comma +/// s.pop(); +/// s +/// } +/// ``` +/// +/// That is fairly complex, and even so would need even more work to be fully +/// production-ready: +/// +/// - production code should ensure `usernames` isn't so large that it will +/// surpass [`conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)`][limits], +/// chunking if too large. (Note that the limits api requires rusqlite to have +/// the "limits" feature). +/// +/// - `repeat_vars` can be implemented in a way that avoids needing to allocate +/// a String. +/// +/// - Etc... +/// +/// [limits]: crate::Connection::limit +/// +/// This complexity reflects the fact that `ParamsFromIter` is mainly intended +/// for advanced use cases — most of the time you should know how many +/// parameters you have statically (and if you don't, you're either doing +/// something tricky, or should take a moment to think about the design). +#[derive(Clone, Debug)] +pub struct ParamsFromIter(I); + +/// Constructor function for a [`ParamsFromIter`]. See its documentation for +/// more. +#[inline] +pub fn params_from_iter(iter: I) -> ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ + ParamsFromIter(iter) +} + +impl Sealed for ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ +} + +impl Params for ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ + #[inline] + fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self.0) + } +} diff --git a/vendor/rusqlite/src/pragma.rs b/vendor/rusqlite/src/pragma.rs new file mode 100644 index 0000000..9b25fb5 --- /dev/null +++ b/vendor/rusqlite/src/pragma.rs @@ -0,0 +1,426 @@ +//! Pragma helpers + +use std::ops::Deref; + +use crate::ffi; +use crate::types::{ToSql, ToSqlOutput, ValueRef}; +use crate::{Connection, Result, Row}; + +pub struct Sql { + buf: String, +} + +impl Sql { + pub fn new() -> Self { + Self { buf: String::new() } + } + + pub fn push_pragma(&mut self, schema_name: Option<&str>, pragma_name: &str) -> Result<()> { + self.push_keyword("PRAGMA")?; + self.push_space(); + if let Some(schema_name) = schema_name { + self.push_schema_name(schema_name); + self.push_dot(); + } + self.push_keyword(pragma_name) + } + + pub fn push_keyword(&mut self, keyword: &str) -> Result<()> { + if !keyword.is_empty() && is_identifier(keyword) { + self.buf.push_str(keyword); + Ok(()) + } else { + Err(err!(ffi::SQLITE_MISUSE, "Invalid keyword \"{keyword}\"")) + } + } + + pub fn push_schema_name(&mut self, schema_name: &str) { + self.push_identifier(schema_name); + } + + pub fn push_identifier(&mut self, s: &str) { + if is_identifier(s) { + self.buf.push_str(s); + } else { + self.wrap_and_escape(s, '"'); + } + } + + pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> { + let value = value.to_sql()?; + let value = match value { + ToSqlOutput::Borrowed(v) => v, + ToSqlOutput::Owned(ref v) => ValueRef::from(v), + #[cfg(any(feature = "blob", feature = "functions", feature = "pointer"))] + _ => { + return Err(err!(ffi::SQLITE_MISUSE, "Unsupported value \"{value:?}\"")); + } + }; + match value { + ValueRef::Integer(i) => { + self.push_int(i); + } + ValueRef::Real(r) => { + self.push_real(r); + } + ValueRef::Text(s) => { + let s = std::str::from_utf8(s)?; + self.push_string_literal(s); + } + _ => { + return Err(err!(ffi::SQLITE_MISUSE, "Unsupported value \"{value:?}\"")); + } + }; + Ok(()) + } + + pub fn push_string_literal(&mut self, s: &str) { + self.wrap_and_escape(s, '\''); + } + + pub fn push_int(&mut self, i: i64) { + self.buf.push_str(&i.to_string()); + } + + pub fn push_real(&mut self, f: f64) { + self.buf.push_str(&f.to_string()); + } + + pub fn push_space(&mut self) { + self.buf.push(' '); + } + + pub fn push_dot(&mut self) { + self.buf.push('.'); + } + + pub fn push_equal_sign(&mut self) { + self.buf.push('='); + } + + pub fn open_brace(&mut self) { + self.buf.push('('); + } + + pub fn close_brace(&mut self) { + self.buf.push(')'); + } + + pub fn as_str(&self) -> &str { + &self.buf + } + + fn wrap_and_escape(&mut self, s: &str, quote: char) { + self.buf.push(quote); + let chars = s.chars(); + for ch in chars { + // escape `quote` by doubling it + if ch == quote { + self.buf.push(ch); + } + self.buf.push(ch); + } + self.buf.push(quote); + } +} + +impl Deref for Sql { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + +impl Connection { + /// Query the current value of `pragma_name`. + /// + /// Some pragmas will return multiple rows/values which cannot be retrieved + /// with this method. + /// + /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20: + /// `SELECT user_version FROM pragma_user_version;` + pub fn pragma_query_value( + &self, + schema_name: Option<&str>, + pragma_name: &str, + f: F, + ) -> Result + where + F: FnOnce(&Row<'_>) -> Result, + { + let mut query = Sql::new(); + query.push_pragma(schema_name, pragma_name)?; + self.query_row(&query, [], f) + } + + /// Query the current rows/values of `pragma_name`. + /// + /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20: + /// `SELECT * FROM pragma_collation_list;` + pub fn pragma_query( + &self, + schema_name: Option<&str>, + pragma_name: &str, + mut f: F, + ) -> Result<()> + where + F: FnMut(&Row<'_>) -> Result<()>, + { + let mut query = Sql::new(); + query.push_pragma(schema_name, pragma_name)?; + let mut stmt = self.prepare(&query)?; + let mut rows = stmt.query([])?; + while let Some(result_row) = rows.next()? { + let row = result_row; + f(row)?; + } + Ok(()) + } + + /// Query the current value(s) of `pragma_name` associated to + /// `pragma_value`. + /// + /// This method can be used with query-only pragmas which need an argument + /// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s) + /// (e.g. `integrity_check`). + /// + /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20: + /// `SELECT * FROM pragma_table_info(?1);` + pub fn pragma( + &self, + schema_name: Option<&str>, + pragma_name: &str, + pragma_value: V, + mut f: F, + ) -> Result<()> + where + F: FnMut(&Row<'_>) -> Result<()>, + V: ToSql, + { + let mut sql = Sql::new(); + sql.push_pragma(schema_name, pragma_name)?; + // The argument may be either in parentheses + // or it may be separated from the pragma name by an equal sign. + // The two syntaxes yield identical results. + sql.open_brace(); + sql.push_value(&pragma_value)?; + sql.close_brace(); + let mut stmt = self.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(result_row) = rows.next()? { + let row = result_row; + f(row)?; + } + Ok(()) + } + + /// Set a new value to `pragma_name`. + /// + /// Some pragmas will return the updated value which cannot be retrieved + /// with this method. + pub fn pragma_update( + &self, + schema_name: Option<&str>, + pragma_name: &str, + pragma_value: V, + ) -> Result<()> + where + V: ToSql, + { + let mut sql = Sql::new(); + sql.push_pragma(schema_name, pragma_name)?; + // The argument may be either in parentheses + // or it may be separated from the pragma name by an equal sign. + // The two syntaxes yield identical results. + sql.push_equal_sign(); + sql.push_value(&pragma_value)?; + self.execute_batch(&sql) + } + + /// Set a new value to `pragma_name` and return the updated value. + /// + /// Only few pragmas automatically return the updated value. + pub fn pragma_update_and_check( + &self, + schema_name: Option<&str>, + pragma_name: &str, + pragma_value: V, + f: F, + ) -> Result + where + F: FnOnce(&Row<'_>) -> Result, + V: ToSql, + { + let mut sql = Sql::new(); + sql.push_pragma(schema_name, pragma_name)?; + // The argument may be either in parentheses + // or it may be separated from the pragma name by an equal sign. + // The two syntaxes yield identical results. + sql.push_equal_sign(); + sql.push_value(&pragma_value)?; + self.query_row(&sql, [], f) + } +} + +fn is_identifier(s: &str) -> bool { + let chars = s.char_indices(); + for (i, ch) in chars { + if i == 0 { + if !is_identifier_start(ch) { + return false; + } + } else if !is_identifier_continue(ch) { + return false; + } + } + true +} + +fn is_identifier_start(c: char) -> bool { + c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F' +} + +fn is_identifier_continue(c: char) -> bool { + c == '$' + || c.is_ascii_digit() + || c.is_ascii_uppercase() + || c == '_' + || c.is_ascii_lowercase() + || c > '\x7F' +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::Sql; + use crate::pragma; + use crate::{Connection, Result}; + + #[test] + fn pragma_query_value() -> Result<()> { + let db = Connection::open_in_memory()?; + let user_version: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?; + assert_eq!(0, user_version); + Ok(()) + } + + #[test] + fn pragma_func_query_value() -> Result<()> { + let db = Connection::open_in_memory()?; + let user_version: i32 = + db.one_column("SELECT user_version FROM pragma_user_version", [])?; + assert_eq!(0, user_version); + Ok(()) + } + + #[test] + fn pragma_query_no_schema() -> Result<()> { + let db = Connection::open_in_memory()?; + let mut user_version = -1; + db.pragma_query(None, "user_version", |row| { + user_version = row.get(0)?; + Ok(()) + })?; + assert_eq!(0, user_version); + Ok(()) + } + + #[test] + fn pragma_query_with_schema() -> Result<()> { + let db = Connection::open_in_memory()?; + let mut user_version = -1; + db.pragma_query(Some("main"), "user_version", |row| { + user_version = row.get(0)?; + Ok(()) + })?; + assert_eq!(0, user_version); + Ok(()) + } + + #[test] + fn pragma() -> Result<()> { + let db = Connection::open_in_memory()?; + let mut columns = Vec::new(); + db.pragma(None, "table_info", "sqlite_master", |row| { + let column: String = row.get(1)?; + columns.push(column); + Ok(()) + })?; + assert_eq!(5, columns.len()); + Ok(()) + } + + #[test] + fn pragma_func() -> Result<()> { + let db = Connection::open_in_memory()?; + let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?1)")?; + let mut columns = Vec::new(); + let mut rows = table_info.query(["sqlite_master"])?; + + while let Some(row) = rows.next()? { + let column: String = row.get(1)?; + columns.push(column); + } + assert_eq!(5, columns.len()); + Ok(()) + } + + #[test] + fn pragma_update() -> Result<()> { + let db = Connection::open_in_memory()?; + db.pragma_update(None, "user_version", 1) + } + + #[test] + fn pragma_update_and_check() -> Result<()> { + let db = Connection::open_in_memory()?; + let journal_mode: String = + db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?; + assert!( + journal_mode == "off" || journal_mode == "memory", + "mode: {journal_mode:?}" + ); + // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking + let mode = + db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get::<_, String>(0))?; + assert!(mode == "off" || mode == "memory", "mode: {mode:?}"); + + let param: &dyn crate::ToSql = &"OFF"; + let mode = + db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?; + assert!(mode == "off" || mode == "memory", "mode: {mode:?}"); + Ok(()) + } + + #[test] + fn is_identifier() { + assert!(pragma::is_identifier("full")); + assert!(pragma::is_identifier("r2d2")); + assert!(!pragma::is_identifier("sp ce")); + assert!(!pragma::is_identifier("semi;colon")); + } + + #[test] + fn double_quote() { + let mut sql = Sql::new(); + sql.push_schema_name(r#"schema";--"#); + assert_eq!(r#""schema"";--""#, sql.as_str()); + } + + #[test] + fn wrap_and_escape() { + let mut sql = Sql::new(); + sql.push_string_literal("value'; --"); + assert_eq!("'value''; --'", sql.as_str()); + } + + #[test] + fn locking_mode() -> Result<()> { + let db = Connection::open_in_memory()?; + db.pragma_update(None, "locking_mode", "exclusive")?; + Ok(()) + } +} diff --git a/vendor/rusqlite/src/raw_statement.rs b/vendor/rusqlite/src/raw_statement.rs new file mode 100644 index 0000000..73ee67e --- /dev/null +++ b/vendor/rusqlite/src/raw_statement.rs @@ -0,0 +1,282 @@ +use super::ffi; +use super::StatementStatus; +use crate::util::ParamIndexCache; +use crate::util::SqliteMallocString; +use std::ffi::{c_int, CStr}; +use std::ptr; +#[cfg(feature = "cache")] +use std::sync::Arc; + +// Private newtype for raw sqlite3_stmts that finalize themselves when dropped. +#[derive(Debug)] +pub struct RawStatement { + ptr: *mut ffi::sqlite3_stmt, + // Cached indices of named parameters, computed on the fly. + cache: ParamIndexCache, + // Cached SQL (trimmed) that we use as the key when we're in the statement + // cache. This is None for statements which didn't come from the statement + // cache. + // + // This is probably the same as `self.sql()` in most cases, but we don't + // care either way -- It's a better cache key as it is anyway since it's the + // actual source we got from rust. + // + // One example of a case where the result of `sqlite_sql` and the value in + // `statement_cache_key` might differ is if the statement has a `tail`. + #[cfg(feature = "cache")] + statement_cache_key: Option>, +} + +impl RawStatement { + #[inline] + pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt) -> Self { + Self { + ptr: stmt, + cache: ParamIndexCache::default(), + #[cfg(feature = "cache")] + statement_cache_key: None, + } + } + + #[inline] + pub fn is_null(&self) -> bool { + self.ptr.is_null() + } + + #[inline] + #[cfg(feature = "cache")] + pub(crate) fn set_statement_cache_key(&mut self, p: impl Into>) { + self.statement_cache_key = Some(p.into()); + } + + #[inline] + #[cfg(feature = "cache")] + pub(crate) fn statement_cache_key(&self) -> Option> { + self.statement_cache_key.clone() + } + + #[inline] + pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { + self.ptr + } + + #[inline] + pub fn column_count(&self) -> usize { + // Note: Can't cache this as it changes if the schema is altered. + unsafe { ffi::sqlite3_column_count(self.ptr) as usize } + } + + #[inline] + pub fn column_type(&self, idx: usize) -> c_int { + unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) } + } + + #[inline] + #[cfg(feature = "column_metadata")] + pub fn column_database_name(&self, idx: usize) -> Option<&CStr> { + unsafe { + let db_name = ffi::sqlite3_column_database_name(self.ptr, idx as c_int); + if db_name.is_null() { + None + } else { + Some(CStr::from_ptr(db_name)) + } + } + } + + #[inline] + #[cfg(feature = "column_metadata")] + pub fn column_table_name(&self, idx: usize) -> Option<&CStr> { + unsafe { + let tbl_name = ffi::sqlite3_column_table_name(self.ptr, idx as c_int); + if tbl_name.is_null() { + None + } else { + Some(CStr::from_ptr(tbl_name)) + } + } + } + + #[inline] + #[cfg(feature = "column_metadata")] + pub fn column_origin_name(&self, idx: usize) -> Option<&CStr> { + unsafe { + let origin_name = ffi::sqlite3_column_origin_name(self.ptr, idx as c_int); + if origin_name.is_null() { + None + } else { + Some(CStr::from_ptr(origin_name)) + } + } + } + + #[inline] + #[cfg(feature = "column_decltype")] + pub fn column_decltype(&self, idx: usize) -> Option<&CStr> { + unsafe { + let decltype = ffi::sqlite3_column_decltype(self.ptr, idx as c_int); + if decltype.is_null() { + None + } else { + Some(CStr::from_ptr(decltype)) + } + } + } + + #[inline] + pub fn column_name(&self, idx: usize) -> Option<&CStr> { + let idx = idx as c_int; + if idx < 0 || idx >= self.column_count() as c_int { + return None; + } + unsafe { + let ptr = ffi::sqlite3_column_name(self.ptr, idx); + // If ptr is null here, it's an OOM, so there's probably nothing + // meaningful we can do. Just assert instead of returning None. + assert!( + !ptr.is_null(), + "Null pointer from sqlite3_column_name: Out of memory?" + ); + Some(CStr::from_ptr(ptr)) + } + } + + #[inline] + #[cfg(not(feature = "unlock_notify"))] + pub fn step(&self) -> c_int { + unsafe { ffi::sqlite3_step(self.ptr) } + } + + #[cfg(feature = "unlock_notify")] + pub fn step(&self) -> c_int { + use crate::unlock_notify; + let mut db = ptr::null_mut::(); + loop { + unsafe { + let mut rc = ffi::sqlite3_step(self.ptr); + // Bail out early for success and errors unrelated to locking. We + // still need check `is_locked` after this, but checking now lets us + // avoid one or two (admittedly cheap) calls into SQLite that we + // don't need to make. + if (rc & 0xff) != ffi::SQLITE_LOCKED { + break rc; + } + if db.is_null() { + db = ffi::sqlite3_db_handle(self.ptr); + } + if !unlock_notify::is_locked(db, rc) { + break rc; + } + rc = unlock_notify::wait_for_unlock_notify(db); + if rc != ffi::SQLITE_OK { + break rc; + } + self.reset(); + } + } + } + + #[inline] + pub fn reset(&self) -> c_int { + unsafe { ffi::sqlite3_reset(self.ptr) } + } + + #[inline] + pub fn bind_parameter_count(&self) -> usize { + unsafe { ffi::sqlite3_bind_parameter_count(self.ptr) as usize } + } + + #[inline] + pub fn bind_parameter_index(&self, name: &str) -> Option { + self.cache.get_or_insert_with(name, |param_cstr| { + let r = unsafe { ffi::sqlite3_bind_parameter_index(self.ptr, param_cstr.as_ptr()) }; + match r { + 0 => None, + i => Some(i as usize), + } + }) + } + + #[inline] + pub fn bind_parameter_name(&self, index: i32) -> Option<&CStr> { + unsafe { + let name = ffi::sqlite3_bind_parameter_name(self.ptr, index); + if name.is_null() { + None + } else { + Some(CStr::from_ptr(name)) + } + } + } + + #[inline] + pub fn clear_bindings(&mut self) { + unsafe { + ffi::sqlite3_clear_bindings(self.ptr); + } // rc is always SQLITE_OK + } + + #[inline] + pub fn sql(&self) -> Option<&CStr> { + if self.ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.ptr)) }) + } + } + + #[inline] + pub fn finalize(mut self) -> c_int { + self.finalize_() + } + + #[inline] + fn finalize_(&mut self) -> c_int { + let r = unsafe { ffi::sqlite3_finalize(self.ptr) }; + self.ptr = ptr::null_mut(); + r + } + + // does not work for PRAGMA + #[inline] + pub fn readonly(&self) -> bool { + unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 } + } + + #[inline] + pub(crate) fn expanded_sql(&self) -> Option { + unsafe { expanded_sql(self.ptr) } + } + + #[inline] + pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 { + unsafe { stmt_status(self.ptr, status, reset) } + } + + #[inline] + pub fn is_explain(&self) -> i32 { + unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) } + } + + // TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE +} + +#[inline] +pub(crate) unsafe fn expanded_sql(ptr: *mut ffi::sqlite3_stmt) -> Option { + SqliteMallocString::from_raw(ffi::sqlite3_expanded_sql(ptr)) +} +#[inline] +pub(crate) unsafe fn stmt_status( + ptr: *mut ffi::sqlite3_stmt, + status: StatementStatus, + reset: bool, +) -> i32 { + assert!(!ptr.is_null()); + ffi::sqlite3_stmt_status(ptr, status as i32, reset as i32) +} + +impl Drop for RawStatement { + fn drop(&mut self) { + self.finalize_(); + } +} diff --git a/vendor/rusqlite/src/row.rs b/vendor/rusqlite/src/row.rs new file mode 100644 index 0000000..277927e --- /dev/null +++ b/vendor/rusqlite/src/row.rs @@ -0,0 +1,680 @@ +use fallible_iterator::FallibleIterator; +use fallible_streaming_iterator::FallibleStreamingIterator; +use std::convert; + +use super::{Error, Result, Statement}; +use crate::types::{FromSql, FromSqlError, ValueRef}; + +/// A handle (lazy fallible streaming iterator) for the resulting rows of a query. +#[must_use = "Rows is lazy and will do nothing unless consumed"] +pub struct Rows<'stmt> { + pub(crate) stmt: Option<&'stmt Statement<'stmt>>, + row: Option>, +} + +impl<'stmt> Rows<'stmt> { + #[inline] + fn reset(&mut self) -> Result<()> { + if let Some(stmt) = self.stmt.take() { + stmt.reset() + } else { + Ok(()) + } + } + + /// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if + /// there is another row, `Err(...)` if there was an error + /// getting the next row, and `Ok(None)` if all rows have been retrieved. + /// + /// ## Note + /// + /// This interface is not compatible with Rust's `Iterator` trait, because + /// the lifetime of the returned row is tied to the lifetime of `self`. + /// This is a fallible "streaming iterator". For a more natural interface, + /// consider using [`query_map`](Statement::query_map) or + /// [`query_and_then`](Statement::query_and_then) instead, which + /// return types that implement `Iterator`. + #[expect(clippy::should_implement_trait)] // cannot implement Iterator + #[inline] + pub fn next(&mut self) -> Result>> { + self.advance()?; + Ok((*self).get()) + } + + /// Map over this `Rows`, converting it to a [`Map`], which + /// implements `FallibleIterator`. + /// ```rust,no_run + /// use fallible_iterator::FallibleIterator; + /// # use rusqlite::{Result, Statement}; + /// fn query(stmt: &mut Statement) -> Result> { + /// let rows = stmt.query([])?; + /// rows.map(|r| r.get(0)).collect() + /// } + /// ``` + // FIXME Hide FallibleStreamingIterator::map + #[inline] + pub fn map(self, f: F) -> Map<'stmt, F> + where + F: FnMut(&Row<'_>) -> Result, + { + Map { rows: self, f } + } + + /// Map over this `Rows`, converting it to a [`MappedRows`], which + /// implements `Iterator`. + #[inline] + pub fn mapped(self, f: F) -> MappedRows<'stmt, F> + where + F: FnMut(&Row<'_>) -> Result, + { + MappedRows { rows: self, map: f } + } + + /// Map over this `Rows` with a fallible function, converting it to a + /// [`AndThenRows`], which implements `Iterator` (instead of + /// `FallibleStreamingIterator`). + #[inline] + pub fn and_then(self, f: F) -> AndThenRows<'stmt, F> + where + F: FnMut(&Row<'_>) -> Result, + { + AndThenRows { rows: self, map: f } + } + + /// Give access to the underlying statement + #[must_use] + pub fn as_ref(&self) -> Option<&Statement<'stmt>> { + self.stmt + } +} + +impl<'stmt> Rows<'stmt> { + #[inline] + pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Self { + Rows { + stmt: Some(stmt), + row: None, + } + } + + #[inline] + pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> { + match self.next()? { + Some(row) => Ok(row), + None => Err(Error::QueryReturnedNoRows), + } + } +} + +impl Drop for Rows<'_> { + #[expect(unused_must_use)] + #[inline] + fn drop(&mut self) { + self.reset(); + } +} + +/// `F` is used to transform the _streaming_ iterator into a _fallible_ +/// iterator. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct Map<'stmt, F> { + rows: Rows<'stmt>, + f: F, +} + +impl FallibleIterator for Map<'_, F> +where + F: FnMut(&Row<'_>) -> Result, +{ + type Item = B; + type Error = Error; + + #[inline] + fn next(&mut self) -> Result> { + match self.rows.next()? { + Some(v) => Ok(Some((self.f)(v)?)), + None => Ok(None), + } + } +} + +/// An iterator over the mapped resulting rows of a query. +/// +/// `F` is used to transform the _streaming_ iterator into a _standard_ +/// iterator. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct MappedRows<'stmt, F> { + rows: Rows<'stmt>, + map: F, +} + +impl Iterator for MappedRows<'_, F> +where + F: FnMut(&Row<'_>) -> Result, +{ + type Item = Result; + + #[inline] + fn next(&mut self) -> Option> { + let map = &mut self.map; + self.rows + .next() + .transpose() + .map(|row_result| row_result.and_then(map)) + } +} + +/// An iterator over the mapped resulting rows of a query, with an Error type +/// unifying with Error. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct AndThenRows<'stmt, F> { + rows: Rows<'stmt>, + map: F, +} + +impl Iterator for AndThenRows<'_, F> +where + E: From, + F: FnMut(&Row<'_>) -> Result, +{ + type Item = Result; + + #[inline] + fn next(&mut self) -> Option { + let map = &mut self.map; + self.rows + .next() + .transpose() + .map(|row_result| row_result.map_err(E::from).and_then(map)) + } +} + +/// `FallibleStreamingIterator` differs from the standard library's `Iterator` +/// in two ways: +/// * each call to `next` (`sqlite3_step`) can fail. +/// * returned `Row` is valid until `next` is called again or `Statement` is +/// reset or finalized. +/// +/// While these iterators cannot be used with Rust `for` loops, `while let` +/// loops offer a similar level of ergonomics: +/// ```rust,no_run +/// # use rusqlite::{Result, Statement}; +/// fn query(stmt: &mut Statement) -> Result<()> { +/// let mut rows = stmt.query([])?; +/// while let Some(row) = rows.next()? { +/// // scan columns value +/// } +/// Ok(()) +/// } +/// ``` +impl<'stmt> FallibleStreamingIterator for Rows<'stmt> { + type Item = Row<'stmt>; + type Error = Error; + + #[inline] + fn advance(&mut self) -> Result<()> { + if let Some(stmt) = self.stmt { + match stmt.step() { + Ok(true) => { + self.row = Some(Row { stmt }); + Ok(()) + } + Ok(false) => { + let r = self.reset(); + self.row = None; + r + } + Err(e) => { + let _ = self.reset(); // prevents infinite loop on error + self.row = None; + Err(e) + } + } + } else { + self.row = None; + Ok(()) + } + } + + #[inline] + fn get(&self) -> Option<&Row<'stmt>> { + self.row.as_ref() + } +} + +/// A single result row of a query. +pub struct Row<'stmt> { + pub(crate) stmt: &'stmt Statement<'stmt>, +} + +impl Row<'_> { + /// Get the value of a particular column of the result row. + /// + /// # Panics + /// + /// Panics if calling [`row.get(idx)`](Row::get) would return an error, + /// including: + /// + /// * If the underlying SQLite column type is not a valid type as a source + /// for `T` + /// * If the underlying SQLite integral value is outside the range + /// representable by `T` + /// * If `idx` is outside the range of columns in the returned query + #[track_caller] + pub fn get_unwrap(&self, idx: I) -> T { + self.get(idx).unwrap() + } + + /// Get the value of a particular column of the result row. + /// + /// ## Failure + /// + /// Returns an `Error::InvalidColumnType` if the underlying SQLite column + /// type is not a valid type as a source for `T`. + /// + /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid + /// column range for this row. + /// + /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column + /// name for this row. + /// + /// If the result type is i128 (which requires the `i128_blob` feature to be + /// enabled), and the underlying SQLite column is a blob whose size is not + /// 16 bytes, `Error::InvalidColumnType` will also be returned. + #[track_caller] + pub fn get(&self, idx: I) -> Result { + let idx = idx.idx(self.stmt)?; + let value = self.stmt.value_ref(idx); + FromSql::column_result(value).map_err(|err| match err { + FromSqlError::InvalidType => Error::InvalidColumnType( + idx, + self.stmt.column_name_unwrap(idx).into(), + value.data_type(), + ), + FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), + FromSqlError::Utf8Error(err) => Error::Utf8Error(idx, err), + FromSqlError::Other(err) => { + Error::FromSqlConversionFailure(idx, value.data_type(), err) + } + FromSqlError::InvalidBlobSize { .. } => { + Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) + } + }) + } + + /// Get the value of a particular column of the result row as a `ValueRef`, + /// allowing data to be read out of a row without copying. + /// + /// This `ValueRef` is valid only as long as this Row, which is enforced by + /// its lifetime. This means that while this method is completely safe, + /// it can be somewhat difficult to use, and most callers will be better + /// served by [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap). + /// + /// ## Failure + /// + /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid + /// column range for this row. + /// + /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column + /// name for this row. + pub fn get_ref(&self, idx: I) -> Result> { + let idx = idx.idx(self.stmt)?; + // Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)` + // returns) to `ValueRef<'a>` is needed because it's only valid until + // the next call to sqlite3_step. + let val_ref = self.stmt.value_ref(idx); + Ok(val_ref) + } + + /// Get the value of a particular column of the result row as a `ValueRef`, + /// allowing data to be read out of a row without copying. + /// + /// This `ValueRef` is valid only as long as this Row, which is enforced by + /// its lifetime. This means that while this method is completely safe, + /// it can be difficult to use, and most callers will be better served by + /// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap). + /// + /// # Panics + /// + /// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an + /// error, including: + /// + /// * If `idx` is outside the range of columns in the returned query. + /// * If `idx` is not a valid column name for this row. + #[track_caller] + pub fn get_ref_unwrap(&self, idx: I) -> ValueRef<'_> { + self.get_ref(idx).unwrap() + } + + /// Return raw pointer at `idx` + /// # Safety + /// This function is unsafe because it uses raw pointer and cast + #[cfg(feature = "pointer")] + pub unsafe fn get_pointer( + &self, + idx: I, + ptr_type: &'static std::ffi::CStr, + ) -> Result> { + let idx = idx.idx(self.stmt)?; + debug_assert_eq!(self.stmt.stmt.column_type(idx), super::ffi::SQLITE_NULL); + let sv = super::ffi::sqlite3_column_value(self.stmt.stmt.ptr(), idx as std::ffi::c_int); + Ok(if sv.is_null() { + None + } else { + super::ffi::sqlite3_value_pointer(sv, ptr_type.as_ptr()) + .cast::() + .as_ref() + }) + } +} + +impl<'stmt> AsRef> for Row<'stmt> { + fn as_ref(&self) -> &Statement<'stmt> { + self.stmt + } +} + +/// Debug `Row` like an ordered `Map, Result<(Type, ValueRef)>>` +/// with column name as key except that for `Type::Blob` only its size is +/// printed (not its content). +impl std::fmt::Debug for Row<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut dm = f.debug_map(); + for c in 0..self.stmt.column_count() { + let name = self.stmt.column_name(c).expect("valid column index"); + dm.key(&name); + let value = self.get_ref(c); + match value { + Ok(value) => { + let dt = value.data_type(); + match value { + ValueRef::Null => { + dm.value(&(dt, ())); + } + ValueRef::Integer(i) => { + dm.value(&(dt, i)); + } + ValueRef::Real(f) => { + dm.value(&(dt, f)); + } + ValueRef::Text(s) => { + dm.value(&(dt, String::from_utf8_lossy(s))); + } + ValueRef::Blob(b) => { + dm.value(&(dt, b.len())); + } + } + } + Err(ref _err) => { + dm.value(&value); + } + } + } + dm.finish() + } +} + +mod sealed { + /// This trait exists just to ensure that the only impls of `trait RowIndex` + /// that are allowed are ones in this crate. + pub trait Sealed {} + impl Sealed for usize {} + impl Sealed for &str {} +} + +/// A trait implemented by types that can index into columns of a row. +/// +/// It is only implemented for `usize` and `&str`. +pub trait RowIndex: sealed::Sealed { + /// Returns the index of the appropriate column, or `Error` if no such + /// column exists. + fn idx(&self, stmt: &Statement<'_>) -> Result; +} + +impl RowIndex for usize { + #[inline] + fn idx(&self, stmt: &Statement<'_>) -> Result { + if *self >= stmt.column_count() { + Err(Error::InvalidColumnIndex(*self)) + } else { + Ok(*self) + } + } +} + +impl RowIndex for &'_ str { + #[inline] + fn idx(&self, stmt: &Statement<'_>) -> Result { + stmt.column_index(self) + } +} + +macro_rules! tuple_try_from_row { + ($($field:ident),*) => { + impl<'a, $($field,)*> convert::TryFrom<&'a Row<'a>> for ($($field,)*) where $($field: FromSql,)* { + type Error = crate::Error; + + // we end with index += 1, which rustc warns about + // unused_variables and unused_mut are allowed for () + #[allow(unused_assignments, unused_variables, unused_mut)] + fn try_from(row: &'a Row<'a>) -> Result { + let mut index = 0; + $( + #[expect(non_snake_case)] + let $field = row.get::<_, $field>(index)?; + index += 1; + )* + Ok(($($field,)*)) + } + } + } +} + +macro_rules! tuples_try_from_row { + () => { + // not very useful, but maybe some other macro users will find this helpful + tuple_try_from_row!(); + }; + ($first:ident $(, $remaining:ident)*) => { + tuple_try_from_row!($first $(, $remaining)*); + tuples_try_from_row!($($remaining),*); + }; +} + +tuples_try_from_row!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +#[cfg(test)] +mod tests { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result}; + + #[test] + fn test_try_from_row_for_tuple_1() -> Result<()> { + use crate::ToSql; + use std::convert::TryFrom; + + let conn = Connection::open_in_memory()?; + conn.execute( + "CREATE TABLE test (a INTEGER)", + crate::params_from_iter(std::iter::empty::<&dyn ToSql>()), + )?; + conn.execute("INSERT INTO test VALUES (42)", [])?; + let val = conn.query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row))?; + assert_eq!(val, (42,)); + let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row)); + fail.unwrap_err(); + Ok(()) + } + + #[test] + fn test_try_from_row_for_tuple_2() -> Result<()> { + use std::convert::TryFrom; + + let conn = Connection::open_in_memory()?; + conn.execute("CREATE TABLE test (a INTEGER, b INTEGER)", [])?; + conn.execute("INSERT INTO test VALUES (42, 47)", [])?; + let val = conn.query_row("SELECT a, b FROM test", [], |row| { + <(u32, u32)>::try_from(row) + })?; + assert_eq!(val, (42, 47)); + let fail = conn.query_row("SELECT a, b FROM test", [], |row| { + <(u32, u32, u32)>::try_from(row) + }); + fail.unwrap_err(); + Ok(()) + } + + #[test] + fn test_try_from_row_for_tuple_16() -> Result<()> { + use std::convert::TryFrom; + + let create_table = "CREATE TABLE test ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER, + e INTEGER, + f INTEGER, + g INTEGER, + h INTEGER, + i INTEGER, + j INTEGER, + k INTEGER, + l INTEGER, + m INTEGER, + n INTEGER, + o INTEGER, + p INTEGER + )"; + + let insert_values = "INSERT INTO test VALUES ( + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + )"; + + type BigTuple = ( + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + ); + + let conn = Connection::open_in_memory()?; + conn.execute(create_table, [])?; + conn.execute(insert_values, [])?; + let val = conn.query_row("SELECT * FROM test", [], |row| BigTuple::try_from(row))?; + // Debug is not implemented for tuples of 16 + assert_eq!(val.0, 0); + assert_eq!(val.1, 1); + assert_eq!(val.2, 2); + assert_eq!(val.3, 3); + assert_eq!(val.4, 4); + assert_eq!(val.5, 5); + assert_eq!(val.6, 6); + assert_eq!(val.7, 7); + assert_eq!(val.8, 8); + assert_eq!(val.9, 9); + assert_eq!(val.10, 10); + assert_eq!(val.11, 11); + assert_eq!(val.12, 12); + assert_eq!(val.13, 13); + assert_eq!(val.14, 14); + assert_eq!(val.15, 15); + + // We don't test one bigger because it's unimplemented + Ok(()) + } + + #[test] + #[cfg(feature = "bundled")] + fn pathological_case() -> Result<()> { + let conn = Connection::open_in_memory()?; + conn.execute_batch( + "CREATE TABLE foo(x); + CREATE TRIGGER oops BEFORE INSERT ON foo BEGIN SELECT RAISE(FAIL, 'Boom'); END;", + )?; + let mut stmt = conn.prepare("INSERT INTO foo VALUES (0) RETURNING rowid;")?; + { + let iterator_count = stmt.query_map([], |_| Ok(()))?.count(); + assert_eq!(1, iterator_count); // should be 0 + use fallible_streaming_iterator::FallibleStreamingIterator; + let fallible_iterator_count = stmt.query([])?.count().unwrap_or(0); + assert_eq!(0, fallible_iterator_count); + } + { + let iterator_last = stmt.query_map([], |_| Ok(()))?.last(); + assert!(iterator_last.is_some()); // should be none + use fallible_iterator::FallibleIterator; + let fallible_iterator_last = stmt.query([])?.map(|_| Ok(())).last(); + assert!(fallible_iterator_last.is_err()); + } + Ok(()) + } + + #[test] + fn as_ref() -> Result<()> { + let conn = Connection::open_in_memory()?; + let mut stmt = conn.prepare("SELECT 'Lisa' as name, 1 as id")?; + let rows = stmt.query([])?; + assert_eq!(rows.as_ref().unwrap().column_count(), 2); + Ok(()) + } + + #[test] + fn debug() -> Result<()> { + let conn = Connection::open_in_memory()?; + let mut stmt = conn.prepare( + "SELECT 'Lisa' as name, 1 as id, 3.14 as pi, X'53514C697465' as blob, NULL as void", + )?; + let mut rows = stmt.query([])?; + let row = rows.next()?.unwrap(); + let s = format!("{row:?}"); + assert_eq!( + s, + r#"{"name": (Text, "Lisa"), "id": (Integer, 1), "pi": (Real, 3.14), "blob": (Blob, 6), "void": (Null, ())}"# + ); + Ok(()) + } + + #[test] + #[cfg(feature = "pointer")] + fn test_pointer() -> Result<()> { + use crate::ffi::fts5_api; + use crate::types::ToSqlOutput; + const PTR_TYPE: &std::ffi::CStr = c"fts5_api_ptr"; + let p_ret: *mut fts5_api = std::ptr::null_mut(); + let ptr = ToSqlOutput::Pointer((&p_ret as *const *mut fts5_api as _, PTR_TYPE, None)); + let db = Connection::open_in_memory()?; + db.query_row("SELECT fts5(?)", [ptr], |_| Ok(()))?; + assert!(!p_ret.is_null()); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/serialize.rs b/vendor/rusqlite/src/serialize.rs new file mode 100644 index 0000000..df04ab9 --- /dev/null +++ b/vendor/rusqlite/src/serialize.rs @@ -0,0 +1,237 @@ +//! Serialize a database. +use std::marker::PhantomData; +use std::ops::Deref; +use std::ptr::NonNull; + +use crate::error::{error_from_handle, error_from_sqlite_code}; +use crate::ffi; +use crate::{Connection, Error, Name, Result}; + +/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database +pub struct SharedData<'conn> { + phantom: PhantomData<&'conn Connection>, + ptr: NonNull, + sz: usize, +} + +/// Owned serialized database +pub struct OwnedData { + ptr: NonNull, + sz: usize, +} + +impl OwnedData { + /// # Safety + /// + /// Caller must be certain that `ptr` is allocated by `sqlite3_malloc64`. + pub unsafe fn from_raw_nonnull(ptr: NonNull, sz: usize) -> Self { + Self { ptr, sz } + } + + fn into_raw(self) -> (*mut u8, usize) { + let raw = (self.ptr.as_ptr(), self.sz); + std::mem::forget(self); + raw + } +} + +impl Drop for OwnedData { + fn drop(&mut self) { + unsafe { + ffi::sqlite3_free(self.ptr.as_ptr().cast()); + } + } +} + +/// Serialized database +pub enum Data<'conn> { + /// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database + Shared(SharedData<'conn>), + /// Owned serialized database + Owned(OwnedData), +} + +impl Deref for Data<'_> { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + let (ptr, sz) = match self { + Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz), + Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz), + }; + unsafe { std::slice::from_raw_parts(ptr, sz) } + } +} + +impl Connection { + /// Serialize a database. + pub fn serialize(&self, schema: N) -> Result> { + let schema = schema.as_cstr()?; + let mut sz = 0; + let mut ptr: *mut u8 = unsafe { + ffi::sqlite3_serialize( + self.handle(), + schema.as_ptr(), + &mut sz, + ffi::SQLITE_SERIALIZE_NOCOPY, + ) + }; + Ok(if ptr.is_null() { + ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) }; + if ptr.is_null() { + return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) }); + } + Data::Owned(OwnedData { + ptr: NonNull::new(ptr).unwrap(), + sz: sz.try_into().unwrap(), + }) + } else { + // shared buffer + Data::Shared(SharedData { + ptr: NonNull::new(ptr).unwrap(), + sz: sz.try_into().unwrap(), + phantom: PhantomData, + }) + }) + } + + /// Deserialize from stream + pub fn deserialize_read_exact( + &mut self, + schema: N, + mut read: R, + sz: usize, + read_only: bool, + ) -> Result<()> { + let ptr = unsafe { ffi::sqlite3_malloc64(sz.try_into().unwrap()) }.cast::(); + if ptr.is_null() { + return Err(error_from_sqlite_code(ffi::SQLITE_NOMEM, None)); + } + let buf = unsafe { std::slice::from_raw_parts_mut(ptr, sz) }; + read.read_exact(buf).map_err(|e| { + Error::SqliteFailure( + ffi::Error { + code: ffi::ErrorCode::CannotOpen, + extended_code: ffi::SQLITE_IOERR, + }, + Some(format!("{e}")), + ) + })?; + let ptr = NonNull::new(ptr).unwrap(); + let data = unsafe { OwnedData::from_raw_nonnull(ptr, sz) }; + self.deserialize(schema, data, read_only) + } + + /// Deserialize `include_bytes` as a read only database + pub fn deserialize_bytes(&mut self, schema: N, data: &'static [u8]) -> Result<()> { + let sz = data.len().try_into().unwrap(); + self.deserialize_( + schema, + data.as_ptr() as *mut _, + sz, + ffi::SQLITE_DESERIALIZE_READONLY, + ) + } + + /// Deserialize a database. + pub fn deserialize( + &mut self, + schema: N, + data: OwnedData, + read_only: bool, + ) -> Result<()> { + let (data, sz) = data.into_raw(); + let sz = sz.try_into().unwrap(); + let flags = if read_only { + ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY + } else { + ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE + }; + self.deserialize_(schema, data, sz, flags) + /* TODO + if let Some(mxSize) = mxSize { + unsafe { + ffi::sqlite3_file_control( + self.handle(), + schema.as_ptr(), + ffi::SQLITE_FCNTL_SIZE_LIMIT, + &mut mxSize, + ) + }; + }*/ + } + + fn deserialize_( + &mut self, + schema: N, + data: *mut u8, + sz: ffi::sqlite_int64, + flags: std::ffi::c_uint, + ) -> Result<()> { + let schema = schema.as_cstr()?; + let rc = unsafe { + ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags) + }; + if rc != ffi::SQLITE_OK { + return Err(unsafe { error_from_handle(self.handle(), rc) }); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::*; + use crate::MAIN_DB; + + #[test] + fn serialize() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE x AS SELECT 'data'")?; + let data = db.serialize(MAIN_DB)?; + let Data::Owned(data) = data else { + panic!("expected OwnedData") + }; + assert!(data.sz > 0); + Ok(()) + } + + #[test] + fn deserialize_read_exact() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE x AS SELECT 'data'")?; + let data = db.serialize(MAIN_DB)?; + + let mut dst = Connection::open_in_memory()?; + let read = data.deref(); + dst.deserialize_read_exact(MAIN_DB, read, read.len(), false)?; + dst.execute("DELETE FROM x", [])?; + Ok(()) + } + + #[test] + fn deserialize_bytes() -> Result<()> { + let data = b""; + let mut dst = Connection::open_in_memory()?; + dst.deserialize_bytes(MAIN_DB, data)?; + Ok(()) + } + + #[test] + fn deserialize() -> Result<()> { + let src = Connection::open_in_memory()?; + src.execute_batch("CREATE TABLE x AS SELECT 'data'")?; + let data = src.serialize(MAIN_DB)?; + let Data::Owned(data) = data else { + panic!("expected OwnedData") + }; + + let mut dst = Connection::open_in_memory()?; + dst.deserialize(MAIN_DB, data, false)?; + dst.execute("DELETE FROM x", [])?; + Ok(()) + } +} diff --git a/vendor/rusqlite/src/session.rs b/vendor/rusqlite/src/session.rs new file mode 100644 index 0000000..bf72b70 --- /dev/null +++ b/vendor/rusqlite/src/session.rs @@ -0,0 +1,963 @@ +//! [Session Extension](https://sqlite.org/sessionintro.html) +#![expect(non_camel_case_types)] + +use std::ffi::{c_char, c_int, c_uchar, c_void, CStr}; +use std::io::{Read, Write}; +use std::marker::PhantomData; +use std::panic::catch_unwind; +use std::ptr; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +use fallible_streaming_iterator::FallibleStreamingIterator; + +use crate::error::{check, error_from_sqlite_code, Error}; +use crate::ffi; +use crate::hooks::Action; +use crate::types::ValueRef; +use crate::{errmsg_to_string, Connection, Name, Result, MAIN_DB}; + +// https://sqlite.org/session.html + +type Filter = Option bool>>; + +/// An instance of this object is a session that can be +/// used to record changes to a database. +pub struct Session<'conn> { + phantom: PhantomData<&'conn Connection>, + s: *mut ffi::sqlite3_session, + filter: Filter, +} + +impl Session<'_> { + /// Create a new session object + #[inline] + pub fn new(db: &Connection) -> Result> { + Session::new_with_name(db, MAIN_DB) + } + + /// Create a new session object + #[inline] + pub fn new_with_name(db: &Connection, name: N) -> Result> { + let name = name.as_cstr()?; + + let db = db.db.borrow_mut().db; + + let mut s: *mut ffi::sqlite3_session = ptr::null_mut(); + check(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) })?; + + Ok(Session { + phantom: PhantomData, + s, + filter: None, + }) + } + + /// Set a table filter + pub fn table_filter(&mut self, filter: Option) + where + F: Fn(&str) -> bool + Send + 'static, + { + unsafe extern "C" fn call_boxed_closure( + p_arg: *mut c_void, + tbl_str: *const c_char, + ) -> c_int + where + F: Fn(&str) -> bool, + { + let tbl_name = CStr::from_ptr(tbl_str).to_str(); + c_int::from( + catch_unwind(|| { + let boxed_filter: *mut F = p_arg.cast::(); + (*boxed_filter)(tbl_name.expect("non-utf8 table name")) + }) + .unwrap_or_default(), + ) + } + + match filter { + Some(filter) => { + let boxed_filter = Box::new(filter); + unsafe { + ffi::sqlite3session_table_filter( + self.s, + Some(call_boxed_closure::), + &*boxed_filter as *const F as *mut _, + ); + } + self.filter = Some(boxed_filter); + } + _ => { + unsafe { ffi::sqlite3session_table_filter(self.s, None, ptr::null_mut()) } + self.filter = None; + } + }; + } + + /// Attach a table. `None` means all tables. + pub fn attach(&mut self, table: Option) -> Result<()> { + let cs = table.as_ref().map(N::as_cstr).transpose()?; + let table = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); + check(unsafe { ffi::sqlite3session_attach(self.s, table) }) + } + + /// Generate a Changeset + pub fn changeset(&mut self) -> Result { + let mut n = 0; + let mut cs: *mut c_void = ptr::null_mut(); + check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) })?; + Ok(Changeset { cs, n }) + } + + /// Write the set of changes represented by this session to `output`. + #[inline] + pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> { + let output_ref = &output; + check(unsafe { + ffi::sqlite3session_changeset_strm( + self.s, + Some(x_output), + output_ref as *const &mut dyn Write as *mut c_void, + ) + }) + } + + /// Generate a Patchset + #[inline] + pub fn patchset(&mut self) -> Result { + let mut n = 0; + let mut ps: *mut c_void = ptr::null_mut(); + check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) })?; + // TODO Validate: same struct + Ok(Changeset { cs: ps, n }) + } + + /// Write the set of patches represented by this session to `output`. + #[inline] + pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> { + let output_ref = &output; + check(unsafe { + ffi::sqlite3session_patchset_strm( + self.s, + Some(x_output), + output_ref as *const &mut dyn Write as *mut c_void, + ) + }) + } + + /// Load the difference between tables. + pub fn diff(&mut self, from: N, table: N) -> Result<()> { + let from = from.as_cstr()?; + let table = table.as_cstr()?; + let table = table.as_ptr(); + unsafe { + let mut errmsg = ptr::null_mut(); + let r = + ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg as *mut *mut _); + if r != ffi::SQLITE_OK { + let errmsg: *mut c_char = errmsg; + let message = errmsg_to_string(&*errmsg); + ffi::sqlite3_free(errmsg as *mut c_void); + return Err(error_from_sqlite_code(r, Some(message))); + } + } + Ok(()) + } + + /// Test if a changeset has recorded any changes + #[inline] + pub fn is_empty(&self) -> bool { + unsafe { ffi::sqlite3session_isempty(self.s) != 0 } + } + + /// Query the current state of the session + #[inline] + pub fn is_enabled(&self) -> bool { + unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 } + } + + /// Enable or disable the recording of changes + #[inline] + pub fn set_enabled(&mut self, enabled: bool) { + unsafe { + ffi::sqlite3session_enable(self.s, c_int::from(enabled)); + } + } + + /// Query the current state of the indirect flag + #[inline] + pub fn is_indirect(&self) -> bool { + unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 } + } + + /// Set or clear the indirect change flag + #[inline] + pub fn set_indirect(&mut self, indirect: bool) { + unsafe { + ffi::sqlite3session_indirect(self.s, c_int::from(indirect)); + } + } +} + +impl Drop for Session<'_> { + #[inline] + fn drop(&mut self) { + if self.filter.is_some() { + self.table_filter(None:: bool>); + } + unsafe { ffi::sqlite3session_delete(self.s) }; + } +} + +/// Invert a changeset +#[inline] +pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> { + let input_ref = &input; + let output_ref = &output; + check(unsafe { + ffi::sqlite3changeset_invert_strm( + Some(x_input), + input_ref as *const &mut dyn Read as *mut c_void, + Some(x_output), + output_ref as *const &mut dyn Write as *mut c_void, + ) + }) +} + +/// Combine two changesets +#[inline] +pub fn concat_strm( + input_a: &mut dyn Read, + input_b: &mut dyn Read, + output: &mut dyn Write, +) -> Result<()> { + let input_a_ref = &input_a; + let input_b_ref = &input_b; + let output_ref = &output; + check(unsafe { + ffi::sqlite3changeset_concat_strm( + Some(x_input), + input_a_ref as *const &mut dyn Read as *mut c_void, + Some(x_input), + input_b_ref as *const &mut dyn Read as *mut c_void, + Some(x_output), + output_ref as *const &mut dyn Write as *mut c_void, + ) + }) +} + +/// Changeset or Patchset +pub struct Changeset { + cs: *mut c_void, + n: c_int, +} + +impl Changeset { + /// Invert a changeset + #[inline] + pub fn invert(&self) -> Result { + let mut n = 0; + let mut cs = ptr::null_mut(); + check(unsafe { + ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs as *mut *mut _) + })?; + Ok(Changeset { cs, n }) + } + + /// Create an iterator to traverse a changeset + #[inline] + pub fn iter(&self) -> Result> { + let mut it = ptr::null_mut(); + check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?; + Ok(ChangesetIter { + phantom: PhantomData, + it, + item: None, + }) + } + + /// Concatenate two changeset objects + #[inline] + pub fn concat(a: &Changeset, b: &Changeset) -> Result { + let mut n = 0; + let mut cs = ptr::null_mut(); + check(unsafe { + ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs as *mut *mut _) + })?; + Ok(Changeset { cs, n }) + } +} + +impl Drop for Changeset { + #[inline] + fn drop(&mut self) { + unsafe { + ffi::sqlite3_free(self.cs); + } + } +} + +/// Cursor for iterating over the elements of a changeset +/// or patchset. +pub struct ChangesetIter<'changeset> { + phantom: PhantomData<&'changeset Changeset>, + it: *mut ffi::sqlite3_changeset_iter, + item: Option, +} + +impl ChangesetIter<'_> { + /// Create an iterator on `input` + #[inline] + pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result> { + let mut it = ptr::null_mut(); + check(unsafe { + ffi::sqlite3changeset_start_strm( + &mut it as *mut *mut _, + Some(x_input), + input as *const &mut dyn Read as *mut c_void, + ) + })?; + Ok(ChangesetIter { + phantom: PhantomData, + it, + item: None, + }) + } +} + +impl FallibleStreamingIterator for ChangesetIter<'_> { + type Error = Error; + type Item = ChangesetItem; + + #[inline] + fn advance(&mut self) -> Result<()> { + let rc = unsafe { ffi::sqlite3changeset_next(self.it) }; + match rc { + ffi::SQLITE_ROW => { + self.item = Some(ChangesetItem { it: self.it }); + Ok(()) + } + ffi::SQLITE_DONE => { + self.item = None; + Ok(()) + } + code => Err(error_from_sqlite_code(code, None)), + } + } + + #[inline] + fn get(&self) -> Option<&ChangesetItem> { + self.item.as_ref() + } +} + +/// Operation +pub struct Operation<'item> { + table_name: &'item str, + number_of_columns: i32, + code: Action, + indirect: bool, +} + +impl Operation<'_> { + /// Returns the table name. + #[inline] + pub fn table_name(&self) -> &str { + self.table_name + } + + /// Returns the number of columns in table + #[inline] + pub fn number_of_columns(&self) -> i32 { + self.number_of_columns + } + + /// Returns the action code. + #[inline] + pub fn code(&self) -> Action { + self.code + } + + /// Returns `true` for an 'indirect' change. + #[inline] + pub fn indirect(&self) -> bool { + self.indirect + } +} + +impl Drop for ChangesetIter<'_> { + #[inline] + fn drop(&mut self) { + unsafe { + ffi::sqlite3changeset_finalize(self.it); + } + } +} + +/// An item passed to a conflict-handler by +/// [`Connection::apply`](Connection::apply), or an item generated by +/// [`ChangesetIter::next`](ChangesetIter::next). +// TODO enum ? Delete, Insert, Update, ... +pub struct ChangesetItem { + it: *mut ffi::sqlite3_changeset_iter, +} + +impl ChangesetItem { + /// Obtain conflicting row values + /// + /// May only be called with an `SQLITE_CHANGESET_DATA` or + /// `SQLITE_CHANGESET_CONFLICT` conflict handler callback. + #[inline] + pub fn conflict(&self, col: usize) -> Result> { + unsafe { + let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut(); + check(ffi::sqlite3changeset_conflict( + self.it, + col as i32, + &mut p_value, + ))?; + if p_value.is_null() { + Err(Error::InvalidColumnIndex(col)) + } else { + Ok(ValueRef::from_value(p_value)) + } + } + } + + /// Determine the number of foreign key constraint violations + /// + /// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict + /// handler callback. + #[inline] + pub fn fk_conflicts(&self) -> Result { + unsafe { + let mut p_out = 0; + check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?; + Ok(p_out) + } + } + + /// Obtain new.* Values + /// + /// May only be called if the type of change is either `SQLITE_UPDATE` or + /// `SQLITE_INSERT`. + #[inline] + pub fn new_value(&self, col: usize) -> Result> { + unsafe { + let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut(); + check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?; + if p_value.is_null() { + Err(Error::InvalidColumnIndex(col)) + } else { + Ok(ValueRef::from_value(p_value)) + } + } + } + + /// Obtain old.* Values + /// + /// May only be called if the type of change is either `SQLITE_DELETE` or + /// `SQLITE_UPDATE`. + #[inline] + pub fn old_value(&self, col: usize) -> Result> { + unsafe { + let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut(); + check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?; + if p_value.is_null() { + Err(Error::InvalidColumnIndex(col)) + } else { + Ok(ValueRef::from_value(p_value)) + } + } + } + + /// Obtain the current operation + #[inline] + pub fn op(&self) -> Result> { + let mut number_of_columns = 0; + let mut code = 0; + let mut indirect = 0; + let tab = unsafe { + let mut pz_tab: *const c_char = ptr::null(); + check(ffi::sqlite3changeset_op( + self.it, + &mut pz_tab, + &mut number_of_columns, + &mut code, + &mut indirect, + ))?; + CStr::from_ptr(pz_tab) + }; + let table_name = tab.to_str()?; + Ok(Operation { + table_name, + number_of_columns, + code: Action::from(code), + indirect: indirect != 0, + }) + } + + /// Obtain the primary key definition of a table + #[inline] + pub fn pk(&self) -> Result<&[u8]> { + let mut number_of_columns = 0; + unsafe { + let mut pks: *mut c_uchar = ptr::null_mut(); + check(ffi::sqlite3changeset_pk( + self.it, + &mut pks, + &mut number_of_columns, + ))?; + Ok(from_raw_parts(pks, number_of_columns as usize)) + } + } +} + +/// Used to combine two or more changesets or +/// patchsets +pub struct Changegroup { + cg: *mut ffi::sqlite3_changegroup, +} + +impl Changegroup { + /// Create a new change group. + #[inline] + pub fn new() -> Result { + let mut cg = ptr::null_mut(); + check(unsafe { ffi::sqlite3changegroup_new(&mut cg) })?; + Ok(Changegroup { cg }) + } + + /// Add a changeset + #[inline] + pub fn add(&mut self, cs: &Changeset) -> Result<()> { + check(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) }) + } + + /// Add a changeset read from `input` to this change group. + #[inline] + pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> { + let input_ref = &input; + check(unsafe { + ffi::sqlite3changegroup_add_strm( + self.cg, + Some(x_input), + input_ref as *const &mut dyn Read as *mut c_void, + ) + }) + } + + /// Obtain a composite Changeset + #[inline] + pub fn output(&mut self) -> Result { + let mut n = 0; + let mut output: *mut c_void = ptr::null_mut(); + check(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) })?; + Ok(Changeset { cs: output, n }) + } + + /// Write the combined set of changes to `output`. + #[inline] + pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> { + let output_ref = &output; + check(unsafe { + ffi::sqlite3changegroup_output_strm( + self.cg, + Some(x_output), + output_ref as *const &mut dyn Write as *mut c_void, + ) + }) + } +} + +impl Drop for Changegroup { + #[inline] + fn drop(&mut self) { + unsafe { + ffi::sqlite3changegroup_delete(self.cg); + } + } +} + +impl Connection { + /// Apply a changeset to a database + pub fn apply(&self, cs: &Changeset, filter: Option, conflict: C) -> Result<()> + where + F: Fn(&str) -> bool + Send + 'static, + C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static, + { + let db = self.db.borrow_mut().db; + + let filtered = filter.is_some(); + let tuple = &mut (filter, conflict); + check(unsafe { + if filtered { + ffi::sqlite3changeset_apply( + db, + cs.n, + cs.cs, + Some(call_filter::), + Some(call_conflict::), + tuple as *mut (Option, C) as *mut c_void, + ) + } else { + ffi::sqlite3changeset_apply( + db, + cs.n, + cs.cs, + None, + Some(call_conflict::), + tuple as *mut (Option, C) as *mut c_void, + ) + } + }) + } + + /// Apply a changeset to a database + pub fn apply_strm( + &self, + input: &mut dyn Read, + filter: Option, + conflict: C, + ) -> Result<()> + where + F: Fn(&str) -> bool + Send + 'static, + C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static, + { + let input_ref = &input; + let db = self.db.borrow_mut().db; + + let filtered = filter.is_some(); + let tuple = &mut (filter, conflict); + check(unsafe { + if filtered { + ffi::sqlite3changeset_apply_strm( + db, + Some(x_input), + input_ref as *const &mut dyn Read as *mut c_void, + Some(call_filter::), + Some(call_conflict::), + tuple as *mut (Option, C) as *mut c_void, + ) + } else { + ffi::sqlite3changeset_apply_strm( + db, + Some(x_input), + input_ref as *const &mut dyn Read as *mut c_void, + None, + Some(call_conflict::), + tuple as *mut (Option, C) as *mut c_void, + ) + } + }) + } +} + +/// Constants passed to the conflict handler +/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details. +#[allow(missing_docs)] +#[repr(i32)] +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ConflictType { + UNKNOWN = -1, + SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA, + SQLITE_CHANGESET_NOTFOUND = ffi::SQLITE_CHANGESET_NOTFOUND, + SQLITE_CHANGESET_CONFLICT = ffi::SQLITE_CHANGESET_CONFLICT, + SQLITE_CHANGESET_CONSTRAINT = ffi::SQLITE_CHANGESET_CONSTRAINT, + SQLITE_CHANGESET_FOREIGN_KEY = ffi::SQLITE_CHANGESET_FOREIGN_KEY, +} +impl From for ConflictType { + fn from(code: i32) -> ConflictType { + match code { + ffi::SQLITE_CHANGESET_DATA => ConflictType::SQLITE_CHANGESET_DATA, + ffi::SQLITE_CHANGESET_NOTFOUND => ConflictType::SQLITE_CHANGESET_NOTFOUND, + ffi::SQLITE_CHANGESET_CONFLICT => ConflictType::SQLITE_CHANGESET_CONFLICT, + ffi::SQLITE_CHANGESET_CONSTRAINT => ConflictType::SQLITE_CHANGESET_CONSTRAINT, + ffi::SQLITE_CHANGESET_FOREIGN_KEY => ConflictType::SQLITE_CHANGESET_FOREIGN_KEY, + _ => ConflictType::UNKNOWN, + } + } +} + +/// Constants returned by the conflict handler +/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details. +#[allow(missing_docs)] +#[repr(i32)] +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ConflictAction { + SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT, + SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE, + SQLITE_CHANGESET_ABORT = ffi::SQLITE_CHANGESET_ABORT, +} + +unsafe extern "C" fn call_filter(p_ctx: *mut c_void, tbl_str: *const c_char) -> c_int +where + F: Fn(&str) -> bool + Send + 'static, + C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static, +{ + let tbl_name = CStr::from_ptr(tbl_str).to_str(); + c_int::from( + catch_unwind(|| { + let tuple: *mut (Option, C) = p_ctx.cast::<(Option, C)>(); + if let Some(ref filter) = (*tuple).0 { + filter(tbl_name.expect("illegal table name")) + } else { + true + } + }) + .unwrap_or_default(), + ) +} + +unsafe extern "C" fn call_conflict( + p_ctx: *mut c_void, + e_conflict: c_int, + p: *mut ffi::sqlite3_changeset_iter, +) -> c_int +where + F: Fn(&str) -> bool + Send + 'static, + C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static, +{ + let conflict_type = ConflictType::from(e_conflict); + let item = ChangesetItem { it: p }; + if let Ok(action) = catch_unwind(|| { + let tuple: *mut (Option, C) = p_ctx.cast::<(Option, C)>(); + (*tuple).1(conflict_type, item) + }) { + action as c_int + } else { + ffi::SQLITE_CHANGESET_ABORT + } +} + +unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_int) -> c_int { + if p_in.is_null() { + return ffi::SQLITE_MISUSE; + } + let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, *len as usize); + let input = p_in as *mut &mut dyn Read; + match (*input).read(bytes) { + Ok(n) => { + *len = n as i32; // TODO Validate: n = 0 may not mean the reader will always no longer be able to + // produce bytes. + ffi::SQLITE_OK + } + Err(_) => ffi::SQLITE_IOERR_READ, // TODO check if err is a (ru)sqlite Error => propagate + } +} + +unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_int) -> c_int { + if p_out.is_null() { + return ffi::SQLITE_MISUSE; + } + // The sessions module never invokes an xOutput callback with the third + // parameter set to a value less than or equal to zero. + let bytes: &[u8] = from_raw_parts(data as *const u8, len as usize); + let output = p_out as *mut &mut dyn Write; + match (*output).write_all(bytes) { + Ok(_) => ffi::SQLITE_OK, + Err(_) => ffi::SQLITE_IOERR_WRITE, // TODO check if err is a (ru)sqlite Error => propagate + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use fallible_streaming_iterator::FallibleStreamingIterator; + use std::io::Read; + use std::sync::atomic::{AtomicBool, Ordering}; + + use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session}; + use crate::hooks::Action; + use crate::{Connection, Result}; + + fn one_changeset_insert() -> Result { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?; + + let mut session = Session::new(&db)?; + assert!(session.is_empty()); + + session.attach::<&str>(None)?; + db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?; + + session.changeset() + } + + fn one_changeset_update() -> Result { + let db = Connection::open_in_memory()?; + db.execute_batch( + "CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL, i INTEGER NOT NULL DEFAULT 0);", + )?; + db.execute_batch("INSERT INTO foo (t) VALUES ('bar');")?; + + let mut session = Session::new(&db)?; + session.attach::<&str>(None)?; + db.execute("UPDATE foo SET i=100 WHERE t='bar';", [])?; + + session.changeset() + } + + fn one_changeset_strm() -> Result> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?; + + let mut session = Session::new(&db)?; + assert!(session.is_empty()); + + session.attach::<&str>(None)?; + db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?; + + let mut output = Vec::new(); + session.changeset_strm(&mut output)?; + Ok(output) + } + + #[test] + fn test_changeset() -> Result<()> { + let changeset = one_changeset_insert()?; + let mut iter = changeset.iter()?; + let item = iter.next()?; + assert!(item.is_some()); + + let item = item.unwrap(); + let op = item.op()?; + assert_eq!("foo", op.table_name()); + assert_eq!(1, op.number_of_columns()); + assert_eq!(Action::SQLITE_INSERT, op.code()); + assert!(!op.indirect()); + + let pk = item.pk()?; + assert_eq!(&[1], pk); + + let new_value = item.new_value(0)?; + assert_eq!(Ok("bar"), new_value.as_str()); + Ok(()) + } + + #[test] + fn test_changeset_strm() -> Result<()> { + let output = one_changeset_strm()?; + assert!(!output.is_empty()); + assert_eq!(14, output.len()); + + let input: &mut dyn Read = &mut output.as_slice(); + let mut iter = ChangesetIter::start_strm(&input)?; + let item = iter.next()?; + assert!(item.is_some()); + Ok(()) + } + + #[test] + fn test_changeset_values() -> Result<()> { + let changeset = one_changeset_update()?; + let mut iter = changeset.iter()?; + let item = iter.next()?.unwrap(); + + let new_value = item.new_value(0); // unchanged + assert_eq!(Err(crate::Error::InvalidColumnIndex(0)), new_value); + let new_value = item.new_value(1)?; // updated + assert_eq!(Ok(100), new_value.as_i64()); + Ok(()) + } + + #[test] + fn test_changeset_apply() -> Result<()> { + let changeset = one_changeset_insert()?; + + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?; + + static CALLED: AtomicBool = AtomicBool::new(false); + db.apply( + &changeset, + None:: bool>, + |_conflict_type, _item| { + CALLED.store(true, Ordering::Relaxed); + ConflictAction::SQLITE_CHANGESET_OMIT + }, + )?; + + assert!(!CALLED.load(Ordering::Relaxed)); + let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| { + row.get::<_, i32>(0) + })?; + assert_eq!(1, check); + + // conflict expected when same changeset applied again on the same db + db.apply( + &changeset, + None:: bool>, + |conflict_type, item| { + CALLED.store(true, Ordering::Relaxed); + assert_eq!(ConflictType::SQLITE_CHANGESET_CONFLICT, conflict_type); + let conflict = item.conflict(0).unwrap(); + assert_eq!(Ok("bar"), conflict.as_str()); + ConflictAction::SQLITE_CHANGESET_OMIT + }, + )?; + assert!(CALLED.load(Ordering::Relaxed)); + Ok(()) + } + + #[test] + fn test_changeset_apply_strm() -> Result<()> { + let output = one_changeset_strm()?; + + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?; + + let mut input = output.as_slice(); + db.apply_strm( + &mut input, + None:: bool>, + |_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT, + )?; + + let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| { + row.get::<_, i32>(0) + })?; + assert_eq!(1, check); + Ok(()) + } + + #[test] + fn test_session_empty() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?; + + let mut session = Session::new(&db)?; + assert!(session.is_empty()); + + session.attach::<&str>(None)?; + db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?; + + assert!(!session.is_empty()); + Ok(()) + } + + #[test] + fn test_session_set_enabled() -> Result<()> { + let db = Connection::open_in_memory()?; + + let mut session = Session::new(&db)?; + assert!(session.is_enabled()); + session.set_enabled(false); + assert!(!session.is_enabled()); + Ok(()) + } + + #[test] + fn test_session_set_indirect() -> Result<()> { + let db = Connection::open_in_memory()?; + + let mut session = Session::new(&db)?; + assert!(!session.is_indirect()); + session.set_indirect(true); + assert!(session.is_indirect()); + Ok(()) + } +} diff --git a/vendor/rusqlite/src/statement.rs b/vendor/rusqlite/src/statement.rs new file mode 100644 index 0000000..7b89d6c --- /dev/null +++ b/vendor/rusqlite/src/statement.rs @@ -0,0 +1,1396 @@ +use std::ffi::{c_int, c_void}; +use std::slice::from_raw_parts; +use std::{fmt, mem, ptr, str}; + +use super::ffi; +use super::str_for_sqlite; +use super::{ + AndThenRows, Connection, Error, MappedRows, Params, RawStatement, Result, Row, Rows, ValueRef, +}; +use crate::bind::BindIndex; +use crate::types::{ToSql, ToSqlOutput}; + +/// A prepared statement. +pub struct Statement<'conn> { + pub(crate) conn: &'conn Connection, + pub(crate) stmt: RawStatement, +} + +impl Statement<'_> { + /// Execute the prepared statement. + /// + /// On success, returns the number of rows that were changed or inserted or + /// deleted (via `sqlite3_changes`). + /// + /// ## Example + /// + /// ### Use with positional parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, params}; + /// fn update_rows(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("UPDATE foo SET bar = ?1 WHERE qux = ?2")?; + /// // For a single parameter, or a parameter where all the values have + /// // the same type, just passing an array is simplest. + /// stmt.execute([2i32])?; + /// // The `rusqlite::params!` macro is mostly useful when the parameters do not + /// // all have the same type, or if there are more than 32 parameters + /// // at once, but it can be used in other cases. + /// stmt.execute(params![1i32])?; + /// // However, it's not required, many cases are fine as: + /// stmt.execute(&[&2i32])?; + /// // Or even: + /// stmt.execute([2i32])?; + /// // If you really want to, this is an option as well. + /// stmt.execute((2i32,))?; + /// Ok(()) + /// } + /// ``` + /// + /// #### Heterogeneous positional parameters + /// + /// ``` + /// use rusqlite::{Connection, Result}; + /// fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> { + /// # // no need to do it for real. + /// # fn sha256(_: &[u8]) -> [u8; 32] { [0; 32] } + /// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?1, ?2, ?3)"; + /// let mut stmt = conn.prepare_cached(query)?; + /// let hash: [u8; 32] = sha256(data); + /// // The easiest way to pass positional parameters of have several + /// // different types is by using a tuple. + /// stmt.execute((path, hash, data))?; + /// // Using the `params!` macro also works, and supports longer parameter lists: + /// stmt.execute(rusqlite::params![path, hash, data])?; + /// Ok(()) + /// } + /// # let c = Connection::open_in_memory().unwrap(); + /// # c.execute_batch("CREATE TABLE files(path TEXT PRIMARY KEY, hash BLOB, data BLOB)").unwrap(); + /// # store_file(&c, "foo/bar.txt", b"bibble").unwrap(); + /// # store_file(&c, "foo/baz.txt", b"bobble").unwrap(); + /// ``` + /// + /// ### Use with named parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, named_params}; + /// fn insert(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?; + /// // The `rusqlite::named_params!` macro (like `params!`) is useful for heterogeneous + /// // sets of parameters (where all parameters are not the same type), or for queries + /// // with many (more than 32) statically known parameters. + /// stmt.execute(named_params! { ":key": "one", ":val": 2 })?; + /// // However, named parameters can also be passed like: + /// stmt.execute(&[(":key", "three"), (":val", "four")])?; + /// // Or even: (note that a &T is required for the value type, currently) + /// stmt.execute(&[(":key", &100), (":val", &200)])?; + /// Ok(()) + /// } + /// ``` + /// + /// ### Use without parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, params}; + /// fn delete_all(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("DELETE FROM users")?; + /// stmt.execute([])?; + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if binding parameters fails, the executed statement + /// returns rows (in which case `query` should be used instead), or the + /// underlying SQLite call fails. + #[inline] + pub fn execute(&mut self, params: P) -> Result { + params.__bind_in(self)?; + self.execute_with_bound_parameters() + } + + /// Execute an INSERT and return the ROWID. + /// + /// # Note + /// + /// This function is a convenience wrapper around + /// [`execute()`](Statement::execute) intended for queries that insert a + /// single item. It is possible to misuse this function in a way that it + /// cannot detect, such as by calling it on a statement which _updates_ + /// a single item rather than inserting one. Please don't do that. + /// + /// # Failure + /// + /// Will return `Err` if no row is inserted or many rows are inserted. + #[inline] + pub fn insert(&mut self, params: P) -> Result { + let changes = self.execute(params)?; + match changes { + 1 => Ok(self.conn.last_insert_rowid()), + _ => Err(Error::StatementChangedRows(changes)), + } + } + + /// Execute the prepared statement, returning a handle to the resulting + /// rows. + /// + /// Due to lifetime restrictions, the rows handle returned by `query` does + /// not implement the `Iterator` trait. Consider using + /// [`query_map`](Statement::query_map) or + /// [`query_and_then`](Statement::query_and_then) instead, which do. + /// + /// ## Example + /// + /// ### Use without parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people")?; + /// let mut rows = stmt.query([])?; + /// + /// let mut names = Vec::new(); + /// while let Some(row) = rows.next()? { + /// names.push(row.get(0)?); + /// } + /// + /// Ok(names) + /// } + /// ``` + /// + /// ### Use with positional parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn query(conn: &Connection, name: &str) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?; + /// let mut rows = stmt.query(rusqlite::params![name])?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// Or, equivalently (but without the [`crate::params!`] macro). + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn query(conn: &Connection, name: &str) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?; + /// let mut rows = stmt.query([name])?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// ### Use with named parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn query(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?; + /// let mut rows = stmt.query(&[(":name", "one")])?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// Note, the `named_params!` macro is provided for syntactic convenience, + /// and so the above example could also be written as: + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, named_params}; + /// fn query(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?; + /// let mut rows = stmt.query(named_params! { ":name": "one" })?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// ## Failure + /// + /// Will return `Err` if binding parameters fails. + #[inline] + pub fn query(&mut self, params: P) -> Result> { + params.__bind_in(self)?; + Ok(Rows::new(self)) + } + + /// Executes the prepared statement and maps a function over the resulting + /// rows, returning an iterator over the mapped function results. + /// + /// `f` is used to transform the _streaming_ iterator into a _standard_ + /// iterator. + /// + /// This is equivalent to `stmt.query(params)?.mapped(f)`. + /// + /// ## Example + /// + /// ### Use with positional params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people")?; + /// let rows = stmt.query_map([], |row| row.get(0))?; + /// + /// let mut names = Vec::new(); + /// for name_result in rows { + /// names.push(name_result?); + /// } + /// + /// Ok(names) + /// } + /// ``` + /// + /// ### Use with named params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?; + /// let rows = stmt.query_map(&[(":id", &"one")], |row| row.get(0))?; + /// + /// let mut names = Vec::new(); + /// for name_result in rows { + /// names.push(name_result?); + /// } + /// + /// Ok(names) + /// } + /// ``` + /// ## Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query_map(&mut self, params: P, f: F) -> Result> + where + P: Params, + F: FnMut(&Row<'_>) -> Result, + { + self.query(params).map(|rows| rows.mapped(f)) + } + + /// Executes the prepared statement and maps a function over the resulting + /// rows, where the function returns a `Result` with `Error` type + /// implementing `std::convert::From` (so errors can be unified). + /// + /// This is equivalent to `stmt.query(params)?.and_then(f)`. + /// + /// ## Example + /// + /// ### Use with named params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// struct Person { + /// name: String, + /// }; + /// + /// fn name_to_person(name: String) -> Result { + /// // ... check for valid name + /// Ok(Person { name }) + /// } + /// + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?; + /// let rows = stmt.query_and_then(&[(":id", "one")], |row| name_to_person(row.get(0)?))?; + /// + /// let mut persons = Vec::new(); + /// for person_result in rows { + /// persons.push(person_result?); + /// } + /// + /// Ok(persons) + /// } + /// ``` + /// + /// ### Use with positional params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?1")?; + /// let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?; + /// + /// let mut persons = Vec::new(); + /// for person_result in rows { + /// persons.push(person_result?); + /// } + /// + /// Ok(persons) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if binding parameters fails. + #[inline] + pub fn query_and_then(&mut self, params: P, f: F) -> Result> + where + P: Params, + E: From, + F: FnMut(&Row<'_>) -> Result, + { + self.query(params).map(|rows| rows.and_then(f)) + } + + /// Return `true` if a query in the SQL statement it executes returns one + /// or more rows and `false` if the SQL returns an empty set. + #[inline] + pub fn exists(&mut self, params: P) -> Result { + let mut rows = self.query(params)?; + let exists = rows.next()?.is_some(); + Ok(exists) + } + + /// Convenience method to execute a query that is expected to return a + /// single row. + /// + /// If the query returns more than one row, all rows except the first are + /// ignored. + /// + /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the + /// query truly is optional, you can call + /// [`.optional()`](crate::OptionalExtension::optional) on the result of + /// this to get a `Result>` (requires that the trait + /// `rusqlite::OptionalExtension` is imported). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + pub fn query_row(&mut self, params: P, f: F) -> Result + where + P: Params, + F: FnOnce(&Row<'_>) -> Result, + { + let mut rows = self.query(params)?; + + rows.get_expected_row().and_then(f) + } + + /// Convenience method to execute a query that is expected to return exactly + /// one row. + /// + /// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row. + /// + /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the + /// query truly is optional, you can call + /// [`.optional()`](crate::OptionalExtension::optional) on the result of + /// this to get a `Result>` (requires that the trait + /// `rusqlite::OptionalExtension` is imported). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + pub fn query_one(&mut self, params: P, f: F) -> Result + where + P: Params, + F: FnOnce(&Row<'_>) -> Result, + { + let mut rows = self.query(params)?; + let row = rows.get_expected_row().and_then(f)?; + if rows.next()?.is_some() { + return Err(Error::QueryReturnedMoreThanOneRow); + } + Ok(row) + } + + /// Consumes the statement. + /// + /// Functionally equivalent to the `Drop` implementation, but allows + /// callers to see any errors that occur. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[inline] + pub fn finalize(mut self) -> Result<()> { + self.finalize_() + } + + /// Return the (one-based) index of an SQL parameter given its name. + /// + /// Note that the initial ":" or "$" or "@" or "?" used to specify the + /// parameter is included as part of the name. + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn example(conn: &Connection) -> Result<()> { + /// let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?; + /// let index = stmt.parameter_index(":example")?; + /// assert_eq!(index, Some(1)); + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return Err if `name` is invalid. Will return Ok(None) if the name + /// is valid but not a bound parameter of this statement. + #[inline] + pub fn parameter_index(&self, name: &str) -> Result> { + Ok(self.stmt.bind_parameter_index(name)) + } + + /// Return the SQL parameter name given its (one-based) index (the inverse + /// of [`Statement::parameter_index`]). + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn example(conn: &Connection) -> Result<()> { + /// let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?; + /// let index = stmt.parameter_name(1); + /// assert_eq!(index, Some(":example")); + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `None` if the column index is out of bounds or if the + /// parameter is positional. + /// + /// # Panics + /// + /// Panics when parameter name is not valid UTF-8. + #[inline] + pub fn parameter_name(&self, index: usize) -> Option<&'_ str> { + self.stmt.bind_parameter_name(index as i32).map(|name| { + name.to_str() + .expect("Invalid UTF-8 sequence in parameter name") + }) + } + + #[inline] + pub(crate) fn bind_parameters

(&mut self, params: P) -> Result<()> + where + P: IntoIterator, + P::Item: ToSql, + { + let expected = self.stmt.bind_parameter_count(); + let mut index = 0; + for p in params { + index += 1; // The leftmost SQL parameter has an index of 1. + if index > expected { + break; + } + self.bind_parameter(&p, index)?; + } + if index != expected { + Err(Error::InvalidParameterCount(index, expected)) + } else { + Ok(()) + } + } + + #[inline] + pub(crate) fn ensure_parameter_count(&self, n: usize) -> Result<()> { + let count = self.parameter_count(); + if count != n { + Err(Error::InvalidParameterCount(n, count)) + } else { + Ok(()) + } + } + + #[inline] + pub(crate) fn bind_parameters_named( + &mut self, + params: &[(S, T)], + ) -> Result<()> { + for (name, value) in params { + let i = name.idx(self)?; + let ts: &dyn ToSql = &value; + self.bind_parameter(ts, i)?; + } + Ok(()) + } + + /// Return the number of parameters that can be bound to this statement. + #[inline] + pub fn parameter_count(&self) -> usize { + self.stmt.bind_parameter_count() + } + + /// Low level API to directly bind a parameter to a given index. + /// + /// Note that the index is one-based, that is, the first parameter index is + /// 1 and not 0. This is consistent with the SQLite API and the values given + /// to parameters bound as `?NNN`. + /// + /// The valid values for `one_based_col_index` begin at `1`, and end at + /// [`Statement::parameter_count`], inclusive. + /// + /// # Caveats + /// + /// This should not generally be used, but is available for special cases + /// such as: + /// + /// - binding parameters where a gap exists. + /// - binding named and positional parameters in the same query. + /// - separating parameter binding from query execution. + /// + /// In general, statements that have had *any* parameters bound this way + /// should have *all* parameters bound this way, and be queried or executed + /// by [`Statement::raw_query`] or [`Statement::raw_execute`], other usage + /// is unsupported and will likely, probably in surprising ways. + /// + /// That is: Do not mix the "raw" statement functions with the rest of the + /// API, or the results may be surprising, and may even change in future + /// versions without comment. + /// + /// # Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn query(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test WHERE name = :name AND value > ?2")?; + /// stmt.raw_bind_parameter(c":name", "foo")?; + /// stmt.raw_bind_parameter(2, 100)?; + /// let mut rows = stmt.raw_query(); + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + #[inline] + pub fn raw_bind_parameter( + &mut self, + one_based_index: I, + param: T, + ) -> Result<()> { + // This is the same as `bind_parameter` but slightly more ergonomic and + // correctly takes `&mut self`. + self.bind_parameter(¶m, one_based_index.idx(self)?) + } + + /// Low level API to execute a statement given that all parameters were + /// bound explicitly with the [`Statement::raw_bind_parameter`] API. + /// + /// # Caveats + /// + /// Any unbound parameters will have `NULL` as their value. + /// + /// This should not generally be used outside special cases, and + /// functions in the [`Statement::execute`] family should be preferred. + /// + /// # Failure + /// + /// Will return `Err` if the executed statement returns rows (in which case + /// `query` should be used instead), or the underlying SQLite call fails. + #[inline] + pub fn raw_execute(&mut self) -> Result { + self.execute_with_bound_parameters() + } + + /// Low level API to get `Rows` for this query given that all parameters + /// were bound explicitly with the [`Statement::raw_bind_parameter`] API. + /// + /// # Caveats + /// + /// Any unbound parameters will have `NULL` as their value. + /// + /// This should not generally be used outside special cases, and + /// functions in the [`Statement::query`] family should be preferred. + /// + /// Note that if the SQL does not return results, [`Statement::raw_execute`] + /// should be used instead. + #[inline] + pub fn raw_query(&mut self) -> Rows<'_> { + Rows::new(self) + } + + // generic because many of these branches can constant fold away. + fn bind_parameter(&self, param: &P, ndx: usize) -> Result<()> { + let value = param.to_sql()?; + + let ptr = unsafe { self.stmt.ptr() }; + let value = match value { + ToSqlOutput::Borrowed(v) => v, + ToSqlOutput::Owned(ref v) => ValueRef::from(v), + + #[cfg(feature = "blob")] + ToSqlOutput::ZeroBlob(len) => { + // TODO sqlite3_bind_zeroblob64 // 3.8.11 + return self + .conn + .decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, ndx as c_int, len) }); + } + #[cfg(feature = "functions")] + ToSqlOutput::Arg(_) => { + return Err(err!(ffi::SQLITE_MISUSE, "Unsupported value \"{value:?}\"")); + } + #[cfg(feature = "pointer")] + ToSqlOutput::Pointer(p) => { + return self.conn.decode_result(unsafe { + ffi::sqlite3_bind_pointer(ptr, ndx as c_int, p.0 as _, p.1.as_ptr(), p.2) + }); + } + }; + self.conn.decode_result(match value { + ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, ndx as c_int) }, + ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, ndx as c_int, i) }, + ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, ndx as c_int, r) }, + ValueRef::Text(s) => unsafe { + let (c_str, len, destructor) = str_for_sqlite(s); + ffi::sqlite3_bind_text64( + ptr, + ndx as c_int, + c_str, + len, + destructor, + ffi::SQLITE_UTF8 as _, + ) + }, + ValueRef::Blob(b) => unsafe { + let length = b.len(); + if length == 0 { + ffi::sqlite3_bind_zeroblob(ptr, ndx as c_int, 0) + } else { + ffi::sqlite3_bind_blob64( + ptr, + ndx as c_int, + b.as_ptr().cast::(), + length as ffi::sqlite3_uint64, + ffi::SQLITE_TRANSIENT(), + ) + } + }, + }) + } + + #[inline] + fn execute_with_bound_parameters(&mut self) -> Result { + self.check_update()?; + let r = self.stmt.step(); + let rr = self.stmt.reset(); + match r { + ffi::SQLITE_DONE => match rr { + ffi::SQLITE_OK => Ok(self.conn.changes() as usize), + _ => Err(self.conn.decode_result(rr).unwrap_err()), + }, + ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults), + _ => Err(self.conn.decode_result(r).unwrap_err()), + } + } + + #[inline] + fn finalize_(&mut self) -> Result<()> { + let mut stmt = unsafe { RawStatement::new(ptr::null_mut()) }; + mem::swap(&mut stmt, &mut self.stmt); + self.conn.decode_result(stmt.finalize()) + } + + #[cfg(feature = "extra_check")] + #[inline] + fn check_update(&self) -> Result<()> { + if self.column_count() > 0 && self.stmt.readonly() { + return Err(Error::ExecuteReturnedResults); + } + Ok(()) + } + + #[cfg(not(feature = "extra_check"))] + #[inline] + #[expect(clippy::unnecessary_wraps)] + fn check_update(&self) -> Result<()> { + Ok(()) + } + + /// Returns a string containing the SQL text of prepared statement with + /// bound parameters expanded. + pub fn expanded_sql(&self) -> Option { + self.stmt + .expanded_sql() + .map(|s| s.to_string_lossy().to_string()) + } + + /// Get the value for one of the status counters for this statement. + #[inline] + pub fn get_status(&self, status: StatementStatus) -> i32 { + self.stmt.get_status(status, false) + } + + /// Reset the value of one of the status counters for this statement, + #[inline] + /// returning the value it had before resetting. + pub fn reset_status(&self, status: StatementStatus) -> i32 { + self.stmt.get_status(status, true) + } + + /// Returns 1 if the prepared statement is an EXPLAIN statement, + /// or 2 if the statement is an EXPLAIN QUERY PLAN, + /// or 0 if it is an ordinary statement or a NULL pointer. + #[inline] + pub fn is_explain(&self) -> i32 { + self.stmt.is_explain() + } + + /// Returns true if the statement is read only. + #[inline] + pub fn readonly(&self) -> bool { + self.stmt.readonly() + } + + /// Safety: This is unsafe, because using `sqlite3_stmt` after the + /// connection has closed is illegal, but `RawStatement` does not enforce + /// this, as it loses our protective `'conn` lifetime bound. + #[inline] + #[cfg(feature = "cache")] + pub(crate) unsafe fn into_raw(mut self) -> RawStatement { + let mut stmt = RawStatement::new(ptr::null_mut()); + mem::swap(&mut stmt, &mut self.stmt); + stmt + } + + /// Reset all bindings + pub fn clear_bindings(&mut self) { + self.stmt.clear_bindings(); + } + + pub(crate) unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { + self.stmt.ptr() + } +} + +impl fmt::Debug for Statement<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let sql = if self.stmt.is_null() { + Ok("") + } else { + self.stmt.sql().unwrap().to_str() + }; + f.debug_struct("Statement") + .field("conn", self.conn) + .field("stmt", &self.stmt) + .field("sql", &sql) + .finish() + } +} + +impl Drop for Statement<'_> { + #[expect(unused_must_use)] + #[inline] + fn drop(&mut self) { + self.finalize_(); + } +} + +impl Statement<'_> { + #[inline] + pub(super) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> { + Statement { conn, stmt } + } + + pub(super) fn value_ref(&self, col: usize) -> ValueRef<'_> { + let raw = unsafe { self.stmt.ptr() }; + + match self.stmt.column_type(col) { + ffi::SQLITE_NULL => ValueRef::Null, + ffi::SQLITE_INTEGER => { + ValueRef::Integer(unsafe { ffi::sqlite3_column_int64(raw, col as c_int) }) + } + ffi::SQLITE_FLOAT => { + ValueRef::Real(unsafe { ffi::sqlite3_column_double(raw, col as c_int) }) + } + ffi::SQLITE_TEXT => { + let s = unsafe { + // Quoting from "Using SQLite" book: + // To avoid problems, an application should first extract the desired type using + // a sqlite3_column_xxx() function, and then call the + // appropriate sqlite3_column_bytes() function. + let text = ffi::sqlite3_column_text(raw, col as c_int); + let len = ffi::sqlite3_column_bytes(raw, col as c_int); + assert!( + !text.is_null(), + "unexpected SQLITE_TEXT column type with NULL data" + ); + from_raw_parts(text.cast::(), len as usize) + }; + + ValueRef::Text(s) + } + ffi::SQLITE_BLOB => { + let (blob, len) = unsafe { + ( + ffi::sqlite3_column_blob(raw, col as c_int), + ffi::sqlite3_column_bytes(raw, col as c_int), + ) + }; + + assert!( + len >= 0, + "unexpected negative return from sqlite3_column_bytes" + ); + if len > 0 { + assert!( + !blob.is_null(), + "unexpected SQLITE_BLOB column type with NULL data" + ); + ValueRef::Blob(unsafe { from_raw_parts(blob.cast::(), len as usize) }) + } else { + // The return value from sqlite3_column_blob() for a zero-length BLOB + // is a NULL pointer. + ValueRef::Blob(&[]) + } + } + _ => unreachable!("sqlite3_column_type returned invalid value"), + } + } + + #[inline] + pub(super) fn step(&self) -> Result { + match self.stmt.step() { + ffi::SQLITE_ROW => Ok(true), + ffi::SQLITE_DONE => Ok(false), + code => Err(self.conn.decode_result(code).unwrap_err()), + } + } + + #[inline] + pub(super) fn reset(&self) -> Result<()> { + match self.stmt.reset() { + ffi::SQLITE_OK => Ok(()), + code => Err(self.conn.decode_result(code).unwrap_err()), + } + } +} + +/// Prepared statement status counters. +/// +/// See `https://www.sqlite.org/c3ref/c_stmtstatus_counter.html` +/// for explanations of each. +/// +/// Note that depending on your version of SQLite, all of these +/// may not be available. +#[repr(i32)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum StatementStatus { + /// Equivalent to `SQLITE_STMTSTATUS_FULLSCAN_STEP` + FullscanStep = 1, + /// Equivalent to `SQLITE_STMTSTATUS_SORT` + Sort = 2, + /// Equivalent to `SQLITE_STMTSTATUS_AUTOINDEX` + AutoIndex = 3, + /// Equivalent to `SQLITE_STMTSTATUS_VM_STEP` + VmStep = 4, + /// Equivalent to `SQLITE_STMTSTATUS_REPREPARE` (3.20.0) + RePrepare = 5, + /// Equivalent to `SQLITE_STMTSTATUS_RUN` (3.20.0) + Run = 6, + /// Equivalent to `SQLITE_STMTSTATUS_FILTER_MISS` + FilterMiss = 7, + /// Equivalent to `SQLITE_STMTSTATUS_FILTER_HIT` + FilterHit = 8, + /// Equivalent to `SQLITE_STMTSTATUS_MEMUSED` (3.20.0) + MemUsed = 99, +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::types::ToSql; + use crate::{params_from_iter, Connection, Error, Result}; + + #[test] + fn test_execute_named() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER)")?; + + assert_eq!( + db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?, + 1 + ); + assert_eq!( + db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])?, + 1 + ); + assert_eq!( + db.execute( + "INSERT INTO foo(x) VALUES (:x)", + crate::named_params! {":x": 3i32} + )?, + 1 + ); + + assert_eq!( + 6i32, + db.query_row::( + "SELECT SUM(x) FROM foo WHERE x > :x", + &[(":x", &0i32)], + |r| r.get(0) + )? + ); + assert_eq!( + 5i32, + db.query_row::( + "SELECT SUM(x) FROM foo WHERE x > :x", + &[(":x", &1i32)], + |r| r.get(0) + )? + ); + Ok(()) + } + + #[test] + fn test_stmt_execute_named() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \ + INTEGER)"; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?; + stmt.execute(&[(":name", "one")])?; + stmt.execute(vec![(":name", "one")].as_slice())?; + + let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?; + assert_eq!( + 2i32, + stmt.query_row::(&[(":name", "one")], |r| r.get(0))? + ); + Ok(()) + } + + #[test] + fn test_query_named() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = r#" + CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER); + INSERT INTO test(id, name) VALUES (1, "one"); + "#; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("SELECT id FROM test where name = :name")?; + let mut rows = stmt.query(&[(":name", "one")])?; + let id: Result = rows.next()?.unwrap().get(0); + assert_eq!(Ok(1), id); + Ok(()) + } + + #[test] + fn test_query_map_named() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = r#" + CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER); + INSERT INTO test(id, name) VALUES (1, "one"); + "#; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("SELECT id FROM test where name = :name")?; + let mut rows = stmt.query_map(&[(":name", "one")], |row| { + let id: Result = row.get(0); + id.map(|i| 2 * i) + })?; + + let doubled_id: i32 = rows.next().unwrap()?; + assert_eq!(2, doubled_id); + Ok(()) + } + + #[test] + fn test_query_and_then_by_name() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = r#" + CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER); + INSERT INTO test(id, name) VALUES (1, "one"); + INSERT INTO test(id, name) VALUES (2, "one"); + "#; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?; + let mut rows = stmt.query_and_then(&[(":name", "one")], |row| { + let id: i32 = row.get(0)?; + if id == 1 { + Ok(id) + } else { + Err(Error::SqliteSingleThreadedMode) + } + })?; + + // first row should be Ok + let doubled_id: i32 = rows.next().unwrap()?; + assert_eq!(1, doubled_id); + + // second row should be an `Err` + #[expect(clippy::match_wild_err_arm)] + match rows.next().unwrap() { + Ok(_) => panic!("invalid Ok"), + Err(Error::SqliteSingleThreadedMode) => (), + Err(_) => panic!("invalid Err"), + } + Ok(()) + } + + #[test] + fn test_unbound_parameters_are_null() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "CREATE TABLE test (x TEXT, y TEXT)"; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?; + stmt.execute(&[(":x", "one")])?; + + let result: Option = db.one_column("SELECT y FROM test WHERE x = 'one'", [])?; + assert!(result.is_none()); + Ok(()) + } + + #[test] + fn test_raw_binding() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?; + { + let mut stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?; + + stmt.raw_bind_parameter(c":name", "example")?; + stmt.raw_bind_parameter(":name", "example")?; + stmt.raw_bind_parameter(3, 50i32)?; + let n = stmt.raw_execute()?; + assert_eq!(n, 1); + } + + { + let mut stmt = db.prepare("SELECT name, value FROM test WHERE value = ?2")?; + stmt.raw_bind_parameter(2, 50)?; + let mut rows = stmt.raw_query(); + { + let row = rows.next()?.unwrap(); + let name: String = row.get(0)?; + assert_eq!(name, "example"); + let value: i32 = row.get(1)?; + assert_eq!(value, 50); + } + assert!(rows.next()?.is_none()); + } + + Ok(()) + } + + #[test] + fn test_unbound_parameters_are_reused() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "CREATE TABLE test (x TEXT, y TEXT)"; + db.execute_batch(sql)?; + + let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?; + stmt.execute(&[(":x", "one")])?; + stmt.execute(&[(c":y", "two")])?; + + let result: String = db.one_column("SELECT x FROM test WHERE y = 'two'", [])?; + assert_eq!(result, "one"); + Ok(()) + } + + #[test] + fn test_insert() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")?; + let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?1)")?; + assert_eq!(stmt.insert([1i32])?, 1); + assert_eq!(stmt.insert([2i32])?, 2); + match stmt.insert([1i32]).unwrap_err() { + Error::StatementChangedRows(0) => (), + err => panic!("Unexpected error {err}"), + } + let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")?; + match multi.insert([]).unwrap_err() { + Error::StatementChangedRows(2) => (), + err => panic!("Unexpected error {err}"), + } + Ok(()) + } + + #[test] + fn test_insert_different_tables() -> Result<()> { + // Test for https://github.com/rusqlite/rusqlite/issues/171 + let db = Connection::open_in_memory()?; + db.execute_batch( + r" + CREATE TABLE foo(x INTEGER); + CREATE TABLE bar(x INTEGER); + ", + )?; + + assert_eq!(db.prepare("INSERT INTO foo VALUES (10)")?.insert([])?, 1); + assert_eq!(db.prepare("INSERT INTO bar VALUES (10)")?.insert([])?, 1); + Ok(()) + } + + #[test] + fn test_exists() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER); + INSERT INTO foo VALUES(1); + INSERT INTO foo VALUES(2); + END;"; + db.execute_batch(sql)?; + let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?1")?; + assert!(stmt.exists([1i32])?); + assert!(stmt.exists([2i32])?); + assert!(!stmt.exists([0i32])?); + Ok(()) + } + #[test] + fn test_tuple_params() -> Result<()> { + let db = Connection::open_in_memory()?; + let s = db.query_row("SELECT printf('[%s]', ?1)", ("abc",), |r| { + r.get::<_, String>(0) + })?; + assert_eq!(s, "[abc]"); + let s = db.query_row( + "SELECT printf('%d %s %d', ?1, ?2, ?3)", + (1i32, "abc", 2i32), + |r| r.get::<_, String>(0), + )?; + assert_eq!(s, "1 abc 2"); + let s = db.query_row( + "SELECT printf('%d %s %d %d', ?1, ?2, ?3, ?4)", + (1, "abc", 2i32, 4i64), + |r| r.get::<_, String>(0), + )?; + assert_eq!(s, "1 abc 2 4"); + #[rustfmt::skip] + let bigtup = ( + 0, "a", 1, "b", 2, "c", 3, "d", + 4, "e", 5, "f", 6, "g", 7, "h", + ); + let query = "SELECT printf( + '%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s', + ?1, ?2, ?3, ?4, + ?5, ?6, ?7, ?8, + ?9, ?10, ?11, ?12, + ?13, ?14, ?15, ?16 + )"; + let s = db.query_row(query, bigtup, |r| r.get::<_, String>(0))?; + assert_eq!(s, "0 a | 1 b | 2 c | 3 d || 4 e | 5 f | 6 g | 7 h"); + Ok(()) + } + + #[test] + fn test_query_row() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + INSERT INTO foo VALUES(2, 4); + END;"; + db.execute_batch(sql)?; + let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?; + let y: Result = stmt.query_row([1i32], |r| r.get(0)); + assert_eq!(3i64, y?); + Ok(()) + } + + #[test] + fn query_one() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo(x INTEGER, y INTEGER);")?; + let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?; + let y: Result = stmt.query_one([1i32], |r| r.get(0)); + assert_eq!(Error::QueryReturnedNoRows, y.unwrap_err()); + db.execute_batch("INSERT INTO foo VALUES(1, 3);")?; + let y: Result = stmt.query_one([1i32], |r| r.get(0)); + assert_eq!(3i64, y?); + db.execute_batch("INSERT INTO foo VALUES(1, 3);")?; + let y: Result = stmt.query_one([1i32], |r| r.get(0)); + assert_eq!(Error::QueryReturnedMoreThanOneRow, y.unwrap_err()); + Ok(()) + } + + #[test] + fn test_query_by_column_name() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql)?; + let mut stmt = db.prepare("SELECT y FROM foo")?; + let y: Result = stmt.query_row([], |r| r.get("y")); + assert_eq!(3i64, y?); + Ok(()) + } + + #[test] + fn test_query_by_column_name_ignore_case() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql)?; + let mut stmt = db.prepare("SELECT y as Y FROM foo")?; + let y: Result = stmt.query_row([], |r| r.get("y")); + assert_eq!(3i64, y?); + Ok(()) + } + + #[test] + fn test_expanded_sql() -> Result<()> { + let db = Connection::open_in_memory()?; + let stmt = db.prepare("SELECT ?1")?; + stmt.bind_parameter(&1, 1)?; + assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql()); + Ok(()) + } + + #[test] + fn test_bind_parameters() -> Result<()> { + let db = Connection::open_in_memory()?; + // dynamic slice: + db.query_row( + "SELECT ?1, ?2, ?3", + [&1u8 as &dyn ToSql, &"one", &Some("one")], + |row| row.get::<_, u8>(0), + )?; + // existing collection: + let data = vec![1, 2, 3]; + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, u8>(0) + })?; + db.query_row( + "SELECT ?1, ?2, ?3", + params_from_iter(data.as_slice()), + |row| row.get::<_, u8>(0), + )?; + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data), |row| { + row.get::<_, u8>(0) + })?; + + use std::collections::BTreeSet; + let data: BTreeSet = ["one", "two", "three"] + .iter() + .map(|s| (*s).to_string()) + .collect(); + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, String>(0) + })?; + + let data = [0; 3]; + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, u8>(0) + })?; + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data.iter()), |row| { + row.get::<_, u8>(0) + })?; + Ok(()) + } + + #[test] + fn test_parameter_name() -> Result<()> { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?; + let stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?; + assert_eq!(stmt.parameter_name(0), None); + assert_eq!(stmt.parameter_name(1), Some(":name")); + assert_eq!(stmt.parameter_name(2), None); + Ok(()) + } + + #[test] + fn test_empty_stmt() -> Result<()> { + let conn = Connection::open_in_memory()?; + let mut stmt = conn.prepare("")?; + assert_eq!(0, stmt.column_count()); + stmt.parameter_index("test")?; + let err = stmt.step().unwrap_err(); + assert_eq!(err.sqlite_error_code(), Some(crate::ErrorCode::ApiMisuse)); + // error msg is different with sqlcipher, so we use assert_ne: + assert_ne!(err.to_string(), "not an error".to_owned()); + stmt.reset()?; // SQLITE_OMIT_AUTORESET = false + stmt.execute([]).unwrap_err(); + Ok(()) + } + + #[test] + fn test_comment_stmt() -> Result<()> { + let conn = Connection::open_in_memory()?; + conn.prepare("/*SELECT 1;*/")?; + Ok(()) + } + + #[test] + fn test_comment_and_sql_stmt() -> Result<()> { + let conn = Connection::open_in_memory()?; + let stmt = conn.prepare("/*...*/ SELECT 1;")?; + assert_eq!(1, stmt.column_count()); + Ok(()) + } + + #[test] + fn test_semi_colon_stmt() -> Result<()> { + let conn = Connection::open_in_memory()?; + let stmt = conn.prepare(";")?; + assert_eq!(0, stmt.column_count()); + Ok(()) + } + + #[test] + fn test_utf16_conversion() -> Result<()> { + let db = Connection::open_in_memory()?; + db.pragma_update(None, "encoding", "UTF-16le")?; + let encoding: String = db.pragma_query_value(None, "encoding", |row| row.get(0))?; + assert_eq!("UTF-16le", encoding); + db.execute_batch("CREATE TABLE foo(x TEXT)")?; + let expected = "テスト"; + db.execute("INSERT INTO foo(x) VALUES (?1)", [&expected])?; + let actual: String = db.one_column("SELECT x FROM foo", [])?; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn test_nul_byte() -> Result<()> { + let db = Connection::open_in_memory()?; + let expected = "a\x00b"; + let actual: String = db.one_column("SELECT ?1", [expected])?; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn is_explain() -> Result<()> { + let db = Connection::open_in_memory()?; + let stmt = db.prepare("SELECT 1;")?; + assert_eq!(0, stmt.is_explain()); + Ok(()) + } + + #[test] + fn readonly() -> Result<()> { + let db = Connection::open_in_memory()?; + let stmt = db.prepare("SELECT 1;")?; + assert!(stmt.readonly()); + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + fn test_error_offset() -> Result<()> { + use crate::ffi::ErrorCode; + let db = Connection::open_in_memory()?; + let r = db.execute_batch("SELECT INVALID_FUNCTION;"); + match r.unwrap_err() { + Error::SqlInputError { error, offset, .. } => { + assert_eq!(error.code, ErrorCode::Unknown); + assert_eq!(offset, 7); + } + err => panic!("Unexpected error {err}"), + } + Ok(()) + } +} diff --git a/vendor/rusqlite/src/trace.rs b/vendor/rusqlite/src/trace.rs new file mode 100644 index 0000000..f3e00e3 --- /dev/null +++ b/vendor/rusqlite/src/trace.rs @@ -0,0 +1,385 @@ +//! Tracing and profiling functions. Error and warning log. + +use std::borrow::Cow; +use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; +use std::marker::PhantomData; +use std::mem; +use std::panic::catch_unwind; +use std::ptr; +use std::time::Duration; + +use super::ffi; +use crate::{Connection, StatementStatus, MAIN_DB}; + +/// Set up the process-wide SQLite error logging callback. +/// +/// # Safety +/// +/// This function is marked unsafe for two reasons: +/// +/// * The function is not threadsafe. No other SQLite calls may be made while +/// `config_log` is running, and multiple threads may not call `config_log` +/// simultaneously. +/// * The provided `callback` itself function has two requirements: +/// * It must not invoke any SQLite calls. +/// * It must be threadsafe if SQLite is used in a multithreaded way. +/// +/// cf [The Error And Warning Log](http://sqlite.org/errlog.html). +#[cfg(not(feature = "loadable_extension"))] +pub unsafe fn config_log(callback: Option) -> crate::Result<()> { + extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) { + let s = unsafe { CStr::from_ptr(msg).to_string_lossy() }; + let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) }; + + drop(catch_unwind(|| callback(err, &s))); + } + + let rc = if let Some(f) = callback { + ffi::sqlite3_config( + ffi::SQLITE_CONFIG_LOG, + log_callback as extern "C" fn(_, _, _), + f as *mut c_void, + ) + } else { + let nullptr: *mut c_void = ptr::null_mut(); + ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr) + }; + + if rc == ffi::SQLITE_OK { + Ok(()) + } else { + Err(crate::error::error_from_sqlite_code(rc, None)) + } +} + +/// Write a message into the error log established by +/// `config_log`. +#[inline] +pub fn log(err_code: c_int, msg: &str) { + let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes"); + unsafe { + ffi::sqlite3_log(err_code, b"%s\0" as *const _ as *const c_char, msg.as_ptr()); + } +} + +bitflags::bitflags! { + /// Trace event codes + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[non_exhaustive] + #[repr(C)] + pub struct TraceEventCodes: c_uint { + /// when a prepared statement first begins running and possibly at other times during the execution + /// of the prepared statement, such as at the start of each trigger subprogram + const SQLITE_TRACE_STMT = ffi::SQLITE_TRACE_STMT; + /// when the statement finishes + const SQLITE_TRACE_PROFILE = ffi::SQLITE_TRACE_PROFILE; + /// whenever a prepared statement generates a single row of result + const SQLITE_TRACE_ROW = ffi::SQLITE_TRACE_ROW; + /// when a database connection closes + const SQLITE_TRACE_CLOSE = ffi::SQLITE_TRACE_CLOSE; + } +} + +/// Trace event +#[non_exhaustive] +pub enum TraceEvent<'s> { + /// when a prepared statement first begins running and possibly at other times during the execution + /// of the prepared statement, such as at the start of each trigger subprogram + Stmt(StmtRef<'s>, &'s str), + /// when the statement finishes + Profile(StmtRef<'s>, Duration), + /// whenever a prepared statement generates a single row of result + Row(StmtRef<'s>), + /// when a database connection closes + Close(ConnRef<'s>), +} + +/// Statement reference +pub struct StmtRef<'s> { + ptr: *mut ffi::sqlite3_stmt, + phantom: PhantomData<&'s ()>, +} + +impl StmtRef<'_> { + fn new(ptr: *mut ffi::sqlite3_stmt) -> Self { + StmtRef { + ptr, + phantom: PhantomData, + } + } + /// SQL text + pub fn sql(&self) -> Cow<'_, str> { + let sql = unsafe { ffi::sqlite3_sql(self.ptr) }; + + if sql.is_null() { + return Cow::default(); + } + + // Safety: sql is a valid pointer to a cstr returned by sqlite3 + unsafe { CStr::from_ptr(sql).to_string_lossy() } + } + /// Expanded SQL text + pub fn expanded_sql(&self) -> Option { + unsafe { + crate::raw_statement::expanded_sql(self.ptr).map(|s| s.to_string_lossy().to_string()) + } + } + /// Get the value for one of the status counters for this statement. + pub fn get_status(&self, status: StatementStatus) -> i32 { + unsafe { crate::raw_statement::stmt_status(self.ptr, status, false) } + } +} + +/// Connection reference +pub struct ConnRef<'s> { + ptr: *mut ffi::sqlite3, + phantom: PhantomData<&'s ()>, +} + +impl ConnRef<'_> { + /// Test for auto-commit mode. + pub fn is_autocommit(&self) -> bool { + unsafe { crate::inner_connection::get_autocommit(self.ptr) } + } + /// the path to the database file, if one exists and is known. + pub fn db_filename(&self) -> Option<&str> { + unsafe { crate::inner_connection::db_filename(self.phantom, self.ptr, MAIN_DB) } + } +} + +impl Connection { + /// Register or clear a callback function that can be + /// used for tracing the execution of SQL statements. + /// + /// Prepared statement placeholders are replaced/logged with their assigned + /// values. There can only be a single tracer defined for each database + /// connection. Setting a new tracer clears the old one. + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + #[deprecated(since = "0.33.0", note = "use trace_v2 instead")] + pub fn trace(&mut self, trace_fn: Option) { + unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) { + let trace_fn: fn(&str) = mem::transmute(p_arg); + let s = CStr::from_ptr(z_sql).to_string_lossy(); + drop(catch_unwind(|| trace_fn(&s))); + } + + let c = self.db.borrow_mut(); + unsafe { + ffi::sqlite3_trace( + c.db(), + trace_fn.as_ref().map(|_| trace_callback as _), + trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void), + ); + } + } + + /// Register or clear a callback function that can be + /// used for profiling the execution of SQL statements. + /// + /// There can only be a single profiler defined for each database + /// connection. Setting a new profiler clears the old one. + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + #[deprecated(since = "0.33.0", note = "use trace_v2 instead")] + pub fn profile(&mut self, profile_fn: Option) { + unsafe extern "C" fn profile_callback( + p_arg: *mut c_void, + z_sql: *const c_char, + nanoseconds: u64, + ) { + let profile_fn: fn(&str, Duration) = mem::transmute(p_arg); + let s = CStr::from_ptr(z_sql).to_string_lossy(); + + let duration = Duration::from_nanos(nanoseconds); + drop(catch_unwind(|| profile_fn(&s, duration))); + } + + let c = self.db.borrow_mut(); + unsafe { + ffi::sqlite3_profile( + c.db(), + profile_fn.as_ref().map(|_| profile_callback as _), + profile_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void), + ); + } + } + + /// Register or clear a trace callback function + pub fn trace_v2(&self, mask: TraceEventCodes, trace_fn: Option)>) { + unsafe extern "C" fn trace_callback( + evt: c_uint, + ctx: *mut c_void, + p: *mut c_void, + x: *mut c_void, + ) -> c_int { + let trace_fn: fn(TraceEvent<'_>) = mem::transmute(ctx); + drop(catch_unwind(|| match evt { + ffi::SQLITE_TRACE_STMT => { + let str = CStr::from_ptr(x as *const c_char).to_string_lossy(); + trace_fn(TraceEvent::Stmt( + StmtRef::new(p as *mut ffi::sqlite3_stmt), + &str, + )) + } + ffi::SQLITE_TRACE_PROFILE => { + let ns = *(x as *const i64); + trace_fn(TraceEvent::Profile( + StmtRef::new(p as *mut ffi::sqlite3_stmt), + Duration::from_nanos(u64::try_from(ns).unwrap_or_default()), + )) + } + ffi::SQLITE_TRACE_ROW => { + trace_fn(TraceEvent::Row(StmtRef::new(p as *mut ffi::sqlite3_stmt))) + } + ffi::SQLITE_TRACE_CLOSE => trace_fn(TraceEvent::Close(ConnRef { + ptr: p as *mut ffi::sqlite3, + phantom: PhantomData, + })), + _ => {} + })); + // The integer return value from the callback is currently ignored, though this may change in future releases. + // Callback implementations should return zero to ensure future compatibility. + ffi::SQLITE_OK + } + let c = self.db.borrow_mut(); + unsafe { + ffi::sqlite3_trace_v2( + c.db(), + mask.bits(), + trace_fn.as_ref().map(|_| trace_callback as _), + trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void), + ); + } + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + use std::sync::{LazyLock, Mutex}; + use std::time::Duration; + + use super::{TraceEvent, TraceEventCodes}; + use crate::{Connection, Result, MAIN_DB}; + + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + #[test] + #[allow(deprecated)] + fn test_trace() -> Result<()> { + static TRACED_STMTS: LazyLock>> = + LazyLock::new(|| Mutex::new(Vec::new())); + fn tracer(s: &str) { + let mut traced_stmts = TRACED_STMTS.lock().unwrap(); + traced_stmts.push(s.to_owned()); + } + + let mut db = Connection::open_in_memory()?; + db.trace(Some(tracer)); + { + let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(())); + let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(())); + } + db.trace(None); + { + let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(())); + let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(())); + } + + let traced_stmts = TRACED_STMTS.lock().unwrap(); + assert_eq!(traced_stmts.len(), 2); + assert_eq!(traced_stmts[0], "SELECT 1"); + assert_eq!(traced_stmts[1], "SELECT 'hello'"); + Ok(()) + } + + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + #[test] + #[allow(deprecated)] + fn test_profile() -> Result<()> { + static PROFILED: LazyLock>> = + LazyLock::new(|| Mutex::new(Vec::new())); + fn profiler(s: &str, d: Duration) { + let mut profiled = PROFILED.lock().unwrap(); + profiled.push((s.to_owned(), d)); + } + + let mut db = Connection::open_in_memory()?; + db.profile(Some(profiler)); + db.execute_batch("PRAGMA application_id = 1")?; + db.profile(None); + db.execute_batch("PRAGMA application_id = 2")?; + + let profiled = PROFILED.lock().unwrap(); + assert_eq!(profiled.len(), 1); + assert_eq!(profiled[0].0, "PRAGMA application_id = 1"); + Ok(()) + } + + #[test] + pub fn trace_v2() -> Result<()> { + use std::borrow::Borrow; + use std::cmp::Ordering; + + let db = Connection::open_in_memory()?; + db.trace_v2( + TraceEventCodes::all(), + Some(|e| match e { + TraceEvent::Stmt(s, sql) => { + assert_eq!(s.sql(), sql); + } + TraceEvent::Profile(s, d) => { + assert_eq!(s.get_status(crate::StatementStatus::Sort), 0); + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + assert_eq!(d.cmp(&Duration::ZERO), Ordering::Greater); + // Timers on the web are not very accurate + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + assert!(matches!( + d.cmp(&Duration::ZERO), + Ordering::Equal | Ordering::Greater + )); + } + TraceEvent::Row(s) => { + assert_eq!(s.expanded_sql().as_deref(), Some(s.sql().borrow())); + } + TraceEvent::Close(db) => { + assert!(db.is_autocommit()); + // https://www.sqlite.org/c3ref/db_filename.html + // if database N is a temporary or in-memory database, + // then this function will return either a NULL pointer or an empty string. + assert!(db.db_filename().is_none_or(|s| s.is_empty())); + } + }), + ); + + db.one_column::("PRAGMA user_version", [])?; + drop(db); + + let db = Connection::open_in_memory()?; + db.trace_v2(TraceEventCodes::empty(), None); + Ok(()) + } + + #[test] + #[cfg(feature = "blob")] + pub fn null_sql() -> Result<()> { + let db = Connection::open_in_memory()?; + let sql = "CREATE TABLE test (content BLOB); + INSERT INTO test VALUES (ZEROBLOB(10));"; + db.execute_batch(sql)?; + let rowid = db.last_insert_rowid(); + + db.trace_v2( + TraceEventCodes::SQLITE_TRACE_ROW, + Some(|e| { + if let TraceEvent::Row(s) = e { + assert_eq!(s.sql(), ""); + } + }), + ); + db.blob_open(MAIN_DB, c"test", c"content", rowid, true)?; + + Ok(()) + } +} diff --git a/vendor/rusqlite/src/transaction.rs b/vendor/rusqlite/src/transaction.rs new file mode 100644 index 0000000..febb80b --- /dev/null +++ b/vendor/rusqlite/src/transaction.rs @@ -0,0 +1,826 @@ +use crate::{Connection, Result}; +use std::ops::Deref; + +/// Options for transaction behavior. See [BEGIN +/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. +#[derive(Copy, Clone)] +#[non_exhaustive] +pub enum TransactionBehavior { + /// DEFERRED means that the transaction does not actually start until the + /// database is first accessed. + Deferred, + /// IMMEDIATE cause the database connection to start a new write + /// immediately, without waiting for a writes statement. + Immediate, + /// EXCLUSIVE prevents other database connections from reading the database + /// while the transaction is underway. + Exclusive, +} + +/// Options for how a Transaction or Savepoint should behave when it is dropped. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum DropBehavior { + /// Roll back the changes. This is the default. + Rollback, + + /// Commit the changes. + Commit, + + /// Do not commit or roll back changes - this will leave the transaction or + /// savepoint open, so should be used with care. + Ignore, + + /// Panic. Used to enforce intentional behavior during development. + Panic, +} + +/// Represents a transaction on a database connection. +/// +/// ## Note +/// +/// Transactions will roll back by default. Use `commit` method to explicitly +/// commit the transaction, or use `set_drop_behavior` to change what happens +/// when the transaction is dropped. +/// +/// ## Example +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result}; +/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } +/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } +/// fn perform_queries(conn: &mut Connection) -> Result<()> { +/// let tx = conn.transaction()?; +/// +/// do_queries_part_1(&tx)?; // tx causes rollback if this fails +/// do_queries_part_2(&tx)?; // tx causes rollback if this fails +/// +/// tx.commit() +/// } +/// ``` +#[derive(Debug)] +pub struct Transaction<'conn> { + conn: &'conn Connection, + drop_behavior: DropBehavior, +} + +/// Represents a savepoint on a database connection. +/// +/// ## Note +/// +/// Savepoints will roll back by default. Use `commit` method to explicitly +/// commit the savepoint, or use `set_drop_behavior` to change what happens +/// when the savepoint is dropped. +/// +/// ## Example +/// +/// ```rust,no_run +/// # use rusqlite::{Connection, Result}; +/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } +/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } +/// fn perform_queries(conn: &mut Connection) -> Result<()> { +/// let sp = conn.savepoint()?; +/// +/// do_queries_part_1(&sp)?; // sp causes rollback if this fails +/// do_queries_part_2(&sp)?; // sp causes rollback if this fails +/// +/// sp.commit() +/// } +/// ``` +#[derive(Debug)] +pub struct Savepoint<'conn> { + conn: &'conn Connection, + name: String, + drop_behavior: DropBehavior, + committed: bool, +} + +impl Transaction<'_> { + /// Begin a new transaction. Cannot be nested; see `savepoint` for nested + /// transactions. + /// + /// Even though we don't mutate the connection, we take a `&mut Connection` + /// to prevent nested transactions on the same connection. For cases + /// where this is unacceptable, [`Transaction::new_unchecked`] is available. + #[inline] + pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result> { + Self::new_unchecked(conn, behavior) + } + + /// Begin a new transaction, failing if a transaction is open. + /// + /// If a transaction is already open, this will return an error. Where + /// possible, [`Transaction::new`] should be preferred, as it provides a + /// compile-time guarantee that transactions are not nested. + #[inline] + pub fn new_unchecked( + conn: &Connection, + behavior: TransactionBehavior, + ) -> Result> { + let query = match behavior { + TransactionBehavior::Deferred => "BEGIN DEFERRED", + TransactionBehavior::Immediate => "BEGIN IMMEDIATE", + TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", + }; + conn.execute_batch(query).map(move |()| Transaction { + conn, + drop_behavior: DropBehavior::Rollback, + }) + } + + /// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested + /// transactions. + /// + /// ## Note + /// + /// Just like outer level transactions, savepoint transactions rollback by + /// default. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true } + /// fn perform_queries(conn: &mut Connection) -> Result<()> { + /// let mut tx = conn.transaction()?; + /// + /// { + /// let sp = tx.savepoint()?; + /// if perform_queries_part_1_succeeds(&sp) { + /// sp.commit()?; + /// } + /// // otherwise, sp will rollback + /// } + /// + /// tx.commit() + /// } + /// ``` + #[inline] + pub fn savepoint(&mut self) -> Result> { + Savepoint::new_(self.conn) + } + + /// Create a new savepoint with a custom savepoint name. See `savepoint()`. + #[inline] + pub fn savepoint_with_name>(&mut self, name: T) -> Result> { + Savepoint::with_name_(self.conn, name) + } + + /// Get the current setting for what happens to the transaction when it is + /// dropped. + #[inline] + #[must_use] + pub fn drop_behavior(&self) -> DropBehavior { + self.drop_behavior + } + + /// Configure the transaction to perform the specified action when it is + /// dropped. + #[inline] + pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { + self.drop_behavior = drop_behavior; + } + + /// A convenience method which consumes and commits a transaction. + #[inline] + pub fn commit(mut self) -> Result<()> { + self.commit_() + } + + #[inline] + fn commit_(&mut self) -> Result<()> { + self.conn.execute_batch("COMMIT")?; + Ok(()) + } + + /// A convenience method which consumes and rolls back a transaction. + #[inline] + pub fn rollback(mut self) -> Result<()> { + self.rollback_() + } + + #[inline] + fn rollback_(&mut self) -> Result<()> { + self.conn.execute_batch("ROLLBACK")?; + Ok(()) + } + + /// Consumes the transaction, committing or rolling back according to the + /// current setting (see `drop_behavior`). + /// + /// Functionally equivalent to the `Drop` implementation, but allows + /// callers to see any errors that occur. + #[inline] + pub fn finish(mut self) -> Result<()> { + self.finish_() + } + + #[inline] + fn finish_(&mut self) -> Result<()> { + if self.conn.is_autocommit() { + return Ok(()); + } + match self.drop_behavior() { + DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()), + DropBehavior::Rollback => self.rollback_(), + DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), + } + } +} + +impl Deref for Transaction<'_> { + type Target = Connection; + + #[inline] + fn deref(&self) -> &Connection { + self.conn + } +} + +#[expect(unused_must_use)] +impl Drop for Transaction<'_> { + #[inline] + fn drop(&mut self) { + self.finish_(); + } +} + +impl Savepoint<'_> { + #[inline] + fn with_name_>(conn: &Connection, name: T) -> Result> { + let name = name.into(); + conn.execute_batch(&format!("SAVEPOINT {name}")) + .map(|()| Savepoint { + conn, + name, + drop_behavior: DropBehavior::Rollback, + committed: false, + }) + } + + #[inline] + fn new_(conn: &Connection) -> Result> { + Savepoint::with_name_(conn, "_rusqlite_sp") + } + + /// Begin a new savepoint. Can be nested. + #[inline] + pub fn new(conn: &mut Connection) -> Result> { + Savepoint::new_(conn) + } + + /// Begin a new savepoint with a user-provided savepoint name. + #[inline] + pub fn with_name>(conn: &mut Connection, name: T) -> Result> { + Savepoint::with_name_(conn, name) + } + + /// Begin a nested savepoint. + #[inline] + pub fn savepoint(&mut self) -> Result> { + Savepoint::new_(self.conn) + } + + /// Begin a nested savepoint with a user-provided savepoint name. + #[inline] + pub fn savepoint_with_name>(&mut self, name: T) -> Result> { + Savepoint::with_name_(self.conn, name) + } + + /// Get the current setting for what happens to the savepoint when it is + /// dropped. + #[inline] + #[must_use] + pub fn drop_behavior(&self) -> DropBehavior { + self.drop_behavior + } + + /// Configure the savepoint to perform the specified action when it is + /// dropped. + #[inline] + pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { + self.drop_behavior = drop_behavior; + } + + /// A convenience method which consumes and commits a savepoint. + #[inline] + pub fn commit(mut self) -> Result<()> { + self.commit_() + } + + #[inline] + fn commit_(&mut self) -> Result<()> { + self.conn.execute_batch(&format!("RELEASE {}", self.name))?; + self.committed = true; + Ok(()) + } + + /// A convenience method which rolls back a savepoint. + /// + /// ## Note + /// + /// Unlike `Transaction`s, savepoints remain active after they have been + /// rolled back, and can be rolled back again or committed. + #[inline] + pub fn rollback(&mut self) -> Result<()> { + self.conn + .execute_batch(&format!("ROLLBACK TO {}", self.name)) + } + + /// Consumes the savepoint, committing or rolling back according to the + /// current setting (see `drop_behavior`). + /// + /// Functionally equivalent to the `Drop` implementation, but allows + /// callers to see any errors that occur. + #[inline] + pub fn finish(mut self) -> Result<()> { + self.finish_() + } + + #[inline] + fn finish_(&mut self) -> Result<()> { + if self.committed { + return Ok(()); + } + match self.drop_behavior() { + DropBehavior::Commit => self + .commit_() + .or_else(|_| self.rollback().and_then(|()| self.commit_())), + DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()), + DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), + } + } +} + +impl Deref for Savepoint<'_> { + type Target = Connection; + + #[inline] + fn deref(&self) -> &Connection { + self.conn + } +} + +#[expect(unused_must_use)] +impl Drop for Savepoint<'_> { + #[inline] + fn drop(&mut self) { + self.finish_(); + } +} + +/// Transaction state of a database +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +#[cfg(feature = "modern_sqlite")] // 3.37.0 +pub enum TransactionState { + /// Equivalent to `SQLITE_TXN_NONE` + None, + /// Equivalent to `SQLITE_TXN_READ` + Read, + /// Equivalent to `SQLITE_TXN_WRITE` + Write, +} + +impl Connection { + /// Begin a new transaction with the default behavior (DEFERRED). + /// + /// The transaction defaults to rolling back when it is dropped. If you + /// want the transaction to commit, you must call + /// [`commit`](Transaction::commit) or + /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior). + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } + /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } + /// fn perform_queries(conn: &mut Connection) -> Result<()> { + /// let tx = conn.transaction()?; + /// + /// do_queries_part_1(&tx)?; // tx causes rollback if this fails + /// do_queries_part_2(&tx)?; // tx causes rollback if this fails + /// + /// tx.commit() + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[inline] + pub fn transaction(&mut self) -> Result> { + Transaction::new(self, self.transaction_behavior) + } + + /// Begin a new transaction with a specified behavior. + /// + /// See [`transaction`](Connection::transaction). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[inline] + pub fn transaction_with_behavior( + &mut self, + behavior: TransactionBehavior, + ) -> Result> { + Transaction::new(self, behavior) + } + + /// Begin a new transaction with the default behavior (DEFERRED). + /// + /// Attempt to open a nested transaction will result in a SQLite error. + /// `Connection::transaction` prevents this at compile time by taking `&mut + /// self`, but `Connection::unchecked_transaction()` may be used to defer + /// the checking until runtime. + /// + /// See [`Connection::transaction`] and [`Transaction::new_unchecked`] + /// (which can be used if the default transaction behavior is undesirable). + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// # use std::rc::Rc; + /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } + /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } + /// fn perform_queries(conn: Rc) -> Result<()> { + /// let tx = conn.unchecked_transaction()?; + /// + /// do_queries_part_1(&tx)?; // tx causes rollback if this fails + /// do_queries_part_2(&tx)?; // tx causes rollback if this fails + /// + /// tx.commit() + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. The specific + /// error returned if transactions are nested is currently unspecified. + pub fn unchecked_transaction(&self) -> Result> { + Transaction::new_unchecked(self, self.transaction_behavior) + } + + /// Begin a new savepoint with the default behavior (DEFERRED). + /// + /// The savepoint defaults to rolling back when it is dropped. If you want + /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or + /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior). + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } + /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } + /// fn perform_queries(conn: &mut Connection) -> Result<()> { + /// let sp = conn.savepoint()?; + /// + /// do_queries_part_1(&sp)?; // sp causes rollback if this fails + /// do_queries_part_2(&sp)?; // sp causes rollback if this fails + /// + /// sp.commit() + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[inline] + pub fn savepoint(&mut self) -> Result> { + Savepoint::new(self) + } + + /// Begin a new savepoint with a specified name. + /// + /// See [`savepoint`](Connection::savepoint). + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite call fails. + #[inline] + pub fn savepoint_with_name>(&mut self, name: T) -> Result> { + Savepoint::with_name(self, name) + } + + /// Determine the transaction state of a database + #[cfg(feature = "modern_sqlite")] // 3.37.0 + pub fn transaction_state( + &self, + db_name: Option, + ) -> Result { + self.db.borrow().txn_state(db_name) + } + + /// Set the default transaction behavior for the connection. + /// + /// ## Note + /// + /// This will only apply to transactions initiated by [`transaction`](Connection::transaction) + /// or [`unchecked_transaction`](Connection::unchecked_transaction). + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, TransactionBehavior}; + /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } + /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } + /// fn perform_queries(conn: &mut Connection) -> Result<()> { + /// conn.set_transaction_behavior(TransactionBehavior::Immediate); + /// + /// let tx = conn.transaction()?; + /// + /// do_queries_part_1(&tx)?; // tx causes rollback if this fails + /// do_queries_part_2(&tx)?; // tx causes rollback if this fails + /// + /// tx.commit() + /// } + /// ``` + pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) { + self.transaction_behavior = behavior; + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::DropBehavior; + use crate::{Connection, Error, Result}; + + fn checked_memory_handle() -> Result { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo (x INTEGER)")?; + Ok(db) + } + + #[test] + fn test_drop() -> Result<()> { + let mut db = checked_memory_handle()?; + { + let tx = db.transaction()?; + tx.execute_batch("INSERT INTO foo VALUES(1)")?; + // default: rollback + } + { + let mut tx = db.transaction()?; + tx.execute_batch("INSERT INTO foo VALUES(2)")?; + tx.set_drop_behavior(DropBehavior::Commit) + } + { + let tx = db.transaction()?; + assert_eq!(2, tx.one_column::("SELECT SUM(x) FROM foo", [])?); + } + Ok(()) + } + fn assert_nested_tx_error(e: Error) { + if let Error::SqliteFailure(e, Some(m)) = &e { + assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR); + // FIXME: Not ideal... + assert_eq!(e.code, crate::ErrorCode::Unknown); + assert!(m.contains("transaction")); + } else { + panic!("Unexpected error type: {e:?}"); + } + } + + #[test] + fn test_unchecked_nesting() -> Result<()> { + let db = checked_memory_handle()?; + + { + let tx = db.unchecked_transaction()?; + let e = tx.unchecked_transaction().unwrap_err(); + assert_nested_tx_error(e); + // default: rollback + } + { + let tx = db.unchecked_transaction()?; + tx.execute_batch("INSERT INTO foo VALUES(1)")?; + // Ensure this doesn't interfere with ongoing transaction + let e = tx.unchecked_transaction().unwrap_err(); + assert_nested_tx_error(e); + + tx.execute_batch("INSERT INTO foo VALUES(1)")?; + tx.commit()?; + } + + assert_eq!(2, db.one_column::("SELECT SUM(x) FROM foo", [])?); + Ok(()) + } + + #[test] + fn test_explicit_rollback_commit() -> Result<()> { + let mut db = checked_memory_handle()?; + { + let mut tx = db.transaction()?; + { + let mut sp = tx.savepoint()?; + sp.execute_batch("INSERT INTO foo VALUES(1)")?; + sp.rollback()?; + sp.execute_batch("INSERT INTO foo VALUES(2)")?; + sp.commit()?; + } + tx.commit()?; + } + { + let tx = db.transaction()?; + tx.execute_batch("INSERT INTO foo VALUES(4)")?; + tx.commit()?; + } + { + let tx = db.transaction()?; + assert_eq!(6, tx.one_column::("SELECT SUM(x) FROM foo", [])?); + } + Ok(()) + } + + #[test] + fn test_savepoint() -> Result<()> { + let mut db = checked_memory_handle()?; + { + let mut tx = db.transaction()?; + tx.execute_batch("INSERT INTO foo VALUES(1)")?; + assert_current_sum(1, &tx)?; + tx.set_drop_behavior(DropBehavior::Commit); + { + let mut sp1 = tx.savepoint()?; + sp1.execute_batch("INSERT INTO foo VALUES(2)")?; + assert_current_sum(3, &sp1)?; + // will roll back sp1 + { + let mut sp2 = sp1.savepoint()?; + sp2.execute_batch("INSERT INTO foo VALUES(4)")?; + assert_current_sum(7, &sp2)?; + // will roll back sp2 + { + let sp3 = sp2.savepoint()?; + sp3.execute_batch("INSERT INTO foo VALUES(8)")?; + assert_current_sum(15, &sp3)?; + sp3.commit()?; + // committed sp3, but will be erased by sp2 rollback + } + assert_current_sum(15, &sp2)?; + } + assert_current_sum(3, &sp1)?; + } + assert_current_sum(1, &tx)?; + } + assert_current_sum(1, &db)?; + Ok(()) + } + + #[test] + fn test_ignore_drop_behavior() -> Result<()> { + let mut db = checked_memory_handle()?; + + let mut tx = db.transaction()?; + { + let mut sp1 = tx.savepoint()?; + insert(1, &sp1)?; + sp1.rollback()?; + insert(2, &sp1)?; + { + let mut sp2 = sp1.savepoint()?; + sp2.set_drop_behavior(DropBehavior::Ignore); + insert(4, &sp2)?; + } + assert_current_sum(6, &sp1)?; + sp1.commit()?; + } + assert_current_sum(6, &tx)?; + Ok(()) + } + + #[test] + fn test_savepoint_drop_behavior_releases() -> Result<()> { + let mut db = checked_memory_handle()?; + + { + let mut sp = db.savepoint()?; + sp.set_drop_behavior(DropBehavior::Commit); + } + assert!(db.is_autocommit()); + { + let mut sp = db.savepoint()?; + sp.set_drop_behavior(DropBehavior::Rollback); + } + assert!(db.is_autocommit()); + + Ok(()) + } + + #[test] + fn test_savepoint_release_error() -> Result<()> { + let mut db = checked_memory_handle()?; + + db.pragma_update(None, "foreign_keys", true)?; + db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?; + { + let mut sp = db.savepoint()?; + sp.execute("INSERT INTO f VALUES (0)", [])?; + sp.set_drop_behavior(DropBehavior::Commit); + } + assert!(db.is_autocommit()); + + Ok(()) + } + + #[test] + fn test_savepoint_names() -> Result<()> { + let mut db = checked_memory_handle()?; + + { + let mut sp1 = db.savepoint_with_name("my_sp")?; + insert(1, &sp1)?; + assert_current_sum(1, &sp1)?; + { + let mut sp2 = sp1.savepoint_with_name("my_sp")?; + sp2.set_drop_behavior(DropBehavior::Commit); + insert(2, &sp2)?; + assert_current_sum(3, &sp2)?; + sp2.rollback()?; + assert_current_sum(1, &sp2)?; + insert(4, &sp2)?; + } + assert_current_sum(5, &sp1)?; + sp1.rollback()?; + { + let mut sp2 = sp1.savepoint_with_name("my_sp")?; + sp2.set_drop_behavior(DropBehavior::Ignore); + insert(8, &sp2)?; + } + assert_current_sum(8, &sp1)?; + sp1.commit()?; + } + assert_current_sum(8, &db)?; + Ok(()) + } + + #[test] + fn test_rc() -> Result<()> { + use std::rc::Rc; + let mut conn = Connection::open_in_memory()?; + let rc_txn = Rc::new(conn.transaction()?); + + // This will compile only if Transaction is Debug + Rc::try_unwrap(rc_txn).unwrap(); + Ok(()) + } + + fn insert(x: i32, conn: &Connection) -> Result { + conn.execute("INSERT INTO foo VALUES(?1)", [x]) + } + + fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> { + assert_eq!(x, conn.one_column::("SELECT SUM(x) FROM foo", [])?); + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn txn_state() -> Result<()> { + use super::TransactionState; + use crate::MAIN_DB; + let db = Connection::open_in_memory()?; + assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?); + assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?); + db.execute_batch("BEGIN")?; + assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?); + let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?; + assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?); + db.pragma_update(None, "user_version", 1)?; + assert_eq!(TransactionState::Write, db.transaction_state::<&str>(None)?); + db.execute_batch("ROLLBACK")?; + Ok(()) + } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn auto_commit() -> Result<()> { + use super::TransactionState; + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE t(i UNIQUE);")?; + assert!(db.is_autocommit()); + let mut stmt = db.prepare("SELECT name FROM sqlite_master")?; + assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?); + { + let mut rows = stmt.query([])?; + assert!(rows.next()?.is_some()); // start reading + assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?); + db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit + assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?); + assert!(rows.next()?.is_some()); // still reading + assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?); + assert!(rows.next()?.is_none()); // end + assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?); + } + Ok(()) + } +} diff --git a/vendor/rusqlite/src/types/chrono.rs b/vendor/rusqlite/src/types/chrono.rs new file mode 100644 index 0000000..81ad4bf --- /dev/null +++ b/vendor/rusqlite/src/types/chrono.rs @@ -0,0 +1,317 @@ +//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. + +use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; + +use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef}; +use crate::Result; + +/// ISO 8601 calendar date without timezone => "YYYY-MM-DD" +impl ToSql for NaiveDate { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.format("%F").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. +impl FromSql for NaiveDate { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value + .as_str() + .and_then(|s| Self::parse_from_str(s, "%F").map_err(FromSqlError::other)) + } +} + +/// ISO 8601 time without timezone => "HH:MM:SS.SSS" +impl ToSql for NaiveTime { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.format("%T%.f").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. +impl FromSql for NaiveTime { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().and_then(|s| { + let fmt = match s.len() { + 5 => "%H:%M", + 8 => "%T", + _ => "%T%.f", + }; + Self::parse_from_str(s, fmt).map_err(FromSqlError::other) + }) + } +} + +/// ISO 8601 combined date and time without timezone => +/// "YYYY-MM-DD HH:MM:SS.SSS" +impl ToSql for NaiveDateTime { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.format("%F %T%.f").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date +/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" +/// also supported) +impl FromSql for NaiveDateTime { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().and_then(|s| { + let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { + "%FT%T%.f" + } else { + "%F %T%.f" + }; + + Self::parse_from_str(s, fmt).map_err(FromSqlError::other) + }) + } +} + +/// UTC time => UTC RFC3339 timestamp +/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00"). +impl ToSql for DateTime { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.format("%F %T%.f%:z").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// Local time => UTC RFC3339 timestamp +/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00"). +impl ToSql for DateTime { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// Date and time with time zone => RFC3339 timestamp +/// ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"). +impl ToSql for DateTime { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.format("%F %T%.f%:z").to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `DateTime`. +impl FromSql for DateTime { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + if value.data_type() == Type::Integer { + return value.as_i64().and_then(|i| { + DateTime::from_timestamp_secs(i).ok_or_else(|| FromSqlError::OutOfRange(i)) + }); + } + { + // Try to parse value as rfc3339 first. + let s = value.as_str()?; + + let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { + "%FT%T%.f%#z" + } else { + "%F %T%.f%#z" + }; + + if let Ok(dt) = DateTime::parse_from_str(s, fmt) { + return Ok(dt.with_timezone(&Utc)); + } + } + + // Couldn't parse as rfc3339 - fall back to NaiveDateTime. + NaiveDateTime::column_result(value).map(|dt| Utc.from_utc_datetime(&dt)) + } +} + +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `DateTime`. +impl FromSql for DateTime { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let utc_dt = DateTime::::column_result(value)?; + Ok(utc_dt.with_timezone(&Local)) + } +} + +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime`. +impl FromSql for DateTime { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let s = String::column_result(value)?; + Self::parse_from_rfc3339(s.as_str()) + .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z")) + .map_err(FromSqlError::other) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{ + types::{FromSql, ValueRef}, + Connection, Result, + }; + use chrono::{ + DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, + Timelike, Utc, + }; + + fn checked_memory_handle() -> Result { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?; + Ok(db) + } + + #[test] + fn test_naive_date() -> Result<()> { + let db = checked_memory_handle()?; + let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap(); + db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("2016-02-23", s); + let t: NaiveDate = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(date, t); + Ok(()) + } + + #[test] + fn test_naive_time() -> Result<()> { + let db = checked_memory_handle()?; + let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("23:56:04", s); + let v: NaiveTime = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(time, v); + Ok(()) + } + + #[test] + fn test_naive_date_time() -> Result<()> { + let db = checked_memory_handle()?; + let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap(); + let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + let dt = NaiveDateTime::new(date, time); + + db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("2016-02-23 23:56:04", s); + let v: NaiveDateTime = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(dt, v); + + db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS" + let hms: NaiveDateTime = db.one_column("SELECT b FROM foo", [])?; + assert_eq!(dt, hms); + Ok(()) + } + + #[test] + fn test_date_time_utc() -> Result<()> { + let db = checked_memory_handle()?; + let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap(); + let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap(); + let dt = NaiveDateTime::new(date, time); + let utc = Utc.from_utc_datetime(&dt); + + db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("2016-02-23 23:56:04.789+00:00", s); + + let v1: DateTime = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(utc, v1); + let v1: DateTime = db.one_column("SELECT i FROM foo", [])?; + assert_eq!(utc.with_nanosecond(0).unwrap(), v1); + + let v2: DateTime = db.one_column("SELECT '2016-02-23 23:56:04.789'", [])?; + assert_eq!(utc, v2); + + let v3: DateTime = db.one_column("SELECT '2016-02-23 23:56:04'", [])?; + assert_eq!(utc - Duration::try_milliseconds(789).unwrap(), v3); + + let v4: DateTime = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'", [])?; + assert_eq!(utc, v4); + Ok(()) + } + + #[test] + fn test_date_time_local() -> Result<()> { + let db = checked_memory_handle()?; + let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap(); + let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap(); + let dt = NaiveDateTime::new(date, time); + let local = Local.from_local_datetime(&dt).single().unwrap(); + + db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?; + + // Stored string should be in UTC + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert!(s.ends_with("+00:00")); + + let v: DateTime = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(local, v); + let v: DateTime = db.one_column("SELECT i FROM foo", [])?; + assert_eq!(local.with_nanosecond(0).unwrap(), v); + Ok(()) + } + + #[test] + fn test_date_time_fixed() -> Result<()> { + let db = checked_memory_handle()?; + let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap(); + + db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?; + + // Stored string should preserve timezone offset + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert!(s.ends_with("+04:00")); + + let v: DateTime = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(time.offset(), v.offset()); + assert_eq!(time, v); + Ok(()) + } + + #[test] + fn test_sqlite_functions() -> Result<()> { + let db = checked_memory_handle()?; + db.one_column::("SELECT CURRENT_TIME", [])?; + db.one_column::("SELECT CURRENT_DATE", [])?; + db.one_column::("SELECT CURRENT_TIMESTAMP", [])?; + db.one_column::, _>("SELECT CURRENT_TIMESTAMP", [])?; + Ok(()) + } + + #[test] + fn test_naive_date_time_param() -> Result<()> { + let db = checked_memory_handle()?; + db.one_column::("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()])?; + Ok(()) + } + + #[test] + fn test_date_time_param() -> Result<()> { + let db = checked_memory_handle()?; + db.one_column::("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()])?; + Ok(()) + } + + #[test] + fn test_lenient_parse_timezone() { + DateTime::::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap(); + DateTime::::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap(); + } +} diff --git a/vendor/rusqlite/src/types/from_sql.rs b/vendor/rusqlite/src/types/from_sql.rs new file mode 100644 index 0000000..11e76ec --- /dev/null +++ b/vendor/rusqlite/src/types/from_sql.rs @@ -0,0 +1,507 @@ +use super::{Value, ValueRef}; +use std::borrow::Cow; +use std::error::Error; +use std::fmt; +use std::str::Utf8Error; + +/// Enum listing possible errors from [`FromSql`] trait. +#[derive(Debug)] +#[non_exhaustive] +pub enum FromSqlError { + /// Error when an SQLite value is requested, but the type of the result + /// cannot be converted to the requested Rust type. + InvalidType, + + /// Error when the i64 value returned by SQLite cannot be stored into the + /// requested type. + OutOfRange(i64), + + /// Error converting a string to UTF-8. + Utf8Error(Utf8Error), + + /// Error when the blob result returned by SQLite cannot be stored into the + /// requested type due to a size mismatch. + InvalidBlobSize { + /// The expected size of the blob. + expected_size: usize, + /// The actual size of the blob that was returned. + blob_size: usize, + }, + + /// An error case available for implementors of the [`FromSql`] trait. + Other(Box), +} + +impl FromSqlError { + /// Converts an arbitrary error type to [`FromSqlError`]. + /// + /// This is a convenience function that boxes and unsizes the error type. It's main purpose is + /// to be usable in the `map_err` method. So instead of + /// `result.map_err(|error| FromSqlError::Other(Box::new(error))` you can write + /// `result.map_err(FromSqlError::other)`. + pub fn other(error: E) -> Self { + Self::Other(Box::new(error)) + } +} + +impl PartialEq for FromSqlError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::InvalidType, Self::InvalidType) => true, + (Self::OutOfRange(n1), Self::OutOfRange(n2)) => n1 == n2, + (Self::Utf8Error(u1), Self::Utf8Error(u2)) => u1 == u2, + ( + Self::InvalidBlobSize { + expected_size: es1, + blob_size: bs1, + }, + Self::InvalidBlobSize { + expected_size: es2, + blob_size: bs2, + }, + ) => es1 == es2 && bs1 == bs2, + (..) => false, + } + } +} + +impl fmt::Display for FromSqlError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::InvalidType => write!(f, "Invalid type"), + Self::OutOfRange(i) => write!(f, "Value {i} out of range"), + Self::Utf8Error(ref err) => err.fmt(f), + Self::InvalidBlobSize { + expected_size, + blob_size, + } => { + write!( + f, + "Cannot read {expected_size} byte value out of {blob_size} byte blob" + ) + } + Self::Other(ref err) => err.fmt(f), + } + } +} + +impl Error for FromSqlError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Utf8Error(ref err) => Some(err), + Self::Other(ref err) => Some(&**err), + _ => None, + } + } +} + +/// Result type for implementors of the [`FromSql`] trait. +pub type FromSqlResult = Result; + +/// A trait for types that can be created from a SQLite value. +pub trait FromSql: Sized { + /// Converts SQLite value into Rust value. + fn column_result(value: ValueRef<'_>) -> FromSqlResult; +} + +macro_rules! from_sql_integral( + ($t:ident) => ( + impl FromSql for $t { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let i = i64::column_result(value)?; + i.try_into().map_err(|_| FromSqlError::OutOfRange(i)) + } + } + ); + (non_zero $nz:ty, $z:ty) => ( + impl FromSql for $nz { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let i = <$z>::column_result(value)?; + <$nz>::new(i).ok_or(FromSqlError::OutOfRange(0)) + } + } + ) +); + +from_sql_integral!(i8); +from_sql_integral!(i16); +from_sql_integral!(i32); +// from_sql_integral!(i64); // Not needed because the native type is i64. +from_sql_integral!(isize); +from_sql_integral!(u8); +from_sql_integral!(u16); +from_sql_integral!(u32); +#[cfg(feature = "fallible_uint")] +from_sql_integral!(u64); +#[cfg(feature = "fallible_uint")] +from_sql_integral!(usize); + +from_sql_integral!(non_zero std::num::NonZeroIsize, isize); +from_sql_integral!(non_zero std::num::NonZeroI8, i8); +from_sql_integral!(non_zero std::num::NonZeroI16, i16); +from_sql_integral!(non_zero std::num::NonZeroI32, i32); +from_sql_integral!(non_zero std::num::NonZeroI64, i64); +#[cfg(feature = "i128_blob")] +from_sql_integral!(non_zero std::num::NonZeroI128, i128); + +#[cfg(feature = "fallible_uint")] +from_sql_integral!(non_zero std::num::NonZeroUsize, usize); +from_sql_integral!(non_zero std::num::NonZeroU8, u8); +from_sql_integral!(non_zero std::num::NonZeroU16, u16); +from_sql_integral!(non_zero std::num::NonZeroU32, u32); +#[cfg(feature = "fallible_uint")] +from_sql_integral!(non_zero std::num::NonZeroU64, u64); +// std::num::NonZeroU128 is not supported since u128 isn't either + +impl FromSql for i64 { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_i64() + } +} + +impl FromSql for f32 { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Integer(i) => Ok(i as Self), + ValueRef::Real(f) => Ok(f as Self), + _ => Err(FromSqlError::InvalidType), + } + } +} + +impl FromSql for f64 { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Integer(i) => Ok(i as Self), + ValueRef::Real(f) => Ok(f), + _ => Err(FromSqlError::InvalidType), + } + } +} + +impl FromSql for bool { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + i64::column_result(value).map(|i| i != 0) + } +} + +impl FromSql for String { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().map(ToString::to_string) + } +} + +impl FromSql for Box { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().map(Into::into) + } +} + +impl FromSql for std::rc::Rc { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().map(Into::into) + } +} + +impl FromSql for std::sync::Arc { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_str().map(Into::into) + } +} + +impl FromSql for Vec { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_blob().map(<[u8]>::to_vec) + } +} + +impl FromSql for Box<[u8]> { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_blob().map(Box::<[u8]>::from) + } +} + +impl FromSql for std::rc::Rc<[u8]> { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_blob().map(std::rc::Rc::<[u8]>::from) + } +} + +impl FromSql for std::sync::Arc<[u8]> { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.as_blob().map(std::sync::Arc::<[u8]>::from) + } +} + +impl FromSql for [u8; N] { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let slice = value.as_blob()?; + slice.try_into().map_err(|_| FromSqlError::InvalidBlobSize { + expected_size: N, + blob_size: slice.len(), + }) + } +} + +#[cfg(feature = "i128_blob")] +impl FromSql for i128 { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let bytes = <[u8; 16]>::column_result(value)?; + Ok(Self::from_be_bytes(bytes) ^ (1_i128 << 127)) + } +} + +#[cfg(feature = "uuid")] +impl FromSql for uuid::Uuid { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let bytes = <[u8; 16]>::column_result(value)?; + Ok(Self::from_u128(u128::from_be_bytes(bytes))) + } +} + +impl FromSql for Option { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Null => Ok(None), + _ => FromSql::column_result(value).map(Some), + } + } +} + +impl FromSql for Cow<'_, T> +where + T: ToOwned, + T::Owned: FromSql, +{ + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + ::column_result(value).map(Cow::Owned) + } +} + +impl FromSql for Value { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value.try_into() + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::{FromSql, FromSqlError}; + use crate::{Connection, Error, Result}; + use std::borrow::Cow; + use std::rc::Rc; + use std::sync::Arc; + + #[test] + fn test_integral_ranges() -> Result<()> { + let db = Connection::open_in_memory()?; + + fn check_ranges(db: &Connection, out_of_range: &[i64], in_range: &[i64]) + where + T: Into + FromSql + std::fmt::Debug, + { + for n in out_of_range { + let err = db + .query_row("SELECT ?1", [n], |r| r.get::<_, T>(0)) + .unwrap_err(); + match err { + Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value), + _ => panic!("unexpected error: {err}"), + } + } + for n in in_range { + assert_eq!( + *n, + db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0)) + .unwrap() + .into() + ); + } + } + + check_ranges::(&db, &[-129, 128], &[-128, 0, 1, 127]); + check_ranges::(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]); + check_ranges::( + &db, + &[-2_147_483_649, 2_147_483_648], + &[-2_147_483_648, -1, 0, 1, 2_147_483_647], + ); + check_ranges::(&db, &[-2, -1, 256], &[0, 1, 255]); + check_ranges::(&db, &[-2, -1, 65536], &[0, 1, 65535]); + check_ranges::(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]); + Ok(()) + } + + #[test] + fn test_nonzero_ranges() -> Result<()> { + let db = Connection::open_in_memory()?; + + macro_rules! check_ranges { + ($nz:ty, $out_of_range:expr, $in_range:expr) => { + for &n in $out_of_range { + assert_eq!( + db.query_row("SELECT ?1", [n], |r| r.get::<_, $nz>(0)), + Err(Error::IntegralValueOutOfRange(0, n)), + "{}", + std::any::type_name::<$nz>() + ); + } + for &n in $in_range { + let non_zero = <$nz>::new(n).unwrap(); + assert_eq!( + Ok(non_zero), + db.query_row("SELECT ?1", [non_zero], |r| r.get::<_, $nz>(0)) + ); + } + }; + } + + check_ranges!(std::num::NonZeroI8, &[0, -129, 128], &[-128, 1, 127]); + check_ranges!( + std::num::NonZeroI16, + &[0, -32769, 32768], + &[-32768, -1, 1, 32767] + ); + check_ranges!( + std::num::NonZeroI32, + &[0, -2_147_483_649, 2_147_483_648], + &[-2_147_483_648, -1, 1, 2_147_483_647] + ); + check_ranges!( + std::num::NonZeroI64, + &[0], + &[-2_147_483_648, -1, 1, 2_147_483_647, i64::MAX, i64::MIN] + ); + check_ranges!( + std::num::NonZeroIsize, + &[0], + &[-2_147_483_648, -1, 1, 2_147_483_647] + ); + check_ranges!(std::num::NonZeroU8, &[0, -2, -1, 256], &[1, 255]); + check_ranges!(std::num::NonZeroU16, &[0, -2, -1, 65536], &[1, 65535]); + check_ranges!( + std::num::NonZeroU32, + &[0, -2, -1, 4_294_967_296], + &[1, 4_294_967_295] + ); + #[cfg(feature = "fallible_uint")] + check_ranges!( + std::num::NonZeroU64, + &[0, -2, -1, -4_294_967_296], + &[1, 4_294_967_295, i64::MAX as u64] + ); + #[cfg(feature = "fallible_uint")] + check_ranges!( + std::num::NonZeroUsize, + &[0, -2, -1, -4_294_967_296], + &[1, 4_294_967_295] + ); + + Ok(()) + } + + #[test] + fn test_cow() -> Result<()> { + let db = Connection::open_in_memory()?; + + assert_eq!( + db.query_row("SELECT 'this is a string'", [], |r| r + .get::<_, Cow<'_, str>>(0)), + Ok(Cow::Borrowed("this is a string")), + ); + assert_eq!( + db.query_row("SELECT x'09ab20fdee87'", [], |r| r + .get::<_, Cow<'_, [u8]>>(0)), + Ok(Cow::Owned(vec![0x09, 0xab, 0x20, 0xfd, 0xee, 0x87])), + ); + assert_eq!( + db.query_row("SELECT 24.5", [], |r| r.get::<_, Cow<'_, f32>>(0),), + Ok(Cow::Borrowed(&24.5)), + ); + + Ok(()) + } + + #[test] + fn test_heap_slice() -> Result<()> { + let db = Connection::open_in_memory()?; + + assert_eq!( + db.query_row("SELECT 'text'", [], |r| r.get::<_, Box>(0)), + Ok(Box::from("text")), + ); + assert_eq!( + db.query_row("SELECT 'Some string slice!'", [], |r| r + .get::<_, Rc>(0)), + Ok(Rc::from("Some string slice!")), + ); + assert_eq!( + db.query_row("SELECT x'012366779988fedc'", [], |r| r + .get::<_, Rc<[u8]>>(0)), + Ok(Rc::from(b"\x01\x23\x66\x77\x99\x88\xfe\xdc".as_slice())), + ); + + assert_eq!( + db.query_row( + "SELECT x'6120737472696e672043414e206265206120626c6f62'", + [], + |r| r.get::<_, Box<[u8]>>(0) + ), + Ok(b"a string CAN be a blob".to_vec().into_boxed_slice()), + ); + assert_eq!( + db.query_row("SELECT 'This is inside an Arc.'", [], |r| r + .get::<_, Arc>(0)), + Ok(Arc::from("This is inside an Arc.")), + ); + assert_eq!( + db.query_row("SELECT x'afd374'", [], |r| r.get::<_, Arc<[u8]>>(0),), + Ok(Arc::from(b"\xaf\xd3\x74".as_slice())), + ); + + Ok(()) + } + + #[test] + fn from_sql_error() { + use std::error::Error as _; + assert_ne!(FromSqlError::InvalidType, FromSqlError::OutOfRange(0)); + assert_ne!(FromSqlError::OutOfRange(0), FromSqlError::OutOfRange(1)); + assert_ne!( + FromSqlError::InvalidBlobSize { + expected_size: 0, + blob_size: 0 + }, + FromSqlError::InvalidBlobSize { + expected_size: 0, + blob_size: 1 + } + ); + assert!(FromSqlError::InvalidType.source().is_none()); + let err = std::io::Error::from(std::io::ErrorKind::UnexpectedEof); + assert!(FromSqlError::Other(Box::new(err)).source().is_some()); + } +} diff --git a/vendor/rusqlite/src/types/jiff.rs b/vendor/rusqlite/src/types/jiff.rs new file mode 100644 index 0000000..68c492f --- /dev/null +++ b/vendor/rusqlite/src/types/jiff.rs @@ -0,0 +1,223 @@ +//! Convert some `jiff` types. + +use jiff::{ + civil::{Date, DateTime, Time}, + Timestamp, +}; + +use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef}; +use crate::Result; + +/// Gregorian calendar date => "YYYY-MM-DD" +impl ToSql for Date { + #[inline] + fn to_sql(&self) -> Result> { + let s = self.to_string(); + Ok(ToSqlOutput::from(s)) + } +} + +/// "YYYY-MM-DD" => Gregorian calendar date. +impl FromSql for Date { + #[inline] + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value + .as_str() + .and_then(|s| s.parse().map_err(FromSqlError::other)) + } +} +/// time => "HH:MM:SS.SSS" +impl ToSql for Time { + #[inline] + fn to_sql(&self) -> Result> { + let date_str = self.to_string(); + Ok(ToSqlOutput::from(date_str)) + } +} + +/// "HH:MM:SS.SSS" => time. +impl FromSql for Time { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value + .as_str() + .and_then(|s| s.parse().map_err(FromSqlError::other)) + } +} + +/// Gregorian datetime => "YYYY-MM-DDTHH:MM:SS.SSS" +impl ToSql for DateTime { + #[inline] + fn to_sql(&self) -> Result> { + let s = self.to_string(); + Ok(ToSqlOutput::from(s)) + } +} + +/// "YYYY-MM-DDTHH:MM:SS.SSS" => Gregorian datetime. +impl FromSql for DateTime { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value + .as_str() + .and_then(|s| s.parse().map_err(FromSqlError::other)) + } +} + +/// UTC time => UTC RFC3339 timestamp +/// ("YYYY-MM-DDTHH:MM:SS.SSSZ"). +impl ToSql for Timestamp { + #[inline] + fn to_sql(&self) -> Result> { + Ok(ToSqlOutput::from(self.to_string())) + } +} + +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `Timestamp`. +impl FromSql for Timestamp { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + if value.data_type() == Type::Integer { + return value + .as_i64() + .and_then(|i| Timestamp::from_second(i).map_err(FromSqlError::other)); + } + value + .as_str()? + .parse::() + .map_err(FromSqlError::other) + } +} + +#[cfg(test)] +mod test { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use crate::{Connection, Result}; + use jiff::{ + civil::{Date, DateTime, Time}, + Timestamp, + }; + + fn checked_memory_handle() -> Result { + let db = Connection::open_in_memory()?; + db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?; + Ok(db) + } + + #[test] + fn test_date() -> Result<()> { + let db = checked_memory_handle()?; + let date = Date::constant(2016, 2, 23); + db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("2016-02-23", s); + let t: Date = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(date, t); + + db.execute("UPDATE foo set b = date(t)", [])?; + let t: Date = db.one_column("SELECT b FROM foo", [])?; + assert_eq!(date, t); + + let r: Result = db.one_column("SELECT '2023-02-29'", []); + assert!(r.is_err()); + Ok(()) + } + + #[test] + fn test_time() -> Result<()> { + let db = checked_memory_handle()?; + let time = Time::constant(23, 56, 4, 0); + db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?; + + let s: String = db.one_column("SELECT t FROM foo", [])?; + assert_eq!("23:56:04", s); + let v: Time = db.one_column("SELECT t FROM foo", [])?; + assert_eq!(time, v); + + db.execute("UPDATE foo set b = time(t)", [])?; + let v: Time = db.one_column("SELECT b FROM foo", [])?; + assert_eq!(time, v); + + let r: Result