story-kit: merge 260_refactor_upgrade_libsqlite3_sys
This commit is contained in:
1
vendor/rusqlite/.cargo-ok
vendored
Normal file
1
vendor/rusqlite/.cargo-ok
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
6
vendor/rusqlite/.cargo_vcs_info.json
vendored
Normal file
6
vendor/rusqlite/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "2a1790a69107cd03dae85d501dcbdb11c5b32ef3"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
3
vendor/rusqlite/.gitignore
vendored
Normal file
3
vendor/rusqlite/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target/
|
||||
/doc/
|
||||
Cargo.lock
|
||||
350
vendor/rusqlite/Cargo.toml
vendored
Normal file
350
vendor/rusqlite/Cargo.toml
vendored
Normal file
@@ -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"
|
||||
242
vendor/rusqlite/Cargo.toml.orig
generated
vendored
Normal file
242
vendor/rusqlite/Cargo.toml.orig
generated
vendored
Normal file
@@ -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
|
||||
19
vendor/rusqlite/LICENSE
vendored
Normal file
19
vendor/rusqlite/LICENSE
vendored
Normal file
@@ -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.
|
||||
259
vendor/rusqlite/README.md
vendored
Normal file
259
vendor/rusqlite/README.md
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
# Rusqlite
|
||||
|
||||
[](https://crates.io/crates/rusqlite)
|
||||
[](https://docs.rs/rusqlite)
|
||||
[](https://github.com/rusqlite/rusqlite/actions)
|
||||
[](https://ci.appveyor.com/project/rusqlite/rusqlite)
|
||||
[](https://codecov.io/gh/rusqlite/rusqlite)
|
||||
[](https://deps.rs/repo/github/rusqlite/rusqlite)
|
||||
[](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<Vec<u8>>,
|
||||
}
|
||||
|
||||
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.
|
||||
18
vendor/rusqlite/benches/cache.rs
vendored
Normal file
18
vendor/rusqlite/benches/cache.rs
vendored
Normal file
@@ -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);
|
||||
17
vendor/rusqlite/benches/exec.rs
vendored
Normal file
17
vendor/rusqlite/benches/exec.rs
vendored
Normal file
@@ -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);
|
||||
405
vendor/rusqlite/bindings.md
vendored
Normal file
405
vendor/rusqlite/bindings.md
vendored
Normal file
@@ -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`
|
||||
23
vendor/rusqlite/examples/load_extension.rs
vendored
Normal file
23
vendor/rusqlite/examples/load_extension.rs
vendored
Normal file
@@ -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(())
|
||||
}
|
||||
49
vendor/rusqlite/examples/loadable_extension.rs
vendored
Normal file
49
vendor/rusqlite/examples/loadable_extension.rs
vendored
Normal file
@@ -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 <https://sqlite.org/c3ref/load_extension.html> 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<bool> {
|
||||
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)
|
||||
}
|
||||
27
vendor/rusqlite/examples/owning_rows.rs
vendored
Normal file
27
vendor/rusqlite/examples/owning_rows.rs
vendored
Normal file
@@ -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<CachedStatement<'conn>>,
|
||||
#[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(())
|
||||
}
|
||||
30
vendor/rusqlite/examples/owning_statement.rs
vendored
Normal file
30
vendor/rusqlite/examples/owning_statement.rs
vendored
Normal file
@@ -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<Connection>,
|
||||
#[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<Rows<'_>> { stmt.query([]) })?;
|
||||
while let Some(row) = rows.next()? {
|
||||
assert_eq!(Ok(1), row.get(0));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
48
vendor/rusqlite/examples/persons/README.md
vendored
Normal file
48
vendor/rusqlite/examples/persons/README.md
vendored
Normal file
@@ -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=`<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
|
||||
```
|
||||
57
vendor/rusqlite/examples/persons/main.rs
vendored
Normal file
57
vendor/rusqlite/examples/persons/main.rs
vendored
Normal file
@@ -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(())
|
||||
}
|
||||
62
vendor/rusqlite/src/auto_extension.rs
vendored
Normal file
62
vendor/rusqlite/src/auto_extension.rs
vendored
Normal file
@@ -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() }
|
||||
}
|
||||
442
vendor/rusqlite/src/backup.rs
vendored
Normal file
442
vendor/rusqlite/src/backup.rs
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
//! Online SQLite backup API.
|
||||
//!
|
||||
//! Alternatively, you can create a backup with a simple
|
||||
//! [`VACUUM INTO <backup_path>`](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<P: AsRef<Path>>(
|
||||
//! 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<N: Name, P: AsRef<Path>>(
|
||||
&self,
|
||||
name: N,
|
||||
dst_path: P,
|
||||
progress: Option<fn(Progress)>,
|
||||
) -> 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<N: Name, P: AsRef<Path>, F: Fn(Progress)>(
|
||||
&mut self,
|
||||
name: N,
|
||||
src_path: P,
|
||||
progress: Option<F>,
|
||||
) -> 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<'a, 'b>> {
|
||||
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<Backup<'a, 'b>> {
|
||||
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<StepResult> {
|
||||
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<fn(Progress)>,
|
||||
) -> 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::<i64, _>("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::<i64, _>("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::<i64, _>("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(())
|
||||
}
|
||||
}
|
||||
71
vendor/rusqlite/src/bind.rs
vendored
Normal file
71
vendor/rusqlite/src/bind.rs
vendored
Normal file
@@ -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<usize>;
|
||||
}
|
||||
|
||||
impl BindIndex for usize {
|
||||
#[inline]
|
||||
fn idx(&self, _: &Statement<'_>) -> Result<usize> {
|
||||
// No validation
|
||||
Ok(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BindIndex for &'_ str {
|
||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||
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<usize> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
564
vendor/rusqlite/src/blob/mod.rs
vendored
Normal file
564
vendor/rusqlite/src/blob/mod.rs
vendored
Normal file
@@ -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<u8>]` 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::<rusqlite::Error>()` (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<u8>]` 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<dyn Error>> {
|
||||
//! 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<dyn Error>> {
|
||||
//! 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<D: Name, N: Name>(
|
||||
&self,
|
||||
db: D,
|
||||
table: N,
|
||||
column: N,
|
||||
row_id: i64,
|
||||
read_only: bool,
|
||||
) -> Result<Blob<'_>> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<u64> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
274
vendor/rusqlite/src/blob/pos_io.rs
vendored
Normal file
274
vendor/rusqlite/src/blob/pos_io.rs
vendored
Normal file
@@ -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<usize> {
|
||||
// Safety: this is safe because `raw_read_at` never stores uninitialized
|
||||
// data into `as_uninit`.
|
||||
let as_uninit: &mut [MaybeUninit<u8>] =
|
||||
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<u8>`.
|
||||
///
|
||||
/// 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<u8>],
|
||||
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::<u8>(), 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::<u8>(), 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<u8>],
|
||||
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<u8>; 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(())
|
||||
}
|
||||
}
|
||||
138
vendor/rusqlite/src/busy.rs
vendored
Normal file
138
vendor/rusqlite/src/busy.rs
vendored
Normal file
@@ -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<fn(i32) -> 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(())
|
||||
}
|
||||
}
|
||||
351
vendor/rusqlite/src/cache.rs
vendored
Normal file
351
vendor/rusqlite/src/cache.rs
vendored
Normal file
@@ -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<CachedStatement<'_>> {
|
||||
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<LruCache<Arc<str>, 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<Statement<'conn>>,
|
||||
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<CachedStatement<'conn>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
236
vendor/rusqlite/src/collation.rs
vendored
Normal file
236
vendor/rusqlite/src/collation.rs
vendored
Normal file
@@ -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<C, N: Name>(&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<N: Name>(&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<C, N: Name>(&mut self, collation_name: N, x_compare: C) -> Result<()>
|
||||
where
|
||||
C: Fn(&str, &str) -> Ordering + Send + 'static,
|
||||
{
|
||||
unsafe extern "C" fn call_boxed_closure<C>(
|
||||
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::<C>();
|
||||
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||
let s1 = {
|
||||
let c_slice = slice::from_raw_parts(arg3.cast::<u8>(), arg2 as usize);
|
||||
String::from_utf8_lossy(c_slice)
|
||||
};
|
||||
let s2 = {
|
||||
let c_slice = slice::from_raw_parts(arg5.cast::<u8>(), 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::<c_void>(),
|
||||
Some(call_boxed_closure::<C>),
|
||||
Some(free_boxed_value::<C>),
|
||||
)
|
||||
};
|
||||
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<N: Name>(&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")
|
||||
}
|
||||
}
|
||||
576
vendor/rusqlite/src/column.rs
vendored
Normal file
576
vendor/rusqlite/src/column.rs
vendored
Normal file
@@ -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:
|
||||
/// <https://www.sqlite.org/c3ref/column_name.html>
|
||||
/// > 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<usize> {
|
||||
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<Column<'_>> {
|
||||
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<ColumnMetadata<'_>> {
|
||||
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<N: Name>(
|
||||
&self,
|
||||
db_name: Option<N>,
|
||||
table_name: N,
|
||||
column_name: N,
|
||||
) -> Result<bool> {
|
||||
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<N: Name>(&self, db_name: Option<N>, table_name: N) -> Result<bool> {
|
||||
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<N: Name>(
|
||||
&self,
|
||||
db_name: Option<N>,
|
||||
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<N: Name>(
|
||||
&self,
|
||||
db_name: Option<N>,
|
||||
table_name: N,
|
||||
column_name: Option<N>,
|
||||
) -> Result<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 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<Option<String>> = 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<Option<&str>> = 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(())
|
||||
}
|
||||
}
|
||||
169
vendor/rusqlite/src/config.rs
vendored
Normal file
169
vendor/rusqlite/src/config.rs
vendored
Normal file
@@ -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<bool> {
|
||||
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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
62
vendor/rusqlite/src/context.rs
vendored
Normal file
62
vendor/rusqlite/src/context.rs
vendored
Normal file
@@ -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::<c_void>(),
|
||||
length as ffi::sqlite3_uint64,
|
||||
ffi::SQLITE_TRANSIENT(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
514
vendor/rusqlite/src/error.rs
vendored
Normal file
514
vendor/rusqlite/src/error.rs
vendored
Normal file
@@ -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<String>),
|
||||
|
||||
/// 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<dyn error::Error + Send + Sync + 'static>),
|
||||
|
||||
/// 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<dyn error::Error + Send + Sync + 'static>),
|
||||
|
||||
/// Error available for the implementors of the
|
||||
/// [`ToSql`](crate::types::ToSql) trait.
|
||||
ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
|
||||
|
||||
/// 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<str::Utf8Error> for Error {
|
||||
#[cold]
|
||||
fn from(err: str::Utf8Error) -> Self {
|
||||
Self::Utf8Error(UNKNOWN_COLUMN, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NulError> 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<FromSqlError> 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<ffi::InitError> 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<ffi::ErrorCode> {
|
||||
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<String>) -> 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<String> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
1271
vendor/rusqlite/src/functions.rs
vendored
Normal file
1271
vendor/rusqlite/src/functions.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1002
vendor/rusqlite/src/hooks/mod.rs
vendored
Normal file
1002
vendor/rusqlite/src/hooks/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
362
vendor/rusqlite/src/hooks/preupdate_hook.rs
vendored
Normal file
362
vendor/rusqlite/src/hooks/preupdate_hook.rs
vendored
Normal file
@@ -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<PreUpdateCase> 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<ValueRef<'_>> {
|
||||
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<ValueRef<'_>> {
|
||||
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<F>(&self, hook: Option<F>) -> 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::<fn(Action, &str, &str, &PreUpdateCase)>);
|
||||
}
|
||||
|
||||
/// ```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<F>(&mut self, hook: Option<F>)
|
||||
where
|
||||
F: FnMut(Action, &str, &str, &PreUpdateCase) + Send + 'static,
|
||||
{
|
||||
unsafe extern "C" fn call_boxed_closure<F>(
|
||||
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::<F>();
|
||||
(*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::<F> 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(())
|
||||
}
|
||||
}
|
||||
446
vendor/rusqlite/src/inner_connection.rs
vendored
Normal file
446
vendor/rusqlite/src/inner_connection.rs
vendored
Normal file
@@ -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<Mutex<*mut ffi::sqlite3>>,
|
||||
#[cfg(feature = "hooks")]
|
||||
pub commit_hook: Option<Box<dyn FnMut() -> bool + Send>>,
|
||||
#[cfg(feature = "hooks")]
|
||||
pub rollback_hook: Option<Box<dyn FnMut() + Send>>,
|
||||
#[cfg(feature = "hooks")]
|
||||
#[expect(clippy::type_complexity)]
|
||||
pub update_hook: Option<Box<dyn FnMut(crate::hooks::Action, &str, &str, i64) + Send>>,
|
||||
#[cfg(feature = "hooks")]
|
||||
pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
|
||||
#[cfg(feature = "hooks")]
|
||||
pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
|
||||
#[cfg(feature = "preupdate_hook")]
|
||||
#[expect(clippy::type_complexity)]
|
||||
pub preupdate_hook: Option<
|
||||
Box<dyn FnMut(crate::hooks::Action, &str, &str, &crate::hooks::PreUpdateCase) + Send>,
|
||||
>,
|
||||
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<Self> {
|
||||
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<N: Name>(
|
||||
&self,
|
||||
dylib_path: &Path,
|
||||
entry_point: Option<N>,
|
||||
) -> 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::<std::ffi::c_void>());
|
||||
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::<c_char>();
|
||||
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<N: Name>(&self, db_name: N) -> Result<bool> {
|
||||
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<N: Name>(
|
||||
&self,
|
||||
db_name: Option<N>,
|
||||
) -> Result<super::transaction::TransactionState> {
|
||||
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<N: Name>(
|
||||
_: 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(())
|
||||
}
|
||||
}
|
||||
2330
vendor/rusqlite/src/lib.rs
vendored
Normal file
2330
vendor/rusqlite/src/lib.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
182
vendor/rusqlite/src/limits.rs
vendored
Normal file
182
vendor/rusqlite/src/limits.rs
vendored
Normal file
@@ -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:
|
||||
/// - <https://www.sqlite.org/c3ref/c_limit_attached.html>
|
||||
/// - <https://www.sqlite.org/limits.html>
|
||||
#[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<i32> {
|
||||
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<i32> {
|
||||
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 <https://sqlite.org/threadsafe.html>
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
45
vendor/rusqlite/src/load_extension_guard.rs
vendored
Normal file
45
vendor/rusqlite/src/load_extension_guard.rs
vendored
Normal file
@@ -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<LoadExtensionGuard<'_>> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
453
vendor/rusqlite/src/params.rs
vendored
Normal file
453
vendor/rusqlite/src/params.rs
vendored
Normal file
@@ -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<T: ToSql, const N: usize> 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<S: BindIndex, T: ToSql> Sealed for &[(S, T)] {}
|
||||
impl<S: BindIndex, T: ToSql> 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<T: ToSql> Sealed for (T,) {}
|
||||
impl<T: ToSql> 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<Row>` 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<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
|
||||
impl<T: ToSql + ?Sized> Params for &[&T; $N] {
|
||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
||||
stmt.bind_parameters(self)
|
||||
}
|
||||
}
|
||||
impl<S: BindIndex, T: ToSql + ?Sized> Sealed for &[(S, &T); $N] {}
|
||||
impl<S: BindIndex, T: ToSql + ?Sized> Params for &[(S, &T); $N] {
|
||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
||||
stmt.bind_parameters_named(self)
|
||||
}
|
||||
}
|
||||
impl<T: ToSql> Sealed for [T; $N] {}
|
||||
impl<T: ToSql> 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<String>) -> 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<bool> {
|
||||
/// 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>(I);
|
||||
|
||||
/// Constructor function for a [`ParamsFromIter`]. See its documentation for
|
||||
/// more.
|
||||
#[inline]
|
||||
pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: ToSql,
|
||||
{
|
||||
ParamsFromIter(iter)
|
||||
}
|
||||
|
||||
impl<I> Sealed for ParamsFromIter<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: ToSql,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Params for ParamsFromIter<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: ToSql,
|
||||
{
|
||||
#[inline]
|
||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
||||
stmt.bind_parameters(self.0)
|
||||
}
|
||||
}
|
||||
426
vendor/rusqlite/src/pragma.rs
vendored
Normal file
426
vendor/rusqlite/src/pragma.rs
vendored
Normal file
@@ -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<T, F>(
|
||||
&self,
|
||||
schema_name: Option<&str>,
|
||||
pragma_name: &str,
|
||||
f: F,
|
||||
) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
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<F>(
|
||||
&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<F, V>(
|
||||
&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<V>(
|
||||
&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<F, T, V>(
|
||||
&self,
|
||||
schema_name: Option<&str>,
|
||||
pragma_name: &str,
|
||||
pragma_value: V,
|
||||
f: F,
|
||||
) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
282
vendor/rusqlite/src/raw_statement.rs
vendored
Normal file
282
vendor/rusqlite/src/raw_statement.rs
vendored
Normal file
@@ -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<Arc<str>>,
|
||||
}
|
||||
|
||||
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<Arc<str>>) {
|
||||
self.statement_cache_key = Some(p.into());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "cache")]
|
||||
pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> {
|
||||
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::<ffi::sqlite3>();
|
||||
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<usize> {
|
||||
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<SqliteMallocString> {
|
||||
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> {
|
||||
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_();
|
||||
}
|
||||
}
|
||||
680
vendor/rusqlite/src/row.rs
vendored
Normal file
680
vendor/rusqlite/src/row.rs
vendored
Normal file
@@ -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<Row<'stmt>>,
|
||||
}
|
||||
|
||||
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<Option<&Row<'stmt>>> {
|
||||
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<Vec<i64>> {
|
||||
/// let rows = stmt.query([])?;
|
||||
/// rows.map(|r| r.get(0)).collect()
|
||||
/// }
|
||||
/// ```
|
||||
// FIXME Hide FallibleStreamingIterator::map
|
||||
#[inline]
|
||||
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<B>,
|
||||
{
|
||||
Map { rows: self, f }
|
||||
}
|
||||
|
||||
/// Map over this `Rows`, converting it to a [`MappedRows`], which
|
||||
/// implements `Iterator`.
|
||||
#[inline]
|
||||
pub fn mapped<F, B>(self, f: F) -> MappedRows<'stmt, F>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<B>,
|
||||
{
|
||||
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<F, T, E>(self, f: F) -> AndThenRows<'stmt, F>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<T, E>,
|
||||
{
|
||||
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<F, B> FallibleIterator for Map<'_, F>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<B>,
|
||||
{
|
||||
type Item = B;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Result<Option<B>> {
|
||||
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<T, F> Iterator for MappedRows<'_, F>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
type Item = Result<T>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Result<T>> {
|
||||
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<T, E, F> Iterator for AndThenRows<'_, F>
|
||||
where
|
||||
E: From<Error>,
|
||||
F: FnMut(&Row<'_>) -> Result<T, E>,
|
||||
{
|
||||
type Item = Result<T, E>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<I: RowIndex, T: FromSql>(&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<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||
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<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
|
||||
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<I: RowIndex>(&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<I: RowIndex, T: 'static>(
|
||||
&self,
|
||||
idx: I,
|
||||
ptr_type: &'static std::ffi::CStr,
|
||||
) -> Result<Option<&T>> {
|
||||
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::<T>()
|
||||
.as_ref()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
|
||||
fn as_ref(&self) -> &Statement<'stmt> {
|
||||
self.stmt
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug `Row` like an ordered `Map<Result<&str>, 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<usize>;
|
||||
}
|
||||
|
||||
impl RowIndex for usize {
|
||||
#[inline]
|
||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||
if *self >= stmt.column_count() {
|
||||
Err(Error::InvalidColumnIndex(*self))
|
||||
} else {
|
||||
Ok(*self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RowIndex for &'_ str {
|
||||
#[inline]
|
||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||
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<Self> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
237
vendor/rusqlite/src/serialize.rs
vendored
Normal file
237
vendor/rusqlite/src/serialize.rs
vendored
Normal file
@@ -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<u8>,
|
||||
sz: usize,
|
||||
}
|
||||
|
||||
/// Owned serialized database
|
||||
pub struct OwnedData {
|
||||
ptr: NonNull<u8>,
|
||||
sz: usize,
|
||||
}
|
||||
|
||||
impl OwnedData {
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must be certain that `ptr` is allocated by `sqlite3_malloc64`.
|
||||
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, 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<N: Name>(&self, schema: N) -> Result<Data<'_>> {
|
||||
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<N: Name, R: std::io::Read>(
|
||||
&mut self,
|
||||
schema: N,
|
||||
mut read: R,
|
||||
sz: usize,
|
||||
read_only: bool,
|
||||
) -> Result<()> {
|
||||
let ptr = unsafe { ffi::sqlite3_malloc64(sz.try_into().unwrap()) }.cast::<u8>();
|
||||
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<N: Name>(&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<N: Name>(
|
||||
&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_<N: Name>(
|
||||
&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(())
|
||||
}
|
||||
}
|
||||
963
vendor/rusqlite/src/session.rs
vendored
Normal file
963
vendor/rusqlite/src/session.rs
vendored
Normal file
@@ -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<Box<dyn Fn(&str) -> 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<'_>> {
|
||||
Session::new_with_name(db, MAIN_DB)
|
||||
}
|
||||
|
||||
/// Create a new session object
|
||||
#[inline]
|
||||
pub fn new_with_name<N: Name>(db: &Connection, name: N) -> Result<Session<'_>> {
|
||||
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<F>(&mut self, filter: Option<F>)
|
||||
where
|
||||
F: Fn(&str) -> bool + Send + 'static,
|
||||
{
|
||||
unsafe extern "C" fn call_boxed_closure<F>(
|
||||
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::<F>();
|
||||
(*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::<F>),
|
||||
&*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<N: Name>(&mut self, table: Option<N>) -> 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<Changeset> {
|
||||
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<Changeset> {
|
||||
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<D: Name, N: Name>(&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::<fn(&str) -> 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<Changeset> {
|
||||
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<ChangesetIter<'_>> {
|
||||
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<Changeset> {
|
||||
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<ChangesetItem>,
|
||||
}
|
||||
|
||||
impl ChangesetIter<'_> {
|
||||
/// Create an iterator on `input`
|
||||
#[inline]
|
||||
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||
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<ValueRef<'_>> {
|
||||
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<i32> {
|
||||
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<ValueRef<'_>> {
|
||||
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<ValueRef<'_>> {
|
||||
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<Operation<'_>> {
|
||||
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<Self> {
|
||||
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<Changeset> {
|
||||
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<F, C>(&self, cs: &Changeset, filter: Option<F>, 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::<F, C>),
|
||||
Some(call_conflict::<F, C>),
|
||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||
)
|
||||
} else {
|
||||
ffi::sqlite3changeset_apply(
|
||||
db,
|
||||
cs.n,
|
||||
cs.cs,
|
||||
None,
|
||||
Some(call_conflict::<F, C>),
|
||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply a changeset to a database
|
||||
pub fn apply_strm<F, C>(
|
||||
&self,
|
||||
input: &mut dyn Read,
|
||||
filter: Option<F>,
|
||||
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::<F, C>),
|
||||
Some(call_conflict::<F, C>),
|
||||
tuple as *mut (Option<F>, 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::<F, C>),
|
||||
tuple as *mut (Option<F>, 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<i32> 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<F, C>(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<F>, C) = p_ctx.cast::<(Option<F>, 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<F, C>(
|
||||
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<F>, C) = p_ctx.cast::<(Option<F>, 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<Changeset> {
|
||||
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<Changeset> {
|
||||
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<Vec<u8>> {
|
||||
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::<fn(&str) -> 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::<fn(&str) -> 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::<fn(&str) -> 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(())
|
||||
}
|
||||
}
|
||||
1396
vendor/rusqlite/src/statement.rs
vendored
Normal file
1396
vendor/rusqlite/src/statement.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
385
vendor/rusqlite/src/trace.rs
vendored
Normal file
385
vendor/rusqlite/src/trace.rs
vendored
Normal file
@@ -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<fn(c_int, &str)>) -> 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<String> {
|
||||
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<fn(&str)>) {
|
||||
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<fn(&str, Duration)>) {
|
||||
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<fn(TraceEvent<'_>)>) {
|
||||
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<Mutex<Vec<String>>> =
|
||||
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<Mutex<Vec<(String, Duration)>>> =
|
||||
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::<u32, _>("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(())
|
||||
}
|
||||
}
|
||||
826
vendor/rusqlite/src/transaction.rs
vendored
Normal file
826
vendor/rusqlite/src/transaction.rs
vendored
Normal file
@@ -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<Transaction<'_>> {
|
||||
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<Transaction<'_>> {
|
||||
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<'_>> {
|
||||
Savepoint::new_(self.conn)
|
||||
}
|
||||
|
||||
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
||||
#[inline]
|
||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||
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_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
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<'_>> {
|
||||
Savepoint::with_name_(conn, "_rusqlite_sp")
|
||||
}
|
||||
|
||||
/// Begin a new savepoint. Can be nested.
|
||||
#[inline]
|
||||
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
|
||||
Savepoint::new_(conn)
|
||||
}
|
||||
|
||||
/// Begin a new savepoint with a user-provided savepoint name.
|
||||
#[inline]
|
||||
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_name_(conn, name)
|
||||
}
|
||||
|
||||
/// Begin a nested savepoint.
|
||||
#[inline]
|
||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||
Savepoint::new_(self.conn)
|
||||
}
|
||||
|
||||
/// Begin a nested savepoint with a user-provided savepoint name.
|
||||
#[inline]
|
||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||
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<'_>> {
|
||||
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<'_>> {
|
||||
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<Connection>) -> 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<'_>> {
|
||||
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<'_>> {
|
||||
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<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_name(self, name)
|
||||
}
|
||||
|
||||
/// Determine the transaction state of a database
|
||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
||||
pub fn transaction_state<N: crate::Name>(
|
||||
&self,
|
||||
db_name: Option<N>,
|
||||
) -> Result<TransactionState> {
|
||||
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<Connection> {
|
||||
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::<i32, _>("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::<i32, _>("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::<i32, _>("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<usize> {
|
||||
conn.execute("INSERT INTO foo VALUES(?1)", [x])
|
||||
}
|
||||
|
||||
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
|
||||
assert_eq!(x, conn.one_column::<i32, _>("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(())
|
||||
}
|
||||
}
|
||||
317
vendor/rusqlite/src/types/chrono.rs
vendored
Normal file
317
vendor/rusqlite/src/types/chrono.rs
vendored
Normal file
@@ -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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<Utc> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
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<Local> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
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<FixedOffset> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
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<Utc>`.
|
||||
impl FromSql for DateTime<Utc> {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<Local>`.
|
||||
impl FromSql for DateTime<Local> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
let utc_dt = DateTime::<Utc>::column_result(value)?;
|
||||
Ok(utc_dt.with_timezone(&Local))
|
||||
}
|
||||
}
|
||||
|
||||
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<FixedOffset>`.
|
||||
impl FromSql for DateTime<FixedOffset> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<Connection> {
|
||||
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<Utc> = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(utc, v1);
|
||||
let v1: DateTime<Utc> = db.one_column("SELECT i FROM foo", [])?;
|
||||
assert_eq!(utc.with_nanosecond(0).unwrap(), v1);
|
||||
|
||||
let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'", [])?;
|
||||
assert_eq!(utc, v2);
|
||||
|
||||
let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'", [])?;
|
||||
assert_eq!(utc - Duration::try_milliseconds(789).unwrap(), v3);
|
||||
|
||||
let v4: DateTime<Utc> = 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<Local> = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(local, v);
|
||||
let v: DateTime<Local> = 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<FixedOffset> = 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::<NaiveTime, _>("SELECT CURRENT_TIME", [])?;
|
||||
db.one_column::<NaiveDate, _>("SELECT CURRENT_DATE", [])?;
|
||||
db.one_column::<NaiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
||||
db.one_column::<DateTime<Utc>, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
db.one_column::<bool, _>("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::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lenient_parse_timezone() {
|
||||
DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap();
|
||||
DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap();
|
||||
}
|
||||
}
|
||||
507
vendor/rusqlite/src/types/from_sql.rs
vendored
Normal file
507
vendor/rusqlite/src/types/from_sql.rs
vendored
Normal file
@@ -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<dyn Error + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
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<E: Error + Send + Sync + 'static>(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<T> = Result<T, FromSqlError>;
|
||||
|
||||
/// 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<Self>;
|
||||
}
|
||||
|
||||
macro_rules! from_sql_integral(
|
||||
($t:ident) => (
|
||||
impl FromSql for $t {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
value.as_i64()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for f32 {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
i64::column_result(value).map(|i| i != 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for String {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(ToString::to_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Box<str> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for std::rc::Rc<str> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for std::sync::Arc<str> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Vec<u8> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(<[u8]>::to_vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Box<[u8]> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(Box::<[u8]>::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for std::rc::Rc<[u8]> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(std::rc::Rc::<[u8]>::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for std::sync::Arc<[u8]> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(std::sync::Arc::<[u8]>::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> FromSql for [u8; N] {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
let bytes = <[u8; 16]>::column_result(value)?;
|
||||
Ok(Self::from_u128(u128::from_be_bytes(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromSql> FromSql for Option<T> {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Null => Ok(None),
|
||||
_ => FromSql::column_result(value).map(Some),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> FromSql for Cow<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
T::Owned: FromSql,
|
||||
{
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
<T::Owned>::column_result(value).map(Cow::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Value {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
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<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
|
||||
where
|
||||
T: Into<i64> + 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::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
|
||||
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
|
||||
check_ranges::<i32>(
|
||||
&db,
|
||||
&[-2_147_483_649, 2_147_483_648],
|
||||
&[-2_147_483_648, -1, 0, 1, 2_147_483_647],
|
||||
);
|
||||
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
|
||||
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
|
||||
check_ranges::<u32>(&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<str>>(0)),
|
||||
Ok(Box::from("text")),
|
||||
);
|
||||
assert_eq!(
|
||||
db.query_row("SELECT 'Some string slice!'", [], |r| r
|
||||
.get::<_, Rc<str>>(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<str>>(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());
|
||||
}
|
||||
}
|
||||
223
vendor/rusqlite/src/types/jiff.rs
vendored
Normal file
223
vendor/rusqlite/src/types/jiff.rs
vendored
Normal file
@@ -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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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<ToSqlOutput<'_>> {
|
||||
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<Self> {
|
||||
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::<Timestamp>()
|
||||
.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<Connection> {
|
||||
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<Date> = 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<Time> = db.one_column("SELECT '25:22:45'", []);
|
||||
assert!(r.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let dt = DateTime::constant(2016, 2, 23, 23, 56, 4, 0);
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!("2016-02-23T23:56:04", s);
|
||||
let v: DateTime = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(dt, v);
|
||||
|
||||
db.execute("UPDATE foo set b = datetime(t)", [])?;
|
||||
let v: DateTime = db.one_column("SELECT b FROM foo", [])?;
|
||||
assert_eq!(dt, v);
|
||||
|
||||
let r: Result<DateTime> = db.one_column("SELECT '2023-02-29T00:00:00'", []);
|
||||
assert!(r.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let ts: Timestamp = "2016-02-23 23:56:04Z".parse().unwrap();
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [ts])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!("2016-02-23T23:56:04Z", s);
|
||||
let v: Timestamp = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(ts, v);
|
||||
let v: Timestamp = db.one_column("SELECT i FROM foo", [])?;
|
||||
assert_eq!(ts, v);
|
||||
|
||||
let r: Result<Timestamp> = db.one_column("SELECT '2023-02-29T00:00:00Z'", []);
|
||||
assert!(r.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_various_formats() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
// Copied over from a test in `src/types/time.rs`. The format numbers
|
||||
// come from <https://sqlite.org/lang_datefunc.html>.
|
||||
let tests = vec![
|
||||
// Rfc3339
|
||||
"2013-10-07T08:23:19.123456789Z",
|
||||
"2013-10-07 08:23:19.123456789Z",
|
||||
// Format 2
|
||||
"2013-10-07 08:23Z",
|
||||
"2013-10-07 08:23+04:00",
|
||||
// Format 3
|
||||
"2013-10-07 08:23:19Z",
|
||||
"2013-10-07 08:23:19+04:00",
|
||||
// Format 4
|
||||
"2013-10-07 08:23:19.123Z",
|
||||
"2013-10-07 08:23:19.123+04:00",
|
||||
// Format 5
|
||||
"2013-10-07T08:23Z",
|
||||
"2013-10-07T08:23+04:00",
|
||||
// Format 6
|
||||
"2013-10-07T08:23:19Z",
|
||||
"2013-10-07T08:23:19+04:00",
|
||||
// Format 7
|
||||
"2013-10-07T08:23:19.123Z",
|
||||
"2013-10-07T08:23:19.123+04:00",
|
||||
];
|
||||
|
||||
for string in tests {
|
||||
let expected: Timestamp = string.parse().unwrap();
|
||||
let result: Timestamp = db.one_column("SELECT ?1", [string])?;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
457
vendor/rusqlite/src/types/mod.rs
vendored
Normal file
457
vendor/rusqlite/src/types/mod.rs
vendored
Normal file
@@ -0,0 +1,457 @@
|
||||
//! Traits dealing with SQLite data types.
|
||||
//!
|
||||
//! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
|
||||
//! the [`ToSql`] and [`FromSql`] traits are provided for the basic types that
|
||||
//! SQLite provides methods for:
|
||||
//!
|
||||
//! * Strings (`String` and `&str`)
|
||||
//! * Blobs (`Vec<u8>` and `&[u8]`)
|
||||
//! * Numbers
|
||||
//!
|
||||
//! The number situation is a little complicated due to the fact that all
|
||||
//! numbers in SQLite are stored as `INTEGER` (`i64`) or `REAL` (`f64`).
|
||||
//!
|
||||
//! [`ToSql`] and [`FromSql`] are implemented for all primitive number types.
|
||||
//! [`FromSql`] has different behaviour depending on the SQL and Rust types, and
|
||||
//! the value.
|
||||
//!
|
||||
//! * `INTEGER` to integer: returns an
|
||||
//! [`Error::IntegralValueOutOfRange`](crate::Error::IntegralValueOutOfRange)
|
||||
//! error if the value does not fit in the Rust type.
|
||||
//! * `REAL` to integer: always returns an
|
||||
//! [`Error::InvalidColumnType`](crate::Error::InvalidColumnType) error.
|
||||
//! * `INTEGER` to float: casts using `as` operator. Never fails.
|
||||
//! * `REAL` to float: casts using `as` operator. Never fails.
|
||||
//!
|
||||
//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
|
||||
//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
|
||||
//! types, so if you store an `i64` in a column with type `REAL` it will be
|
||||
//! stored as an `INTEGER`, not a `REAL` (unless the column is part of a
|
||||
//! [STRICT table](https://www.sqlite.org/stricttables.html)).
|
||||
//!
|
||||
//! If the `time` feature is enabled, implementations are
|
||||
//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
|
||||
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
|
||||
//! can be parsed by SQLite's builtin
|
||||
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
|
||||
//! want different storage for datetimes, you can use a newtype.
|
||||
#![cfg_attr(
|
||||
feature = "time",
|
||||
doc = r##"
|
||||
For example, to store datetimes as `i64`s counting the number of seconds since
|
||||
the Unix epoch:
|
||||
|
||||
```
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use rusqlite::Result;
|
||||
|
||||
pub struct DateTimeSql(pub time::OffsetDateTime);
|
||||
|
||||
impl FromSql for DateTimeSql {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
i64::column_result(value).and_then(|as_i64| {
|
||||
time::OffsetDateTime::from_unix_timestamp(as_i64)
|
||||
.map(|odt| DateTimeSql(odt))
|
||||
.map_err(FromSqlError::other)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for DateTimeSql {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(self.0.unix_timestamp().into())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"##
|
||||
)]
|
||||
//! [`ToSql`] and [`FromSql`] are also implemented for `Option<T>` where `T`
|
||||
//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
|
||||
//! a value was NULL (which gets translated to `None`).
|
||||
|
||||
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
|
||||
pub use self::to_sql::{ToSql, ToSqlOutput};
|
||||
pub use self::value::Value;
|
||||
pub use self::value_ref::ValueRef;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
mod from_sql;
|
||||
#[cfg(feature = "jiff")]
|
||||
mod jiff;
|
||||
#[cfg(feature = "serde_json")]
|
||||
mod serde_json;
|
||||
#[cfg(feature = "time")]
|
||||
mod time;
|
||||
mod to_sql;
|
||||
#[cfg(feature = "url")]
|
||||
mod url;
|
||||
mod value;
|
||||
mod value_ref;
|
||||
|
||||
/// Empty struct that can be used to fill in a query parameter as `NULL`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # use rusqlite::types::{Null};
|
||||
///
|
||||
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
||||
/// conn.execute("INSERT INTO people (name) VALUES (?1)", [Null])
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Null;
|
||||
|
||||
/// SQLite data types.
|
||||
/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
/// NULL
|
||||
Null,
|
||||
/// 64-bit signed integer
|
||||
Integer,
|
||||
/// 64-bit IEEE floating point number
|
||||
Real,
|
||||
/// String
|
||||
Text,
|
||||
/// BLOB
|
||||
Blob,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Null => f.pad("Null"),
|
||||
Self::Integer => f.pad("Integer"),
|
||||
Self::Real => f.pad("Real"),
|
||||
Self::Text => f.pad("Text"),
|
||||
Self::Blob => f.pad("Blob"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::Value;
|
||||
use crate::{params, Connection, Error, Result, Statement};
|
||||
use std::ffi::{c_double, c_int};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blob() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let v1234 = vec![1u8, 2, 3, 4];
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&v1234])?;
|
||||
|
||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo", [])?;
|
||||
assert_eq!(v, v1234);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_blob() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let empty = vec![];
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
|
||||
|
||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo", [])?;
|
||||
assert_eq!(v, empty);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = "hello, world!";
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
|
||||
|
||||
let from: String = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(from, s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = "hello, world!";
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [s.to_owned()])?;
|
||||
|
||||
let from: String = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(from, s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
|
||||
|
||||
assert_eq!(10, db.one_column::<i64, _>("SELECT i FROM foo", [])?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = "hello, world!";
|
||||
let b = Some(vec![1u8, 2, 3, 4]);
|
||||
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [Some(s)])?;
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&b])?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
|
||||
{
|
||||
let row1 = rows.next()?.unwrap();
|
||||
let s1: Option<String> = row1.get_unwrap(0);
|
||||
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
|
||||
assert_eq!(s, s1.unwrap());
|
||||
assert!(b1.is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let row2 = rows.next()?.unwrap();
|
||||
let s2: Option<String> = row2.get_unwrap(0);
|
||||
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
|
||||
assert!(s2.is_none());
|
||||
assert_eq!(b, b2);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::cognitive_complexity)]
|
||||
fn test_mismatched_types() -> Result<()> {
|
||||
fn is_invalid_column_type(err: Error) -> bool {
|
||||
matches!(err, Error::InvalidColumnType(..))
|
||||
}
|
||||
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
|
||||
let row = rows.next()?.unwrap();
|
||||
|
||||
// check the correct types come back as expected
|
||||
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0)?);
|
||||
assert_eq!("text", row.get::<_, String>(1)?);
|
||||
assert_eq!(1, row.get::<_, c_int>(2)?);
|
||||
assert!((1.5 - row.get::<_, c_double>(3)?).abs() < f64::EPSILON);
|
||||
assert_eq!(row.get::<_, Option<c_int>>(4)?, None);
|
||||
assert_eq!(row.get::<_, Option<c_double>>(4)?, None);
|
||||
assert_eq!(row.get::<_, Option<String>>(4)?, None);
|
||||
|
||||
// check some invalid types
|
||||
|
||||
// 0 is actually a blob (Vec<u8>)
|
||||
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
|
||||
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
|
||||
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, c_double>(0).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(row.get::<_, String>(0).unwrap_err()));
|
||||
#[cfg(feature = "time")]
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, time::OffsetDateTime>(0).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Option<c_int>>(0).unwrap_err()
|
||||
));
|
||||
|
||||
// 1 is actually a text (String)
|
||||
assert!(is_invalid_column_type(row.get::<_, c_int>(1).unwrap_err()));
|
||||
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, c_double>(1).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Vec<u8>>(1).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Option<c_int>>(1).unwrap_err()
|
||||
));
|
||||
|
||||
// 2 is actually an integer
|
||||
assert!(is_invalid_column_type(row.get::<_, String>(2).unwrap_err()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Vec<u8>>(2).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Option<String>>(2).unwrap_err()
|
||||
));
|
||||
|
||||
// 3 is actually a float (c_double)
|
||||
assert!(is_invalid_column_type(row.get::<_, c_int>(3).unwrap_err()));
|
||||
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get::<_, String>(3).unwrap_err()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Vec<u8>>(3).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Option<c_int>>(3).unwrap_err()
|
||||
));
|
||||
|
||||
// 4 is actually NULL
|
||||
assert!(is_invalid_column_type(row.get::<_, c_int>(4).unwrap_err()));
|
||||
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, c_double>(4).unwrap_err()
|
||||
));
|
||||
assert!(is_invalid_column_type(row.get::<_, String>(4).unwrap_err()));
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, Vec<u8>>(4).unwrap_err()
|
||||
));
|
||||
#[cfg(feature = "time")]
|
||||
assert!(is_invalid_column_type(
|
||||
row.get::<_, time::OffsetDateTime>(4).unwrap_err()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dynamic_type() -> Result<()> {
|
||||
use super::Value;
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
|
||||
let row = rows.next()?.unwrap();
|
||||
assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);
|
||||
assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
|
||||
assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
|
||||
match row.get::<_, Value>(3)? {
|
||||
Value::Real(val) => assert!((1.5 - val).abs() < f64::EPSILON),
|
||||
x => panic!("Invalid Value {x:?}"),
|
||||
}
|
||||
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! test_conversion {
|
||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect $expected_value:expr) => {
|
||||
$db_etc.insert_statement.execute(params![$insert_value])?;
|
||||
let res = $db_etc
|
||||
.query_statement
|
||||
.query_row([], |row| row.get::<_, $get_type>(0));
|
||||
assert_eq!(res?, $expected_value);
|
||||
$db_etc.delete_statement.execute([])?;
|
||||
};
|
||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect_from_sql_error) => {
|
||||
$db_etc.insert_statement.execute(params![$insert_value])?;
|
||||
let res = $db_etc
|
||||
.query_statement
|
||||
.query_row([], |row| row.get::<_, $get_type>(0));
|
||||
res.unwrap_err();
|
||||
$db_etc.delete_statement.execute([])?;
|
||||
};
|
||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect_to_sql_error) => {
|
||||
$db_etc
|
||||
.insert_statement
|
||||
.execute(params![$insert_value])
|
||||
.unwrap_err();
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::float_cmp)]
|
||||
fn test_numeric_conversions() -> Result<()> {
|
||||
// Test what happens when we store a f32 and retrieve an i32 etc.
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (x)")?;
|
||||
|
||||
// SQLite actually ignores the column types, so we just need to test
|
||||
// different numeric values.
|
||||
|
||||
struct DbEtc<'conn> {
|
||||
insert_statement: Statement<'conn>,
|
||||
query_statement: Statement<'conn>,
|
||||
delete_statement: Statement<'conn>,
|
||||
}
|
||||
|
||||
let mut db_etc = DbEtc {
|
||||
insert_statement: db.prepare("INSERT INTO foo VALUES (?1)")?,
|
||||
query_statement: db.prepare("SELECT x FROM foo")?,
|
||||
delete_statement: db.prepare("DELETE FROM foo")?,
|
||||
};
|
||||
|
||||
// Basic non-converting test.
|
||||
test_conversion!(db_etc, 0u8, u8, expect 0u8);
|
||||
|
||||
// In-range integral conversions.
|
||||
test_conversion!(db_etc, 100u8, i8, expect 100i8);
|
||||
test_conversion!(db_etc, 200u8, u8, expect 200u8);
|
||||
test_conversion!(db_etc, 100u16, i8, expect 100i8);
|
||||
test_conversion!(db_etc, 200u16, u8, expect 200u8);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, u32::MAX, u64, expect u32::MAX as u64);
|
||||
|
||||
test_conversion!(db_etc, i64::MIN, i64, expect i64::MIN);
|
||||
test_conversion!(db_etc, i64::MAX, i64, expect i64::MAX);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, i64::MAX, u64, expect i64::MAX as u64);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, 100usize, usize, expect 100usize);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, 100u64, u64, expect 100u64);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, i64::MAX as u64, u64, expect i64::MAX as u64);
|
||||
|
||||
// Out-of-range integral conversions.
|
||||
test_conversion!(db_etc, 200u8, i8, expect_from_sql_error);
|
||||
test_conversion!(db_etc, 400u16, i8, expect_from_sql_error);
|
||||
test_conversion!(db_etc, 400u16, u8, expect_from_sql_error);
|
||||
test_conversion!(db_etc, -1i8, u8, expect_from_sql_error);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, i64::MIN, u64, expect_from_sql_error);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, u64::MAX, i64, expect_to_sql_error);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, u64::MAX, u64, expect_to_sql_error);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
test_conversion!(db_etc, i64::MAX as u64 + 1, u64, expect_to_sql_error);
|
||||
|
||||
// FromSql integer to float, always works.
|
||||
test_conversion!(db_etc, i64::MIN, f32, expect i64::MIN as f32);
|
||||
test_conversion!(db_etc, i64::MAX, f32, expect i64::MAX as f32);
|
||||
test_conversion!(db_etc, i64::MIN, f64, expect i64::MIN as f64);
|
||||
test_conversion!(db_etc, i64::MAX, f64, expect i64::MAX as f64);
|
||||
|
||||
// FromSql float to int conversion, never works even if the actual value
|
||||
// is an integer.
|
||||
test_conversion!(db_etc, 0f64, i64, expect_from_sql_error);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
133
vendor/rusqlite/src/types/serde_json.rs
vendored
Normal file
133
vendor/rusqlite/src/types/serde_json.rs
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
|
||||
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Serialize JSON `Value` to text:
|
||||
///
|
||||
///
|
||||
/// | JSON | SQLite |
|
||||
/// |----------|---------|
|
||||
/// | Null | NULL |
|
||||
/// | Bool | 'true' / 'false' |
|
||||
/// | Number | INT or REAL except u64 |
|
||||
/// | _ | TEXT |
|
||||
impl ToSql for Value {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
match self {
|
||||
Self::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
|
||||
Self::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
|
||||
Self::Number(n) if n.is_f64() => Ok(ToSqlOutput::from(n.as_f64().unwrap())),
|
||||
_ => serde_json::to_string(self)
|
||||
.map(ToSqlOutput::from)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize SQLite value to JSON `Value`:
|
||||
///
|
||||
/// | SQLite | JSON |
|
||||
/// |----------|---------|
|
||||
/// | NULL | Null |
|
||||
/// | 'null' | Null |
|
||||
/// | 'true' | Bool |
|
||||
/// | 1 | Number |
|
||||
/// | 0.1 | Number |
|
||||
/// | '"text"' | String |
|
||||
/// | 'text' | _Error_ |
|
||||
/// | '[0, 1]' | Array |
|
||||
/// | '{"x": 1}' | Object |
|
||||
impl FromSql for Value {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(s) => serde_json::from_slice(s), // KO for b"text"
|
||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||
ValueRef::Integer(i) => Ok(Self::Number(Number::from(i))),
|
||||
ValueRef::Real(f) => {
|
||||
match Number::from_f64(f) {
|
||||
Some(n) => Ok(Self::Number(n)),
|
||||
_ => return Err(FromSqlError::InvalidType), // FIXME
|
||||
}
|
||||
}
|
||||
ValueRef::Null => Ok(Self::Null),
|
||||
}
|
||||
.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::ToSql;
|
||||
use crate::{Connection, Result};
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_value() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let json = r#"{"foo": 13, "bar": "baz"}"#;
|
||||
let data: Value = serde_json::from_str(json).unwrap();
|
||||
db.execute(
|
||||
"INSERT INTO foo (t, b) VALUES (?1, ?2)",
|
||||
[&data as &dyn ToSql, &json.as_bytes()],
|
||||
)?;
|
||||
|
||||
let t: Value = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(data, t);
|
||||
let b: Value = db.one_column("SELECT b FROM foo", [])?;
|
||||
assert_eq!(data, b);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_sql() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
let v: Option<String> = db.one_column("SELECT ?", [Value::Null])?;
|
||||
assert_eq!(None, v);
|
||||
let v: String = db.one_column("SELECT ?", [Value::Bool(true)])?;
|
||||
assert_eq!("true", v);
|
||||
let v: i64 = db.one_column("SELECT ?", [Value::Number(Number::from(1))])?;
|
||||
assert_eq!(1, v);
|
||||
let v: f64 = db.one_column("SELECT ?", [Value::Number(Number::from_f64(0.1).unwrap())])?;
|
||||
assert_eq!(0.1, v);
|
||||
let v: String = db.one_column("SELECT ?", [Value::String("text".to_owned())])?;
|
||||
assert_eq!("\"text\"", v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_sql() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
let v: Value = db.one_column("SELECT NULL", [])?;
|
||||
assert_eq!(Value::Null, v);
|
||||
let v: Value = db.one_column("SELECT 'null'", [])?;
|
||||
assert_eq!(Value::Null, v);
|
||||
let v: Value = db.one_column("SELECT 'true'", [])?;
|
||||
assert_eq!(Value::Bool(true), v);
|
||||
let v: Value = db.one_column("SELECT 1", [])?;
|
||||
assert_eq!(Value::Number(Number::from(1)), v);
|
||||
let v: Value = db.one_column("SELECT 0.1", [])?;
|
||||
assert_eq!(Value::Number(Number::from_f64(0.1).unwrap()), v);
|
||||
let v: Value = db.one_column("SELECT '\"text\"'", [])?;
|
||||
assert_eq!(Value::String("text".to_owned()), v);
|
||||
let v: Result<Value> = db.one_column("SELECT 'text'", []);
|
||||
assert!(v.is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
429
vendor/rusqlite/src/types/time.rs
vendored
Normal file
429
vendor/rusqlite/src/types/time.rs
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`OffsetDateTime`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`PrimitiveDateTime`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`Date`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`Time`].
|
||||
//! Time Strings in:
|
||||
//! - Format 2: "YYYY-MM-DD HH:MM"
|
||||
//! - Format 5: "YYYY-MM-DDTHH:MM"
|
||||
//! - Format 8: "HH:MM"
|
||||
//!
|
||||
//! without an explicit second value will assume 0 seconds.
|
||||
//! Time String that contain an optional timezone without an explicit date are unsupported.
|
||||
//! All other assumptions described in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) section are unsupported.
|
||||
|
||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef};
|
||||
use crate::{Error, Result};
|
||||
use time::format_description::FormatItem;
|
||||
use time::macros::format_description;
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const PRIMITIVE_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
|
||||
);
|
||||
const TIME_ENCODING: &[FormatItem<'_>] =
|
||||
format_description!(version = 2, "[hour]:[minute]:[second].[subsecond]");
|
||||
|
||||
const DATE_FORMAT: &[FormatItem<'_>] = format_description!(version = 2, "[year]-[month]-[day]");
|
||||
const TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
||||
);
|
||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
||||
);
|
||||
const UTC_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][optional [Z]]"
|
||||
);
|
||||
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
|
||||
/// `OffsetDatetime` => RFC3339 format ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM")
|
||||
impl ToSql for OffsetDateTime {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let time_string = self
|
||||
.format(&OFFSET_DATE_TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(time_string))
|
||||
}
|
||||
}
|
||||
|
||||
// Supports parsing formats 2-7 and 12 (unix timestamp) from https://www.sqlite.org/lang_datefunc.html
|
||||
// Formats 2-7 without a timezone assumes UTC
|
||||
impl FromSql for OffsetDateTime {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
if value.data_type() == Type::Integer {
|
||||
return value
|
||||
.as_i64()
|
||||
.and_then(|i| OffsetDateTime::from_unix_timestamp(i).map_err(FromSqlError::other));
|
||||
}
|
||||
value.as_str().and_then(|s| {
|
||||
if let Some(b' ') = s.as_bytes().get(23) {
|
||||
// legacy
|
||||
return Self::parse(s, &LEGACY_DATE_TIME_FORMAT).map_err(FromSqlError::other);
|
||||
}
|
||||
if s[8..].contains('+') || s[8..].contains('-') {
|
||||
// Formats 2-7 with timezone
|
||||
return Self::parse(s, &OFFSET_DATE_TIME_FORMAT).map_err(FromSqlError::other);
|
||||
}
|
||||
// Formats 2-7 without timezone
|
||||
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
|
||||
.map(|p| p.assume_utc())
|
||||
.map_err(FromSqlError::other)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
||||
impl ToSql for Date {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let date_str = self
|
||||
.format(&DATE_FORMAT)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(date_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
||||
impl FromSql for Date {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
Self::parse(s, &DATE_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
||||
impl ToSql for Time {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let time_str = self
|
||||
.format(&TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(time_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
||||
impl FromSql for Time {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
Self::parse(s, &TIME_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
|
||||
impl ToSql for PrimitiveDateTime {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let date_time_str = self
|
||||
.format(&PRIMITIVE_DATE_TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(date_time_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// YYYY-MM-DD HH:MM
|
||||
/// YYYY-MM-DDTHH:MM
|
||||
/// YYYY-MM-DD HH:MM:SS
|
||||
/// YYYY-MM-DDTHH:MM:SS
|
||||
/// YYYY-MM-DD HH:MM:SS.SSS
|
||||
/// YYYY-MM-DDTHH:MM:SS.SSS
|
||||
/// => ISO 8601 combined date and time with timezone
|
||||
impl FromSql for PrimitiveDateTime {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
Self::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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 time::macros::{date, datetime, time};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
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_offset_date_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let mut ts_vec = vec![];
|
||||
|
||||
let make_datetime = |secs: i128, nanos: i128| {
|
||||
OffsetDateTime::from_unix_timestamp_nanos(1_000_000_000 * secs + nanos).unwrap()
|
||||
};
|
||||
|
||||
ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
|
||||
ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
|
||||
ts_vec.push(make_datetime(1_500_391_124, 1_000_000)); //July 18, 2017
|
||||
ts_vec.push(make_datetime(2_000_000_000, 2_000_000)); //May 18, 2033
|
||||
ts_vec.push(make_datetime(3_000_000_000, 999_999_999)); //January 24, 2065
|
||||
ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
|
||||
|
||||
for ts in ts_vec {
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [ts])?;
|
||||
|
||||
let from: OffsetDateTime = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(from, ts);
|
||||
|
||||
let from: OffsetDateTime = db.one_column("SELECT i FROM foo", [])?;
|
||||
assert_eq!(from, ts.truncate_to_second());
|
||||
|
||||
db.execute("DELETE FROM foo", [])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let tests = vec![
|
||||
// Rfc3339
|
||||
(
|
||||
"2013-10-07T08:23:19.123456789Z",
|
||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.123456789Z",
|
||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
||||
),
|
||||
// Format 2
|
||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07 08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07 08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
||||
// Format 3
|
||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
("2013-10-07 08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
(
|
||||
"2013-10-07 08:23:19+04:00",
|
||||
datetime!(2013-10-07 8:23:19 +4),
|
||||
),
|
||||
// Format 4
|
||||
(
|
||||
"2013-10-07 08:23:19.123",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.123+04:00",
|
||||
datetime!(2013-10-07 8:23:19.123 +4),
|
||||
),
|
||||
// Format 5
|
||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07T08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07T08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
||||
// Format 6
|
||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
("2013-10-07T08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
(
|
||||
"2013-10-07T08:23:19+04:00",
|
||||
datetime!(2013-10-07 8:23:19 +4),
|
||||
),
|
||||
// Format 7
|
||||
(
|
||||
"2013-10-07T08:23:19.123",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07T08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07T08:23:19.123+04:00",
|
||||
datetime!(2013-10-07 8:23:19.123 +4),
|
||||
),
|
||||
// Legacy
|
||||
(
|
||||
"2013-10-07 08:23:12:987 -07:00",
|
||||
datetime!(2013-10-07 8:23:12.987 -7),
|
||||
),
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: OffsetDateTime = db.one_column("SELECT ?1", [s])?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = date!(2016 - 02 - 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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let time = time!(23:56:04.00001);
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!("23:56:04.00001", s);
|
||||
let v: Time = db.one_column("SELECT t FROM foo", [])?;
|
||||
assert_eq!(time, v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let dt = date!(2016 - 02 - 23).with_time(time!(23:56:04));
|
||||
|
||||
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.0", s);
|
||||
let v: PrimitiveDateTime = 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: PrimitiveDateTime = db.one_column("SELECT b FROM foo", [])?;
|
||||
assert_eq!(dt, hms);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Date = db.one_column("SELECT ?1", ["2013-10-07"])?;
|
||||
assert_eq!(result, date!(2013 - 10 - 07));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let tests = vec![
|
||||
("08:23", time!(08:23)),
|
||||
("08:23:19", time!(08:23:19)),
|
||||
("08:23:19.111", time!(08:23:19.111)),
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: Time = db.one_column("SELECT ?1", [s])?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let tests = vec![
|
||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23)),
|
||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19)),
|
||||
("2013-10-07T08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23)),
|
||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19)),
|
||||
("2013-10-07 08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: PrimitiveDateTime = db.one_column("SELECT ?1", [s])?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_functions() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
db.one_column::<Time, _>("SELECT CURRENT_TIME", [])?;
|
||||
db.one_column::<Date, _>("SELECT CURRENT_DATE", [])?;
|
||||
db.one_column::<PrimitiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
||||
db.one_column::<OffsetDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = OffsetDateTime::now_utc().time();
|
||||
let result: Result<bool> = db.one_column(
|
||||
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
|
||||
[now],
|
||||
);
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = OffsetDateTime::now_utc().date();
|
||||
let result: Result<bool> = db.one_column(
|
||||
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
|
||||
[now],
|
||||
);
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = PrimitiveDateTime::new(
|
||||
OffsetDateTime::now_utc().date(),
|
||||
OffsetDateTime::now_utc().time(),
|
||||
);
|
||||
let result: Result<bool> = db.one_column(
|
||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
||||
[now],
|
||||
);
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Result<bool> = db.one_column(
|
||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
||||
[OffsetDateTime::now_utc()],
|
||||
);
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
601
vendor/rusqlite/src/types/to_sql.rs
vendored
Normal file
601
vendor/rusqlite/src/types/to_sql.rs
vendored
Normal file
@@ -0,0 +1,601 @@
|
||||
use super::{Null, Value, ValueRef};
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
use crate::Error;
|
||||
use crate::Result;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// `ToSqlOutput` represents the possible output types for implementers of the
|
||||
/// [`ToSql`] trait.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ToSqlOutput<'a> {
|
||||
/// A borrowed SQLite-representable value.
|
||||
Borrowed(ValueRef<'a>),
|
||||
|
||||
/// An owned SQLite-representable value.
|
||||
Owned(Value),
|
||||
|
||||
/// A BLOB of the given length that is filled with
|
||||
/// zeroes.
|
||||
#[cfg(feature = "blob")]
|
||||
ZeroBlob(i32),
|
||||
|
||||
/// n-th arg of an SQL scalar function
|
||||
#[cfg(feature = "functions")]
|
||||
Arg(usize),
|
||||
|
||||
/// Pointer passing interface
|
||||
#[cfg(feature = "pointer")]
|
||||
Pointer(
|
||||
(
|
||||
*const std::os::raw::c_void,
|
||||
&'static std::ffi::CStr,
|
||||
Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
#[cfg(feature = "pointer")]
|
||||
impl<'a> ToSqlOutput<'a> {
|
||||
/// Pass an `Rc` as a raw pointer to SQLite
|
||||
///
|
||||
/// # Warning
|
||||
/// Leak memory if an error happens before the returned pointer is bound to an SQLite statement.
|
||||
pub fn from_rc<T>(rc: std::rc::Rc<T>, ptr_type: &'static std::ffi::CStr) -> ToSqlOutput<'a> {
|
||||
unsafe extern "C" fn free_rc(p: *mut std::ffi::c_void) {
|
||||
std::rc::Rc::decrement_strong_count(p);
|
||||
}
|
||||
ToSqlOutput::Pointer((
|
||||
std::rc::Rc::into_raw(rc).cast::<std::ffi::c_void>(),
|
||||
ptr_type,
|
||||
Some(free_rc),
|
||||
))
|
||||
}
|
||||
/// Pass a `Box` as a raw pointer to SQLite
|
||||
///
|
||||
/// # Warning
|
||||
/// Leak memory if an error happens before the returned pointer is bound to an SQLite statement.
|
||||
pub fn new_boxed<T>(v: T, ptr_type: &'static std::ffi::CStr) -> ToSqlOutput<'a> {
|
||||
use crate::util::free_boxed_value;
|
||||
|
||||
ToSqlOutput::Pointer((
|
||||
Box::into_raw(Box::new(v)).cast::<std::ffi::c_void>(),
|
||||
ptr_type,
|
||||
Some(free_boxed_value::<T>),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Generically allow any type that can be converted into a ValueRef
|
||||
// to be converted into a ToSqlOutput as well.
|
||||
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
|
||||
where
|
||||
&'a T: Into<ValueRef<'a>>,
|
||||
{
|
||||
#[inline]
|
||||
fn from(t: &'a T) -> Self {
|
||||
ToSqlOutput::Borrowed(t.into())
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot also generically allow any type that can be converted
|
||||
// into a Value to be converted into a ToSqlOutput because of
|
||||
// coherence rules (https://github.com/rust-lang/rust/pull/46192),
|
||||
// so we'll manually implement it for all the types we know can
|
||||
// be converted into Values.
|
||||
macro_rules! from_value(
|
||||
($t:ty) => (
|
||||
impl From<$t> for ToSqlOutput<'_> {
|
||||
#[inline]
|
||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
||||
}
|
||||
);
|
||||
(non_zero $t:ty) => (
|
||||
impl From<$t> for ToSqlOutput<'_> {
|
||||
#[inline]
|
||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.get().into())}
|
||||
}
|
||||
)
|
||||
);
|
||||
from_value!(String);
|
||||
from_value!(Null);
|
||||
from_value!(bool);
|
||||
from_value!(i8);
|
||||
from_value!(i16);
|
||||
from_value!(i32);
|
||||
from_value!(i64);
|
||||
from_value!(isize);
|
||||
from_value!(u8);
|
||||
from_value!(u16);
|
||||
from_value!(u32);
|
||||
from_value!(f32);
|
||||
from_value!(f64);
|
||||
from_value!(Vec<u8>);
|
||||
|
||||
from_value!(non_zero std::num::NonZeroI8);
|
||||
from_value!(non_zero std::num::NonZeroI16);
|
||||
from_value!(non_zero std::num::NonZeroI32);
|
||||
from_value!(non_zero std::num::NonZeroI64);
|
||||
from_value!(non_zero std::num::NonZeroIsize);
|
||||
from_value!(non_zero std::num::NonZeroU8);
|
||||
from_value!(non_zero std::num::NonZeroU16);
|
||||
from_value!(non_zero std::num::NonZeroU32);
|
||||
|
||||
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
|
||||
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
|
||||
// worth adding another case to Value.
|
||||
#[cfg(feature = "i128_blob")]
|
||||
from_value!(i128);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
from_value!(non_zero std::num::NonZeroI128);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
from_value!(uuid::Uuid);
|
||||
|
||||
impl ToSql for ToSqlOutput<'_> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(match *self {
|
||||
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
||||
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
|
||||
|
||||
#[cfg(feature = "blob")]
|
||||
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
|
||||
#[cfg(feature = "functions")]
|
||||
ToSqlOutput::Arg(i) => ToSqlOutput::Arg(i),
|
||||
#[cfg(feature = "pointer")]
|
||||
ToSqlOutput::Pointer(p) => ToSqlOutput::Pointer(p),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that can be converted into SQLite values. Returns
|
||||
/// [`crate::Error::ToSqlConversionFailure`] if the conversion fails.
|
||||
pub trait ToSql {
|
||||
/// Converts Rust value to SQLite value
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
|
||||
}
|
||||
|
||||
impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
self.as_ref().to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToSql + ?Sized> ToSql for Box<T> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
self.as_ref().to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
self.as_ref().to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
self.as_ref().to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
// We should be able to use a generic impl like this:
|
||||
//
|
||||
// impl<T: Copy> ToSql for T where T: Into<Value> {
|
||||
// fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
// Ok(ToSqlOutput::from((*self).into()))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// instead of the following macro, but this runs afoul of
|
||||
// https://github.com/rust-lang/rust/issues/30191 and reports conflicting
|
||||
// implementations even when there aren't any.
|
||||
|
||||
macro_rules! to_sql_self(
|
||||
($t:ty) => (
|
||||
impl ToSql for $t {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(*self))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
to_sql_self!(Null);
|
||||
to_sql_self!(bool);
|
||||
to_sql_self!(i8);
|
||||
to_sql_self!(i16);
|
||||
to_sql_self!(i32);
|
||||
to_sql_self!(i64);
|
||||
to_sql_self!(isize);
|
||||
to_sql_self!(u8);
|
||||
to_sql_self!(u16);
|
||||
to_sql_self!(u32);
|
||||
to_sql_self!(f32);
|
||||
to_sql_self!(f64);
|
||||
|
||||
to_sql_self!(std::num::NonZeroI8);
|
||||
to_sql_self!(std::num::NonZeroI16);
|
||||
to_sql_self!(std::num::NonZeroI32);
|
||||
to_sql_self!(std::num::NonZeroI64);
|
||||
to_sql_self!(std::num::NonZeroIsize);
|
||||
to_sql_self!(std::num::NonZeroU8);
|
||||
to_sql_self!(std::num::NonZeroU16);
|
||||
to_sql_self!(std::num::NonZeroU32);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
to_sql_self!(i128);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
to_sql_self!(std::num::NonZeroI128);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
to_sql_self!(uuid::Uuid);
|
||||
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
macro_rules! to_sql_self_fallible(
|
||||
($t:ty) => (
|
||||
impl ToSql for $t {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(
|
||||
i64::try_from(*self).map_err(
|
||||
// TODO: Include the values in the error message.
|
||||
|err| Error::ToSqlConversionFailure(err.into())
|
||||
)?
|
||||
)))
|
||||
}
|
||||
}
|
||||
);
|
||||
(non_zero $t:ty) => (
|
||||
impl ToSql for $t {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(
|
||||
i64::try_from(self.get()).map_err(
|
||||
// TODO: Include the values in the error message.
|
||||
|err| Error::ToSqlConversionFailure(err.into())
|
||||
)?
|
||||
)))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Special implementations for usize and u64 because these conversions can fail.
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
to_sql_self_fallible!(u64);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
to_sql_self_fallible!(usize);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
to_sql_self_fallible!(non_zero std::num::NonZeroU64);
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
to_sql_self_fallible!(non_zero std::num::NonZeroUsize);
|
||||
|
||||
impl<T: ?Sized> ToSql for &'_ T
|
||||
where
|
||||
T: ToSql,
|
||||
{
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
(*self).to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for String {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for str {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Vec<u8> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self.as_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> ToSql for [u8; N] {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(&self[..]))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for [u8] {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Value {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToSql> ToSql for Option<T> {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
match *self {
|
||||
None => Ok(ToSqlOutput::from(Null)),
|
||||
Some(ref t) => t.to_sql(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::{ToSql, ToSqlOutput};
|
||||
use crate::{types::Value, types::ValueRef, Result};
|
||||
|
||||
fn is_to_sql<T: ToSql>() {}
|
||||
|
||||
#[test]
|
||||
fn to_sql() -> Result<()> {
|
||||
assert_eq!(
|
||||
ToSqlOutput::Borrowed(ValueRef::Null).to_sql()?,
|
||||
ToSqlOutput::Borrowed(ValueRef::Null)
|
||||
);
|
||||
assert_eq!(
|
||||
ToSqlOutput::Owned(Value::Null).to_sql()?,
|
||||
ToSqlOutput::Borrowed(ValueRef::Null)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integral_types() {
|
||||
is_to_sql::<i8>();
|
||||
is_to_sql::<i16>();
|
||||
is_to_sql::<i32>();
|
||||
is_to_sql::<i64>();
|
||||
is_to_sql::<isize>();
|
||||
is_to_sql::<u8>();
|
||||
is_to_sql::<u16>();
|
||||
is_to_sql::<u32>();
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
is_to_sql::<u64>();
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
is_to_sql::<usize>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonzero_types() {
|
||||
is_to_sql::<std::num::NonZeroI8>();
|
||||
is_to_sql::<std::num::NonZeroI16>();
|
||||
is_to_sql::<std::num::NonZeroI32>();
|
||||
is_to_sql::<std::num::NonZeroI64>();
|
||||
is_to_sql::<std::num::NonZeroIsize>();
|
||||
is_to_sql::<std::num::NonZeroU8>();
|
||||
is_to_sql::<std::num::NonZeroU16>();
|
||||
is_to_sql::<std::num::NonZeroU32>();
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
is_to_sql::<std::num::NonZeroU64>();
|
||||
#[cfg(feature = "fallible_uint")]
|
||||
is_to_sql::<std::num::NonZeroUsize>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_u8_array() {
|
||||
let a: [u8; 99] = [0u8; 99];
|
||||
let _a: &[&dyn ToSql] = crate::params![a];
|
||||
let r = ToSql::to_sql(&a);
|
||||
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_str() {
|
||||
use std::borrow::Cow;
|
||||
let s = "str";
|
||||
let cow: Cow<str> = Cow::Borrowed(s);
|
||||
let r = cow.to_sql();
|
||||
r.unwrap();
|
||||
let cow: Cow<str> = Cow::Owned::<str>(String::from(s));
|
||||
let r = cow.to_sql();
|
||||
r.unwrap();
|
||||
// Ensure this compiles.
|
||||
let _p: &[&dyn ToSql] = crate::params![cow];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_dyn() {
|
||||
let s: Box<dyn ToSql> = Box::new("Hello world!");
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = ToSql::to_sql(&s);
|
||||
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_deref() {
|
||||
let s: Box<str> = "Hello world!".into();
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_direct() {
|
||||
let s: Box<str> = "Hello world!".into();
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = ToSql::to_sql(&s);
|
||||
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cells() {
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
let source_str: Box<str> = "Hello world!".into();
|
||||
|
||||
let s: Rc<Box<str>> = Rc::new(source_str.clone());
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
|
||||
let s: Arc<Box<str>> = Arc::new(source_str.clone());
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
|
||||
let s: Arc<str> = Arc::from(&*source_str);
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
|
||||
let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
|
||||
let s: Rc<str> = Rc::from(&*source_str);
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
|
||||
let s: Rc<dyn ToSql> = Rc::new(source_str);
|
||||
let _s: &[&dyn ToSql] = crate::params![s];
|
||||
let r = s.to_sql();
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[test]
|
||||
fn test_i128() -> Result<()> {
|
||||
use crate::Connection;
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
|
||||
db.execute(
|
||||
"
|
||||
INSERT INTO foo(i128, desc) VALUES
|
||||
(?1, 'zero'),
|
||||
(?2, 'neg one'), (?3, 'neg two'),
|
||||
(?4, 'pos one'), (?5, 'pos two'),
|
||||
(?6, 'min'), (?7, 'max')",
|
||||
[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
|
||||
)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
|
||||
|
||||
let res = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
&[
|
||||
(i128::MIN, "min".to_owned()),
|
||||
(-2, "neg two".to_owned()),
|
||||
(-1, "neg one".to_owned()),
|
||||
(0, "zero".to_owned()),
|
||||
(1, "pos one".to_owned()),
|
||||
(2, "pos two".to_owned()),
|
||||
(i128::MAX, "max".to_owned()),
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[test]
|
||||
fn test_non_zero_i128() -> Result<()> {
|
||||
use std::num::NonZeroI128;
|
||||
macro_rules! nz {
|
||||
($x:expr) => {
|
||||
NonZeroI128::new($x).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let db = crate::Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
|
||||
db.execute(
|
||||
"INSERT INTO foo(i128, desc) VALUES
|
||||
(?1, 'neg one'), (?2, 'neg two'),
|
||||
(?3, 'pos one'), (?4, 'pos two'),
|
||||
(?5, 'min'), (?6, 'max')",
|
||||
[
|
||||
nz!(-1),
|
||||
nz!(-2),
|
||||
nz!(1),
|
||||
nz!(2),
|
||||
nz!(i128::MIN),
|
||||
nz!(i128::MAX),
|
||||
],
|
||||
)?;
|
||||
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
|
||||
|
||||
let res = stmt
|
||||
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
|
||||
.collect::<Result<Vec<(NonZeroI128, String)>, _>>()?;
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
&[
|
||||
(nz!(i128::MIN), "min".to_owned()),
|
||||
(nz!(-2), "neg two".to_owned()),
|
||||
(nz!(-1), "neg one".to_owned()),
|
||||
(nz!(1), "pos one".to_owned()),
|
||||
(nz!(2), "pos two".to_owned()),
|
||||
(nz!(i128::MAX), "max".to_owned()),
|
||||
]
|
||||
);
|
||||
let err = db.query_row("SELECT ?1", [0i128], |row| row.get::<_, NonZeroI128>(0));
|
||||
assert_eq!(err, Err(crate::Error::IntegralValueOutOfRange(0, 0)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
#[test]
|
||||
fn test_uuid() -> Result<()> {
|
||||
use crate::{params, Connection};
|
||||
use uuid::Uuid;
|
||||
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")?;
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO foo (id, label) VALUES (?1, ?2)",
|
||||
params![id, "target"],
|
||||
)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?;
|
||||
|
||||
let mut rows = stmt.query(params![id])?;
|
||||
let row = rows.next()?.unwrap();
|
||||
|
||||
let found_id: Uuid = row.get_unwrap(0);
|
||||
let found_label: String = row.get_unwrap(1);
|
||||
|
||||
assert_eq!(found_id, id);
|
||||
assert_eq!(found_label, "target");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
85
vendor/rusqlite/src/types/url.rs
vendored
Normal file
85
vendor/rusqlite/src/types/url.rs
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`Url`].
|
||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use crate::Result;
|
||||
use url::Url;
|
||||
|
||||
/// Serialize `Url` to text.
|
||||
impl ToSql for Url {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize text to `Url`.
|
||||
impl FromSql for Url {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(s) => {
|
||||
let s = std::str::from_utf8(s).map_err(FromSqlError::other)?;
|
||||
Self::parse(s).map_err(FromSqlError::other)
|
||||
}
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::{params, Connection, Error, Result};
|
||||
use url::{ParseError, Url};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
fn get_url(db: &Connection, id: i64) -> Result<Url> {
|
||||
db.one_column("SELECT v FROM urls WHERE i = ?", [id])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_url() -> Result<()> {
|
||||
let db = &checked_memory_handle()?;
|
||||
|
||||
let url0 = Url::parse("http://www.example1.com").unwrap();
|
||||
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
|
||||
let url2 = "http://www.example2.com/👌";
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
|
||||
// also insert a non-hex encoded url (which might be present if it was
|
||||
// inserted separately)
|
||||
params![url0, url1, url2, "illegal"],
|
||||
)?;
|
||||
|
||||
assert_eq!(get_url(db, 0)?, url0);
|
||||
|
||||
assert_eq!(get_url(db, 1)?, url1);
|
||||
|
||||
// Should successfully read it, even though it wasn't inserted as an
|
||||
// escaped url.
|
||||
let out_url2: Url = get_url(db, 2)?;
|
||||
assert_eq!(out_url2, Url::parse(url2).unwrap());
|
||||
|
||||
// Make sure the conversion error comes through correctly.
|
||||
let err = get_url(db, 3).unwrap_err();
|
||||
match err {
|
||||
Error::FromSqlConversionFailure(_, _, e) => {
|
||||
assert_eq!(
|
||||
*e.downcast::<ParseError>().unwrap(),
|
||||
ParseError::RelativeUrlWithoutBase,
|
||||
);
|
||||
}
|
||||
e => {
|
||||
panic!("Expected conversion failure, got {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
171
vendor/rusqlite/src/types/value.rs
vendored
Normal file
171
vendor/rusqlite/src/types/value.rs
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
use super::{Null, Type};
|
||||
|
||||
/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
|
||||
/// dictated by SQLite (not by the caller).
|
||||
///
|
||||
/// See [`ValueRef`](crate::types::ValueRef) for a non-owning dynamic type
|
||||
/// value.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
/// The value is a `NULL` value.
|
||||
Null,
|
||||
/// The value is a signed integer.
|
||||
Integer(i64),
|
||||
/// The value is a floating point number.
|
||||
Real(f64),
|
||||
/// The value is a text string.
|
||||
Text(String),
|
||||
/// The value is a blob of data
|
||||
Blob(Vec<u8>),
|
||||
}
|
||||
|
||||
impl From<Null> for Value {
|
||||
#[inline]
|
||||
fn from(_: Null) -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
#[inline]
|
||||
fn from(i: bool) -> Self {
|
||||
Self::Integer(i as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for Value {
|
||||
#[inline]
|
||||
fn from(i: isize) -> Self {
|
||||
Self::Integer(i as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
impl From<i128> for Value {
|
||||
#[inline]
|
||||
fn from(i: i128) -> Self {
|
||||
// We store these biased (e.g. with the most significant bit flipped)
|
||||
// so that comparisons with negative numbers work properly.
|
||||
Self::Blob(i128::to_be_bytes(i ^ (1_i128 << 127)).to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
impl From<uuid::Uuid> for Value {
|
||||
#[inline]
|
||||
fn from(id: uuid::Uuid) -> Self {
|
||||
Self::Blob(id.as_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_i64(
|
||||
($t:ty) => (
|
||||
impl From<$t> for Value {
|
||||
#[inline]
|
||||
fn from(i: $t) -> Value {
|
||||
Value::Integer(i64::from(i))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
from_i64!(i8);
|
||||
from_i64!(i16);
|
||||
from_i64!(i32);
|
||||
from_i64!(u8);
|
||||
from_i64!(u16);
|
||||
from_i64!(u32);
|
||||
|
||||
impl From<i64> for Value {
|
||||
#[inline]
|
||||
fn from(i: i64) -> Self {
|
||||
Self::Integer(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Value {
|
||||
#[inline]
|
||||
fn from(f: f32) -> Self {
|
||||
Self::Real(f.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
#[inline]
|
||||
fn from(f: f64) -> Self {
|
||||
Self::Real(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
#[inline]
|
||||
fn from(s: String) -> Self {
|
||||
Self::Text(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Value {
|
||||
#[inline]
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
Self::Blob(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for Value
|
||||
where
|
||||
T: Into<Self>,
|
||||
{
|
||||
#[inline]
|
||||
fn from(v: Option<T>) -> Self {
|
||||
match v {
|
||||
Some(x) => x.into(),
|
||||
None => Self::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Returns SQLite fundamental datatype.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn data_type(&self) -> Type {
|
||||
match *self {
|
||||
Self::Null => Type::Null,
|
||||
Self::Integer(_) => Type::Integer,
|
||||
Self::Real(_) => Type::Real,
|
||||
Self::Text(_) => Type::Text,
|
||||
Self::Blob(_) => Type::Blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::Value;
|
||||
use crate::types::Type;
|
||||
|
||||
#[test]
|
||||
fn from() {
|
||||
assert_eq!(Value::from(2f32), Value::Real(2f64));
|
||||
assert_eq!(Value::from(3.), Value::Real(3.));
|
||||
assert_eq!(Value::from(vec![0u8]), Value::Blob(vec![0u8]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_type() {
|
||||
assert_eq!(Value::Null.data_type(), Type::Null);
|
||||
assert_eq!(Value::Integer(0).data_type(), Type::Integer);
|
||||
assert_eq!(Value::Real(0.).data_type(), Type::Real);
|
||||
assert_eq!(Value::Text(String::new()).data_type(), Type::Text);
|
||||
assert_eq!(Value::Blob(vec![]).data_type(), Type::Blob);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_option() {
|
||||
assert_eq!(Value::from(None as Option<i64>), Value::Null);
|
||||
assert_eq!(Value::from(Some(0)), Value::Integer(0));
|
||||
}
|
||||
}
|
||||
350
vendor/rusqlite/src/types/value_ref.rs
vendored
Normal file
350
vendor/rusqlite/src/types/value_ref.rs
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
use super::{Type, Value};
|
||||
use crate::types::{FromSqlError, FromSqlResult};
|
||||
|
||||
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically, the
|
||||
/// memory backing this value is owned by SQLite.
|
||||
///
|
||||
/// See [`Value`](Value) for an owning dynamic type value.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ValueRef<'a> {
|
||||
/// The value is a `NULL` value.
|
||||
Null,
|
||||
/// The value is a signed integer.
|
||||
Integer(i64),
|
||||
/// The value is a floating point number.
|
||||
Real(f64),
|
||||
/// The value is a text string.
|
||||
Text(&'a [u8]),
|
||||
/// The value is a blob of data
|
||||
Blob(&'a [u8]),
|
||||
}
|
||||
|
||||
impl ValueRef<'_> {
|
||||
/// Returns SQLite fundamental datatype.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn data_type(&self) -> Type {
|
||||
match *self {
|
||||
ValueRef::Null => Type::Null,
|
||||
ValueRef::Integer(_) => Type::Integer,
|
||||
ValueRef::Real(_) => Type::Real,
|
||||
ValueRef::Text(_) => Type::Text,
|
||||
ValueRef::Blob(_) => Type::Blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValueRef<'a> {
|
||||
/// If `self` is case `Integer`, returns the integral value. Otherwise,
|
||||
/// returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
||||
match *self {
|
||||
ValueRef::Integer(i) => Ok(i),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Integer`, returns the integral value.
|
||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
|
||||
match *self {
|
||||
ValueRef::Null => Ok(None),
|
||||
ValueRef::Integer(i) => Ok(Some(i)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Real`, returns the floating point value. Otherwise,
|
||||
/// returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
||||
match *self {
|
||||
ValueRef::Real(f) => Ok(f),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Real`, returns the floating point value.
|
||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
|
||||
match *self {
|
||||
ValueRef::Null => Ok(None),
|
||||
ValueRef::Real(f) => Ok(Some(f)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Text`, returns the string value. Otherwise, returns
|
||||
/// [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
||||
match *self {
|
||||
ValueRef::Text(t) => std::str::from_utf8(t).map_err(FromSqlError::Utf8Error),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Text`, returns the string value.
|
||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
|
||||
match *self {
|
||||
ValueRef::Null => Ok(None),
|
||||
ValueRef::Text(t) => std::str::from_utf8(t)
|
||||
.map_err(FromSqlError::Utf8Error)
|
||||
.map(Some),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
|
||||
/// [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
|
||||
match *self {
|
||||
ValueRef::Blob(b) => Ok(b),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Blob`, returns the byte slice.
|
||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
||||
#[inline]
|
||||
pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
||||
match *self {
|
||||
ValueRef::Null => Ok(None),
|
||||
ValueRef::Blob(b) => Ok(Some(b)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte slice that makes up this `ValueRef` if it's either
|
||||
/// [`ValueRef::Blob`] or [`ValueRef::Text`].
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
|
||||
match self {
|
||||
ValueRef::Text(s) | ValueRef::Blob(s) => Ok(s),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is [`ValueRef::Blob`] or [`ValueRef::Text`] returns the byte
|
||||
/// slice that makes up this value
|
||||
#[inline]
|
||||
pub fn as_bytes_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
||||
match *self {
|
||||
ValueRef::Null => Ok(None),
|
||||
ValueRef::Text(s) | ValueRef::Blob(s) => Ok(Some(s)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ValueRef<'_>> for Value {
|
||||
type Error = FromSqlError;
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn try_from(borrowed: ValueRef<'_>) -> Result<Self, Self::Error> {
|
||||
match borrowed {
|
||||
ValueRef::Null => Ok(Self::Null),
|
||||
ValueRef::Integer(i) => Ok(Self::Integer(i)),
|
||||
ValueRef::Real(r) => Ok(Self::Real(r)),
|
||||
ValueRef::Text(s) => std::str::from_utf8(s)
|
||||
.map(|s| Self::Text(s.to_string()))
|
||||
.map_err(FromSqlError::Utf8Error),
|
||||
ValueRef::Blob(b) => Ok(Self::Blob(b.to_vec())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||
#[inline]
|
||||
fn from(s: &str) -> ValueRef<'_> {
|
||||
ValueRef::Text(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for ValueRef<'a> {
|
||||
#[inline]
|
||||
fn from(s: &[u8]) -> ValueRef<'_> {
|
||||
ValueRef::Blob(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Value> for ValueRef<'a> {
|
||||
#[inline]
|
||||
fn from(value: &'a Value) -> Self {
|
||||
match *value {
|
||||
Value::Null => ValueRef::Null,
|
||||
Value::Integer(i) => ValueRef::Integer(i),
|
||||
Value::Real(r) => ValueRef::Real(r),
|
||||
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
|
||||
Value::Blob(ref b) => ValueRef::Blob(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for ValueRef<'_>
|
||||
where
|
||||
T: Into<Self>,
|
||||
{
|
||||
#[inline]
|
||||
fn from(s: Option<T>) -> Self {
|
||||
match s {
|
||||
Some(x) => x.into(),
|
||||
None => ValueRef::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "functions",
|
||||
feature = "session",
|
||||
feature = "vtab",
|
||||
feature = "preupdate_hook"
|
||||
))]
|
||||
impl ValueRef<'_> {
|
||||
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> Self {
|
||||
use crate::ffi;
|
||||
use std::slice::from_raw_parts;
|
||||
|
||||
match ffi::sqlite3_value_type(value) {
|
||||
ffi::SQLITE_NULL => ValueRef::Null,
|
||||
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
|
||||
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
|
||||
ffi::SQLITE_TEXT => {
|
||||
let text = ffi::sqlite3_value_text(value);
|
||||
let len = ffi::sqlite3_value_bytes(value);
|
||||
assert!(
|
||||
!text.is_null(),
|
||||
"unexpected SQLITE_TEXT value type with NULL data"
|
||||
);
|
||||
let s = from_raw_parts(text.cast::<u8>(), len as usize);
|
||||
ValueRef::Text(s)
|
||||
}
|
||||
ffi::SQLITE_BLOB => {
|
||||
let (blob, len) = (
|
||||
ffi::sqlite3_value_blob(value),
|
||||
ffi::sqlite3_value_bytes(value),
|
||||
);
|
||||
|
||||
assert!(
|
||||
len >= 0,
|
||||
"unexpected negative return from sqlite3_value_bytes"
|
||||
);
|
||||
if len > 0 {
|
||||
assert!(
|
||||
!blob.is_null(),
|
||||
"unexpected SQLITE_BLOB value type with NULL data"
|
||||
);
|
||||
ValueRef::Blob(from_raw_parts(blob.cast::<u8>(), len as usize))
|
||||
} else {
|
||||
// The return value from sqlite3_value_blob() for a zero-length BLOB
|
||||
// is a NULL pointer.
|
||||
ValueRef::Blob(&[])
|
||||
}
|
||||
}
|
||||
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO sqlite3_value_frombind // 3.28.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::ValueRef;
|
||||
use crate::types::FromSqlResult;
|
||||
|
||||
#[test]
|
||||
fn as_i64() -> FromSqlResult<()> {
|
||||
assert!(ValueRef::Real(1.0).as_i64().is_err());
|
||||
assert_eq!(ValueRef::Integer(1).as_i64(), Ok(1));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_i64_or_null() -> FromSqlResult<()> {
|
||||
assert_eq!(ValueRef::Null.as_i64_or_null(), Ok(None));
|
||||
assert!(ValueRef::Real(1.0).as_i64_or_null().is_err());
|
||||
assert_eq!(ValueRef::Integer(1).as_i64_or_null(), Ok(Some(1)));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_f64() -> FromSqlResult<()> {
|
||||
assert!(ValueRef::Integer(1).as_f64().is_err());
|
||||
assert_eq!(ValueRef::Real(1.0).as_f64(), Ok(1.0));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_f64_or_null() -> FromSqlResult<()> {
|
||||
assert_eq!(ValueRef::Null.as_f64_or_null(), Ok(None));
|
||||
assert!(ValueRef::Integer(1).as_f64_or_null().is_err());
|
||||
assert_eq!(ValueRef::Real(1.0).as_f64_or_null(), Ok(Some(1.0)));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_str() -> FromSqlResult<()> {
|
||||
assert!(ValueRef::Null.as_str().is_err());
|
||||
assert_eq!(ValueRef::Text(b"").as_str(), Ok(""));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_str_or_null() -> FromSqlResult<()> {
|
||||
assert_eq!(ValueRef::Null.as_str_or_null(), Ok(None));
|
||||
assert!(ValueRef::Integer(1).as_str_or_null().is_err());
|
||||
assert_eq!(ValueRef::Text(b"").as_str_or_null(), Ok(Some("")));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_blob() -> FromSqlResult<()> {
|
||||
assert!(ValueRef::Null.as_blob().is_err());
|
||||
assert_eq!(ValueRef::Blob(b"").as_blob(), Ok(&b""[..]));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_blob_or_null() -> FromSqlResult<()> {
|
||||
assert_eq!(ValueRef::Null.as_blob_or_null(), Ok(None));
|
||||
assert!(ValueRef::Integer(1).as_blob_or_null().is_err());
|
||||
assert_eq!(ValueRef::Blob(b"").as_blob_or_null(), Ok(Some(&b""[..])));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_bytes() -> FromSqlResult<()> {
|
||||
assert!(ValueRef::Null.as_bytes().is_err());
|
||||
assert_eq!(ValueRef::Blob(b"").as_bytes(), Ok(&b""[..]));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn as_bytes_or_null() -> FromSqlResult<()> {
|
||||
assert_eq!(ValueRef::Null.as_bytes_or_null(), Ok(None));
|
||||
assert!(ValueRef::Integer(1).as_bytes_or_null().is_err());
|
||||
assert_eq!(ValueRef::Blob(b"").as_bytes_or_null(), Ok(Some(&b""[..])));
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn from_value() {
|
||||
use crate::types::Value;
|
||||
assert_eq!(
|
||||
ValueRef::from(&Value::Text("".to_owned())),
|
||||
ValueRef::Text(b"")
|
||||
);
|
||||
assert_eq!(ValueRef::from(&Value::Blob(vec![])), ValueRef::Blob(b""));
|
||||
}
|
||||
#[test]
|
||||
fn from_option() {
|
||||
assert_eq!(ValueRef::from(None as Option<&str>), ValueRef::Null);
|
||||
assert_eq!(ValueRef::from(Some("")), ValueRef::Text(b""));
|
||||
}
|
||||
}
|
||||
121
vendor/rusqlite/src/unlock_notify.rs
vendored
Normal file
121
vendor/rusqlite/src/unlock_notify.rs
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
|
||||
|
||||
use std::ffi::{c_int, c_void};
|
||||
use std::panic::catch_unwind;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
use crate::ffi;
|
||||
|
||||
struct UnlockNotification {
|
||||
cond: Condvar, // Condition variable to wait on
|
||||
mutex: Mutex<bool>, // Mutex to protect structure
|
||||
}
|
||||
|
||||
impl UnlockNotification {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
cond: Condvar::new(),
|
||||
mutex: Mutex::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn fired(&self) {
|
||||
let mut flag = unpoison(self.mutex.lock());
|
||||
*flag = true;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
|
||||
fn wait(&self) {
|
||||
let mut fired = unpoison(self.mutex.lock());
|
||||
while !*fired {
|
||||
fired = unpoison(self.cond.wait(fired));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unpoison<T>(r: Result<T, std::sync::PoisonError<T>>) -> T {
|
||||
r.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
|
||||
/// This function is an unlock-notify callback
|
||||
unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
|
||||
use std::slice::from_raw_parts;
|
||||
let args = from_raw_parts(ap_arg as *const &UnlockNotification, n_arg as usize);
|
||||
for un in args {
|
||||
drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired())));
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
|
||||
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|
||||
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
|
||||
&& ffi::sqlite3_extended_errcode(db) == ffi::SQLITE_LOCKED_SHAREDCACHE
|
||||
}
|
||||
|
||||
/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`
|
||||
/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the
|
||||
/// associated database connection.
|
||||
///
|
||||
/// This function calls `sqlite3_unlock_notify()` to register for an
|
||||
/// unlock-notify callback, then blocks until that callback is delivered
|
||||
/// and returns `SQLITE_OK`. The caller should then retry the failed operation.
|
||||
///
|
||||
/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock
|
||||
/// the system, then this function returns `SQLITE_LOCKED` immediately. In
|
||||
/// this case the caller should not retry the operation and should roll
|
||||
/// back the current transaction (if any).
|
||||
#[cfg(feature = "unlock_notify")]
|
||||
pub unsafe fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int {
|
||||
let un = UnlockNotification::new();
|
||||
/* Register for an unlock-notify callback. */
|
||||
let rc = ffi::sqlite3_unlock_notify(
|
||||
db,
|
||||
Some(unlock_notify_cb),
|
||||
&un as *const UnlockNotification as *mut c_void,
|
||||
);
|
||||
debug_assert!(
|
||||
rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK
|
||||
);
|
||||
if rc == ffi::SQLITE_OK {
|
||||
un.wait();
|
||||
}
|
||||
rc
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior};
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no thread on this platform"
|
||||
)]
|
||||
#[test]
|
||||
fn test_unlock_notify() -> Result<()> {
|
||||
let url = "file::memory:?cache=shared";
|
||||
let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI;
|
||||
let db1 = Connection::open_with_flags(url, flags)?;
|
||||
db1.execute_batch("CREATE TABLE foo (x)")?;
|
||||
let (rx, tx) = sync_channel(0);
|
||||
let child = thread::spawn(move || {
|
||||
let mut db2 = Connection::open_with_flags(url, flags).unwrap();
|
||||
let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap();
|
||||
tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap();
|
||||
rx.send(1).unwrap();
|
||||
let ten_millis = time::Duration::from_millis(10);
|
||||
thread::sleep(ten_millis);
|
||||
tx2.commit().unwrap();
|
||||
});
|
||||
assert_eq!(tx.recv().unwrap(), 1);
|
||||
assert_eq!(42, db1.one_column::<i64, _>("SELECT x FROM foo", [])?);
|
||||
child.join().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
55
vendor/rusqlite/src/util/mod.rs
vendored
Normal file
55
vendor/rusqlite/src/util/mod.rs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Internal utilities
|
||||
pub(crate) mod param_cache;
|
||||
mod small_cstr;
|
||||
pub(crate) use param_cache::ParamIndexCache;
|
||||
pub(crate) use small_cstr::SmallCString;
|
||||
|
||||
// Doesn't use any modern features or vtab stuff, but is only used by them.
|
||||
mod sqlite_string;
|
||||
pub(crate) use sqlite_string::{alloc, SqliteMallocString};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "collation",
|
||||
feature = "functions",
|
||||
feature = "vtab",
|
||||
feature = "pointer"
|
||||
))]
|
||||
pub(crate) unsafe extern "C" fn free_boxed_value<T>(p: *mut std::ffi::c_void) {
|
||||
drop(Box::from_raw(p.cast::<T>()));
|
||||
}
|
||||
|
||||
use crate::Result;
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub enum Named<'a> {
|
||||
Small(SmallCString),
|
||||
C(&'a CStr),
|
||||
}
|
||||
impl std::ops::Deref for Named<'_> {
|
||||
type Target = CStr;
|
||||
#[inline]
|
||||
fn deref(&self) -> &CStr {
|
||||
match self {
|
||||
Named::Small(s) => s.as_cstr(),
|
||||
Named::C(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database, table, column, collation, function, module, vfs name
|
||||
pub trait Name: std::fmt::Debug {
|
||||
/// As C string
|
||||
fn as_cstr(&self) -> Result<Named<'_>>;
|
||||
}
|
||||
impl Name for &str {
|
||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
||||
let ss = SmallCString::new(self)?;
|
||||
Ok(Named::Small(ss))
|
||||
}
|
||||
}
|
||||
impl Name for &CStr {
|
||||
#[inline]
|
||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
||||
Ok(Named::C(self))
|
||||
}
|
||||
}
|
||||
63
vendor/rusqlite/src/util/param_cache.rs
vendored
Normal file
63
vendor/rusqlite/src/util/param_cache.rs
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
use super::SmallCString;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Maps parameter names to parameter indices.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
// BTreeMap seems to do better here unless we want to pull in a custom hash
|
||||
// function.
|
||||
pub(crate) struct ParamIndexCache(RefCell<BTreeMap<SmallCString, usize>>);
|
||||
|
||||
impl ParamIndexCache {
|
||||
pub fn get_or_insert_with<F>(&self, s: &str, func: F) -> Option<usize>
|
||||
where
|
||||
F: FnOnce(&std::ffi::CStr) -> Option<usize>,
|
||||
{
|
||||
let mut cache = self.0.borrow_mut();
|
||||
// Avoid entry API, needs allocation to test membership.
|
||||
if let Some(v) = cache.get(s) {
|
||||
return Some(*v);
|
||||
}
|
||||
// If there's an internal nul in the name it couldn't have been a
|
||||
// parameter, so early return here is ok.
|
||||
let name = SmallCString::new(s).ok()?;
|
||||
let val = func(&name)?;
|
||||
cache.insert(name, val);
|
||||
Some(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_cache() {
|
||||
let p = ParamIndexCache::default();
|
||||
let v = p.get_or_insert_with("foo", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "foo");
|
||||
Some(3)
|
||||
});
|
||||
assert_eq!(v, Some(3));
|
||||
let v = p.get_or_insert_with("foo", |_| {
|
||||
panic!("shouldn't be called this time");
|
||||
});
|
||||
assert_eq!(v, Some(3));
|
||||
let v = p.get_or_insert_with("gar\0bage", |_| {
|
||||
panic!("shouldn't be called here either");
|
||||
});
|
||||
assert_eq!(v, None);
|
||||
let v = p.get_or_insert_with("bar", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
||||
None
|
||||
});
|
||||
assert_eq!(v, None);
|
||||
let v = p.get_or_insert_with("bar", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
||||
Some(30)
|
||||
});
|
||||
assert_eq!(v, Some(30));
|
||||
}
|
||||
}
|
||||
159
vendor/rusqlite/src/util/small_cstr.rs
vendored
Normal file
159
vendor/rusqlite/src/util/small_cstr.rs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
|
||||
/// Similar to `std::ffi::CString`, but avoids heap allocating if the string is
|
||||
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
|
||||
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SmallCString(SmallVec<[u8; 16]>);
|
||||
|
||||
impl SmallCString {
|
||||
#[inline]
|
||||
pub fn new(s: &str) -> Result<Self, NulError> {
|
||||
if s.as_bytes().contains(&0_u8) {
|
||||
return Err(Self::fabricate_nul_error(s));
|
||||
}
|
||||
let mut buf = SmallVec::with_capacity(s.len() + 1);
|
||||
buf.extend_from_slice(s.as_bytes());
|
||||
buf.push(0);
|
||||
let res = Self(buf);
|
||||
res.debug_checks();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.debug_checks();
|
||||
// Constructor takes a &str so this is safe.
|
||||
unsafe { std::str::from_utf8_unchecked(self.as_bytes_without_nul()) }
|
||||
}
|
||||
|
||||
/// Get the bytes not including the NUL terminator. E.g. the bytes which
|
||||
/// make up our `str`:
|
||||
/// - `SmallCString::new("foo").as_bytes_without_nul() == b"foo"`
|
||||
/// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"`
|
||||
#[inline]
|
||||
pub fn as_bytes_without_nul(&self) -> &[u8] {
|
||||
self.debug_checks();
|
||||
&self.0[..self.len()]
|
||||
}
|
||||
|
||||
/// Get the bytes behind this str *including* the NUL terminator. This
|
||||
/// should never return an empty slice.
|
||||
#[inline]
|
||||
pub fn as_bytes_with_nul(&self) -> &[u8] {
|
||||
self.debug_checks();
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(debug_assertions)]
|
||||
fn debug_checks(&self) {
|
||||
debug_assert_ne!(self.0.len(), 0);
|
||||
debug_assert_eq!(self.0[self.0.len() - 1], 0);
|
||||
let strbytes = &self.0[..(self.0.len() - 1)];
|
||||
debug_assert!(!strbytes.contains(&0));
|
||||
debug_assert!(std::str::from_utf8(strbytes).is_ok());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn debug_checks(&self) {}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_ne!(self.0.len(), 0);
|
||||
self.0.len() - 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(unused)] // clippy wants this function.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cstr(&self) -> &CStr {
|
||||
let bytes = self.as_bytes_with_nul();
|
||||
debug_assert!(CStr::from_bytes_with_nul(bytes).is_ok());
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn fabricate_nul_error(b: &str) -> NulError {
|
||||
CString::new(b).unwrap_err()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SmallCString {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self(smallvec![0])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SmallCString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SmallCString").field(&self.as_str()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for SmallCString {
|
||||
type Target = CStr;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &CStr {
|
||||
self.as_cstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::borrow::Borrow<str> for SmallCString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_small_cstring() {
|
||||
// We don't go through the normal machinery for default, so make sure
|
||||
// things work.
|
||||
assert_eq!(SmallCString::default().0, SmallCString::new("").unwrap().0);
|
||||
assert_eq!(SmallCString::new("foo").unwrap().len(), 3);
|
||||
assert_eq!(
|
||||
SmallCString::new("foo").unwrap().as_bytes_with_nul(),
|
||||
b"foo\0"
|
||||
);
|
||||
assert_eq!(
|
||||
SmallCString::new("foo").unwrap().as_bytes_without_nul(),
|
||||
b"foo",
|
||||
);
|
||||
|
||||
assert_eq!(SmallCString::new("😀").unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
SmallCString::new("😀").unwrap().0.as_slice(),
|
||||
b"\xf0\x9f\x98\x80\0",
|
||||
);
|
||||
assert_eq!(
|
||||
SmallCString::new("😀").unwrap().as_bytes_without_nul(),
|
||||
b"\xf0\x9f\x98\x80",
|
||||
);
|
||||
|
||||
assert_eq!(SmallCString::new("").unwrap().len(), 0);
|
||||
assert!(SmallCString::new("").unwrap().is_empty());
|
||||
|
||||
assert_eq!(SmallCString::new("").unwrap().0.as_slice(), b"\0");
|
||||
assert_eq!(SmallCString::new("").unwrap().as_bytes_without_nul(), b"");
|
||||
|
||||
SmallCString::new("\0").unwrap_err();
|
||||
SmallCString::new("\0abc").unwrap_err();
|
||||
SmallCString::new("abc\0").unwrap_err();
|
||||
}
|
||||
}
|
||||
230
vendor/rusqlite/src/util/sqlite_string.rs
vendored
Normal file
230
vendor/rusqlite/src/util/sqlite_string.rs
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// This is used when either vtab or modern-sqlite is on. Different methods are
|
||||
// used in each feature. Avoid having to track this for each function. We will
|
||||
// still warn for anything that's not used by either, though.
|
||||
#![cfg_attr(not(feature = "vtab"), allow(dead_code))]
|
||||
use crate::ffi;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// Space to hold this string must be obtained
|
||||
// from an SQLite memory allocation function
|
||||
pub(crate) fn alloc(s: &str) -> *mut c_char {
|
||||
SqliteMallocString::from_str(s).into_raw()
|
||||
}
|
||||
|
||||
/// A string we own that's allocated on the SQLite heap. Automatically calls
|
||||
/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
|
||||
/// on it. If constructed from a rust string, `sqlite3_malloc64` is used.
|
||||
///
|
||||
/// It has identical representation to a nonnull `*mut c_char`, so you can use
|
||||
/// it transparently as one. It's nonnull, so Option<SqliteMallocString> can be
|
||||
/// used for nullable ones (it's still just one pointer).
|
||||
///
|
||||
/// Most strings shouldn't use this! Only places where the string needs to be
|
||||
/// freed with `sqlite3_free`. This includes `sqlite3_extended_sql` results,
|
||||
/// some error message pointers... Note that misuse is extremely dangerous!
|
||||
///
|
||||
/// Note that this is *not* a lossless interface. Incoming strings with internal
|
||||
/// NULs are modified, and outgoing strings which are non-UTF8 are modified.
|
||||
/// This seems unavoidable -- it tries very hard to not panic.
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct SqliteMallocString {
|
||||
ptr: NonNull<c_char>,
|
||||
_boo: PhantomData<Box<[c_char]>>,
|
||||
}
|
||||
// This is owned data for a primitive type, and thus it's safe to implement
|
||||
// these. That said, nothing needs them, and they make things easier to misuse.
|
||||
|
||||
// unsafe impl Send for SqliteMallocString {}
|
||||
// unsafe impl Sync for SqliteMallocString {}
|
||||
|
||||
impl SqliteMallocString {
|
||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
||||
#[inline]
|
||||
pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
|
||||
Self {
|
||||
ptr,
|
||||
_boo: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
||||
#[inline]
|
||||
pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
|
||||
NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
|
||||
}
|
||||
|
||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
||||
/// it.
|
||||
#[inline]
|
||||
pub(crate) fn into_inner(self) -> NonNull<c_char> {
|
||||
let p = self.ptr;
|
||||
std::mem::forget(self);
|
||||
p
|
||||
}
|
||||
|
||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
||||
/// it.
|
||||
#[inline]
|
||||
pub(crate) fn into_raw(self) -> *mut c_char {
|
||||
self.into_inner().as_ptr()
|
||||
}
|
||||
|
||||
/// Borrow the pointer behind `self`. We still manage it when this function
|
||||
/// returns. If you want to relinquish ownership, use `into_raw`.
|
||||
#[inline]
|
||||
pub(crate) fn as_ptr(&self) -> *const c_char {
|
||||
self.ptr.as_ptr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_cstr(&self) -> &CStr {
|
||||
unsafe { CStr::from_ptr(self.as_ptr()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
|
||||
self.as_cstr().to_string_lossy()
|
||||
}
|
||||
|
||||
/// Convert `s` into a SQLite string.
|
||||
///
|
||||
/// This should almost never be done except for cases like error messages or
|
||||
/// other strings that SQLite frees.
|
||||
///
|
||||
/// If `s` contains internal NULs, we'll replace them with
|
||||
/// `NUL_REPLACE_CHAR`.
|
||||
///
|
||||
/// Except for `debug_assert`s which may trigger during testing, this
|
||||
/// function never panics. If we hit integer overflow or the allocation
|
||||
/// fails, we call `handle_alloc_error` which aborts the program after
|
||||
/// calling a global hook.
|
||||
///
|
||||
/// This means it's safe to use in extern "C" functions even outside
|
||||
/// `catch_unwind`.
|
||||
pub(crate) fn from_str(s: &str) -> Self {
|
||||
let s = if s.as_bytes().contains(&0) {
|
||||
std::borrow::Cow::Owned(make_nonnull(s))
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(s)
|
||||
};
|
||||
debug_assert!(!s.as_bytes().contains(&0));
|
||||
let bytes: &[u8] = s.as_ref().as_bytes();
|
||||
let src_ptr: *const c_char = bytes.as_ptr().cast();
|
||||
let src_len = bytes.len();
|
||||
let maybe_len_plus_1 = s.len().checked_add(1).and_then(|v| v.try_into().ok());
|
||||
unsafe {
|
||||
let res_ptr = maybe_len_plus_1
|
||||
.and_then(|len_to_alloc| {
|
||||
// `>` because we added 1.
|
||||
debug_assert!(len_to_alloc > 0);
|
||||
debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
|
||||
NonNull::new(ffi::sqlite3_malloc64(len_to_alloc).cast::<c_char>())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
use std::alloc::{handle_alloc_error, Layout};
|
||||
// Report via handle_alloc_error so that it can be handled with any
|
||||
// other allocation errors and properly diagnosed.
|
||||
//
|
||||
// This is safe:
|
||||
// - `align` is never 0
|
||||
// - `align` is always a power of 2.
|
||||
// - `size` needs no realignment because it's guaranteed to be aligned
|
||||
// (everything is aligned to 1)
|
||||
// - `size` is also never zero, although this function doesn't actually require
|
||||
// it now.
|
||||
let len = s.len().saturating_add(1).min(isize::MAX as usize);
|
||||
let layout = Layout::from_size_align_unchecked(len, 1);
|
||||
// Note: This call does not return.
|
||||
handle_alloc_error(layout);
|
||||
});
|
||||
let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
|
||||
src_ptr.copy_to_nonoverlapping(buf, src_len);
|
||||
buf.add(src_len).write(0);
|
||||
debug_assert_eq!(CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
|
||||
Self::from_raw_nonnull(res_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NUL_REPLACE: &str = "␀";
|
||||
|
||||
#[cold]
|
||||
fn make_nonnull(v: &str) -> String {
|
||||
v.replace('\0', NUL_REPLACE)
|
||||
}
|
||||
|
||||
impl Drop for SqliteMallocString {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
let to_check = [
|
||||
("", ""),
|
||||
("\0", "␀"),
|
||||
("␀", "␀"),
|
||||
("\0bar", "␀bar"),
|
||||
("foo\0bar", "foo␀bar"),
|
||||
("foo\0", "foo␀"),
|
||||
("a\0b\0c\0\0d", "a␀b␀c␀␀d"),
|
||||
("foobar0123", "foobar0123"),
|
||||
];
|
||||
|
||||
for &(input, output) in &to_check {
|
||||
let s = SqliteMallocString::from_str(input);
|
||||
assert_eq!(s.to_string_lossy(), output);
|
||||
assert_eq!(s.as_cstr().to_str().unwrap(), output);
|
||||
}
|
||||
}
|
||||
|
||||
// This will trigger an asan error if into_raw still freed the ptr.
|
||||
#[test]
|
||||
fn test_lossy() {
|
||||
let p = SqliteMallocString::from_str("abcd").into_raw();
|
||||
// Make invalid
|
||||
let s = unsafe {
|
||||
p.cast::<u8>().write(b'\xff');
|
||||
SqliteMallocString::from_raw(p).unwrap()
|
||||
};
|
||||
assert_eq!(s.to_string_lossy().as_ref(), "\u{FFFD}bcd");
|
||||
}
|
||||
|
||||
// This will trigger an asan error if into_raw still freed the ptr.
|
||||
#[test]
|
||||
fn test_into_raw() {
|
||||
let mut v = vec![];
|
||||
for i in 0..1000 {
|
||||
v.push(SqliteMallocString::from_str(&i.to_string()).into_raw());
|
||||
v.push(SqliteMallocString::from_str(&format!("abc {i} 😀")).into_raw());
|
||||
}
|
||||
unsafe {
|
||||
for (i, s) in v.chunks_mut(2).enumerate() {
|
||||
let s0 = std::mem::replace(&mut s[0], std::ptr::null_mut());
|
||||
let s1 = std::mem::replace(&mut s[1], std::ptr::null_mut());
|
||||
assert_eq!(CStr::from_ptr(s0).to_str().unwrap(), &i.to_string());
|
||||
assert_eq!(CStr::from_ptr(s1).to_str().unwrap(), &format!("abc {i} 😀"));
|
||||
let _ = SqliteMallocString::from_raw(s0).unwrap();
|
||||
let _ = SqliteMallocString::from_raw(s1).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc() {
|
||||
let err = alloc("error");
|
||||
unsafe { ffi::sqlite3_free(err.cast()) };
|
||||
}
|
||||
}
|
||||
27
vendor/rusqlite/src/version.rs
vendored
Normal file
27
vendor/rusqlite/src/version.rs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::ffi;
|
||||
use std::ffi::CStr;
|
||||
|
||||
/// Returns the SQLite version as an integer; e.g., `3016002` for version
|
||||
/// 3.16.2.
|
||||
///
|
||||
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn version_number() -> i32 {
|
||||
unsafe { ffi::sqlite3_libversion_number() }
|
||||
}
|
||||
|
||||
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
|
||||
///
|
||||
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when version is not valid UTF-8.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn version() -> &'static str {
|
||||
let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
|
||||
cstr.to_str()
|
||||
.expect("SQLite version string is not valid UTF8 ?!")
|
||||
}
|
||||
221
vendor/rusqlite/src/vtab/array.rs
vendored
Normal file
221
vendor/rusqlite/src/vtab/array.rs
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
//! Array Virtual Table.
|
||||
//!
|
||||
//! Note: `rarray`, not `carray` is the name of the table valued function we
|
||||
//! define.
|
||||
//!
|
||||
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c)
|
||||
//! C extension: `https://www.sqlite.org/carray.html`
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use rusqlite::{types::Value, Connection, Result, params};
|
||||
//! # use std::rc::Rc;
|
||||
//! fn example(db: &Connection) -> Result<()> {
|
||||
//! // Note: This should be done once (usually when opening the DB).
|
||||
//! rusqlite::vtab::array::load_module(&db)?;
|
||||
//! let v = [1i64, 2, 3, 4];
|
||||
//! // Note: A `Rc<Vec<Value>>` must be used as the parameter.
|
||||
//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
|
||||
//! let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
||||
//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
|
||||
//! for value in rows {
|
||||
//! println!("{}", value?);
|
||||
//! }
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::ffi::{c_int, CStr};
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::types::{ToSql, ToSqlOutput, Value};
|
||||
use crate::vtab::{
|
||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConnection,
|
||||
VTabCursor,
|
||||
};
|
||||
use crate::{Connection, Result};
|
||||
|
||||
// http://sqlite.org/bindptr.html
|
||||
|
||||
const ARRAY_TYPE: &CStr = c"rarray";
|
||||
|
||||
/// Array parameter / pointer
|
||||
pub type Array = Rc<Vec<Value>>;
|
||||
|
||||
impl ToSql for Array {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from_rc(self.clone(), ARRAY_TYPE))
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the "rarray" module.
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(c"rarray", eponymous_only_module::<ArrayTab>(), aux)
|
||||
}
|
||||
|
||||
// Column numbers
|
||||
// const CARRAY_COLUMN_VALUE : c_int = 0;
|
||||
const CARRAY_COLUMN_POINTER: c_int = 1;
|
||||
|
||||
/// An instance of the Array virtual table
|
||||
#[repr(C)]
|
||||
struct ArrayTab {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab,
|
||||
}
|
||||
|
||||
unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
|
||||
type Aux = ();
|
||||
type Cursor = ArrayTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
_: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
_args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
let vtab = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
};
|
||||
Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab))
|
||||
}
|
||||
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
// Index of the pointer= constraint
|
||||
let mut ptr_idx = false;
|
||||
for (constraint, mut constraint_usage) in info.constraints_and_usages() {
|
||||
if !constraint.is_usable() {
|
||||
continue;
|
||||
}
|
||||
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
||||
continue;
|
||||
}
|
||||
if let CARRAY_COLUMN_POINTER = constraint.column() {
|
||||
ptr_idx = true;
|
||||
constraint_usage.set_argv_index(1);
|
||||
constraint_usage.set_omit(true);
|
||||
}
|
||||
}
|
||||
if ptr_idx {
|
||||
info.set_estimated_cost(1_f64);
|
||||
info.set_estimated_rows(100);
|
||||
info.set_idx_num(1);
|
||||
} else {
|
||||
info.set_estimated_cost(2_147_483_647_f64);
|
||||
info.set_estimated_rows(2_147_483_647);
|
||||
info.set_idx_num(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
|
||||
Ok(ArrayTabCursor::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// A cursor for the Array virtual table
|
||||
#[repr(C)]
|
||||
struct ArrayTabCursor<'vtab> {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab_cursor,
|
||||
/// The rowid
|
||||
row_id: i64,
|
||||
/// Pointer to the array of values ("pointer")
|
||||
ptr: Option<&'vtab Vec<Value>>,
|
||||
phantom: PhantomData<&'vtab ArrayTab>,
|
||||
}
|
||||
|
||||
impl ArrayTabCursor<'_> {
|
||||
fn new<'vtab>() -> ArrayTabCursor<'vtab> {
|
||||
ArrayTabCursor {
|
||||
base: ffi::sqlite3_vtab_cursor::default(),
|
||||
row_id: 0,
|
||||
ptr: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> i64 {
|
||||
match self.ptr {
|
||||
Some(a) => a.len() as i64,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl VTabCursor for ArrayTabCursor<'_> {
|
||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
||||
if idx_num > 0 {
|
||||
self.ptr = unsafe { args.get_pointer(0, ARRAY_TYPE) };
|
||||
} else {
|
||||
self.ptr = None;
|
||||
}
|
||||
self.row_id = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<()> {
|
||||
self.row_id += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eof(&self) -> bool {
|
||||
self.row_id > self.len()
|
||||
}
|
||||
|
||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||
match i {
|
||||
CARRAY_COLUMN_POINTER => Ok(()),
|
||||
_ => {
|
||||
if let Some(array) = self.ptr {
|
||||
let value = &array[(self.row_id - 1) as usize];
|
||||
ctx.set_result(&value)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::types::Value;
|
||||
use crate::vtab::array;
|
||||
use crate::{Connection, Result};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn test_array_module() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
array::load_module(&db)?;
|
||||
|
||||
let v = vec![1i64, 2, 3, 4];
|
||||
let values: Vec<Value> = v.into_iter().map(Value::from).collect();
|
||||
let ptr = Rc::new(values);
|
||||
{
|
||||
let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
||||
|
||||
let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
|
||||
assert_eq!(2, Rc::strong_count(&ptr));
|
||||
let mut count = 0;
|
||||
for (i, value) in rows.enumerate() {
|
||||
assert_eq!(i as i64, value? - 1);
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(4, count);
|
||||
}
|
||||
assert_eq!(1, Rc::strong_count(&ptr));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
Normal file
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
//! CSV Virtual Table.
|
||||
//!
|
||||
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C
|
||||
//! extension: `https://www.sqlite.org/csv.html`
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use rusqlite::{Connection, Result};
|
||||
//! fn example() -> Result<()> {
|
||||
//! // Note: This should be done once (usually when opening the DB).
|
||||
//! let db = Connection::open_in_memory()?;
|
||||
//! rusqlite::vtab::csvtab::load_module(&db)?;
|
||||
//! // Assume my_csv.csv
|
||||
//! let schema = "
|
||||
//! CREATE VIRTUAL TABLE my_csv_data
|
||||
//! USING csv(filename = 'my_csv.csv')
|
||||
//! ";
|
||||
//! db.execute_batch(schema)?;
|
||||
//! // Now the `my_csv_data` (virtual) table can be queried as normal...
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
use std::ffi::c_int;
|
||||
use std::fs::File;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::types::Null;
|
||||
use crate::vtab::{
|
||||
escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, Filters, IndexInfo,
|
||||
VTab, VTabConfig, VTabConnection, VTabCursor, VTabKind,
|
||||
};
|
||||
use crate::{Connection, Error, Result};
|
||||
|
||||
/// Register the "csv" module.
|
||||
/// ```sql
|
||||
/// CREATE VIRTUAL TABLE vtab USING csv(
|
||||
/// filename=FILENAME -- Name of file containing CSV content
|
||||
/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);'
|
||||
/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no".
|
||||
/// [, columns=N] -- Assume the CSV file contains N columns.
|
||||
/// [, delimiter=C] -- CSV delimiter. Default ','.
|
||||
/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote.
|
||||
/// );
|
||||
/// ```
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(c"csv", read_only_module::<CsvTab>(), aux)
|
||||
}
|
||||
|
||||
/// An instance of the CSV virtual table
|
||||
#[repr(C)]
|
||||
struct CsvTab {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab,
|
||||
/// Name of the CSV file
|
||||
filename: String,
|
||||
has_headers: bool,
|
||||
delimiter: u8,
|
||||
quote: u8,
|
||||
/// Offset to start of data
|
||||
offset_first_row: csv::Position,
|
||||
}
|
||||
|
||||
impl CsvTab {
|
||||
fn reader(&self) -> Result<csv::Reader<File>, csv::Error> {
|
||||
csv::ReaderBuilder::new()
|
||||
.has_headers(self.has_headers)
|
||||
.delimiter(self.delimiter)
|
||||
.quote(self.quote)
|
||||
.from_path(&self.filename)
|
||||
}
|
||||
|
||||
fn parse_byte(arg: &str) -> Option<u8> {
|
||||
if arg.len() == 1 {
|
||||
arg.bytes().next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
type Aux = ();
|
||||
type Cursor = CsvTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
db: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
if args.len() < 4 {
|
||||
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
||||
}
|
||||
|
||||
let mut vtab = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
filename: String::new(),
|
||||
has_headers: false,
|
||||
delimiter: b',',
|
||||
quote: b'"',
|
||||
offset_first_row: csv::Position::new(),
|
||||
};
|
||||
let mut schema = None;
|
||||
let mut n_col = None;
|
||||
|
||||
let args = &args[3..];
|
||||
for c_slice in args {
|
||||
let (param, value) = super::parameter(c_slice)?;
|
||||
match param {
|
||||
"filename" => {
|
||||
if !Path::new(value).exists() {
|
||||
return Err(Error::ModuleError(format!("file '{value}' does not exist")));
|
||||
}
|
||||
value.clone_into(&mut vtab.filename);
|
||||
}
|
||||
"schema" => {
|
||||
schema = Some(value.to_owned());
|
||||
}
|
||||
"columns" => {
|
||||
if let Ok(n) = value.parse::<u16>() {
|
||||
if n_col.is_some() {
|
||||
return Err(Error::ModuleError(
|
||||
"more than one 'columns' parameter".to_owned(),
|
||||
));
|
||||
} else if n == 0 {
|
||||
return Err(Error::ModuleError(
|
||||
"must have at least one column".to_owned(),
|
||||
));
|
||||
}
|
||||
n_col = Some(n);
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'columns': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
"header" => {
|
||||
if let Some(b) = parse_boolean(value) {
|
||||
vtab.has_headers = b;
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'header': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
"delimiter" => {
|
||||
if let Some(b) = Self::parse_byte(value) {
|
||||
vtab.delimiter = b;
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'delimiter': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
"quote" => {
|
||||
if let Some(b) = Self::parse_byte(value) {
|
||||
if b == b'0' {
|
||||
vtab.quote = 0;
|
||||
} else {
|
||||
vtab.quote = b;
|
||||
}
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'quote': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized parameter '{param}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vtab.filename.is_empty() {
|
||||
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
||||
}
|
||||
|
||||
let mut cols: Vec<String> = Vec::new();
|
||||
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
|
||||
let mut reader = vtab.reader()?;
|
||||
if vtab.has_headers {
|
||||
{
|
||||
let headers = reader.headers()?;
|
||||
// headers ignored if cols is not empty
|
||||
if n_col.is_none() && schema.is_none() {
|
||||
cols = headers
|
||||
.into_iter()
|
||||
.map(|header| escape_double_quote(header).into_owned())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
vtab.offset_first_row = reader.position().clone();
|
||||
} else {
|
||||
let mut record = csv::ByteRecord::new();
|
||||
if reader.read_byte_record(&mut record)? {
|
||||
for (i, _) in record.iter().enumerate() {
|
||||
cols.push(format!("c{i}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(n_col) = n_col {
|
||||
for i in 0..n_col {
|
||||
cols.push(format!("c{i}"));
|
||||
}
|
||||
}
|
||||
|
||||
if cols.is_empty() && schema.is_none() {
|
||||
return Err(Error::ModuleError("no column specified".to_owned()));
|
||||
}
|
||||
|
||||
if schema.is_none() {
|
||||
let mut sql = String::from("CREATE TABLE x(");
|
||||
for (i, col) in cols.iter().enumerate() {
|
||||
sql.push('"');
|
||||
sql.push_str(col);
|
||||
sql.push_str("\" TEXT");
|
||||
if i == cols.len() - 1 {
|
||||
sql.push_str(");");
|
||||
} else {
|
||||
sql.push_str(", ");
|
||||
}
|
||||
}
|
||||
schema = Some(sql);
|
||||
}
|
||||
db.config(VTabConfig::DirectOnly)?;
|
||||
Ok((schema.unwrap(), vtab))
|
||||
}
|
||||
|
||||
// Only a forward full table scan is supported.
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
info.set_estimated_cost(1_000_000.);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&mut self) -> Result<CsvTabCursor<'_>> {
|
||||
Ok(CsvTabCursor::new(self.reader()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateVTab<'_> for CsvTab {
|
||||
const KIND: VTabKind = VTabKind::Default;
|
||||
}
|
||||
|
||||
/// A cursor for the CSV virtual table
|
||||
#[repr(C)]
|
||||
struct CsvTabCursor<'vtab> {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab_cursor,
|
||||
/// The CSV reader object
|
||||
reader: csv::Reader<File>,
|
||||
/// Current cursor position used as rowid
|
||||
row_number: usize,
|
||||
/// Values of the current row
|
||||
cols: csv::StringRecord,
|
||||
eof: bool,
|
||||
phantom: PhantomData<&'vtab CsvTab>,
|
||||
}
|
||||
|
||||
impl CsvTabCursor<'_> {
|
||||
fn new<'vtab>(reader: csv::Reader<File>) -> CsvTabCursor<'vtab> {
|
||||
CsvTabCursor {
|
||||
base: ffi::sqlite3_vtab_cursor::default(),
|
||||
reader,
|
||||
row_number: 0,
|
||||
cols: csv::StringRecord::new(),
|
||||
eof: false,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor to the associated virtual table.
|
||||
fn vtab(&self) -> &CsvTab {
|
||||
unsafe { &*(self.base.pVtab as *const CsvTab) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl VTabCursor for CsvTabCursor<'_> {
|
||||
// Only a full table scan is supported. So `filter` simply rewinds to
|
||||
// the beginning.
|
||||
fn filter(
|
||||
&mut self,
|
||||
_idx_num: c_int,
|
||||
_idx_str: Option<&str>,
|
||||
_args: &Filters<'_>,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let offset_first_row = self.vtab().offset_first_row.clone();
|
||||
self.reader.seek(offset_first_row)?;
|
||||
}
|
||||
self.row_number = 0;
|
||||
self.next()
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<()> {
|
||||
{
|
||||
self.eof = self.reader.is_done();
|
||||
if self.eof {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.eof = !self.reader.read_record(&mut self.cols)?;
|
||||
}
|
||||
|
||||
self.row_number += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eof(&self) -> bool {
|
||||
self.eof
|
||||
}
|
||||
|
||||
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
|
||||
if col < 0 || col as usize >= self.cols.len() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"column index out of bounds: {col}"
|
||||
)));
|
||||
}
|
||||
if self.cols.is_empty() {
|
||||
return ctx.set_result(&Null);
|
||||
}
|
||||
// TODO Affinity
|
||||
ctx.set_result(&self.cols[col as usize].to_owned())
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
Ok(self.row_number as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<csv::Error> for Error {
|
||||
#[cold]
|
||||
fn from(err: csv::Error) -> Self {
|
||||
Self::ModuleError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::vtab::csvtab;
|
||||
use crate::{Connection, Result};
|
||||
use fallible_iterator::FallibleIterator;
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[test]
|
||||
fn test_csv_module() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
csvtab::load_module(&db)?;
|
||||
db.execute_batch(
|
||||
"CREATE VIRTUAL TABLE vtab USING csv(filename = 'test.csv', header = yes)",
|
||||
)?;
|
||||
|
||||
{
|
||||
let mut s = db.prepare("SELECT rowid, * FROM vtab")?;
|
||||
{
|
||||
let headers = s.column_names();
|
||||
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
|
||||
}
|
||||
|
||||
let ids: Result<Vec<i32>> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect();
|
||||
let sum = ids?.iter().sum::<i32>();
|
||||
assert_eq!(sum, 15);
|
||||
}
|
||||
db.execute_batch("DROP TABLE vtab")
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[test]
|
||||
fn test_csv_cursor() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
csvtab::load_module(&db)?;
|
||||
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
|
||||
|
||||
{
|
||||
let mut s = db.prepare(
|
||||
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
|
||||
v1.rowid < v2.rowid",
|
||||
)?;
|
||||
|
||||
let mut rows = s.query([])?;
|
||||
let row = rows.next()?.unwrap();
|
||||
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
|
||||
}
|
||||
db.execute_batch("DROP TABLE vtab")
|
||||
}
|
||||
}
|
||||
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
Normal file
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
344
vendor/rusqlite/src/vtab/series.rs
vendored
Normal file
344
vendor/rusqlite/src/vtab/series.rs
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
//! Generate series virtual table.
|
||||
//!
|
||||
//! Port of C [generate series
|
||||
//! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c):
|
||||
//! `https://www.sqlite.org/series.html`
|
||||
use std::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::types::Type;
|
||||
use crate::vtab::{
|
||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConfig,
|
||||
VTabConnection, VTabCursor,
|
||||
};
|
||||
use crate::{error::error_from_sqlite_code, Connection, Result};
|
||||
|
||||
/// Register the `generate_series` module.
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(
|
||||
c"generate_series",
|
||||
eponymous_only_module::<SeriesTab>(),
|
||||
aux,
|
||||
)
|
||||
}
|
||||
|
||||
// Column numbers
|
||||
// const SERIES_COLUMN_VALUE : c_int = 0;
|
||||
const SERIES_COLUMN_START: c_int = 1;
|
||||
const SERIES_COLUMN_STOP: c_int = 2;
|
||||
const SERIES_COLUMN_STEP: c_int = 3;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct QueryPlanFlags: c_int {
|
||||
// start = $value -- constraint exists
|
||||
const START = 1;
|
||||
// stop = $value -- constraint exists
|
||||
const STOP = 2;
|
||||
// step = $value -- constraint exists
|
||||
const STEP = 4;
|
||||
// output in descending order
|
||||
const DESC = 8;
|
||||
// output in ascending order
|
||||
const ASC = 16;
|
||||
// Both start and stop
|
||||
const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of the Series virtual table
|
||||
#[repr(C)]
|
||||
struct SeriesTab {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab,
|
||||
}
|
||||
|
||||
unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
||||
type Aux = ();
|
||||
type Cursor = SeriesTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
db: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
_args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
let vtab = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
};
|
||||
db.config(VTabConfig::Innocuous)?;
|
||||
Ok((
|
||||
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
||||
vtab,
|
||||
))
|
||||
}
|
||||
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
// The query plan bitmask
|
||||
let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();
|
||||
// Mask of unusable constraints
|
||||
let mut unusable_mask: QueryPlanFlags = QueryPlanFlags::empty();
|
||||
// Constraints on start, stop, and step
|
||||
let mut a_idx: [Option<usize>; 3] = [None, None, None];
|
||||
for (i, constraint) in info.constraints().enumerate() {
|
||||
if constraint.column() < SERIES_COLUMN_START {
|
||||
continue;
|
||||
}
|
||||
let (i_col, i_mask) = match constraint.column() {
|
||||
SERIES_COLUMN_START => (0, QueryPlanFlags::START),
|
||||
SERIES_COLUMN_STOP => (1, QueryPlanFlags::STOP),
|
||||
SERIES_COLUMN_STEP => (2, QueryPlanFlags::STEP),
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
if !constraint.is_usable() {
|
||||
unusable_mask |= i_mask;
|
||||
} else if constraint.operator() == IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
||||
idx_num |= i_mask;
|
||||
a_idx[i_col] = Some(i);
|
||||
}
|
||||
}
|
||||
// Number of arguments that SeriesTabCursor::filter expects
|
||||
let mut n_arg = 0;
|
||||
for j in a_idx.iter().flatten() {
|
||||
n_arg += 1;
|
||||
let mut constraint_usage = info.constraint_usage(*j);
|
||||
constraint_usage.set_argv_index(n_arg);
|
||||
constraint_usage.set_omit(true);
|
||||
#[cfg(all(test, feature = "modern_sqlite"))]
|
||||
debug_assert_eq!(Ok("BINARY"), info.collation(*j));
|
||||
}
|
||||
if !(unusable_mask & !idx_num).is_empty() {
|
||||
return Err(error_from_sqlite_code(ffi::SQLITE_CONSTRAINT, None));
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::BOTH) {
|
||||
// Both start= and stop= boundaries are available.
|
||||
#[expect(clippy::bool_to_int_with_if)]
|
||||
info.set_estimated_cost(f64::from(
|
||||
2 - if idx_num.contains(QueryPlanFlags::STEP) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
));
|
||||
info.set_estimated_rows(1000);
|
||||
let order_by_consumed = {
|
||||
let mut order_bys = info.order_bys();
|
||||
if let Some(order_by) = order_bys.next() {
|
||||
if order_by.column() == 0 {
|
||||
if order_by.is_order_by_desc() {
|
||||
idx_num |= QueryPlanFlags::DESC;
|
||||
} else {
|
||||
idx_num |= QueryPlanFlags::ASC;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if order_by_consumed {
|
||||
info.set_order_by_consumed(true);
|
||||
}
|
||||
} else {
|
||||
// If either boundary is missing, we have to generate a huge span
|
||||
// of numbers. Make this case very expensive so that the query
|
||||
// planner will work hard to avoid it.
|
||||
info.set_estimated_rows(2_147_483_647);
|
||||
}
|
||||
info.set_idx_num(idx_num.bits());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
|
||||
Ok(SeriesTabCursor::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// A cursor for the Series virtual table
|
||||
#[repr(C)]
|
||||
struct SeriesTabCursor<'vtab> {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab_cursor,
|
||||
/// True to count down rather than up
|
||||
is_desc: bool,
|
||||
/// The rowid
|
||||
row_id: i64,
|
||||
/// Current value ("value")
|
||||
value: i64,
|
||||
/// Minimum value ("start")
|
||||
min_value: i64,
|
||||
/// Maximum value ("stop")
|
||||
max_value: i64,
|
||||
/// Increment ("step")
|
||||
step: i64,
|
||||
phantom: PhantomData<&'vtab SeriesTab>,
|
||||
}
|
||||
|
||||
impl SeriesTabCursor<'_> {
|
||||
fn new<'vtab>() -> SeriesTabCursor<'vtab> {
|
||||
SeriesTabCursor {
|
||||
base: ffi::sqlite3_vtab_cursor::default(),
|
||||
is_desc: false,
|
||||
row_id: 0,
|
||||
value: 0,
|
||||
min_value: 0,
|
||||
max_value: 0,
|
||||
step: 0,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl VTabCursor for SeriesTabCursor<'_> {
|
||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
||||
let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
|
||||
let mut i = 0;
|
||||
if idx_num.contains(QueryPlanFlags::START) {
|
||||
self.min_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.min_value = 0;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STOP) {
|
||||
self.max_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.max_value = 0xffff_ffff;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STEP) {
|
||||
self.step = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
if self.step == 0 {
|
||||
self.step = 1;
|
||||
} else if self.step < 0 {
|
||||
self.step = -self.step;
|
||||
if !idx_num.contains(QueryPlanFlags::ASC) {
|
||||
idx_num |= QueryPlanFlags::DESC;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.step = 1;
|
||||
};
|
||||
for arg in args.iter() {
|
||||
if arg.data_type() == Type::Null {
|
||||
// If any of the constraints have a NULL value, then return no rows.
|
||||
self.min_value = 1;
|
||||
self.max_value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.is_desc = idx_num.contains(QueryPlanFlags::DESC);
|
||||
if self.is_desc {
|
||||
self.value = self.max_value;
|
||||
if self.step > 0 {
|
||||
self.value -= (self.max_value - self.min_value) % self.step;
|
||||
}
|
||||
} else {
|
||||
self.value = self.min_value;
|
||||
}
|
||||
self.row_id = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<()> {
|
||||
if self.is_desc {
|
||||
self.value -= self.step;
|
||||
} else {
|
||||
self.value += self.step;
|
||||
}
|
||||
self.row_id += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eof(&self) -> bool {
|
||||
if self.is_desc {
|
||||
self.value < self.min_value
|
||||
} else {
|
||||
self.value > self.max_value
|
||||
}
|
||||
}
|
||||
|
||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||
let x = match i {
|
||||
SERIES_COLUMN_START => self.min_value,
|
||||
SERIES_COLUMN_STOP => self.max_value,
|
||||
SERIES_COLUMN_STEP => self.step,
|
||||
_ => self.value,
|
||||
};
|
||||
ctx.set_result(&x)
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::vtab::series;
|
||||
use crate::{Connection, Result};
|
||||
use fallible_iterator::FallibleIterator;
|
||||
|
||||
#[test]
|
||||
fn test_series_module() -> Result<()> {
|
||||
let version = unsafe { ffi::sqlite3_libversion_number() };
|
||||
if version < 3_008_012 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let db = Connection::open_in_memory()?;
|
||||
series::load_module(&db)?;
|
||||
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)")?;
|
||||
|
||||
let series = s.query_map([], |row| row.get::<_, i32>(0))?;
|
||||
|
||||
let mut expected = 0;
|
||||
for value in series {
|
||||
assert_eq!(expected, value?);
|
||||
expected += 5;
|
||||
}
|
||||
|
||||
let mut s =
|
||||
db.prepare("SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(vec![1, 3, 5, 7, 9], series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series LIMIT 5")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(vec![0, 1, 2, 3, 4], series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(0,32,5) ORDER BY value DESC")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
|
||||
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
let empty = Vec::<i32>::new();
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,10,NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL,10,2)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL,2)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL) ORDER BY value DESC")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
Normal file
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
||||
use std::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use fallible_iterator::FallibleIterator;
|
||||
|
||||
use crate::types::Type;
|
||||
use crate::vtab::{
|
||||
update_module_with_tx, Context, CreateVTab, Filters, IndexInfo, Inserts, TransactionVTab,
|
||||
UpdateVTab, Updates, VTab, VTabConnection, VTabCursor, VTabKind,
|
||||
};
|
||||
use crate::{ffi, ValueRef};
|
||||
use crate::{Connection, Error, Result};
|
||||
|
||||
/// Register the "vtablog" module.
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(c"vtablog", update_module_with_tx::<VTabLog>(), aux)
|
||||
}
|
||||
|
||||
/// An instance of the vtablog virtual table
|
||||
#[repr(C)]
|
||||
struct VTabLog {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab,
|
||||
/// Associated connection
|
||||
db: *mut ffi::sqlite3,
|
||||
/// Number of rows in the table
|
||||
n_row: i64,
|
||||
/// Instance number for this vtablog table
|
||||
i_inst: usize,
|
||||
/// Number of cursors created
|
||||
n_cursor: usize,
|
||||
}
|
||||
|
||||
impl VTabLog {
|
||||
fn connect_create(
|
||||
db: &mut VTabConnection,
|
||||
_: Option<&()>,
|
||||
args: &[&[u8]],
|
||||
is_create: bool,
|
||||
) -> Result<(String, Self)> {
|
||||
static N_INST: AtomicUsize = AtomicUsize::new(1);
|
||||
let i_inst = N_INST.fetch_add(1, Ordering::SeqCst);
|
||||
println!(
|
||||
"VTabLog::{}(tab={}, args={:?}):",
|
||||
if is_create { "create" } else { "connect" },
|
||||
i_inst,
|
||||
args.iter().map(|b| str::from_utf8(b)).collect::<Vec<_>>(),
|
||||
);
|
||||
let mut schema = None;
|
||||
let mut n_row = None;
|
||||
|
||||
let args = &args[3..];
|
||||
for c_slice in args {
|
||||
let (param, value) = super::parameter(c_slice)?;
|
||||
match param {
|
||||
"schema" => {
|
||||
if schema.is_some() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"more than one '{param}' parameter"
|
||||
)));
|
||||
}
|
||||
schema = Some(value.to_owned())
|
||||
}
|
||||
"rows" => {
|
||||
if n_row.is_some() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"more than one '{param}' parameter"
|
||||
)));
|
||||
}
|
||||
if let Ok(n) = i64::from_str(value) {
|
||||
n_row = Some(n)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized parameter '{param}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if schema.is_none() {
|
||||
return Err(Error::ModuleError("no schema defined".to_owned()));
|
||||
}
|
||||
let vtab = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
db: unsafe { db.handle() },
|
||||
n_row: n_row.unwrap_or(10),
|
||||
i_inst,
|
||||
n_cursor: 0,
|
||||
};
|
||||
Ok((schema.unwrap(), vtab))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VTabLog {
|
||||
fn drop(&mut self) {
|
||||
println!("VTabLog::drop({})", self.i_inst);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'vtab> VTab<'vtab> for VTabLog {
|
||||
type Aux = ();
|
||||
type Cursor = VTabLogCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
db: &mut VTabConnection,
|
||||
aux: Option<&Self::Aux>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
Self::connect_create(db, aux, args, false)
|
||||
}
|
||||
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
println!(
|
||||
"VTabLog::best_index({}, num_of_order_by: {}, col_used: {}, distinct: {:?})",
|
||||
self.i_inst,
|
||||
info.num_of_order_by(),
|
||||
info.col_used(),
|
||||
info.distinct()
|
||||
);
|
||||
let mut in_constraint = None;
|
||||
for (i, constraint) in info.constraints().enumerate() {
|
||||
println!(
|
||||
" constraint[{}]: col={}, usable={}, op={:?}, rhs={:?}, in={:?}",
|
||||
i,
|
||||
constraint.column(),
|
||||
constraint.is_usable(),
|
||||
constraint.operator(),
|
||||
info.rhs_value(i),
|
||||
info.is_in_constraint(i),
|
||||
);
|
||||
if info.is_in_constraint(i)? {
|
||||
in_constraint = Some(i);
|
||||
}
|
||||
}
|
||||
info.set_estimated_cost(500.);
|
||||
info.set_estimated_rows(500);
|
||||
info.set_idx_str("idx");
|
||||
info.set_idx_cstr(c"idx");
|
||||
if let Some(idx) = in_constraint {
|
||||
info.set_in_constraint(idx, true)?;
|
||||
info.constraint_usage(idx).set_argv_index(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&'vtab mut self) -> Result<Self::Cursor> {
|
||||
self.n_cursor += 1;
|
||||
println!(
|
||||
"VTabLog::open(tab={}, cursor={})",
|
||||
self.i_inst, self.n_cursor
|
||||
);
|
||||
Ok(VTabLogCursor {
|
||||
base: ffi::sqlite3_vtab_cursor::default(),
|
||||
i_cursor: self.n_cursor,
|
||||
row_id: 0,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateVTab<'_> for VTabLog {
|
||||
const KIND: VTabKind = VTabKind::Default;
|
||||
|
||||
fn create(
|
||||
db: &mut VTabConnection,
|
||||
aux: Option<&Self::Aux>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
Self::connect_create(db, aux, args, true)
|
||||
}
|
||||
|
||||
fn destroy(&self) -> Result<()> {
|
||||
println!("VTabLog::destroy({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateVTab<'_> for VTabLog {
|
||||
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
|
||||
println!("VTabLog::delete({}, {arg:?})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(&mut self, args: &Inserts<'_>) -> Result<i64> {
|
||||
println!(
|
||||
"VTabLog::insert({}, on_conflict:{:?}, {:?})",
|
||||
self.i_inst,
|
||||
unsafe { args.on_conflict(self.db) },
|
||||
args.iter().collect::<Vec<ValueRef<'_>>>()
|
||||
);
|
||||
Ok(self.n_row)
|
||||
}
|
||||
|
||||
fn update(&mut self, args: &Updates<'_>) -> Result<()> {
|
||||
println!(
|
||||
"VTabLog::update({}, on_conflict:{:?}, {:?})",
|
||||
self.i_inst,
|
||||
unsafe { args.on_conflict(self.db) },
|
||||
args.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v, args.no_change(i)))
|
||||
.collect::<Vec<(ValueRef<'_>, bool)>>()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionVTab<'_> for VTabLog {
|
||||
fn begin(&mut self) -> Result<()> {
|
||||
println!("VTabLog::begin({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> Result<()> {
|
||||
println!("VTabLog::sync({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> Result<()> {
|
||||
println!("VTabLog::commit({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rollback(&mut self) -> Result<()> {
|
||||
println!("VTabLog::rollback({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A cursor for the Series virtual table
|
||||
#[repr(C)]
|
||||
struct VTabLogCursor<'vtab> {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab_cursor,
|
||||
/// Cursor number
|
||||
i_cursor: usize,
|
||||
/// The rowid
|
||||
row_id: i64,
|
||||
phantom: PhantomData<&'vtab VTabLog>,
|
||||
}
|
||||
|
||||
impl VTabLogCursor<'_> {
|
||||
fn vtab(&self) -> &VTabLog {
|
||||
unsafe { &*(self.base.pVtab as *const VTabLog) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VTabLogCursor<'_> {
|
||||
fn drop(&mut self) {
|
||||
println!(
|
||||
"VTabLogCursor::drop(tab={}, cursor={})",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl VTabCursor for VTabLogCursor<'_> {
|
||||
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
||||
println!(
|
||||
"VTabLogCursor::filter(tab={}, cursor={}, idx_num={idx_num}, idx_str={idx_str:?}, args={})",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
args.len()
|
||||
);
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if arg.data_type() == Type::Null {
|
||||
println!(
|
||||
" in_values[{}]: {:?}",
|
||||
i,
|
||||
args.in_values(i)?.collect::<Vec<ValueRef>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
self.row_id = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<()> {
|
||||
println!(
|
||||
"VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
self.row_id,
|
||||
self.row_id + 1
|
||||
);
|
||||
self.row_id += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eof(&self) -> bool {
|
||||
let eof = self.row_id >= self.vtab().n_row;
|
||||
println!(
|
||||
"VTabLogCursor::eof(tab={}, cursor={}): {}",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
eof,
|
||||
);
|
||||
eof
|
||||
}
|
||||
|
||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||
if ctx.no_change() {
|
||||
println!(
|
||||
"VTabLogCursor::column(tab={}, cursor={}, i={}): no change",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
i,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let value = if i < 26 {
|
||||
format!(
|
||||
"{}{}",
|
||||
"abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(),
|
||||
self.row_id
|
||||
)
|
||||
} else {
|
||||
format!("{i}{}", self.row_id)
|
||||
};
|
||||
println!(
|
||||
"VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
i,
|
||||
value,
|
||||
);
|
||||
if i == 0 {
|
||||
println!(" db busy: {:?}", unsafe {
|
||||
ctx.get_connection().map(|c| c.is_busy())
|
||||
})
|
||||
}
|
||||
ctx.set_result(&value)
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
println!(
|
||||
"VTabLogCursor::rowid(tab={}, cursor={}): {}",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
self.row_id,
|
||||
);
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_module() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
super::load_module(&db)?;
|
||||
|
||||
db.execute_batch(
|
||||
"CREATE VIRTUAL TABLE temp.log USING vtablog(
|
||||
schema='CREATE TABLE x(a,b,c)',
|
||||
rows=3
|
||||
);",
|
||||
)?;
|
||||
let mut stmt = db.prepare("SELECT * FROM log;")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
while rows.next()?.is_some() {}
|
||||
db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
|
||||
db.execute(
|
||||
"INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
|
||||
["a", "b", "c"],
|
||||
)?;
|
||||
db.execute(
|
||||
"UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
|
||||
["bn", "cn", "a1"],
|
||||
)?;
|
||||
db.query_one("SELECT b, c FROM log WHERE a = 'a1'", [], |_| Ok(0))?;
|
||||
db.execute("UPDATE log SET b = '' WHERE a IN (?1, ?2)", ["a1", "a2"])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
6
vendor/rusqlite/test.csv
vendored
Normal file
6
vendor/rusqlite/test.csv
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"colA","colB","colC"
|
||||
1,2,3
|
||||
a,b,c
|
||||
a,"b",c
|
||||
"a","b","c .. z"
|
||||
"a","b","c,d"
|
||||
|
41
vendor/rusqlite/tests/auto_ext.rs
vendored
Normal file
41
vendor/rusqlite/tests/auto_ext.rs
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
#[cfg(all(feature = "bundled", not(feature = "loadable_extension")))]
|
||||
#[test]
|
||||
fn auto_ext() -> rusqlite::Result<()> {
|
||||
use rusqlite::auto_extension::*;
|
||||
use rusqlite::{ffi, Connection, Error, Result};
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
fn test_ok(_: Connection) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
unsafe extern "C" fn sqlite_test_ok(
|
||||
db: *mut ffi::sqlite3,
|
||||
pz_err_msg: *mut *mut c_char,
|
||||
_: *const ffi::sqlite3_api_routines,
|
||||
) -> c_int {
|
||||
init_auto_extension(db, pz_err_msg, test_ok)
|
||||
}
|
||||
fn test_err(_: Connection) -> Result<()> {
|
||||
Err(Error::SqliteFailure(
|
||||
ffi::Error::new(ffi::SQLITE_CORRUPT),
|
||||
Some("AutoExtErr".to_owned()),
|
||||
))
|
||||
}
|
||||
unsafe extern "C" fn sqlite_test_err(
|
||||
db: *mut ffi::sqlite3,
|
||||
pz_err_msg: *mut *mut c_char,
|
||||
_: *const ffi::sqlite3_api_routines,
|
||||
) -> c_int {
|
||||
init_auto_extension(db, pz_err_msg, test_err)
|
||||
}
|
||||
|
||||
//assert!(!cancel_auto_extension(sqlite_test_ok));
|
||||
unsafe { register_auto_extension(sqlite_test_ok)? };
|
||||
Connection::open_in_memory()?;
|
||||
assert!(cancel_auto_extension(sqlite_test_ok));
|
||||
assert!(!cancel_auto_extension(sqlite_test_ok));
|
||||
unsafe { register_auto_extension(sqlite_test_err)? };
|
||||
Connection::open_in_memory().unwrap_err();
|
||||
reset_auto_extension();
|
||||
Ok(())
|
||||
}
|
||||
32
vendor/rusqlite/tests/config_log.rs
vendored
Normal file
32
vendor/rusqlite/tests/config_log.rs
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
//! This file contains unit tests for `rusqlite::trace::config_log`. This
|
||||
//! function affects SQLite process-wide and so is not safe to run as a normal
|
||||
//! #[test] in the library.
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
fn main() {
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
static LOGS_RECEIVED: LazyLock<Mutex<Vec<(c_int, String)>>> =
|
||||
LazyLock::new(|| Mutex::new(Vec::new()));
|
||||
|
||||
fn log_handler(err: c_int, message: &str) {
|
||||
let mut logs_received = LOGS_RECEIVED.lock().unwrap();
|
||||
logs_received.push((err, message.to_owned()));
|
||||
}
|
||||
|
||||
use rusqlite::trace;
|
||||
|
||||
unsafe { trace::config_log(Some(log_handler)) }.unwrap();
|
||||
trace::log(10, "First message from rusqlite");
|
||||
unsafe { trace::config_log(None) }.unwrap();
|
||||
trace::log(11, "Second message from rusqlite");
|
||||
|
||||
let logs_received = LOGS_RECEIVED.lock().unwrap();
|
||||
assert_eq!(logs_received.len(), 1);
|
||||
assert_eq!(logs_received[0].0, 10);
|
||||
assert_eq!(logs_received[0].1, "First message from rusqlite");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "trace"))]
|
||||
fn main() {}
|
||||
23
vendor/rusqlite/tests/deny_single_threaded_sqlite_config.rs
vendored
Normal file
23
vendor/rusqlite/tests/deny_single_threaded_sqlite_config.rs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//! Ensure we reject connections when SQLite is in single-threaded mode, as it
|
||||
//! would violate safety if multiple Rust threads tried to use connections.
|
||||
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
#[cfg(not(feature = "loadable_extension"))]
|
||||
#[test]
|
||||
fn test_error_when_singlethread_mode() {
|
||||
use rusqlite::ffi;
|
||||
use rusqlite::Connection;
|
||||
// put SQLite into single-threaded mode
|
||||
unsafe {
|
||||
// Note: macOS system SQLite seems to return an error if you attempt to
|
||||
// reconfigure to single-threaded mode.
|
||||
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
|
||||
return;
|
||||
}
|
||||
assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
|
||||
}
|
||||
let res = Connection::open_in_memory();
|
||||
res.unwrap_err();
|
||||
}
|
||||
102
vendor/rusqlite/tests/vtab.rs
vendored
Normal file
102
vendor/rusqlite/tests/vtab.rs
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
|
||||
#[cfg(all(feature = "vtab", target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
#[cfg(feature = "vtab")]
|
||||
#[test]
|
||||
fn test_dummy_module() -> rusqlite::Result<()> {
|
||||
use rusqlite::vtab::{
|
||||
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, Filters, IndexInfo,
|
||||
VTab, VTabConnection, VTabCursor,
|
||||
};
|
||||
use rusqlite::{version_number, Connection, Result};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
let module = eponymous_only_module::<DummyTab>();
|
||||
|
||||
#[repr(C)]
|
||||
struct DummyTab {
|
||||
/// Base class. Must be first
|
||||
base: sqlite3_vtab,
|
||||
}
|
||||
|
||||
unsafe impl<'vtab> VTab<'vtab> for DummyTab {
|
||||
type Aux = ();
|
||||
type Cursor = DummyTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
_: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
_args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
let vtab = Self {
|
||||
base: sqlite3_vtab::default(),
|
||||
};
|
||||
Ok(("CREATE TABLE x(value)".to_owned(), vtab))
|
||||
}
|
||||
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
info.set_estimated_cost(1.);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
|
||||
Ok(DummyTabCursor::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
struct DummyTabCursor<'vtab> {
|
||||
/// Base class. Must be first
|
||||
base: sqlite3_vtab_cursor,
|
||||
/// The rowid
|
||||
row_id: i64,
|
||||
phantom: PhantomData<&'vtab DummyTab>,
|
||||
}
|
||||
|
||||
unsafe impl VTabCursor for DummyTabCursor<'_> {
|
||||
fn filter(
|
||||
&mut self,
|
||||
_idx_num: c_int,
|
||||
_idx_str: Option<&str>,
|
||||
_args: &Filters<'_>,
|
||||
) -> Result<()> {
|
||||
self.row_id = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<()> {
|
||||
self.row_id += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eof(&self) -> bool {
|
||||
self.row_id > 1
|
||||
}
|
||||
|
||||
fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> {
|
||||
ctx.set_result(&self.row_id)
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
db.create_module::<DummyTab, _>(c"dummy", module, None)?;
|
||||
|
||||
let version = version_number();
|
||||
if version < 3_009_000 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut s = db.prepare("SELECT * FROM dummy()")?;
|
||||
|
||||
let dummy = s.query_row([], |row| row.get::<_, i32>(0))?;
|
||||
assert_eq!(1, dummy);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user