Merge branch 'master' into sub_type

This commit is contained in:
gwenn 2023-08-19 12:46:57 +02:00 committed by GitHub
commit 5c1fefbef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 13369 additions and 6451 deletions

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"

View File

@ -17,6 +17,9 @@ env:
# smaller. (TODO: use -Cdebuginfo=0 if it doesn't make backtraces useless)
RUSTFLAGS: -Cdebuginfo=1
permissions:
contents: read
jobs:
test:
name: Test ${{ matrix.target }}
@ -36,7 +39,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# This has a matcher for test panics, so we use it even though elsewhere
# we use actions-rs/toolchain.
- uses: hecrj/setup-rust-action@v1
@ -46,9 +49,9 @@ jobs:
# The `{ sharedKey: ... }` allows different actions to share the cache.
# We're using a `fullBuild` key mostly as a "this needs to do the
# complete" that needs to do the complete build (that is, including
# `--features 'bundled-full session buildtime_bindgen`), which is very
# `--features 'bundled-full session buildtime_bindgen'`), which is very
# slow, and has several deps.
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
with: { sharedKey: fullBuild }
- run: cargo build --features bundled --workspace --all-targets --verbose
@ -80,14 +83,14 @@ jobs:
# TODO: find a way to test this on windows :(
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# This has a matcher for test panics, so we use it even though elsewhere
# we use actions-rs/toolchain.
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable${{ matrix.host }}
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
with: { sharedKey: fullBuild }
- run: cargo test --features 'bundled-sqlcipher' --workspace --all-targets --verbose
@ -113,9 +116,9 @@ jobs:
name: Test with winsqlite3
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
# TODO: Should this test GNU toolchain? What about +crt-static?
# TODO: Is it worth testing other features?
- run: cargo build --features winsqlite3 --workspace --all-targets --verbose
@ -125,9 +128,9 @@ jobs:
name: Test with sqlcipher
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: sudo apt-get install sqlcipher libsqlcipher-dev
- run: sqlcipher --version
# TODO: Is it worth testing other features?
@ -138,13 +141,13 @@ jobs:
name: Address Sanitizer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# Need nightly rust.
- uses: hecrj/setup-rust-action@v1
with:
rust-version: nightly
components: rust-src
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Tests with asan
env:
RUSTFLAGS: -Zsanitizer=address -Cdebuginfo=0
@ -162,11 +165,11 @@ jobs:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
with:
components: clippy
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets --workspace --features bundled -- -D warnings
# Clippy with all non-conflicting features
- run: cargo clippy --all-targets --workspace --features 'bundled-full session buildtime_bindgen' -- -D warnings
@ -176,7 +179,7 @@ jobs:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
with:
components: rustfmt
@ -187,9 +190,9 @@ jobs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
with: { sharedKey: fullBuild }
- run: cargo doc --features 'bundled-full session buildtime_bindgen' --no-deps
env: { RUSTDOCFLAGS: -Dwarnings }
@ -198,15 +201,38 @@ jobs:
name: Generate code coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
# TODO: we don't use caching here because it's unclear if it will cause
# the coverage to get less accurate (this is the case for some coverage
# tools, although possibly not tarpaulin?)
- name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
args: '--features "bundled-full session buildtime_bindgen"'
components: 'llvm-tools-preview'
- uses: taiki-e/install-action@main
with:
tool: grcov
- name: Run tests for coverage
run: |
cargo test --verbose
cargo test --features="bundled-full" --verbose
cargo test --features="bundled-full session buildtime_bindgen" --verbose
cargo test --features="bundled-sqlcipher-vendored-openssl" --verbose
env:
RUSTFLAGS: -Cinstrument-coverage
RUSTDOCFLAGS: -Cinstrument-coverage
LLVM_PROFILE_FILE: rusqlite-%p-%m.profraw
- name: Produce coverage info
run: |
grcov $(find . -name "rusqlite-*.profraw" -print) \
-s . \
--branch \
--ignore-not-existing \
--ignore='target/*' \
--ignore='benches/*' \
--ignore='/*' \
--binary-path ./target/debug/ \
--excl-line='#\[derive' \
-t lcov \
-o lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
with:
files: lcov.info
fail_ci_if_error: true

View File

@ -1,12 +1,12 @@
[package]
name = "rusqlite"
# Note: Update version in README.md when you change this.
version = "0.28.0"
version = "0.29.0"
authors = ["The rusqlite developers"]
edition = "2018"
edition = "2021"
description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/rusqlite/rusqlite"
documentation = "http://docs.rs/rusqlite/"
documentation = "https://docs.rs/rusqlite/"
readme = "README.md"
keywords = ["sqlite", "database", "ffi"]
license = "MIT"
@ -77,6 +77,8 @@ column_decltype = []
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
# Note: doesn't support 32-bit.
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
# 3.23.0
serialize = ["modern_sqlite"]
# Helper feature for enabling most non-build-related optional features
# or dependencies (except `session`). This is useful for running tests / clippy
@ -112,14 +114,13 @@ bundled-full = ["modern-full", "bundled"]
[dependencies]
time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
bitflags = "1.2"
bitflags = "2.0"
hashlink = "0.8"
chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.1", optional = true }
url = { version = "2.1", optional = true }
lazy_static = { version = "1.4", optional = true }
fallible-iterator = "0.2"
fallible-iterator = "0.3"
fallible-streaming-iterator = "0.1"
uuid = { version = "1.0", optional = true }
smallvec = "1.6.1"
@ -137,7 +138,7 @@ bencher = "0.1"
[dependencies.libsqlite3-sys]
path = "libsqlite3-sys"
version = "0.25.0"
version = "0.26.0"
[[test]]
name = "config_log"

View File

@ -15,7 +15,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Add DropBehavior::Panic to enforce intentional commit or rollback.
* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
* Make `Statement::column_index` case insensitive (#330).
* Make `Statement::column_index` case-insensitive (#330).
* Add comment to justify `&mut Connection` in `Transaction`.
* Fix `tyvar_behind_raw_pointer` warnings.
* Fix handful of clippy warnings.
@ -29,7 +29,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
# Version 0.13.0 (2017-11-13)
* Added ToSqlConversionFailure case to Error enum.
* Now depends on chrono 0.4, bitflats 1.0, and (optionally) cc 1.0 / bindgen 0.31.
* Now depends on chrono 0.4, bitflags 1.0, and (optionally) cc 1.0 / bindgen 0.31.
* The ToSql/FromSql implementations for time::Timespec now include
and expect fractional seconds and timezone in the serialized string.
* The RowIndex type used in Row::get is now publicly exported.
@ -61,18 +61,18 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
precense of all expected SQLite constants and functions.
presence of all expected SQLite constants and functions.
* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
some features will not compile unless a sufficiently-recent SQLite version is used. See
the README for requirements of particular features.
* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by
rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can be
skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is
technically a breaking change but is unlikely to affect anyone in practice, since prior to this
version the check that rusqlite was using would cause a segfault if linked against a SQLite
older than 3.7.0.
* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
SQLite version is at least as new as the SQLite version found at buildtime. This check can by
SQLite version is at least as new as the SQLite version found at buildtime. This check can be
skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`.
* Removes the `libc` dependency in favor of using `std::os::raw`
@ -137,7 +137,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
This behavior is more correct. Previously there were runtime checks to prevent misuse, but
other changes in this release to reset statements as soon as possible introduced yet another
hazard related to the lack of these lifetime connections. We were already recommending the
use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles
use of `query_map` and `query_and_then` over raw `query`; both of those still return handles
that implement `Iterator`.
* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
`Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
@ -239,7 +239,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Add `column_names()` to `SqliteStatement`.
* By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a
new conneciton.
new connection.
* Fix generated bindings (e.g., `sqlite3_exec` was wrong).
* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.

View File

@ -27,7 +27,7 @@ In your Cargo.toml:
# 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.28.0", features = ["bundled"] }
rusqlite = { version = "0.29.0", features = ["bundled"] }
```
Simple example usage:
@ -113,8 +113,8 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
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).
* `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 the
`time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time).
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
types from the [`time` crate](https://crates.io/crates/time).
* `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).
@ -149,11 +149,11 @@ 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.39.0 (as of `rusqlite` 0.28.0 / `libsqlite3-sys`
0.25.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
is currently SQLite 3.41.2 (as of `rusqlite` 0.29.0 / `libsqlite3-sys`
0.26.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.28.0"
version = "0.29.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.)

View File

@ -1,6 +1,6 @@
[package]
name = "libsqlite3-sys"
version = "0.25.2"
version = "0.26.0"
authors = ["The rusqlite developers"]
edition = "2018"
repository = "https://github.com/rusqlite/rusqlite"
@ -44,7 +44,7 @@ winsqlite3 = []
openssl-sys = { version = "0.9", optional = true }
[build-dependencies]
bindgen = { version = "0.61", optional = true, default-features = false, features = ["runtime"] }
bindgen = { version = "0.66", optional = true, default-features = false, features = ["runtime"] }
pkg-config = { version = "0.3.19", optional = true }
cc = { version = "1.0", optional = true }
vcpkg = { version = "0.2", optional = true }

View File

@ -1,5 +1,29 @@
/* automatically generated by rust-bindgen 0.60.1 */
extern "C" {
pub fn sqlite3_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_cancel_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.14.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3014000;
pub const SQLITE_SOURCE_ID: &[u8; 61usize] =
@ -1729,16 +1753,6 @@ extern "C" {
onoff: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_auto_extension(
xEntryPoint: ::std::option::Option<unsafe extern "C" fn()>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_cancel_auto_extension(
xEntryPoint: ::std::option::Option<unsafe extern "C" fn()>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_reset_auto_extension();
}
@ -1879,10 +1893,10 @@ pub struct sqlite3_module {
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info {
pub nConstraint: ::std::os::raw::c_int,
pub aConstraint: *mut sqlite3_index_info_sqlite3_index_constraint,
pub aConstraint: *mut sqlite3_index_constraint,
pub nOrderBy: ::std::os::raw::c_int,
pub aOrderBy: *mut sqlite3_index_info_sqlite3_index_orderby,
pub aConstraintUsage: *mut sqlite3_index_info_sqlite3_index_constraint_usage,
pub aOrderBy: *mut sqlite3_index_orderby,
pub aConstraintUsage: *mut sqlite3_index_constraint_usage,
pub idxNum: ::std::os::raw::c_int,
pub idxStr: *mut ::std::os::raw::c_char,
pub needToFreeIdxStr: ::std::os::raw::c_int,
@ -1894,7 +1908,7 @@ pub struct sqlite3_index_info {
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_constraint {
pub struct sqlite3_index_constraint {
pub iColumn: ::std::os::raw::c_int,
pub op: ::std::os::raw::c_uchar,
pub usable: ::std::os::raw::c_uchar,
@ -1902,13 +1916,13 @@ pub struct sqlite3_index_info_sqlite3_index_constraint {
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_orderby {
pub struct sqlite3_index_orderby {
pub iColumn: ::std::os::raw::c_int,
pub desc: ::std::os::raw::c_uchar,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_constraint_usage {
pub struct sqlite3_index_constraint_usage {
pub argvIndex: ::std::os::raw::c_int,
pub omit: ::std::os::raw::c_uchar,
}

View File

@ -26,17 +26,26 @@ fn is_compiler(compiler_name: &str) -> bool {
env::var("CARGO_CFG_TARGET_ENV").map_or(false, |v| v == compiler_name)
}
/// Copy bindgen file from `dir` to `out_path`.
fn copy_bindings<T: AsRef<Path>>(dir: &str, bindgen_name: &str, out_path: T) {
std::fs::copy(format!("{dir}/{bindgen_name}"), out_path)
.expect("Could not copy bindings to output directory");
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
if cfg!(feature = "in_gecko") {
// When inside mozilla-central, we are included into the build with
// sqlite3.o directly, so we don't want to provide any linker arguments.
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory");
copy_bindings("sqlite3", "bindgen_bundled_version.rs", out_path);
return;
}
if cfg!(all(
println!("cargo:rerun-if-env-changed=LIBSQLITE3_SYS_USE_PKG_CONFIG");
if env::var_os("LIBSQLITE3_SYS_USE_PKG_CONFIG").map_or(false, |s| s != "0") {
build_linked::main(&out_dir, &out_path);
} else if cfg!(all(
feature = "sqlcipher",
not(feature = "bundled-sqlcipher")
)) {
@ -92,17 +101,13 @@ mod build_bundled {
#[cfg(feature = "buildtime_bindgen")]
{
use super::{bindings, HeaderLocation};
let header = HeaderLocation::FromPath(format!("{}/sqlite3.h", lib_name));
let header = HeaderLocation::FromPath(lib_name.to_owned());
bindings::write_to_out_dir(header, out_path);
}
#[cfg(not(feature = "buildtime_bindgen"))]
{
use std::fs;
fs::copy(format!("{lib_name}/bindgen_bundled_version.rs"), out_path)
.expect("Could not copy bindings to output directory");
super::copy_bindings(lib_name, "bindgen_bundled_version.rs", out_path);
}
// println!("cargo:rerun-if-changed=sqlite3/sqlite3.c");
// println!("cargo:rerun-if-changed=sqlcipher/sqlite3.c");
println!("cargo:rerun-if-changed={lib_name}/sqlite3.c");
println!("cargo:rerun-if-changed=sqlite3/wasm32-wasi-vfs.c");
let mut cfg = cc::Build::new();
@ -181,16 +186,10 @@ mod build_bundled {
cfg.include(env::var("DEP_OPENSSL_INCLUDE").unwrap());
// cargo will resolve downstream to the static lib in
// openssl-sys
} else if is_windows {
// Windows without `-vendored-openssl` takes this to link against a prebuilt
// OpenSSL lib
cfg.include(inc_dir.to_string_lossy().as_ref());
let lib = lib_dir.join("libcrypto.lib");
cfg.flag(lib.to_string_lossy().as_ref());
} else if use_openssl {
cfg.include(inc_dir.to_string_lossy().as_ref());
// branch not taken on Windows, just `crypto` is fine.
println!("cargo:rustc-link-lib=dylib=crypto");
let lib_name = if is_windows { "libcrypto" } else { "crypto" };
println!("cargo:rustc-link-lib=dylib={}", lib_name);
println!("cargo:rustc-link-search={}", lib_dir.to_string_lossy());
} else if is_apple {
cfg.flag("-DSQLCIPHER_CRYPTO_CC");
@ -242,7 +241,7 @@ mod build_bundled {
cfg.flag("-DHAVE_LOCALTIME_R");
}
// Target wasm32-wasi can't compile the default VFS
if is_compiler("wasm32-wasi") {
if env::var("TARGET").map_or(false, |v| v == "wasm32-wasi") {
cfg.flag("-DSQLITE_OS_OTHER")
// https://github.com/rust-lang/rust/issues/74393
.flag("-DLONGDOUBLE_TYPE=double");
@ -270,6 +269,11 @@ mod build_bundled {
}
println!("cargo:rerun-if-env-changed=SQLITE_MAX_EXPR_DEPTH");
if let Ok(limit) = env::var("SQLITE_MAX_COLUMN") {
cfg.flag(&format!("-DSQLITE_MAX_COLUMN={limit}"));
}
println!("cargo:rerun-if-env-changed=SQLITE_MAX_COLUMN");
if let Ok(extras) = env::var("LIBSQLITE3_FLAGS") {
for extra in extras.split_whitespace() {
if extra.starts_with("-D") || extra.starts_with("-U") {
@ -344,7 +348,7 @@ impl From<HeaderLocation> for String {
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
HeaderLocation::FromPath(path) => format!("{}/sqlite3.h", path),
}
}
}
@ -371,11 +375,7 @@ mod build_linked {
// on buildtime_bindgen instead, but this is still supported as we
// have runtime version checks and there are good reasons to not
// want to run bindgen.
std::fs::copy(
format!("{}/bindgen_bundled_version.rs", lib_name()),
out_path,
)
.expect("Could not copy bindings to output directory");
super::copy_bindings(lib_name(), "bindgen_bundled_version.rs", out_path);
} else {
bindings::write_to_out_dir(header, out_path);
}
@ -433,8 +433,7 @@ mod build_linked {
.print_system_libs(false)
.probe(link_lib)
{
if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h");
if let Some(header) = lib.include_paths.pop() {
HeaderLocation::FromPath(header.to_string_lossy().into())
} else {
HeaderLocation::Wrapper
@ -453,8 +452,7 @@ mod build_linked {
if cfg!(feature = "vcpkg") && is_compiler("msvc") {
// See if vcpkg can find it.
if let Ok(mut lib) = vcpkg::Config::new().probe(lib_name()) {
if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h");
if let Some(header) = lib.include_paths.pop() {
return Some(HeaderLocation::FromPath(header.to_string_lossy().into()));
}
}
@ -470,14 +468,13 @@ mod bindings {
#![allow(dead_code)]
use super::HeaderLocation;
use std::fs;
use std::path::Path;
static PREBUILT_BINDGEN_PATHS: &[&str] = &["bindgen-bindings/bindgen_3.14.0.rs"];
static PREBUILT_BINDGENS: &[&str] = &["bindgen_3.14.0.rs"];
pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1];
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
let name = PREBUILT_BINDGENS[PREBUILT_BINDGENS.len() - 1];
super::copy_bindings("bindgen-bindings", name, out_path);
}
}
@ -486,8 +483,6 @@ mod bindings {
use super::HeaderLocation;
use bindgen::callbacks::{IntKind, ParseCallbacks};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use super::win_target;
@ -496,9 +491,12 @@ mod bindings {
struct SqliteTypeChooser;
impl ParseCallbacks for SqliteTypeChooser {
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
if value >= i32::MIN as i64 && value <= i32::MAX as i64 {
Some(IntKind::I32)
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
if name == "SQLITE_SERIALIZE_NOCOPY"
|| name.starts_with("SQLITE_DESERIALIZE_")
|| name.starts_with("SQLITE_PREPARE_")
{
Some(IntKind::UInt)
} else {
None
}
@ -519,12 +517,40 @@ mod bindings {
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
let header: String = header.into();
let mut output = Vec::new();
let mut bindings = bindgen::builder()
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.disable_nested_struct_naming()
.trust_clang_mangling(false)
.header(header.clone())
.parse_callbacks(Box::new(SqliteTypeChooser))
.rustfmt_bindings(true);
.blocklist_function("sqlite3_auto_extension")
.raw_line(
r#"extern "C" {
pub fn sqlite3_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}"#,
)
.blocklist_function("sqlite3_cancel_auto_extension")
.raw_line(
r#"extern "C" {
pub fn sqlite3_cancel_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}"#,
);
if cfg!(any(feature = "sqlcipher", feature = "bundled-sqlcipher")) {
bindings = bindings.clang_arg("-DSQLITE_HAS_CODEC");
@ -592,38 +618,14 @@ mod bindings {
.blocklist_function("sqlite3_vsnprintf")
.blocklist_function("sqlite3_str_vappendf")
.blocklist_type("va_list")
.blocklist_type("__builtin_va_list")
.blocklist_type("__gnuc_va_list")
.blocklist_type("__va_list_tag")
.blocklist_item("__GNUC_VA_LIST");
.blocklist_item("__.*");
}
bindings
.layout_tests(false)
.generate()
.unwrap_or_else(|_| panic!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))
.expect("could not write output of bindgen");
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
// can. This flag was added in SQLite 3.8.3, but oring it in in prior
// versions of SQLite is harmless. We don't want to not build just
// because this flag is missing (e.g., if we're linking against
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's
// output.
if !output.contains("pub const SQLITE_DETERMINISTIC") {
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
}
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(out_path)
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes())
.write_to_file(out_path)
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
}
}

View File

@ -1,9 +1,33 @@
/* automatically generated by rust-bindgen 0.61.0 */
/* automatically generated by rust-bindgen 0.63.0 */
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.39.2\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3039002;
extern "C" {
pub fn sqlite3_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_cancel_auto_extension(
xEntryPoint: ::std::option::Option<
unsafe extern "C" fn(
db: *mut sqlite3,
pzErrMsg: *mut *const ::std::os::raw::c_char,
pThunk: *const sqlite3_api_routines,
) -> ::std::os::raw::c_int,
>,
) -> ::std::os::raw::c_int;
}
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.39.4\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3039004;
pub const SQLITE_SOURCE_ID: &[u8; 85usize] =
b"2022-07-21 15:24:47 698edb77537b67c41adc68f9b892db56bcf9a55e00371a61420f3ddd668ealt1\0";
b"2022-09-29 15:55:41 a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26alt1\0";
pub const SQLITE_OK: i32 = 0;
pub const SQLITE_ERROR: i32 = 1;
pub const SQLITE_INTERNAL: i32 = 2;
@ -2036,16 +2060,6 @@ extern "C" {
onoff: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_auto_extension(
xEntryPoint: ::std::option::Option<unsafe extern "C" fn()>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_cancel_auto_extension(
xEntryPoint: ::std::option::Option<unsafe extern "C" fn()>,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_reset_auto_extension();
}
@ -2189,10 +2203,10 @@ pub struct sqlite3_module {
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info {
pub nConstraint: ::std::os::raw::c_int,
pub aConstraint: *mut sqlite3_index_info_sqlite3_index_constraint,
pub aConstraint: *mut sqlite3_index_constraint,
pub nOrderBy: ::std::os::raw::c_int,
pub aOrderBy: *mut sqlite3_index_info_sqlite3_index_orderby,
pub aConstraintUsage: *mut sqlite3_index_info_sqlite3_index_constraint_usage,
pub aOrderBy: *mut sqlite3_index_orderby,
pub aConstraintUsage: *mut sqlite3_index_constraint_usage,
pub idxNum: ::std::os::raw::c_int,
pub idxStr: *mut ::std::os::raw::c_char,
pub needToFreeIdxStr: ::std::os::raw::c_int,
@ -2204,7 +2218,7 @@ pub struct sqlite3_index_info {
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_constraint {
pub struct sqlite3_index_constraint {
pub iColumn: ::std::os::raw::c_int,
pub op: ::std::os::raw::c_uchar,
pub usable: ::std::os::raw::c_uchar,
@ -2212,13 +2226,13 @@ pub struct sqlite3_index_info_sqlite3_index_constraint {
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_orderby {
pub struct sqlite3_index_orderby {
pub iColumn: ::std::os::raw::c_int,
pub desc: ::std::os::raw::c_uchar,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_index_info_sqlite3_index_constraint_usage {
pub struct sqlite3_index_constraint_usage {
pub argvIndex: ::std::os::raw::c_int,
pub omit: ::std::os::raw::c_uchar,
}

View File

@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.39.2. By combining all the individual C code files into this
** version 3.39.4. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@ -452,9 +452,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.39.2"
#define SQLITE_VERSION_NUMBER 3039002
#define SQLITE_SOURCE_ID "2022-07-21 15:24:47 698edb77537b67c41adc68f9b892db56bcf9a55e00371a61420f3ddd668ealt1"
#define SQLITE_VERSION "3.39.4"
#define SQLITE_VERSION_NUMBER 3039004
#define SQLITE_SOURCE_ID "2022-09-29 15:55:41 a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26alt1"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -13204,6 +13204,11 @@ struct fts5_api {
/************** End of sqlite3.h *********************************************/
/************** Continuing where we left off in sqliteInt.h ******************/
/*
** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory.
*/
#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1
/*
** Include the configuration header output by 'configure' if we're using the
** autoconf-based build
@ -29665,8 +29670,13 @@ SQLITE_PRIVATE void *sqlite3OomFault(sqlite3 *db){
}
DisableLookaside;
if( db->pParse ){
Parse *pParse;
sqlite3ErrorMsg(db->pParse, "out of memory");
db->pParse->rc = SQLITE_NOMEM_BKPT;
for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){
pParse->nErr++;
pParse->rc = SQLITE_NOMEM;
}
}
}
return 0;
@ -33561,7 +33571,7 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
va_list ap;
sqlite3 *db = pParse->db;
assert( db!=0 );
assert( db->pParse==pParse );
assert( db->pParse==pParse || db->pParse->pToplevel==pParse );
db->errByteOffset = -2;
va_start(ap, zFormat);
zMsg = sqlite3VMPrintf(db, zFormat, ap);
@ -41424,6 +41434,7 @@ static const char *unixTempFileDir(void){
static int unixGetTempname(int nBuf, char *zBuf){
const char *zDir;
int iLimit = 0;
int rc = SQLITE_OK;
/* It's odd to simulate an io-error here, but really this is just
** using the io-error infrastructure to test that SQLite handles this
@ -41432,18 +41443,26 @@ static int unixGetTempname(int nBuf, char *zBuf){
zBuf[0] = 0;
SimulateIOError( return SQLITE_IOERR );
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
zDir = unixTempFileDir();
if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH;
do{
u64 r;
sqlite3_randomness(sizeof(r), &r);
assert( nBuf>2 );
zBuf[nBuf-2] = 0;
sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
zDir, r, 0);
if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR;
}while( osAccess(zBuf,0)==0 );
return SQLITE_OK;
if( zDir==0 ){
rc = SQLITE_IOERR_GETTEMPPATH;
}else{
do{
u64 r;
sqlite3_randomness(sizeof(r), &r);
assert( nBuf>2 );
zBuf[nBuf-2] = 0;
sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
zDir, r, 0);
if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){
rc = SQLITE_ERROR;
break;
}
}while( osAccess(zBuf,0)==0 );
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
return rc;
}
#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
@ -45582,10 +45601,12 @@ SQLITE_API int sqlite3_win32_set_directory8(
const char *zValue /* New value for directory being set or reset */
){
char **ppDirectory = 0;
int rc;
#ifndef SQLITE_OMIT_AUTOINIT
int rc = sqlite3_initialize();
rc = sqlite3_initialize();
if( rc ) return rc;
#endif
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){
ppDirectory = &sqlite3_data_directory;
}else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){
@ -45600,14 +45621,19 @@ SQLITE_API int sqlite3_win32_set_directory8(
if( zValue && zValue[0] ){
zCopy = sqlite3_mprintf("%s", zValue);
if ( zCopy==0 ){
return SQLITE_NOMEM_BKPT;
rc = SQLITE_NOMEM_BKPT;
goto set_directory8_done;
}
}
sqlite3_free(*ppDirectory);
*ppDirectory = zCopy;
return SQLITE_OK;
rc = SQLITE_OK;
}else{
rc = SQLITE_ERROR;
}
return SQLITE_ERROR;
set_directory8_done:
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
return rc;
}
/*
@ -48381,6 +48407,18 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){
return 0;
}
/*
** If sqlite3_temp_directory is not, take the mutex and return true.
**
** If sqlite3_temp_directory is NULL, omit the mutex and return false.
*/
static int winTempDirDefined(void){
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( sqlite3_temp_directory!=0 ) return 1;
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
return 0;
}
/*
** Create a temporary file name and store the resulting pointer into pzBuf.
** The pointer returned in pzBuf must be freed via sqlite3_free().
@ -48417,20 +48455,23 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
*/
nDir = nMax - (nPre + 15);
assert( nDir>0 );
if( sqlite3_temp_directory ){
if( winTempDirDefined() ){
int nDirLen = sqlite3Strlen30(sqlite3_temp_directory);
if( nDirLen>0 ){
if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){
nDirLen++;
}
if( nDirLen>nDir ){
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0);
}
sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory);
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
}
#if defined(__CYGWIN__)
else{
static const char *azDirs[] = {
@ -49219,7 +49260,7 @@ static BOOL winIsVerbatimPathname(
** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
** bytes in size.
*/
static int winFullPathname(
static int winFullPathnameNoMutex(
sqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
@ -49398,6 +49439,19 @@ static int winFullPathname(
}
#endif
}
static int winFullPathname(
sqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
char *zFull /* Output buffer */
){
int rc;
sqlite3_mutex *pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR);
sqlite3_mutex_enter(pMutex);
rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull);
sqlite3_mutex_leave(pMutex);
return rc;
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
@ -51742,14 +51796,24 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){
*/
SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
PCache *pCache = p->pCache;
sqlite3_pcache_page *pOther;
assert( p->nRef>0 );
assert( newPgno>0 );
assert( sqlite3PcachePageSanity(p) );
pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno));
pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0);
if( pOther ){
PgHdr *pXPage = (PgHdr*)pOther->pExtra;
assert( pXPage->nRef==0 );
pXPage->nRef++;
pCache->nRefSum++;
sqlite3PcacheDrop(pXPage);
}
sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno);
p->pgno = newPgno;
if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
assert( sqlite3PcachePageSanity(p) );
}
}
@ -53131,23 +53195,26 @@ static void pcache1Rekey(
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = (PgHdr1 *)pPg;
PgHdr1 **pp;
unsigned int h;
unsigned int hOld, hNew;
assert( pPage->iKey==iOld );
assert( pPage->pCache==pCache );
assert( iOld!=iNew ); /* The page number really is changing */
pcache1EnterMutex(pCache->pGroup);
h = iOld%pCache->nHash;
pp = &pCache->apHash[h];
assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */
hOld = iOld%pCache->nHash;
pp = &pCache->apHash[hOld];
while( (*pp)!=pPage ){
pp = &(*pp)->pNext;
}
*pp = pPage->pNext;
h = iNew%pCache->nHash;
assert( pcache1FetchNoMutex(p, iNew, 0)==0 ); /* iNew not in cache */
hNew = iNew%pCache->nHash;
pPage->iKey = iNew;
pPage->pNext = pCache->apHash[h];
pCache->apHash[h] = pPage;
pPage->pNext = pCache->apHash[hNew];
pCache->apHash[hNew] = pPage;
if( iNew>pCache->iMaxKey ){
pCache->iMaxKey = iNew;
}
@ -59905,6 +59972,7 @@ static int pager_open_journal(Pager *pPager){
if( rc!=SQLITE_OK ){
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
pPager->journalOff = 0;
}else{
assert( pPager->eState==PAGER_WRITER_LOCKED );
pPager->eState = PAGER_WRITER_CACHEMOD;
@ -61503,7 +61571,7 @@ SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager *pPager){
SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager *pPager){
assert( assert_pager_state(pPager) );
if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0;
if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0;
if( isOpen(pPager->jfd) && pPager->journalOff>0 ) return 0;
return 1;
}
@ -68647,7 +68715,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage);
memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz));
sz += sz2;
}else if( NEVER(iFree+sz>usableSize) ){
}else if( iFree+sz>usableSize ){
return SQLITE_CORRUPT_PAGE(pPage);
}
@ -75003,8 +75071,6 @@ static int balance_nonroot(
Pgno pgno; /* Temp var to store a page number in */
u8 abDone[NB+2]; /* True after i'th new page is populated */
Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */
Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */
u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */
CellArray b; /* Parsed information on cells being balanced */
memset(abDone, 0, sizeof(abDone));
@ -75428,42 +75494,39 @@ static int balance_nonroot(
** of the table is closer to a linear scan through the file. That in turn
** helps the operating system to deliver pages from the disk more rapidly.
**
** An O(n^2) insertion sort algorithm is used, but since n is never more
** than (NB+2) (a small constant), that should not be a problem.
** An O(N*N) sort algorithm is used, but since N is never more than NB+2
** (5), that is not a performance concern.
**
** When NB==3, this one optimization makes the database about 25% faster
** for large insertions and deletions.
*/
for(i=0; i<nNew; i++){
aPgOrder[i] = aPgno[i] = apNew[i]->pgno;
aPgFlags[i] = apNew[i]->pDbPage->flags;
for(j=0; j<i; j++){
if( NEVER(aPgno[j]==aPgno[i]) ){
/* This branch is taken if the set of sibling pages somehow contains
** duplicate entries. This can happen if the database is corrupt.
** It would be simpler to detect this as part of the loop below, but
** we do the detection here in order to avoid populating the pager
** cache with two separate objects associated with the same
** page number. */
assert( CORRUPT_DB );
rc = SQLITE_CORRUPT_BKPT;
goto balance_cleanup;
}
}
aPgno[i] = apNew[i]->pgno;
assert( apNew[i]->pDbPage->flags & PGHDR_WRITEABLE );
assert( apNew[i]->pDbPage->flags & PGHDR_DIRTY );
}
for(i=0; i<nNew; i++){
int iBest = 0; /* aPgno[] index of page number to use */
for(j=1; j<nNew; j++){
if( aPgOrder[j]<aPgOrder[iBest] ) iBest = j;
for(i=0; i<nNew-1; i++){
int iB = i;
for(j=i+1; j<nNew; j++){
if( apNew[j]->pgno < apNew[iB]->pgno ) iB = j;
}
pgno = aPgOrder[iBest];
aPgOrder[iBest] = 0xffffffff;
if( iBest!=i ){
if( iBest>i ){
sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0);
}
sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]);
apNew[i]->pgno = pgno;
/* If apNew[i] has a page number that is bigger than any of the
** subsequence apNew[i] entries, then swap apNew[i] with the subsequent
** entry that has the smallest page number (which we know to be
** entry apNew[iB]).
*/
if( iB!=i ){
Pgno pgnoA = apNew[i]->pgno;
Pgno pgnoB = apNew[iB]->pgno;
Pgno pgnoTemp = (PENDING_BYTE/pBt->pageSize)+1;
u16 fgA = apNew[i]->pDbPage->flags;
u16 fgB = apNew[iB]->pDbPage->flags;
sqlite3PagerRekey(apNew[i]->pDbPage, pgnoTemp, fgB);
sqlite3PagerRekey(apNew[iB]->pDbPage, pgnoA, fgA);
sqlite3PagerRekey(apNew[i]->pDbPage, pgnoB, fgB);
apNew[i]->pgno = pgnoB;
apNew[iB]->pgno = pgnoA;
}
}
@ -81395,6 +81458,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall(
addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function,
p1, p2, p3, (char*)pCtx, P4_FUNCCTX);
sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef);
sqlite3MayAbort(pParse);
return addr;
}
@ -81730,6 +81794,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
|| opcode==OP_VDestroy
|| opcode==OP_VCreate
|| opcode==OP_ParseSchema
|| opcode==OP_Function || opcode==OP_PureFunc
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull)
&& ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort))
){
@ -101783,7 +101848,7 @@ SQLITE_API void sqlite3pager_reset(Pager *pPager);
#define CIPHER_STR(s) #s
#ifndef CIPHER_VERSION_NUMBER
#define CIPHER_VERSION_NUMBER 4.5.2
#define CIPHER_VERSION_NUMBER 4.5.3
#endif
#ifndef CIPHER_VERSION_BUILD
@ -138008,6 +138073,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
*/
case PragTyp_TEMP_STORE_DIRECTORY: {
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( !zRight ){
returnSingleText(v, sqlite3_temp_directory);
}else{
@ -138017,6 +138083,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
sqlite3ErrorMsg(pParse, "not a writable directory");
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
goto pragma_out;
}
}
@ -138034,6 +138101,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
#endif /* SQLITE_OMIT_WSD */
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
break;
}
@ -138052,6 +138120,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
*/
case PragTyp_DATA_STORE_DIRECTORY: {
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( !zRight ){
returnSingleText(v, sqlite3_data_directory);
}else{
@ -138061,6 +138130,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
sqlite3ErrorMsg(pParse, "not a writable directory");
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
goto pragma_out;
}
}
@ -138072,6 +138142,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
#endif /* SQLITE_OMIT_WSD */
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
break;
}
#endif
@ -142560,7 +142631,7 @@ static void generateSortTail(
if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak);
VdbeCoverage(v);
codeOffset(v, p->iOffset, addrContinue);
assert( p->iLimit==0 && p->iOffset==0 );
sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab);
bSeq = 0;
}else{
@ -142568,6 +142639,9 @@ static void generateSortTail(
codeOffset(v, p->iOffset, addrContinue);
iSortTab = iTab;
bSeq = 1;
if( p->iOffset>0 ){
sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1);
}
}
for(i=0, iCol=nKey+bSeq-1; i<nColumn; i++){
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
@ -144560,10 +144634,11 @@ static int multiSelectOrderBy(
*/
sqlite3VdbeResolveLabel(v, labelEnd);
/* Reassembly the compound query so that it will be freed correctly
/* Reassemble the compound query so that it will be freed correctly
** by the calling function */
if( pSplit->pPrior ){
sqlite3SelectDelete(db, pSplit->pPrior);
sqlite3ParserAddCleanup(pParse,
(void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior);
}
pSplit->pPrior = pPrior;
pPrior->pNext = pSplit;
@ -146082,6 +146157,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
|| p->pSrc->nSrc!=1
|| p->pSrc->a[0].pSelect
|| pAggInfo->nFunc!=1
|| p->pHaving
){
return 0;
}
@ -149319,6 +149395,23 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
Vdbe *v;
char *z;
/* If this is a new CREATE TABLE statement, and if shadow tables
** are read-only, and the trigger makes a change to a shadow table,
** then raise an error - do not allow the trigger to be created. */
if( sqlite3ReadOnlyShadowTables(db) ){
TriggerStep *pStep;
for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){
if( pStep->zTarget!=0
&& sqlite3ShadowTableName(db, pStep->zTarget)
){
sqlite3ErrorMsg(pParse,
"trigger \"%s\" may not write to shadow table \"%s\"",
pTrig->zName, pStep->zTarget);
goto triggerfinish_cleanup;
}
}
}
/* Make an entry in the sqlite_schema table */
v = sqlite3GetVdbe(pParse);
if( v==0 ) goto triggerfinish_cleanup;
@ -155143,7 +155236,8 @@ static int codeEqualityTerm(
}
sqlite3ExprDelete(db, pX);
}else{
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
int n = sqlite3ExprVectorSize(pX->pLeft);
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n));
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
}
pX = pExpr;
@ -182345,7 +182439,7 @@ struct Fts3MultiSegReader {
int nAdvance; /* How many seg-readers to advance */
Fts3SegFilter *pFilter; /* Pointer to filter object */
char *aBuffer; /* Buffer to merge doclists in */
int nBuffer; /* Allocated size of aBuffer[] in bytes */
i64 nBuffer; /* Allocated size of aBuffer[] in bytes */
int iColFilter; /* If >=0, filter for this column */
int bRestart;
@ -185041,7 +185135,7 @@ static int fts3TermSelectMerge(
**
** Similar padding is added in the fts3DoclistOrMerge() function.
*/
pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1);
pTS->aaOutput[0] = sqlite3_malloc64((i64)nDoclist + FTS3_VARINT_MAX + 1);
pTS->anOutput[0] = nDoclist;
if( pTS->aaOutput[0] ){
memcpy(pTS->aaOutput[0], aDoclist, nDoclist);
@ -186529,7 +186623,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
nDistance = iPrev - nMaxUndeferred;
}
aOut = (char *)sqlite3_malloc(nPoslist+8);
aOut = (char *)sqlite3Fts3MallocZero(nPoslist+FTS3_BUFFER_PADDING);
if( !aOut ){
sqlite3_free(aPoslist);
return SQLITE_NOMEM;
@ -186898,7 +186992,7 @@ static int fts3EvalIncrPhraseNext(
if( bEof==0 ){
int nList = 0;
int nByte = a[p->nToken-1].nList;
char *aDoclist = sqlite3_malloc(nByte+FTS3_BUFFER_PADDING);
char *aDoclist = sqlite3_malloc64((i64)nByte+FTS3_BUFFER_PADDING);
if( !aDoclist ) return SQLITE_NOMEM;
memcpy(aDoclist, a[p->nToken-1].pList, nByte+1);
memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING);
@ -191134,7 +191228,7 @@ static int porterNext(
if( n>c->nAllocated ){
char *pNew;
c->nAllocated = n+20;
pNew = sqlite3_realloc(c->zToken, c->nAllocated);
pNew = sqlite3_realloc64(c->zToken, c->nAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->zToken = pNew;
}
@ -191886,7 +191980,7 @@ static int simpleNext(
if( n>c->nTokenAllocated ){
char *pNew;
c->nTokenAllocated = n+20;
pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
pNew = sqlite3_realloc64(c->pToken, c->nTokenAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->pToken = pNew;
}
@ -193048,7 +193142,7 @@ static int fts3PendingListAppendVarint(
/* Allocate or grow the PendingList as required. */
if( !p ){
p = sqlite3_malloc(sizeof(*p) + 100);
p = sqlite3_malloc64(sizeof(*p) + 100);
if( !p ){
return SQLITE_NOMEM;
}
@ -193057,14 +193151,14 @@ static int fts3PendingListAppendVarint(
p->nData = 0;
}
else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){
int nNew = p->nSpace * 2;
p = sqlite3_realloc(p, sizeof(*p) + nNew);
i64 nNew = p->nSpace * 2;
p = sqlite3_realloc64(p, sizeof(*p) + nNew);
if( !p ){
sqlite3_free(*pp);
*pp = 0;
return SQLITE_NOMEM;
}
p->nSpace = nNew;
p->nSpace = (int)nNew;
p->aData = (char *)&p[1];
}
@ -193621,7 +193715,7 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
int nByte = sqlite3_blob_bytes(p->pSegments);
*pnBlob = nByte;
if( paBlob ){
char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING);
char *aByte = sqlite3_malloc64((i64)nByte + FTS3_NODE_PADDING);
if( !aByte ){
rc = SQLITE_NOMEM;
}else{
@ -193738,7 +193832,7 @@ static int fts3SegReaderNext(
int nTerm = fts3HashKeysize(pElem);
if( (nTerm+1)>pReader->nTermAlloc ){
sqlite3_free(pReader->zTerm);
pReader->zTerm = (char*)sqlite3_malloc((nTerm+1)*2);
pReader->zTerm = (char*)sqlite3_malloc64(((i64)nTerm+1)*2);
if( !pReader->zTerm ) return SQLITE_NOMEM;
pReader->nTermAlloc = (nTerm+1)*2;
}
@ -193746,7 +193840,7 @@ static int fts3SegReaderNext(
pReader->zTerm[nTerm] = '\0';
pReader->nTerm = nTerm;
aCopy = (char*)sqlite3_malloc(nCopy);
aCopy = (char*)sqlite3_malloc64(nCopy);
if( !aCopy ) return SQLITE_NOMEM;
memcpy(aCopy, pList->aData, nCopy);
pReader->nNode = pReader->nDoclist = nCopy;
@ -194033,7 +194127,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(
nExtra = nRoot + FTS3_NODE_PADDING;
}
pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra);
pReader = (Fts3SegReader *)sqlite3_malloc64(sizeof(Fts3SegReader) + nExtra);
if( !pReader ){
return SQLITE_NOMEM;
}
@ -194125,7 +194219,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
if( nElem==nAlloc ){
Fts3HashElem **aElem2;
nAlloc += 16;
aElem2 = (Fts3HashElem **)sqlite3_realloc(
aElem2 = (Fts3HashElem **)sqlite3_realloc64(
aElem, nAlloc*sizeof(Fts3HashElem *)
);
if( !aElem2 ){
@ -194459,7 +194553,7 @@ static int fts3NodeAddTerm(
** this is not expected to be a serious problem.
*/
assert( pTree->aData==(char *)&pTree[1] );
pTree->aData = (char *)sqlite3_malloc(nReq);
pTree->aData = (char *)sqlite3_malloc64(nReq);
if( !pTree->aData ){
return SQLITE_NOMEM;
}
@ -194477,7 +194571,7 @@ static int fts3NodeAddTerm(
if( isCopyTerm ){
if( pTree->nMalloc<nTerm ){
char *zNew = sqlite3_realloc(pTree->zMalloc, nTerm*2);
char *zNew = sqlite3_realloc64(pTree->zMalloc, (i64)nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@ -194503,7 +194597,7 @@ static int fts3NodeAddTerm(
** now. Instead, the term is inserted into the parent of pTree. If pTree
** has no parent, one is created here.
*/
pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize);
pNew = (SegmentNode *)sqlite3_malloc64(sizeof(SegmentNode) + p->nNodeSize);
if( !pNew ){
return SQLITE_NOMEM;
}
@ -194641,7 +194735,7 @@ static int fts3SegWriterAdd(
){
int nPrefix; /* Size of term prefix in bytes */
int nSuffix; /* Size of term suffix in bytes */
int nReq; /* Number of bytes required on leaf page */
i64 nReq; /* Number of bytes required on leaf page */
int nData;
SegmentWriter *pWriter = *ppWriter;
@ -194650,13 +194744,13 @@ static int fts3SegWriterAdd(
sqlite3_stmt *pStmt;
/* Allocate the SegmentWriter structure */
pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter));
pWriter = (SegmentWriter *)sqlite3_malloc64(sizeof(SegmentWriter));
if( !pWriter ) return SQLITE_NOMEM;
memset(pWriter, 0, sizeof(SegmentWriter));
*ppWriter = pWriter;
/* Allocate a buffer in which to accumulate data */
pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize);
pWriter->aData = (char *)sqlite3_malloc64(p->nNodeSize);
if( !pWriter->aData ) return SQLITE_NOMEM;
pWriter->nSize = p->nNodeSize;
@ -194731,7 +194825,7 @@ static int fts3SegWriterAdd(
** the buffer to make it large enough.
*/
if( nReq>pWriter->nSize ){
char *aNew = sqlite3_realloc(pWriter->aData, nReq);
char *aNew = sqlite3_realloc64(pWriter->aData, nReq);
if( !aNew ) return SQLITE_NOMEM;
pWriter->aData = aNew;
pWriter->nSize = nReq;
@ -194756,7 +194850,7 @@ static int fts3SegWriterAdd(
*/
if( isCopyTerm ){
if( nTerm>pWriter->nMalloc ){
char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2);
char *zNew = sqlite3_realloc64(pWriter->zMalloc, (i64)nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@ -195064,12 +195158,12 @@ static void fts3ColumnFilter(
static int fts3MsrBufferData(
Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */
char *pList,
int nList
i64 nList
){
if( nList>pMsr->nBuffer ){
char *pNew;
pMsr->nBuffer = nList*2;
pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer);
pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer);
if( !pNew ) return SQLITE_NOMEM;
pMsr->aBuffer = pNew;
}
@ -195125,7 +195219,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext(
fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp);
if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){
rc = fts3MsrBufferData(pMsr, pList, nList+1);
rc = fts3MsrBufferData(pMsr, pList, (i64)nList+1);
if( rc!=SQLITE_OK ) return rc;
assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 );
pList = pMsr->aBuffer;
@ -195262,11 +195356,11 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){
return SQLITE_OK;
}
static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, int nReq){
static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, i64 nReq){
if( nReq>pCsr->nBuffer ){
char *aNew;
pCsr->nBuffer = nReq*2;
aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer);
aNew = sqlite3_realloc64(pCsr->aBuffer, pCsr->nBuffer);
if( !aNew ){
return SQLITE_NOMEM;
}
@ -195357,7 +195451,8 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
){
pCsr->nDoclist = apSegment[0]->nDoclist;
if( fts3SegReaderIsPending(apSegment[0]) ){
rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist);
rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist,
(i64)pCsr->nDoclist);
pCsr->aDoclist = pCsr->aBuffer;
}else{
pCsr->aDoclist = apSegment[0]->aDoclist;
@ -195410,7 +195505,8 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
rc = fts3GrowSegReaderBuffer(pCsr, nByte+nDoclist+FTS3_NODE_PADDING);
rc = fts3GrowSegReaderBuffer(pCsr,
(i64)nByte+nDoclist+FTS3_NODE_PADDING);
if( rc ) return rc;
if( isFirst ){
@ -195436,7 +195532,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
fts3SegReaderSort(apSegment, nMerge, j, xCmp);
}
if( nDoclist>0 ){
rc = fts3GrowSegReaderBuffer(pCsr, nDoclist+FTS3_NODE_PADDING);
rc = fts3GrowSegReaderBuffer(pCsr, (i64)nDoclist+FTS3_NODE_PADDING);
if( rc ) return rc;
memset(&pCsr->aBuffer[nDoclist], 0, FTS3_NODE_PADDING);
pCsr->aDoclist = pCsr->aBuffer;
@ -196149,7 +196245,7 @@ struct NodeReader {
static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){
if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){
int nAlloc = nMin;
char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc);
char *a = (char *)sqlite3_realloc64(pBlob->a, nAlloc);
if( a ){
pBlob->nAlloc = nAlloc;
pBlob->a = a;
@ -196946,7 +197042,7 @@ static int fts3RepackSegdirLevel(
if( nIdx>=nAlloc ){
int *aNew;
nAlloc += 16;
aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int));
aNew = sqlite3_realloc64(aIdx, nAlloc*sizeof(int));
if( !aNew ){
rc = SQLITE_NOMEM;
break;
@ -197320,7 +197416,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
/* Allocate space for the cursor, filter and writer objects */
const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter);
pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc);
pWriter = (IncrmergeWriter *)sqlite3_malloc64(nAlloc);
if( !pWriter ) return SQLITE_NOMEM;
pFilter = (Fts3SegFilter *)&pWriter[1];
pCsr = (Fts3MultiSegReader *)&pFilter[1];
@ -197956,7 +198052,7 @@ SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(
return SQLITE_OK;
}
pRet = (char *)sqlite3_malloc(p->pList->nData);
pRet = (char *)sqlite3_malloc64(p->pList->nData);
if( !pRet ) return SQLITE_NOMEM;
nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy);
@ -197976,7 +198072,7 @@ SQLITE_PRIVATE int sqlite3Fts3DeferToken(
int iCol /* Column that token must appear in (or -1) */
){
Fts3DeferredToken *pDeferred;
pDeferred = sqlite3_malloc(sizeof(*pDeferred));
pDeferred = sqlite3_malloc64(sizeof(*pDeferred));
if( !pDeferred ){
return SQLITE_NOMEM;
}
@ -209555,7 +209651,7 @@ static int geopolyUpdate(
sqlite3_free(p);
nChange = 1;
}
for(jj=1; jj<pRtree->nAux; jj++){
for(jj=1; jj<nData-2; jj++){
nChange++;
sqlite3_bind_value(pUp, jj+2, aData[jj+2]);
}
@ -210158,8 +210254,9 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
if( U_SUCCESS(status) ){
sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete);
}else{
assert(!pExpr);
pExpr = sqlite3_get_auxdata(p, 0);
}
if( !pExpr ){
icuFunctionError(p, "uregex_open", status);
return;
}
@ -242045,7 +242142,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2022-07-21 15:24:47 698edb77537b67c41adc68f9b892db56bcf9a55e00371a61420f3ddd668e6603", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2022-09-29 15:55:41 a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26b309", -1, SQLITE_TRANSIENT);
}
/*

View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.39.2"
#define SQLITE_VERSION_NUMBER 3039002
#define SQLITE_SOURCE_ID "2022-07-21 15:24:47 698edb77537b67c41adc68f9b892db56bcf9a55e00371a61420f3ddd668ealt1"
#define SQLITE_VERSION "3.39.4"
#define SQLITE_VERSION_NUMBER 3039004
#define SQLITE_SOURCE_ID "2022-09-29 15:55:41 a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26alt1"
/*
** CAPI3REF: Run-Time Library Version Numbers

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.39.4"
#define SQLITE_VERSION_NUMBER 3039004
#define SQLITE_SOURCE_ID "2022-09-29 15:55:41 a29f9949895322123f7c38fbe94c649a9d6e6c9cd0c3b41c96d694552f26b309"
#define SQLITE_VERSION "3.42.0"
#define SQLITE_VERSION_NUMBER 3042000
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -563,6 +563,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8))
#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8))
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8))
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8))
@ -670,13 +671,17 @@ SQLITE_API int sqlite3_exec(
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
** of an [sqlite3_io_methods] object.
** of an [sqlite3_io_methods] object. These values are ordered from
** lest restrictive to most restrictive.
**
** The argument to xLock() is always SHARED or higher. The argument to
** xUnlock is either SHARED or NONE.
*/
#define SQLITE_LOCK_NONE 0
#define SQLITE_LOCK_SHARED 1
#define SQLITE_LOCK_RESERVED 2
#define SQLITE_LOCK_PENDING 3
#define SQLITE_LOCK_EXCLUSIVE 4
#define SQLITE_LOCK_NONE 0 /* xUnlock() only */
#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */
#define SQLITE_LOCK_RESERVED 2 /* xLock() only */
#define SQLITE_LOCK_PENDING 3 /* xLock() only */
#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */
/*
** CAPI3REF: Synchronization Type Flags
@ -754,7 +759,14 @@ struct sqlite3_file {
** <li> [SQLITE_LOCK_PENDING], or
** <li> [SQLITE_LOCK_EXCLUSIVE].
** </ul>
** xLock() increases the lock. xUnlock() decreases the lock.
** xLock() upgrades the database file lock. In other words, xLock() moves the
** database file lock in the direction NONE toward EXCLUSIVE. The argument to
** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
** SQLITE_LOCK_NONE. If the database file lock is already at or above the
** requested lock, then the call to xLock() is a no-op.
** xUnlock() downgrades the database file lock to either SHARED or NONE.
* If the lock is already at or below the requested lock state, then the call
** to xUnlock() is a no-op.
** The xCheckReservedLock() method checks whether any database connection,
** either in this process or in some other process, is holding a RESERVED,
** PENDING, or EXCLUSIVE lock on the file. It returns true
@ -859,9 +871,8 @@ struct sqlite3_io_methods {
** opcode causes the xFileControl method to write the current state of
** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED],
** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE])
** into an integer that the pArg argument points to. This capability
** is used during testing and is only available when the SQLITE_TEST
** compile-time option is used.
** into an integer that the pArg argument points to.
** This capability is only available if SQLite is compiled with [SQLITE_DEBUG].
**
** <li>[[SQLITE_FCNTL_SIZE_HINT]]
** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS
@ -1165,7 +1176,6 @@ struct sqlite3_io_methods {
** in wal mode after the client has finished copying pages from the wal
** file to the database file, but before the *-shm file is updated to
** record the fact that the pages have been checkpointed.
** </ul>
**
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
@ -1178,10 +1188,16 @@ struct sqlite3_io_methods {
** the database is not a wal-mode db, or if there is no such connection in any
** other process. This opcode cannot be used to detect transactions opened
** by clients within the current process, only within other processes.
** </ul>
**
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** Used by the cksmvfs VFS module only.
** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the
** [checksum VFS shim] only.
**
** <li>[[SQLITE_FCNTL_RESET_CACHE]]
** If there is currently no transaction open on the database, and the
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
** purges the contents of the in-memory page cache. If there is an open
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@ -1224,6 +1240,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKPT_START 39
#define SQLITE_FCNTL_EXTERNAL_READER 40
#define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1253,6 +1270,26 @@ typedef struct sqlite3_mutex sqlite3_mutex;
*/
typedef struct sqlite3_api_routines sqlite3_api_routines;
/*
** CAPI3REF: File Name
**
** Type [sqlite3_filename] is used by SQLite to pass filenames to the
** xOpen method of a [VFS]. It may be cast to (const char*) and treated
** as a normal, nul-terminated, UTF-8 buffer containing the filename, but
** may also be passed to special APIs such as:
**
** <ul>
** <li> sqlite3_filename_database()
** <li> sqlite3_filename_journal()
** <li> sqlite3_filename_wal()
** <li> sqlite3_uri_parameter()
** <li> sqlite3_uri_boolean()
** <li> sqlite3_uri_int64()
** <li> sqlite3_uri_key()
** </ul>
*/
typedef const char *sqlite3_filename;
/*
** CAPI3REF: OS Interface Object
**
@ -1431,7 +1468,7 @@ struct sqlite3_vfs {
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
@ -1618,20 +1655,23 @@ SQLITE_API int sqlite3_os_end(void);
** must ensure that no other SQLite interfaces are invoked by other
** threads while sqlite3_config() is running.</b>
**
** The sqlite3_config() interface
** may only be invoked prior to library initialization using
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
** [sqlite3_shutdown()] then it will return SQLITE_MISUSE.
** Note, however, that ^sqlite3_config() can be called as part of the
** implementation of an application-defined [sqlite3_os_init()].
**
** The first argument to sqlite3_config() is an integer
** [configuration option] that determines
** what property of SQLite is to be configured. Subsequent arguments
** vary depending on the [configuration option]
** in the first argument.
**
** For most configuration options, the sqlite3_config() interface
** may only be invoked prior to library initialization using
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
** The exceptional configuration options that may be invoked at any time
** are called "anytime configuration options".
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
** [sqlite3_shutdown()] with a first argument that is not an anytime
** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE.
** Note, however, that ^sqlite3_config() can be called as part of the
** implementation of an application-defined [sqlite3_os_init()].
**
** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
** ^If the option is unknown or SQLite is unable to set the option
** then this routine returns a non-zero [error code].
@ -1739,6 +1779,23 @@ struct sqlite3_mem_methods {
** These constants are the available integer configuration options that
** can be passed as the first argument to the [sqlite3_config()] interface.
**
** Most of the configuration options for sqlite3_config()
** will only work if invoked prior to [sqlite3_initialize()] or after
** [sqlite3_shutdown()]. The few exceptions to this rule are called
** "anytime configuration options".
** ^Calling [sqlite3_config()] with a first argument that is not an
** anytime configuration option in between calls to [sqlite3_initialize()] and
** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE.
**
** The set of anytime configuration options can change (by insertions
** and/or deletions) from one release of SQLite to the next.
** As of SQLite version 3.42.0, the complete set of anytime configuration
** options is:
** <ul>
** <li> SQLITE_CONFIG_LOG
** <li> SQLITE_CONFIG_PCACHE_HDRSZ
** </ul>
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
** should check the return code from [sqlite3_config()] to make sure that
@ -2085,28 +2142,28 @@ struct sqlite3_mem_methods {
** compile-time option is not set, then the default maximum is 1073741824.
** </dl>
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
@ -2147,7 +2204,7 @@ struct sqlite3_mem_methods {
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero.
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd>
@ -2297,8 +2354,12 @@ struct sqlite3_mem_methods {
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
** </ol>
** Because resetting a database is destructive and irreversible, the
** process requires the use of this obscure API and multiple steps to help
** ensure that it does not happen by accident.
** process requires the use of this obscure API and multiple steps to
** help ensure that it does not happen by accident. Because this
** feature must be capable of resetting corrupt databases, and
** shutting down virtual tables may require access to that corrupt
** storage, the library must abandon any installed virtual tables
** without calling their xDestroy() methods.
**
** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the
@ -2309,6 +2370,7 @@ struct sqlite3_mem_methods {
** <ul>
** <li> The [PRAGMA writable_schema=ON] statement.
** <li> The [PRAGMA journal_mode=OFF] statement.
** <li> The [PRAGMA schema_version=N] statement.
** <li> Writes to the [sqlite_dbpage] virtual table.
** <li> Direct writes to [shadow tables].
** </ul>
@ -2336,7 +2398,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_DQS_DML]]
** <dt>SQLITE_DBCONFIG_DQS_DML</td>
** <dt>SQLITE_DBCONFIG_DQS_DML</dt>
** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
** the legacy [double-quoted string literal] misfeature for DML statements
** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
@ -2345,7 +2407,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_DQS_DDL]]
** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
** <dt>SQLITE_DBCONFIG_DQS_DDL</dt>
** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates
** the legacy [double-quoted string literal] misfeature for DDL statements,
** such as CREATE TABLE and CREATE INDEX. The
@ -2354,7 +2416,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]]
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td>
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</dt>
** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to
** assume that database schemas are untainted by malicious content.
** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite
@ -2374,7 +2436,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td>
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag. When activated, this flag causes all newly
** created database file to have a schema format version number (the 4-byte
@ -2383,7 +2445,7 @@ struct sqlite3_mem_methods {
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
** newly created databases are generally not understandable by SQLite versions
** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there
** is now scarcely any need to generated database files that are compatible
** is now scarcely any need to generate database files that are compatible
** all the way back to version 3.0.0, and so this setting is of little
** practical use, but is provided so that SQLite can continue to claim the
** ability to generate new database files that are compatible with version
@ -2394,6 +2456,38 @@ struct sqlite3_mem_methods {
** not considered a bug since SQLite versions 3.3.0 and earlier do not support
** either generated columns or decending indexes.
** </dd>
**
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt>
** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in
** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears
** a flag that enables collection of the sqlite3_stmt_scanstatus_v2()
** statistics. For statistics to be collected, the flag must be set on
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default. This option takes two arguments: an integer and a pointer to
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
** argument points to.
** </dd>
**
** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]]
** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt>
** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order
** in which tables and indexes are scanned so that the scans start at the end
** and work toward the beginning rather than starting at the beginning and
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
** same as setting [PRAGMA reverse_unordered_selects]. This option takes
** two arguments which are an integer and a pointer to an integer. The first
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
** reverse scan order flag, respectively. If the second argument is not NULL,
** then 0 or 1 is written into the integer that the second argument points to
** depending on if the reverse scan order flag is set after processing the
** first argument.
** </dd>
**
** </dl>
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
@ -2414,7 +2508,9 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
@ -2636,8 +2732,12 @@ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*);
** ^A call to sqlite3_interrupt(D) that occurs when there are no running
** SQL statements is a no-op and has no effect on SQL statements
** that are started after the sqlite3_interrupt() call returns.
**
** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether
** or not an interrupt is currently in effect for [database connection] D.
*/
SQLITE_API void sqlite3_interrupt(sqlite3*);
SQLITE_API int sqlite3_is_interrupted(sqlite3*);
/*
** CAPI3REF: Determine If An SQL Statement Is Complete
@ -3255,8 +3355,8 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same
** information as is provided by the [sqlite3_profile()] callback.
** ^The P argument is a pointer to the [prepared statement] and the
** X argument points to a 64-bit integer which is the estimated of
** the number of nanosecond that the prepared statement took to run.
** X argument points to a 64-bit integer which is approximately
** the number of nanoseconds that the prepared statement took to run.
** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes.
**
** [[SQLITE_TRACE_ROW]] <dt>SQLITE_TRACE_ROW</dt>
@ -3319,7 +3419,7 @@ SQLITE_API int sqlite3_trace_v2(
**
** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback
** function X to be invoked periodically during long running calls to
** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for
** [sqlite3_step()] and [sqlite3_prepare()] and similar for
** database connection D. An example use for this
** interface is to keep a GUI updated during a large query.
**
@ -3344,6 +3444,13 @@ SQLITE_API int sqlite3_trace_v2(
** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
** The progress handler callback would originally only be invoked from the
** bytecode engine. It still might be invoked during [sqlite3_prepare()]
** and similar because those routines might force a reparse of the schema
** which involves running the bytecode engine. However, beginning with
** SQLite version 3.41.0, the progress handler callback might also be
** invoked directly from [sqlite3_prepare()] while analyzing and generating
** code for complex queries.
*/
SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
@ -3380,13 +3487,18 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
**
** <dl>
** ^(<dt>[SQLITE_OPEN_READONLY]</dt>
** <dd>The database is opened in read-only mode. If the database does not
** already exist, an error is returned.</dd>)^
** <dd>The database is opened in read-only mode. If the database does
** not already exist, an error is returned.</dd>)^
**
** ^(<dt>[SQLITE_OPEN_READWRITE]</dt>
** <dd>The database is opened for reading and writing if possible, or reading
** only if the file is write protected by the operating system. In either
** case the database must already exist, otherwise an error is returned.</dd>)^
** <dd>The database is opened for reading and writing if possible, or
** reading only if the file is write protected by the operating
** system. In either case the database must already exist, otherwise
** an error is returned. For historical reasons, if opening in
** read-write mode fails due to OS-level permissions, an attempt is
** made to open it in read-only mode. [sqlite3_db_readonly()] can be
** used to determine whether the database is actually
** read-write.</dd>)^
**
** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt>
** <dd>The database is opened for reading and writing, and is created if
@ -3424,6 +3536,9 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** <dd>The database is opened [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
** The [use of shared cache mode is discouraged] and hence shared cache
** capabilities may be omitted from many builds of SQLite. In such cases,
** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
** <dd>The database is opened [shared cache] disabled, overriding
@ -3439,7 +3554,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** to return an extended result code.</dd>
**
** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt>
** <dd>The database filename is not allowed to be a symbolic link</dd>
** <dd>The database filename is not allowed to contain a symbolic link</dd>
** </dl>)^
**
** If the 3rd parameter to sqlite3_open_v2() is not one of the
@ -3698,10 +3813,10 @@ SQLITE_API int sqlite3_open_v2(
**
** See the [URI filename] documentation for additional information.
*/
SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam);
SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault);
SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64);
SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N);
/*
** CAPI3REF: Translate filenames
@ -3730,9 +3845,9 @@ SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
** return value from [sqlite3_db_filename()], then the result is
** undefined and is likely a memory access violation.
*/
SQLITE_API const char *sqlite3_filename_database(const char*);
SQLITE_API const char *sqlite3_filename_journal(const char*);
SQLITE_API const char *sqlite3_filename_wal(const char*);
SQLITE_API const char *sqlite3_filename_database(sqlite3_filename);
SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename);
SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename);
/*
** CAPI3REF: Database File Corresponding To A Journal
@ -3798,14 +3913,14 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
** then the corresponding [sqlite3_module.xClose() method should also be
** invoked prior to calling sqlite3_free_filename(Y).
*/
SQLITE_API char *sqlite3_create_filename(
SQLITE_API sqlite3_filename sqlite3_create_filename(
const char *zDatabase,
const char *zJournal,
const char *zWal,
int nParam,
const char **azParam
);
SQLITE_API void sqlite3_free_filename(char*);
SQLITE_API void sqlite3_free_filename(sqlite3_filename);
/*
** CAPI3REF: Error Codes And Messages
@ -5364,10 +5479,21 @@ SQLITE_API int sqlite3_create_window_function(
** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in
** schema structures such as [CHECK constraints], [DEFAULT clauses],
** [expression indexes], [partial indexes], or [generated columns].
** The SQLITE_DIRECTONLY flags is a security feature which is recommended
** for all [application-defined SQL functions], and especially for functions
** that have side-effects or that could potentially leak sensitive
** information.
** <p>
** The SQLITE_DIRECTONLY flag is recommended for any
** [application-defined SQL function]
** that has side-effects or that could potentially leak sensitive information.
** This will prevent attacks in which an application is tricked
** into using a database file that has had its schema surreptiously
** modified to invoke the application-defined function in ways that are
** harmful.
** <p>
** Some people say it is good practice to set SQLITE_DIRECTONLY on all
** [application-defined SQL functions], regardless of whether or not they
** are security sensitive, as doing so prevents those functions from being used
** inside of the database schema, and thus ensures that the database
** can be inspected and modified using generic tools (such as the [CLI])
** that do not have access to the application-defined functions.
** </dd>
**
** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd>
@ -5573,6 +5699,28 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
SQLITE_API int sqlite3_value_nochange(sqlite3_value*);
SQLITE_API int sqlite3_value_frombind(sqlite3_value*);
/*
** CAPI3REF: Report the internal text encoding state of an sqlite3_value object
** METHOD: sqlite3_value
**
** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8],
** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding
** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X)
** returns something other than SQLITE_TEXT, then the return value from
** sqlite3_value_encoding(X) is meaningless. ^Calls to
** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], [sqlite3_value_text16be(X)],
** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or
** [sqlite3_value_bytes16(X)] might change the encoding of the value X and
** thus change the return from subsequent calls to sqlite3_value_encoding(X).
**
** This routine is intended for used by applications that test and validate
** the SQLite implementation. This routine is inquiring about the opaque
** internal state of an [sqlite3_value] object. Ordinary applications should
** not need to know what the internal state of an sqlite3_value object is and
** hence should not need to use this interface.
*/
SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
** METHOD: sqlite3_value
@ -5625,7 +5773,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
**
** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
** allocate error occurs.
** allocation error occurs.
**
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
@ -5830,9 +5978,10 @@ typedef void (*sqlite3_destructor_type)(void*);
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** ^SQLite takes the text result from the application from
** the 2nd parameter of the sqlite3_result_text* interfaces.
** ^If the 3rd parameter to the sqlite3_result_text* interfaces
** is negative, then SQLite takes result text from the 2nd parameter
** through the first zero character.
** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces
** other than sqlite3_result_text64() is negative, then SQLite computes
** the string length itself by searching the 2nd parameter for the first
** zero character.
** ^If the 3rd parameter to the sqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
@ -6106,6 +6255,13 @@ SQLITE_API void sqlite3_activate_cerod(
** of the default VFS is not implemented correctly, or not implemented at
** all, then the behavior of sqlite3_sleep() may deviate from the description
** in the previous paragraphs.
**
** If a negative argument is passed to sqlite3_sleep() the results vary by
** VFS and operating system. Some system treat a negative argument as an
** instruction to sleep forever. Others understand it to mean do not sleep
** at all. ^In SQLite version 3.42.0 and later, a negative
** argument passed into sqlite3_sleep() is changed to zero before it is relayed
** down into the xSleep method of the VFS.
*/
SQLITE_API int sqlite3_sleep(int);
@ -6328,7 +6484,7 @@ SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N);
** <li> [sqlite3_filename_wal()]
** </ul>
*/
SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Determine if a database is read-only
@ -6465,7 +6621,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** function C that is invoked prior to each autovacuum of the database
** file. ^The callback is passed a copy of the generic data pointer (P),
** the schema-name of the attached database that is being autovacuumed,
** the the size of the database file in pages, the number of free pages,
** the size of the database file in pages, the number of free pages,
** and the number of bytes per page, respectively. The callback should
** return the number of free pages that should be removed by the
** autovacuum. ^If the callback returns zero, then no autovacuum happens.
@ -6586,6 +6742,11 @@ SQLITE_API void *sqlite3_update_hook(
** to the same database. Sharing is enabled if the argument is true
** and disabled if the argument is false.)^
**
** This interface is omitted if SQLite is compiled with
** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE]
** compile-time option is recommended because the
** [use of shared cache mode is discouraged].
**
** ^Cache sharing is enabled and disabled for an entire process.
** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]).
** In prior versions of SQLite,
@ -6684,7 +6845,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** ^The soft heap limit may not be greater than the hard heap limit.
** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N)
** is invoked with a value of N that is greater than the hard heap limit,
** the the soft heap limit is set to the value of the hard heap limit.
** the soft heap limit is set to the value of the hard heap limit.
** ^The soft heap limit is automatically enabled whenever the hard heap
** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and
** the soft heap limit is outside the range of 1..N, then the soft heap
@ -6945,15 +7106,6 @@ SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void));
*/
SQLITE_API void sqlite3_reset_auto_extension(void);
/*
** The interface to the virtual-table mechanism is currently considered
** to be experimental. The interface might change in incompatible ways.
** If this is a problem for you, do not use the interface at this time.
**
** When the virtual-table mechanism stabilizes, we will declare the
** interface fixed, support it indefinitely, and remove this comment.
*/
/*
** Structures used by the virtual table interface
*/
@ -7072,10 +7224,10 @@ struct sqlite3_module {
** when the omit flag is true there is no guarantee that the constraint will
** not be checked again using byte code.)^
**
** ^The idxNum and idxPtr values are recorded and passed into the
** ^The idxNum and idxStr values are recorded and passed into the
** [xFilter] method.
** ^[sqlite3_free()] is used to free idxPtr if and only if
** needToFreeIdxPtr is true.
** ^[sqlite3_free()] is used to free idxStr if and only if
** needToFreeIdxStr is true.
**
** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in
** the correct order to satisfy the ORDER BY clause so that no separate
@ -7195,7 +7347,7 @@ struct sqlite3_index_info {
** the [sqlite3_vtab_collation()] interface. For most real-world virtual
** tables, the collating sequence of constraints does not matter (for example
** because the constraints are numeric) and so the sqlite3_vtab_collation()
** interface is no commonly needed.
** interface is not commonly needed.
*/
#define SQLITE_INDEX_CONSTRAINT_EQ 2
#define SQLITE_INDEX_CONSTRAINT_GT 4
@ -7354,16 +7506,6 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
*/
SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
/*
** The interface to the virtual-table mechanism defined above (back up
** to a comment remarkably similar to this one) is currently considered
** to be experimental. The interface might change in incompatible ways.
** If this is a problem for you, do not use the interface at this time.
**
** When the virtual-table mechanism stabilizes, we will declare the
** interface fixed, support it indefinitely, and remove this comment.
*/
/*
** CAPI3REF: A Handle To An Open BLOB
** KEYWORDS: {BLOB handle} {BLOB handles}
@ -7747,9 +7889,9 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** is undefined if the mutex is not currently entered by the
** calling thread or is not currently allocated.
**
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
** sqlite3_mutex_leave() is a NULL pointer, then all three routines
** behave as no-ops.
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(),
** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer,
** then any of the four routines behaves as a no-op.
**
** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
*/
@ -8979,7 +9121,7 @@ typedef struct sqlite3_backup sqlite3_backup;
** if the application incorrectly accesses the destination [database connection]
** and so no error code is reported, but the operations may malfunction
** nevertheless. Use of the destination database connection while a
** backup is in progress might also also cause a mutex deadlock.
** backup is in progress might also cause a mutex deadlock.
**
** If running in [shared cache mode], the application must
** guarantee that the shared cache used by the destination database
@ -9407,7 +9549,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
*/
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */
/*
@ -9483,18 +9625,28 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** identify that virtual table as being safe to use from within triggers
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
** virtual table can do no serious harm even if it is controlled by a
** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
** flag unless absolutely necessary.
** </dd>
**
** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]<dt>SQLITE_VTAB_USES_ALL_SCHEMAS</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** instruct the query planner to begin at least a read transaction on
** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the
** virtual table is used.
** </dd>
** </dl>
*/
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
#define SQLITE_VTAB_INNOCUOUS 2
#define SQLITE_VTAB_DIRECTONLY 3
#define SQLITE_VTAB_USES_ALL_SCHEMAS 4
/*
** CAPI3REF: Determine The Virtual Table Conflict Policy
@ -9567,7 +9719,7 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*);
** <li><p> Otherwise, "BINARY" is returned.
** </ol>
*/
SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
/*
** CAPI3REF: Determine if a virtual table query is DISTINCT
@ -9724,21 +9876,20 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
** is undefined and probably harmful.
**
** The X parameter in a call to sqlite3_vtab_in_first(X,P) or
** sqlite3_vtab_in_next(X,P) must be one of the parameters to the
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
** xFilter method which invokes these routines, and specifically
** a parameter that was previously selected for all-at-once IN constraint
** processing use the [sqlite3_vtab_in()] interface in the
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
** an xFilter argument that was selected for all-at-once IN constraint
** processing, then these routines return [SQLITE_MISUSE])^ or perhaps
** exhibit some other undefined or harmful behavior.
** processing, then these routines return [SQLITE_ERROR].)^
**
** ^(Use these routines to access all values on the right-hand side
** of the IN constraint using code like the following:
**
** <blockquote><pre>
** &nbsp; for(rc=sqlite3_vtab_in_first(pList, &pVal);
** &nbsp; rc==SQLITE_OK && pVal
** &nbsp; rc==SQLITE_OK && pVal;
** &nbsp; rc=sqlite3_vtab_in_next(pList, &pVal)
** &nbsp; ){
** &nbsp; // do something with pVal
@ -9836,6 +9987,10 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
** managed by the prepared statement S and will be automatically freed when
** S is finalized.
**
** Not all values are available for all query elements. When a value is
** not available, the output variable is set to -1 if the value is numeric,
** or to NULL if it is a string (SQLITE_SCANSTAT_NAME).
**
** <dl>
** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt>
** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be
@ -9863,12 +10018,24 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
** description for the X-th loop.
**
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt>
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
** <dd>^The "int" variable pointed to by the V parameter will be set to the
** "select-id" for the X-th loop. The select-id identifies which query or
** subquery the loop is part of. The main query has a select-id of zero.
** The select-id is the same value as is output in the first column
** of an [EXPLAIN QUERY PLAN] query.
** id for the X-th query plan element. The id value is unique within the
** statement. The select-id is the same value as is output in the first
** column of an [EXPLAIN QUERY PLAN] query.
**
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
** <dd>The "int" variable pointed to by the V parameter will be set to the
** the id of the parent of the current query element, if applicable, or
** to zero if the query element has no parent. This is the same value as
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
**
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
** <dd>The sqlite3_int64 output value is set to the number of cycles,
** according to the processor time-stamp counter, that elapsed while the
** query element was being processed. This value is not available for
** all query elements - if it is unavailable the output variable is
** set to -1.
** </dl>
*/
#define SQLITE_SCANSTAT_NLOOP 0
@ -9877,12 +10044,14 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
#define SQLITE_SCANSTAT_NAME 3
#define SQLITE_SCANSTAT_EXPLAIN 4
#define SQLITE_SCANSTAT_SELECTID 5
#define SQLITE_SCANSTAT_PARENTID 6
#define SQLITE_SCANSTAT_NCYCLE 7
/*
** CAPI3REF: Prepared Statement Scan Status
** METHOD: sqlite3_stmt
**
** This interface returns information about the predicted and measured
** These interfaces return information about the predicted and measured
** performance for pStmt. Advanced applications can use this
** interface to compare the predicted and the measured performance and
** issue warnings and/or rerun [ANALYZE] if discrepancies are found.
@ -9893,19 +10062,25 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
**
** The "iScanStatusOp" parameter determines which status information to return.
** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior
** of this interface is undefined.
** ^The requested measurement is written into a variable pointed to by
** the "pOut" parameter.
** Parameter "idx" identifies the specific loop to retrieve statistics for.
** Loops are numbered starting from zero. ^If idx is out of range - less than
** zero or greater than or equal to the total number of loops used to implement
** the statement - a non-zero value is returned and the variable that pOut
** points to is unchanged.
** of this interface is undefined. ^The requested measurement is written into
** a variable pointed to by the "pOut" parameter.
**
** ^Statistics might not be available for all loops in all statements. ^In cases
** where there exist loops with no available statistics, this function behaves
** as if the loop did not exist - it returns non-zero and leave the variable
** that pOut points to unchanged.
** The "flags" parameter must be passed a mask of flags. At present only
** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX
** is specified, then status information is available for all elements
** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If
** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements
** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of
** the EXPLAIN QUERY PLAN output) are available. Invoking API
** sqlite3_stmt_scanstatus() is equivalent to calling
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
**
** Parameter "idx" identifies the specific query element to retrieve statistics
** for. Query elements are numbered starting from zero. A value of -1 may be
** to query for statistics regarding the entire query. ^If idx is out of range
** - less than -1 or greater than or equal to the total number of query
** elements used to implement the statement - a non-zero value is returned and
** the variable that pOut points to is unchanged.
**
** See also: [sqlite3_stmt_scanstatus_reset()]
*/
@ -9915,6 +10090,19 @@ SQLITE_API int sqlite3_stmt_scanstatus(
int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
void *pOut /* Result written here */
);
SQLITE_API int sqlite3_stmt_scanstatus_v2(
sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
int idx, /* Index of loop to report on */
int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
int flags, /* Mask of flags defined below */
void *pOut /* Result written here */
);
/*
** CAPI3REF: Prepared Statement Scan Status
** KEYWORDS: {scan status flags}
*/
#define SQLITE_SCANSTAT_COMPLEX 0x0001
/*
** CAPI3REF: Zero Scan-Status Counters
@ -10005,6 +10193,10 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** function is not defined for operations on WITHOUT ROWID tables, or for
** DELETE operations on rowid tables.
**
** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from
** the previous call on the same [database connection] D, or NULL for
** the first call on D.
**
** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
** provide additional information about a preupdate event. These routines
@ -10410,6 +10602,19 @@ SQLITE_API int sqlite3_deserialize(
# undef double
#endif
#if defined(__wasi__)
# undef SQLITE_WASI
# define SQLITE_WASI 1
# undef SQLITE_OMIT_WAL
# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */
# ifndef SQLITE_OMIT_LOAD_EXTENSION
# define SQLITE_OMIT_LOAD_EXTENSION
# endif
# ifndef SQLITE_THREADSAFE
# define SQLITE_THREADSAFE 0
# endif
#endif
#ifdef __cplusplus
} /* End of the 'extern "C"' block */
#endif
@ -10616,16 +10821,20 @@ SQLITE_API int sqlite3session_create(
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
/*
** CAPIREF: Conigure a Session Object
** CAPI3REF: Configure a Session Object
** METHOD: sqlite3_session
**
** This method is used to configure a session object after it has been
** created. At present the only valid value for the second parameter is
** [SQLITE_SESSION_OBJCONFIG_SIZE].
** created. At present the only valid values for the second parameter are
** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID].
**
** Arguments for sqlite3session_object_config()
*/
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
/*
** CAPI3REF: Options for sqlite3session_object_config
**
** The following values may passed as the the 4th parameter to
** The following values may passed as the the 2nd parameter to
** sqlite3session_object_config().
**
** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
@ -10641,12 +10850,21 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
**
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
** the first table has been attached to the session object.
**
** <dt>SQLITE_SESSION_OBJCONFIG_ROWID <dd>
** This option is used to set, clear or query the flag that enables
** collection of data for tables with no explicit PRIMARY KEY.
**
** Normally, tables with no explicit PRIMARY KEY are simply ignored
** by the sessions module. However, if this flag is set, it behaves
** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted
** as their leftmost columns.
**
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
** the first table has been attached to the session object.
*/
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
/*
*/
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
#define SQLITE_SESSION_OBJCONFIG_ROWID 2
/*
** CAPI3REF: Enable Or Disable A Session Object
@ -11779,9 +11997,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
** Invert the changeset before applying it. This is equivalent to inverting
** a changeset using sqlite3changeset_invert() before applying it. It is
** an error to specify this flag with a patchset.
**
** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd>
** Do not invoke the conflict handler callback for any changes that
** would not actually modify the database even if they were applied.
** Specifically, this means that the conflict handler is not invoked
** for:
** <ul>
** <li>a delete change if the row being deleted cannot be found,
** <li>an update change if the modified fields are already set to
** their new values in the conflicting row, or
** <li>an insert change if all fields of the conflicting row match
** the row being inserted.
** </ul>
*/
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
/*
** CAPI3REF: Constants Passed To The Conflict Handler

View File

@ -331,9 +331,9 @@ struct sqlite3_api_routines {
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
char *(*create_filename)(const char*,const char*,const char*,
const char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
void (*free_filename)(char*);
void (*free_filename)(const char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
@ -357,6 +357,10 @@ struct sqlite3_api_routines {
unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
unsigned int);
const char *(*db_name)(sqlite3*,int);
/* Version 3.40.0 and later */
int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*);
};
/*
@ -681,6 +685,10 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_serialize sqlite3_api->serialize
#endif
#define sqlite3_db_name sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -31,9 +31,6 @@ mod bindings {
}
pub use bindings::*;
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
impl Default for sqlite3_vtab {
fn default() -> Self {
unsafe { mem::zeroed() }

View File

@ -9,8 +9,8 @@ export SQLITE3_LIB_DIR="$SCRIPT_DIR/sqlite3"
export SQLITE3_INCLUDE_DIR="$SQLITE3_LIB_DIR"
# Download and extract amalgamation
SQLITE=sqlite-amalgamation-3390400
curl -O https://sqlite.org/2022/$SQLITE.zip
SQLITE=sqlite-amalgamation-3420000
curl -O https://sqlite.org/2023/$SQLITE.zip
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.c" > "$SQLITE3_LIB_DIR/sqlite3.c"
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.h" > "$SQLITE3_LIB_DIR/sqlite3.h"
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3ext.h" > "$SQLITE3_LIB_DIR/sqlite3ext.h"

View File

@ -8,7 +8,7 @@ mkdir -p "$SCRIPT_DIR/../target" "$SCRIPT_DIR/sqlcipher"
export SQLCIPHER_LIB_DIR="$SCRIPT_DIR/sqlcipher"
export SQLCIPHER_INCLUDE_DIR="$SQLCIPHER_LIB_DIR"
SQLCIPHER_VERSION="4.5.2"
SQLCIPHER_VERSION="4.5.3"
# Download and generate sqlcipher amalgamation
mkdir -p $SCRIPT_DIR/sqlcipher.src
[ -e "v${SQLCIPHER_VERSION}.tar.gz" ] || curl -sfL -O "https://github.com/sqlcipher/sqlcipher/archive/v${SQLCIPHER_VERSION}.tar.gz"

View File

@ -336,7 +336,7 @@ mod test {
backup.step(-1)?;
}
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)")?;
@ -346,7 +346,7 @@ mod test {
backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
assert_eq!(42 + 43, the_answer);
Ok(())
}
@ -368,7 +368,7 @@ mod test {
backup.step(-1)?;
}
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)")?;
@ -379,7 +379,7 @@ mod test {
backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
assert_eq!(42 + 43, the_answer);
Ok(())
}
@ -406,7 +406,7 @@ mod test {
backup.step(-1)?;
}
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)")?;
@ -421,7 +421,7 @@ mod test {
backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
assert_eq!(42 + 43, the_answer);
Ok(())
}

View File

@ -134,7 +134,7 @@
//! // 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 (?)",
//! "INSERT INTO test_table (content) VALUES (?1)",
//! [ZeroBlob(64)],
//! )?;
//!
@ -175,7 +175,7 @@
//! // 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 (?)",
//! "INSERT INTO test_table (content) VALUES (?1)",
//! [ZeroBlob(64)],
//! )?;
//!
@ -274,7 +274,6 @@ impl Blob<'_> {
#[inline]
#[must_use]
pub fn len(&self) -> usize {
use std::convert::TryInto;
self.size().try_into().unwrap()
}

View File

@ -265,7 +265,7 @@ mod test {
));
blob.raw_read_at_exact(&mut s2, 5).unwrap_err();
let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
let end_pos = blob.stream_position().unwrap();
assert_eq!(end_pos, 1);
Ok(())
}

View File

@ -1,4 +1,4 @@
///! Busy handler (when the database is locked)
//! Busy handler (when the database is locked)
use std::convert::TryInto;
use std::mem;
use std::os::raw::{c_int, c_void};

View File

@ -1,7 +1,7 @@
//! Prepared statements cache for faster execution.
use crate::raw_statement::RawStatement;
use crate::{Connection, Result, Statement};
use crate::{Connection, PrepFlags, Result, Statement};
use hashlink::LruCache;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
@ -17,13 +17,13 @@ impl Connection {
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// {
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// 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 (?)")?;
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
/// stmt.execute(["Bob Jones"])?;
/// }
/// Ok(())
@ -144,7 +144,7 @@ impl StatementCache {
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(trimmed),
None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT),
};
stmt.map(|mut stmt| {
stmt.stmt.set_statement_cache_key(trimmed);

View File

@ -203,7 +203,7 @@ mod test {
assert_eq!(ty, Type::Integer);
}
e => {
panic!("Unexpected error type: {:?}", e);
panic!("Unexpected error type: {e:?}");
}
}
match row.get::<_, String>("y").unwrap_err() {
@ -213,7 +213,7 @@ mod test {
assert_eq!(ty, Type::Null);
}
e => {
panic!("Unexpected error type: {:?}", e);
panic!("Unexpected error type: {e:?}");
}
}
Ok(())

View File

@ -61,6 +61,13 @@ pub enum DbConfig {
/// sqlite_master tables) are untainted by malicious content.
#[cfg(feature = "modern_sqlite")]
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
}
impl Connection {

View File

@ -252,11 +252,7 @@ impl fmt::Display for Error {
),
Error::FromSqlConversionFailure(i, ref t, ref err) => {
if i != UNKNOWN_COLUMN {
write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
)
write!(f, "Conversion error from type {t} at index: {i}, {err}")
} else {
err.fmt(f)
}
@ -278,15 +274,12 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
Error::InvalidColumnType(i, ref name, ref t) => write!(
f,
"Invalid column type {} at index: {}, name: {}",
t, i, name
),
Error::InvalidColumnType(i, ref name, ref t) => {
write!(f, "Invalid column type {t} at index: {i}, name: {name}")
}
Error::InvalidParameterCount(i1, n1) => write!(
f,
"Wrong number of parameters passed to query. Got {}, needed {}",
i1, n1
"Wrong number of parameters passed to query. Got {i1}, needed {n1}"
),
Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
@ -393,7 +386,6 @@ impl Error {
#[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
// TODO sqlite3_error_offset // 3.38.0, #1130
Error::SqliteFailure(ffi::Error::new(code), message)
}
@ -443,3 +435,25 @@ pub fn check(code: c_int) -> Result<()> {
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 std::os::raw::c_char,
) -> std::os::raw::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
}
}
}

View File

@ -71,21 +71,13 @@ use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
// Extended constraint error codes were added in SQLite 3.7.16. We don't have
// an explicit feature check for that, and this doesn't really warrant one.
// We'll use the extended code if we're on the bundled version (since it's
// at least 3.17.0) and the normal constraint error code if not.
fn constraint_error_code() -> i32 {
ffi::SQLITE_CONSTRAINT_FUNCTION
}
if let Error::SqliteFailure(ref err, ref s) = *err {
ffi::sqlite3_result_error_code(ctx, err.extended_code);
if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
} else {
ffi::sqlite3_result_error_code(ctx, constraint_error_code());
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
if let Ok(cstr) = str_to_cstring(&err.to_string()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
@ -833,9 +825,9 @@ mod test {
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
)?;
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
let result: f64 = db.one_column("SELECT half(6)")?;
assert!((3f64 - result?).abs() < f64::EPSILON);
assert!((3f64 - result).abs() < f64::EPSILON);
Ok(())
}
@ -848,11 +840,11 @@ mod test {
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
)?;
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
assert!((3f64 - result?).abs() < f64::EPSILON);
let result: f64 = db.one_column("SELECT half(6)")?;
assert!((3f64 - result).abs() < f64::EPSILON);
db.remove_function("half", 1)?;
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
let result: Result<f64> = db.one_column("SELECT half(6)");
result.unwrap_err();
Ok(())
}
@ -860,7 +852,7 @@ mod test {
// This implementation of a regexp scalar function uses SQLite's auxiliary data
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
// expression multiple times within one query.
fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<(bool, SubType)> {
fn regexp_with_auxiliary(ctx: &Context<'_>) -> Result<(bool, SubType)> {
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
let regexp: std::sync::Arc<Regex> = ctx
@ -881,7 +873,7 @@ mod test {
}
#[test]
fn test_function_regexp_with_auxilliary() -> Result<()> {
fn test_function_regexp_with_auxiliary() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch(
"BEGIN;
@ -895,21 +887,17 @@ mod test {
"regexp",
2,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
regexp_with_auxilliary,
regexp_with_auxiliary,
)?;
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?;
assert!(result?);
assert!(result);
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
[],
|r| r.get(0),
);
let result: i64 =
db.one_column("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1")?;
assert_eq!(2, result?);
assert_eq!(2, result);
Ok(())
}
@ -937,7 +925,7 @@ mod test {
("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')"),
] {
let result: String = db.query_row(query, [], |r| r.get(0))?;
let result: String = db.one_column(query)?;
assert_eq!(expected, result);
}
Ok(())
@ -956,11 +944,8 @@ mod test {
Ok((true, None))
})?;
let res: bool = db.query_row(
"SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
[],
|r| r.get(0),
)?;
let res: bool =
db.one_column("SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)")?;
// Doesn't actually matter, we'll assert in the function if there's a problem.
assert!(res);
Ok(())
@ -1015,11 +1000,11 @@ mod test {
// sum should return NULL when given no columns (contrast with count below)
let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
let result: Option<i64> = db.query_row(no_result, [], |r| r.get(0))?;
let result: Option<i64> = db.one_column(no_result)?;
assert!(result.is_none());
let single_sum = "SELECT my_sum(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
let result: i64 = db.one_column(single_sum)?;
assert_eq!(4, result);
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
@ -1041,11 +1026,11 @@ mod test {
// count should return 0 when given no columns (contrast with sum above)
let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
let result: i64 = db.query_row(no_result, [], |r| r.get(0))?;
let result: i64 = db.one_column(no_result)?;
assert_eq!(result, 0);
let single_sum = "SELECT my_count(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
let result: i64 = db.one_column(single_sum)?;
assert_eq!(2, result);
Ok(())
}

View File

@ -656,7 +656,7 @@ unsafe fn free_boxed_hook<F>(p: *mut c_void) {
unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
expect_optional_utf8(p_str, description)
.unwrap_or_else(|| panic!("received empty {}", description))
.unwrap_or_else(|| panic!("received empty {description}"))
}
unsafe fn expect_optional_utf8<'a>(
@ -667,7 +667,7 @@ unsafe fn expect_optional_utf8<'a>(
return None;
}
std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
.unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
.unwrap_or_else(|_| panic!("received non-utf8 string as {description}"))
.into()
}

View File

@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex};
use super::ffi;
use super::str_for_sqlite;
use super::{Connection, InterruptHandle, OpenFlags, Result};
use super::{Connection, InterruptHandle, OpenFlags, PrepFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
@ -69,13 +69,13 @@ impl InnerConnection {
// Replicate the check for sane open flags from SQLite, because the check in
// SQLite itself wasn't added until version 3.7.3.
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits(), 0x02);
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits(), 0x04);
debug_assert_eq!(
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits(),
0x40
);
if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
if (1 << (flags.bits() & 0x7)) & 0x46 == 0 {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
None,
@ -218,33 +218,24 @@ impl InnerConnection {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
let mut c_stmt = ptr::null_mut();
pub fn prepare<'a>(
&mut self,
conn: &'a Connection,
sql: &str,
flags: PrepFlags,
) -> Result<Statement<'a>> {
let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null();
let mut c_tail: *const c_char = ptr::null();
// TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728
#[cfg(not(feature = "unlock_notify"))]
let r = unsafe {
ffi::sqlite3_prepare_v2(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
)
};
let r = unsafe { self.prepare_(c_sql, len, flags, &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_v2(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
);
rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail);
if !unlock_notify::is_locked(self.db, rc) {
break;
}
@ -261,8 +252,6 @@ impl InnerConnection {
}
// If the input text contains no SQL (if the input is an empty string or a
// comment) then *ppStmt is set to NULL.
let c_stmt: *mut ffi::sqlite3_stmt = c_stmt;
let c_tail: *const c_char = c_tail;
let tail = if c_tail.is_null() {
0
} else {
@ -278,6 +267,32 @@ impl InnerConnection {
}))
}
#[inline]
#[cfg(not(feature = "modern_sqlite"))]
unsafe fn prepare_(
&self,
z_sql: *const c_char,
n_byte: c_int,
_: PrepFlags,
pp_stmt: *mut *mut ffi::sqlite3_stmt,
pz_tail: *mut *const c_char,
) -> c_int {
ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail)
}
#[inline]
#[cfg(feature = "modern_sqlite")]
unsafe fn prepare_(
&self,
z_sql: *const c_char,
n_byte: c_int,
flags: PrepFlags,
pp_stmt: *mut *mut ffi::sqlite3_stmt,
pz_tail: *mut *const c_char,
) -> c_int {
ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail)
}
#[inline]
pub fn changes(&self) -> u64 {
#[cfg(not(feature = "modern_sqlite"))]
@ -371,15 +386,7 @@ impl Drop for InnerConnection {
#[allow(unused_must_use)]
#[inline]
fn drop(&mut self) {
use std::thread::panicking;
if let Err(e) = self.close() {
if panicking() {
eprintln!("Error while closing SQLite connection: {e:?}");
} else {
panic!("Error while closing SQLite connection: {:?}", e);
}
}
self.close();
}
}
@ -390,7 +397,7 @@ pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
// threading mode checks are not necessary (and do not work) on target
// platforms that do not have threading (such as webassembly)
#[cfg(any(target_arch = "wasm32"))]
#[cfg(target_arch = "wasm32")]
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
Ok(())
}

View File

@ -62,7 +62,7 @@ use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::path::Path;
use std::result;
use std::str;
use std::sync::atomic::Ordering;
@ -75,7 +75,7 @@ use crate::types::ValueRef;
pub use crate::cache::CachedStatement;
pub use crate::column::Column;
pub use crate::error::Error;
pub use crate::error::{to_sqlite_error, Error};
pub use crate::ffi::ErrorCode;
#[cfg(feature = "load_extension")]
pub use crate::load_extension_guard::LoadExtensionGuard;
@ -119,6 +119,9 @@ mod params;
mod pragma;
mod raw_statement;
mod row;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serialize;
#[cfg(feature = "session")]
#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session;
@ -140,13 +143,6 @@ pub(crate) use util::SmallCString;
// Number of cached prepared statements we'll hold on to.
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
/// To be used when your statement has no [parameter][sqlite-varparam].
///
/// [sqlite-varparam]: https://sqlite.org/lang_expr.html#varparam
///
/// This is deprecated in favor of using an empty array literal.
#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
pub const NO_PARAMS: &[&dyn ToSql] = &[];
/// A macro making it more convenient to longer lists of
/// parameters as a `&[&dyn ToSql]`.
@ -330,7 +326,6 @@ impl DatabaseName<'_> {
pub struct Connection {
db: RefCell<InnerConnection>,
cache: StatementCache,
path: Option<PathBuf>,
}
unsafe impl Send for Connection {}
@ -427,7 +422,6 @@ impl Connection {
InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: Some(path.as_ref().to_path_buf()),
})
}
@ -452,7 +446,6 @@ impl Connection {
InnerConnection::open_with_flags(&c_path, flags, Some(&c_vfs)).map(|db| Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: Some(path.as_ref().to_path_buf()),
})
}
@ -534,7 +527,7 @@ impl Connection {
/// ```rust,no_run
/// # use rusqlite::{Connection};
/// fn update_rows(conn: &Connection) {
/// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
/// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
/// Ok(updated) => println!("{} rows were updated", updated),
/// Err(err) => println!("update failed: {}", err),
/// }
@ -580,12 +573,23 @@ impl Connection {
/// Returns the path to the database file, if one exists and is known.
///
/// Returns `Some("")` for a temporary or in-memory database.
///
/// Note that in some cases [PRAGMA
/// database_list](https://sqlite.org/pragma.html#pragma_database_list) is
/// likely to be more robust.
#[inline]
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
pub fn path(&self) -> Option<&str> {
unsafe {
let db = self.handle();
let db_name = DatabaseName::Main.as_cstring().unwrap();
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
if db_filename.is_null() {
None
} else {
CStr::from_ptr(db_filename).to_str().ok()
}
}
}
/// Attempts to free as much heap memory as possible from the database
@ -598,26 +602,6 @@ impl Connection {
self.db.borrow_mut().release_memory()
}
/// Convenience method to prepare and execute a single SQL statement with
/// named parameter(s).
///
/// On success, returns the number of rows that were changed or inserted or
/// deleted (via `sqlite3_changes`).
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
#[deprecated = "You can use `execute` with named params now."]
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
// This function itself is deprecated, so it's fine
#![allow(deprecated)]
self.prepare(sql).and_then(|mut stmt| {
stmt.check_no_tail()
.and_then(|_| stmt.execute_named(params))
})
}
/// Get the SQLite rowid of the most recent successful INSERT.
///
/// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under
@ -665,26 +649,10 @@ impl Connection {
stmt.query_row(params, f)
}
/// Convenience method to execute a query with named parameter(s) that is
/// expected to return a single row.
///
/// If the query returns more than one row, all rows except the first are
/// ignored.
///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
/// query truly is optional, you can call `.optional()` on the result of
/// this to get a `Result<Option<T>>`.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
#[deprecated = "You can use `query_row` with named params now."]
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
self.query_row(sql, params, f)
// https://sqlite.org/tclsqlite.html#onecolumn
#[cfg(test)]
pub(crate) fn one_column<T: crate::types::FromSql>(&self, sql: &str) -> Result<T> {
self.query_row(sql, [], |r| r.get(0))
}
/// Convenience method to execute a query that is expected to return a
@ -733,7 +701,7 @@ impl Connection {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?)")?;
/// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
/// stmt.execute(["Joe Smith"])?;
/// stmt.execute(["Bob Jones"])?;
/// Ok(())
@ -746,7 +714,18 @@ impl Connection {
/// or if the underlying SQLite call fails.
#[inline]
pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql)
self.prepare_with_flags(sql, PrepFlags::default())
}
/// Prepare a SQL statement for execution.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
#[inline]
pub fn prepare_with_flags(&self, sql: &str, flags: PrepFlags) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql, flags)
}
/// Close the SQLite connection.
@ -915,12 +894,31 @@ impl Connection {
/// This function is unsafe because improper use may impact the Connection.
#[inline]
pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result<Connection> {
let db_path = db_filename(db);
let db = InnerConnection::new(db, false);
Ok(Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: db_path,
})
}
/// Create a `Connection` from a raw owned handle.
///
/// The returned connection will attempt to close the inner connection
/// when dropped/closed. This function should only be called on connections
/// owned by the caller.
///
/// # Safety
///
/// This function is unsafe because improper use may impact the Connection.
/// In particular, it should only be called on connections created
/// and owned by the caller, e.g. as a result of calling
/// ffi::sqlite3_open().
#[inline]
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
let db = InnerConnection::new(db, true);
Ok(Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
})
}
@ -973,7 +971,7 @@ impl Connection {
impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Connection")
.field("path", &self.path)
.field("path", &self.path())
.finish()
}
}
@ -1041,11 +1039,12 @@ impl<'conn> Iterator for Batch<'conn, '_> {
bitflags::bitflags! {
/// Flags for opening SQLite database connections. See
/// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
/// [sqlite3_open_v2](https://www.sqlite.org/c3ref/open.html) for details.
///
/// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
/// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
/// some discussion about these flags.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct OpenFlags: ::std::os::raw::c_int {
/// The database is opened in read-only mode.
@ -1122,6 +1121,19 @@ impl Default for OpenFlags {
}
}
bitflags::bitflags! {
/// Prepare flags. See
/// [sqlite3_prepare_v3](https://sqlite.org/c3ref/c_prepare_normalize.html) for details.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PrepFlags: ::std::os::raw::c_uint {
/// A hint to the query planner that the prepared statement will be retained for a long time and probably reused many times.
const SQLITE_PREPARE_PERSISTENT = 0x01;
/// Causes the SQL compiler to return an error (error code SQLITE_ERROR) if the statement uses any virtual tables.
const SQLITE_PREPARE_NO_VTAB = 0x04;
}
}
/// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or
/// later. If you are running against a SQLite older than that, rusqlite
/// attempts to ensure safety by performing configuration and initialization of
@ -1164,16 +1176,6 @@ impl InterruptHandle {
}
}
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
let db_name = DatabaseName::Main.as_cstring().unwrap();
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
if db_filename.is_null() {
None
} else {
CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
}
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
@ -1188,13 +1190,21 @@ mod test {
// this function is never called, but is still type checked; in
// particular, calls with specific instantiations will require
// that those types are `Send`.
#[allow(dead_code, unconditional_recursion)]
#[allow(
dead_code,
unconditional_recursion,
clippy::extra_unused_type_parameters
)]
fn ensure_send<T: Send>() {
ensure_send::<Connection>();
ensure_send::<InterruptHandle>();
}
#[allow(dead_code, unconditional_recursion)]
#[allow(
dead_code,
unconditional_recursion,
clippy::extra_unused_type_parameters
)]
fn ensure_sync<T: Sync>() {
ensure_sync::<InterruptHandle>();
}
@ -1261,9 +1271,9 @@ mod test {
let path_string = path.to_str().unwrap();
let db = Connection::open(path_string)?;
let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
assert_eq!(42i64, the_answer?);
assert_eq!(42i64, the_answer);
Ok(())
}
@ -1275,6 +1285,21 @@ mod test {
db.close().unwrap();
}
#[test]
fn test_path() -> Result<()> {
let tmp = tempfile::tempdir().unwrap();
let db = Connection::open("")?;
assert_eq!(Some(""), db.path());
let db = Connection::open_in_memory()?;
assert_eq!(Some(""), db.path());
let db = Connection::open("file:dummy.db?mode=memory&cache=shared")?;
assert_eq!(Some(""), db.path());
let path = tmp.path().join("file.db");
let db = Connection::open(path)?;
assert!(db.path().map(|p| p.ends_with("file.db")).unwrap_or(false));
Ok(())
}
#[test]
fn test_open_failure() {
let filename = "no_such_file.db";
@ -1285,9 +1310,7 @@ mod test {
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
assert!(
msg.contains(filename),
"error message '{}' does not contain '{}'",
msg,
filename
"error message '{msg}' does not contain '{filename}'"
);
} else {
panic!("SqliteFailure expected");
@ -1318,9 +1341,9 @@ mod test {
}
let db = Connection::open(&db_path)?;
let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
assert_eq!(42i64, the_answer?);
assert_eq!(42i64, the_answer);
Ok(())
}
@ -1400,13 +1423,10 @@ mod test {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [2i32])?);
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [1i32])?);
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [2i32])?);
assert_eq!(
3i32,
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(3i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
Ok(())
}
@ -1414,12 +1434,11 @@ mod test {
#[cfg(feature = "extra_check")]
fn test_execute_select() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err();
assert_eq!(
err,
Error::ExecuteReturnedResults,
"Unexpected error: {}",
err
"Unexpected error: {err}"
);
}
@ -1435,7 +1454,7 @@ mod test {
.unwrap_err();
match err {
Error::MultipleStatement => (),
_ => panic!("Unexpected error: {}", err),
_ => panic!("Unexpected error: {err}"),
}
}
@ -1459,7 +1478,7 @@ mod test {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
assert_eq!(insert_stmt.execute([1i32])?, 1);
assert_eq!(insert_stmt.execute([2i32])?, 1);
assert_eq!(insert_stmt.execute([3i32])?, 1);
@ -1468,7 +1487,7 @@ mod test {
assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
assert_eq!(insert_stmt.execute([types::Null])?, 1);
let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
let mut update_stmt = db.prepare("UPDATE foo SET x=?1 WHERE x<?2")?;
assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
@ -1480,12 +1499,12 @@ mod test {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
assert_eq!(insert_stmt.execute([1i32])?, 1);
assert_eq!(insert_stmt.execute([2i32])?, 1);
assert_eq!(insert_stmt.execute([3i32])?, 1);
let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
let mut query = db.prepare("SELECT x FROM foo WHERE x < ?1 ORDER BY x DESC")?;
{
let mut rows = query.query([4i32])?;
let mut v = Vec::<i32>::new();
@ -1541,15 +1560,12 @@ mod test {
END;";
db.execute_batch(sql)?;
assert_eq!(
10i64,
db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(10i64, db.one_column::<i64>("SELECT SUM(x) FROM foo")?);
let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
let result: Result<i64> = db.one_column("SELECT x FROM foo WHERE x > 5");
match result.unwrap_err() {
Error::QueryReturnedNoRows => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
@ -1562,21 +1578,21 @@ mod test {
fn test_optional() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 <> 0");
let result = result.optional();
match result? {
None => (),
_ => panic!("Unexpected result"),
}
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0));
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 == 0");
let result = result.optional();
match result? {
Some(1) => (),
_ => panic!("Unexpected result"),
}
let bad_query_result: Result<i64> = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0));
let bad_query_result: Result<i64> = db.one_column("NOT A PROPER QUERY");
let bad_query_result = bad_query_result.optional();
bad_query_result.unwrap_err();
Ok(())
@ -1585,11 +1601,8 @@ mod test {
#[test]
fn test_pragma_query_row() -> Result<()> {
let db = Connection::open_in_memory()?;
assert_eq!(
"memory",
db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
);
let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
assert_eq!("memory", db.one_column::<String>("PRAGMA journal_mode")?);
let mode = db.one_column::<String>("PRAGMA journal_mode=off")?;
if cfg!(features = "bundled") {
assert_eq!(mode, "off");
} else {
@ -1604,7 +1617,7 @@ mod test {
// > MEMORY or OFF and can not be changed to a different value. An
// > attempt to change the journal_mode of an in-memory database to
// > any setting other than MEMORY or OFF is ignored.
assert!(mode == "memory" || mode == "off", "Got mode {:?}", mode);
assert!(mode == "memory" || mode == "off", "Got mode {mode:?}");
}
Ok(())
@ -1691,7 +1704,7 @@ mod test {
assert_eq!(err.code, ErrorCode::ConstraintViolation);
check_extended_code(err.extended_code);
}
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
@ -1756,7 +1769,7 @@ mod test {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(i, x);")?;
let vals = ["foobar", "1234", "qwerty"];
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?1, ?2)")?;
for (i, v) in vals.iter().enumerate() {
let i_to_insert = i as i64;
assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1);
@ -1796,6 +1809,16 @@ mod test {
Ok(())
}
#[test]
fn test_from_handle_owned() -> Result<()> {
let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut();
let r = unsafe { ffi::sqlite3_open(":memory:\0".as_ptr() as *const i8, &mut handle) };
assert_eq!(r, ffi::SQLITE_OK);
let db = unsafe { Connection::from_handle_owned(handle) }?;
db.execute_batch("PRAGMA VACUUM")?;
Ok(())
}
mod query_and_then_tests {
use super::*;
@ -1873,7 +1896,7 @@ mod test {
match bad_type.unwrap_err() {
Error::InvalidColumnType(..) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let bad_idx: Result<Vec<String>> =
@ -1881,7 +1904,7 @@ mod test {
match bad_idx.unwrap_err() {
Error::InvalidColumnIndex(_) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
@ -1926,7 +1949,7 @@ mod test {
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let bad_idx: CustomResult<Vec<String>> = query
@ -1935,7 +1958,7 @@ mod test {
match bad_idx.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let non_sqlite_err: CustomResult<Vec<String>> = query
@ -1944,7 +1967,7 @@ mod test {
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
@ -1981,7 +2004,7 @@ mod test {
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let bad_idx: CustomResult<String> =
@ -1989,7 +2012,7 @@ mod test {
match bad_idx.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let non_sqlite_err: CustomResult<String> =
@ -1997,7 +2020,7 @@ mod test {
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
@ -2022,7 +2045,7 @@ mod test {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let b: Box<dyn ToSql> = Box::new(5);
db.execute("INSERT INTO foo VALUES(?)", [b])?;
db.execute("INSERT INTO foo VALUES(?1)", [b])?;
db.query_row("SELECT x FROM foo", [], |r| {
assert_eq!(5, r.get_unwrap::<_, i32>(0));
Ok(())
@ -2034,10 +2057,10 @@ mod test {
let db = Connection::open_in_memory()?;
db.query_row(
"SELECT
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?;",
?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10,
?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20,
?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30,
?31, ?32, ?33, ?34;",
params![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
@ -2079,10 +2102,7 @@ mod test {
fn test_returning() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
let row_id =
db.query_row::<i64, _, _>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [], |r| {
r.get(0)
})?;
let row_id = db.one_column::<i64>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID")?;
assert_eq!(row_id, 1);
Ok(())
}

View File

@ -70,7 +70,7 @@ use sealed::Sealed;
/// ```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 (?, ?)")?;
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?;
///
/// // Using a tuple:
/// stmt.execute((0, "foobar"))?;
@ -138,9 +138,7 @@ use sealed::Sealed;
/// ## No parameters
///
/// You can just use an empty tuple or the empty array literal to run a query
/// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
/// common in previous versions of this library is no longer needed, and is now
/// deprecated).
/// that accepts no parameters.
///
/// ### Example (no parameters)
///
@ -192,8 +190,7 @@ pub trait Params: Sealed {
}
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API.
// 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
@ -357,7 +354,7 @@ impl_for_array_ref!(
/// 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 (?, ?, ?)")?;
/// 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...

View File

@ -211,7 +211,7 @@ impl Connection {
/// (e.g. `integrity_check`).
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT * FROM pragma_table_info(?);`
/// `SELECT * FROM pragma_table_info(?1);`
pub fn pragma<F, V>(
&self,
schema_name: Option<DatabaseName<'_>>,
@ -303,15 +303,15 @@ fn is_identifier(s: &str) -> bool {
}
fn is_identifier_start(c: char) -> bool {
('A'..='Z').contains(&c) || c == '_' || ('a'..='z').contains(&c) || c > '\x7F'
c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F'
}
fn is_identifier_continue(c: char) -> bool {
c == '$'
|| ('0'..='9').contains(&c)
|| ('A'..='Z').contains(&c)
|| c.is_ascii_digit()
|| c.is_ascii_uppercase()
|| c == '_'
|| ('a'..='z').contains(&c)
|| c.is_ascii_lowercase()
|| c > '\x7F'
}
@ -333,10 +333,7 @@ mod test {
#[cfg(feature = "modern_sqlite")]
fn pragma_func_query_value() -> Result<()> {
let db = Connection::open_in_memory()?;
let user_version: i32 =
db.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
row.get(0)
})?;
let user_version: i32 = db.one_column("SELECT user_version FROM pragma_user_version")?;
assert_eq!(0, user_version);
Ok(())
}
@ -382,7 +379,7 @@ mod test {
#[cfg(feature = "modern_sqlite")]
fn pragma_func() -> Result<()> {
let db = Connection::open_in_memory()?;
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)")?;
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"])?;
@ -408,18 +405,17 @@ mod test {
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
assert!(
journal_mode == "off" || journal_mode == "memory",
"mode: {:?}",
journal_mode,
"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);
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);
assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
Ok(())
}

View File

@ -169,8 +169,10 @@ impl RawStatement {
}
#[inline]
pub fn clear_bindings(&self) -> c_int {
unsafe { ffi::sqlite3_clear_bindings(self.ptr) }
pub fn clear_bindings(&self) {
unsafe {
ffi::sqlite3_clear_bindings(self.ptr);
} // rc is always SQLITE_OK
}
#[inline]
@ -195,7 +197,6 @@ impl RawStatement {
}
// does not work for PRAGMA
#[cfg(feature = "extra_check")]
#[inline]
pub fn readonly(&self) -> bool {
unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 }

View File

@ -257,6 +257,7 @@ impl<'stmt> Row<'stmt> {
/// * 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()
}
@ -277,6 +278,7 @@ impl<'stmt> Row<'stmt> {
/// 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);
@ -335,23 +337,10 @@ impl<'stmt> Row<'stmt> {
///
/// * 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()
}
/// Renamed to [`get_ref`](Row::get_ref).
#[deprecated = "Use [`get_ref`](Row::get_ref) instead."]
#[inline]
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
self.get_ref(idx)
}
/// Renamed to [`get_ref_unwrap`](Row::get_ref_unwrap).
#[deprecated = "Use [`get_ref_unwrap`](Row::get_ref_unwrap) instead."]
#[inline]
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
self.get_ref_unwrap(idx)
}
}
impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
@ -360,6 +349,46 @@ impl<'stmt> AsRef<Statement<'stmt>> for Row<'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<'stmt> std::fmt::Debug for Row<'stmt> {
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);
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 Params`
/// that are allowed are ones in this crate.

162
src/serialize.rs Normal file
View File

@ -0,0 +1,162 @@
//! Serialize a database.
use std::convert::TryInto;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use crate::error::error_from_handle;
use crate::ffi;
use crate::{Connection, DatabaseName, 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_malloc`.
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<'conn> Deref for Data<'conn> {
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<'conn>(&'conn self, schema: DatabaseName<'_>) -> Result<Data<'conn>> {
let schema = schema.as_cstring()?;
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 a database.
pub fn deserialize(
&mut self,
schema: DatabaseName<'_>,
data: OwnedData,
read_only: bool,
) -> Result<()> {
let schema = schema.as_cstring()?;
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
};
let rc = unsafe {
ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags)
};
if rc != ffi::SQLITE_OK {
// TODO sqlite3_free(data) ?
return Err(unsafe { error_from_handle(self.handle(), rc) });
}
/* TODO
if let Some(mxSize) = mxSize {
unsafe {
ffi::sqlite3_file_control(
self.handle(),
schema.as_ptr(),
ffi::SQLITE_FCNTL_SIZE_LIMIT,
&mut mxSize,
)
};
}*/
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Connection, DatabaseName, Result};
#[test]
fn serialize() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = db.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else { panic!("expected OwnedData")};
assert!(data.sz > 0);
Ok(())
}
#[test]
fn deserialize() -> Result<()> {
let src = Connection::open_in_memory()?;
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = src.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else { panic!("expected OwnedData")};
let mut dst = Connection::open_in_memory()?;
dst.deserialize(DatabaseName::Main, data, false)?;
dst.execute("DELETE FROM x", [])?;
Ok(())
}
}

View File

@ -75,13 +75,10 @@ impl Session<'_> {
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8(c_slice)
};
if let Ok(true) =
c_int::from(
catch_unwind(|| (*boxed_filter)(tbl_name.expect("non-utf8 table name")))
{
1
} else {
0
}
.unwrap_or_default(),
)
}
match filter {
@ -193,7 +190,7 @@ impl Session<'_> {
#[inline]
pub fn set_enabled(&mut self, enabled: bool) {
unsafe {
ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
ffi::sqlite3session_enable(self.s, c_int::from(enabled));
}
}
@ -207,7 +204,7 @@ impl Session<'_> {
#[inline]
pub fn set_indirect(&mut self, indirect: bool) {
unsafe {
ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
ffi::sqlite3session_indirect(self.s, c_int::from(indirect));
}
}
}
@ -708,13 +705,9 @@ where
str::from_utf8(c_slice)
};
match *tuple {
(Some(ref filter), _) => {
if let Ok(true) = catch_unwind(|| filter(tbl_name.expect("illegal table name"))) {
1
} else {
0
}
}
(Some(ref filter), _) => c_int::from(
catch_unwind(|| filter(tbl_name.expect("illegal table name"))).unwrap_or_default(),
),
_ => unimplemented!(),
}
}
@ -786,7 +779,7 @@ mod test {
assert!(session.is_empty());
session.attach(None)?;
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
session.changeset()
}
@ -799,7 +792,7 @@ mod test {
assert!(session.is_empty());
session.attach(None)?;
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
let mut output = Vec::new();
session.changeset_strm(&mut output)?;
@ -859,7 +852,7 @@ mod test {
)?;
assert!(!CALLED.load(Ordering::Relaxed));
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
row.get::<_, i32>(0)
})?;
assert_eq!(1, check);
@ -894,7 +887,7 @@ mod test {
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
)?;
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
row.get::<_, i32>(0)
})?;
assert_eq!(1, check);
@ -910,7 +903,7 @@ mod test {
assert!(session.is_empty());
session.attach(None)?;
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
assert!(!session.is_empty());
Ok(())

View File

@ -33,7 +33,7 @@ impl Statement<'_> {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, params};
/// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("UPDATE foo SET bar = ? WHERE qux = ?")?;
/// let mut stmt = conn.prepare("UPDATE foo SET bar = ?1 WHERE qux = ?2")?;
/// // For a single parameter, or a parameter where all the values have
/// // the same type, just passing an array is simplest.
/// stmt.execute([2i32])?;
@ -58,7 +58,7 @@ impl Statement<'_> {
/// fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> {
/// # // no need to do it for real.
/// # fn sha256(_: &[u8]) -> [u8; 32] { [0; 32] }
/// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?, ?, ?)";
/// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?1, ?2, ?3)";
/// let mut stmt = conn.prepare_cached(query)?;
/// let hash: [u8; 32] = sha256(data);
/// // The easiest way to pass positional parameters of have several
@ -114,31 +114,6 @@ impl Statement<'_> {
self.execute_with_bound_parameters()
}
/// Execute the prepared statement with named parameter(s).
///
/// Note: This function is deprecated in favor of [`Statement::execute`],
/// which can now take named parameters directly.
///
/// If any parameters that were in the prepared statement are not included
/// in `params`, they will continue to use the most-recently bound value
/// from a previous call to `execute_named`, or `NULL` if they have never
/// been bound.
///
/// On success, returns the number of rows that were changed or inserted or
/// deleted (via `sqlite3_changes`).
///
/// # Failure
///
/// Will return `Err` if binding parameters fails, the executed statement
/// returns rows (in which case `query` should be used instead), or the
/// underlying SQLite call fails.
#[doc(hidden)]
#[deprecated = "You can use `execute` with named params now."]
#[inline]
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
self.execute(params)
}
/// Execute an INSERT and return the ROWID.
///
/// # Note
@ -193,7 +168,7 @@ impl Statement<'_> {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn query(conn: &Connection, name: &str) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
/// let mut rows = stmt.query(rusqlite::params![name])?;
/// while let Some(row) = rows.next()? {
/// // ...
@ -207,7 +182,7 @@ impl Statement<'_> {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn query(conn: &Connection, name: &str) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
/// let mut rows = stmt.query([name])?;
/// while let Some(row) = rows.next()? {
/// // ...
@ -254,26 +229,6 @@ impl Statement<'_> {
Ok(Rows::new(self))
}
/// Execute the prepared statement with named parameter(s), returning a
/// handle for the resulting rows.
///
/// Note: This function is deprecated in favor of [`Statement::query`],
/// which can now take named parameters directly.
///
/// If any parameters that were in the prepared statement are not included
/// in `params`, they will continue to use the most-recently bound value
/// from a previous call to `query_named`, or `NULL` if they have never been
/// bound.
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
#[doc(hidden)]
#[deprecated = "You can use `query` with named params now."]
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
self.query(params)
}
/// Executes the prepared statement and maps a function over the resulting
/// rows, returning an iterator over the mapped function results.
///
@ -328,37 +283,6 @@ impl Statement<'_> {
self.query(params).map(|rows| rows.mapped(f))
}
/// Execute the prepared statement with named parameter(s), returning an
/// iterator over the result of calling the mapping function over the
/// query's rows.
///
/// Note: This function is deprecated in favor of [`Statement::query_map`],
/// which can now take named parameters directly.
///
/// If any parameters that were in the prepared statement
/// are not included in `params`, they will continue to use the
/// most-recently bound value from a previous call to `query_named`,
/// or `NULL` if they have never been bound.
///
/// `f` is used to transform the _streaming_ iterator into a _standard_
/// iterator.
///
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
#[doc(hidden)]
#[deprecated = "You can use `query_map` with named params now."]
pub fn query_map_named<T, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
f: F,
) -> Result<MappedRows<'_, F>>
where
F: FnMut(&Row<'_>) -> Result<T>,
{
self.query_map(params, f)
}
/// Executes the prepared statement and maps a function over the resulting
/// rows, where the function returns a `Result` with `Error` type
/// implementing `std::convert::From<Error>` (so errors can be unified).
@ -398,7 +322,7 @@ impl Statement<'_> {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?")?;
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?1")?;
/// let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
///
/// let mut persons = Vec::new();
@ -423,36 +347,6 @@ impl Statement<'_> {
self.query(params).map(|rows| rows.and_then(f))
}
/// Execute the prepared statement with named parameter(s), returning an
/// iterator over the result of calling the mapping function over the
/// query's rows.
///
/// Note: This function is deprecated in favor of
/// [`Statement::query_and_then`], which can now take named parameters
/// directly.
///
/// If any parameters that were in the prepared statement are not included
/// in `params`, they will continue to use the most-recently bound value
/// from a previous call to `query_named`, or `NULL` if they have never been
/// bound.
///
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
#[doc(hidden)]
#[deprecated = "You can use `query_and_then` with named params now."]
pub fn query_and_then_named<T, E, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
f: F,
) -> Result<AndThenRows<'_, F>>
where
E: From<Error>,
F: FnMut(&Row<'_>) -> Result<T, E>,
{
self.query_and_then(params, f)
}
/// Return `true` if a query in the SQL statement it executes returns one
/// or more rows and `false` if the SQL returns an empty set.
#[inline]
@ -487,35 +381,6 @@ impl Statement<'_> {
rows.get_expected_row().and_then(f)
}
/// Convenience method to execute a query with named parameter(s) that is
/// expected to return a single row.
///
/// Note: This function is deprecated in favor of
/// [`Statement::query_and_then`], which can now take named parameters
/// directly.
///
/// If the query returns more than one row, all rows except the first are
/// ignored.
///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
/// query truly is optional, you can call
/// [`.optional()`](crate::OptionalExtension::optional) on the result of
/// this to get a `Result<Option<T>>` (requires that the trait
/// `rusqlite::OptionalExtension` is imported).
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
#[doc(hidden)]
#[deprecated = "You can use `query_row` with named params now."]
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
self.query_row(params, f)
}
/// Consumes the statement.
///
/// Functionally equivalent to the `Drop` implementation, but allows
@ -844,6 +709,12 @@ impl Statement<'_> {
self.stmt.is_explain()
}
/// Returns true if the statement is read only.
#[inline]
pub fn readonly(&self) -> bool {
self.stmt.readonly()
}
#[cfg(feature = "extra_check")]
#[inline]
pub(crate) fn check_no_tail(&self) -> Result<()> {
@ -870,6 +741,11 @@ impl Statement<'_> {
mem::swap(&mut stmt, &mut self.stmt);
stmt
}
/// Reset all bindings
pub fn clear_bindings(&mut self) {
self.stmt.clear_bindings()
}
}
impl fmt::Debug for Statement<'_> {
@ -1009,13 +885,12 @@ mod test {
use crate::{params_from_iter, Connection, Error, Result};
#[test]
#[allow(deprecated)]
fn test_execute_named() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
1
);
assert_eq!(
@ -1032,7 +907,7 @@ mod test {
assert_eq!(
6i32,
db.query_row_named::<i32, _>(
db.query_row::<i32, _, _>(
"SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)],
|r| r.get(0)
@ -1050,7 +925,6 @@ mod test {
}
#[test]
#[allow(deprecated)]
fn test_stmt_execute_named() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
@ -1058,13 +932,9 @@ mod test {
db.execute_batch(sql)?;
let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?;
stmt.execute_named(&[(":name", &"one")])?;
stmt.execute(&[(":name", &"one")])?;
let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?;
assert_eq!(
1i32,
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))?
);
assert_eq!(
1i32,
stmt.query_row::<i32, _, _>(&[(":name", "one")], |r| r.get(0))?
@ -1073,7 +943,6 @@ mod test {
}
#[test]
#[allow(deprecated)]
fn test_query_named() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = r#"
@ -1083,24 +952,13 @@ mod test {
db.execute_batch(sql)?;
let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
// legacy `_named` api
{
let mut rows = stmt.query_named(&[(":name", &"one")])?;
let id: Result<i32> = rows.next()?.unwrap().get(0);
assert_eq!(Ok(1), id);
}
// plain api
{
let mut rows = stmt.query(&[(":name", "one")])?;
let id: Result<i32> = rows.next()?.unwrap().get(0);
assert_eq!(Ok(1), id);
}
let mut rows = stmt.query(&[(":name", "one")])?;
let id: Result<i32> = rows.next()?.unwrap().get(0);
assert_eq!(Ok(1), id);
Ok(())
}
#[test]
#[allow(deprecated)]
fn test_query_map_named() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = r#"
@ -1110,61 +968,13 @@ mod test {
db.execute_batch(sql)?;
let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
// legacy `_named` api
{
let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| {
let id: Result<i32> = row.get(0);
id.map(|i| 2 * i)
})?;
let doubled_id: i32 = rows.next().unwrap()?;
assert_eq!(2, doubled_id);
}
// plain api
{
let mut rows = stmt.query_map(&[(":name", "one")], |row| {
let id: Result<i32> = row.get(0);
id.map(|i| 2 * i)
})?;
let doubled_id: i32 = rows.next().unwrap()?;
assert_eq!(2, doubled_id);
}
Ok(())
}
#[test]
#[allow(deprecated)]
fn test_query_and_then_named() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
INSERT INTO test(id, name) VALUES (1, "one");
INSERT INTO test(id, name) VALUES (2, "one");
"#;
db.execute_batch(sql)?;
let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0)?;
if id == 1 {
Ok(id)
} else {
Err(Error::SqliteSingleThreadedMode)
}
let mut rows = stmt.query_map(&[(":name", "one")], |row| {
let id: Result<i32> = row.get(0);
id.map(|i| 2 * i)
})?;
// first row should be Ok
let doubled_id: i32 = rows.next().unwrap()?;
assert_eq!(1, doubled_id);
// second row should be Err
#[allow(clippy::match_wild_err_arm)]
match rows.next().unwrap() {
Ok(_) => panic!("invalid Ok"),
Err(Error::SqliteSingleThreadedMode) => (),
Err(_) => panic!("invalid Err"),
}
assert_eq!(2, doubled_id);
Ok(())
}
@ -1203,17 +1013,15 @@ mod test {
}
#[test]
#[allow(deprecated)]
fn test_unbound_parameters_are_null() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql)?;
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
stmt.execute_named(&[(":x", &"one")])?;
stmt.execute(&[(":x", &"one")])?;
let result: Option<String> =
db.query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0))?;
let result: Option<String> = db.one_column("SELECT y FROM test WHERE x = 'one'")?;
assert!(result.is_none());
Ok(())
}
@ -1259,8 +1067,7 @@ mod test {
stmt.execute(&[(":x", "one")])?;
stmt.execute(&[(":y", "two")])?;
let result: String =
db.query_row("SELECT x FROM test WHERE y = 'two'", [], |row| row.get(0))?;
let result: String = db.one_column("SELECT x FROM test WHERE y = 'two'")?;
assert_eq!(result, "one");
Ok(())
}
@ -1269,17 +1076,17 @@ mod test {
fn test_insert() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")?;
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")?;
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?1)")?;
assert_eq!(stmt.insert([1i32])?, 1);
assert_eq!(stmt.insert([2i32])?, 2);
match stmt.insert([1i32]).unwrap_err() {
Error::StatementChangedRows(0) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")?;
match multi.insert([]).unwrap_err() {
Error::StatementChangedRows(2) => (),
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
@ -1309,7 +1116,7 @@ mod test {
INSERT INTO foo VALUES(2);
END;";
db.execute_batch(sql)?;
let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?")?;
let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?1")?;
assert!(stmt.exists([1i32])?);
assert!(stmt.exists([2i32])?);
assert!(!stmt.exists([0i32])?);
@ -1318,18 +1125,18 @@ mod test {
#[test]
fn test_tuple_params() -> Result<()> {
let db = Connection::open_in_memory()?;
let s = db.query_row("SELECT printf('[%s]', ?)", ("abc",), |r| {
let s = db.query_row("SELECT printf('[%s]', ?1)", ("abc",), |r| {
r.get::<_, String>(0)
})?;
assert_eq!(s, "[abc]");
let s = db.query_row(
"SELECT printf('%d %s %d', ?, ?, ?)",
"SELECT printf('%d %s %d', ?1, ?2, ?3)",
(1i32, "abc", 2i32),
|r| r.get::<_, String>(0),
)?;
assert_eq!(s, "1 abc 2");
let s = db.query_row(
"SELECT printf('%d %s %d %d', ?, ?, ?, ?)",
"SELECT printf('%d %s %d %d', ?1, ?2, ?3, ?4)",
(1, "abc", 2i32, 4i64),
|r| r.get::<_, String>(0),
)?;
@ -1341,10 +1148,10 @@ mod test {
);
let query = "SELECT printf(
'%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s',
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?
?1, ?2, ?3, ?4,
?5, ?6, ?7, ?8,
?9, ?10, ?11, ?12,
?13, ?14, ?15, ?16
)";
let s = db.query_row(query, bigtup, |r| r.get::<_, String>(0))?;
assert_eq!(s, "0 a | 1 b | 2 c | 3 d || 4 e | 5 f | 6 g | 7 h");
@ -1360,7 +1167,7 @@ mod test {
INSERT INTO foo VALUES(2, 4);
END;";
db.execute_batch(sql)?;
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?")?;
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?;
let y: Result<i64> = stmt.query_row([1i32], |r| r.get(0));
assert_eq!(3i64, y?);
Ok(())
@ -1397,7 +1204,7 @@ mod test {
#[test]
fn test_expanded_sql() -> Result<()> {
let db = Connection::open_in_memory()?;
let stmt = db.prepare("SELECT ?")?;
let stmt = db.prepare("SELECT ?1")?;
stmt.bind_parameter(&1, 1)?;
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
Ok(())
@ -1499,8 +1306,8 @@ mod test {
assert_eq!("UTF-16le", encoding);
db.execute_batch("CREATE TABLE foo(x TEXT)")?;
let expected = "テスト";
db.execute("INSERT INTO foo(x) VALUES (?)", [&expected])?;
let actual: String = db.query_row("SELECT x FROM foo", [], |row| row.get(0))?;
db.execute("INSERT INTO foo(x) VALUES (?1)", [&expected])?;
let actual: String = db.one_column("SELECT x FROM foo")?;
assert_eq!(expected, actual);
Ok(())
}
@ -1509,7 +1316,7 @@ mod test {
fn test_nul_byte() -> Result<()> {
let db = Connection::open_in_memory()?;
let expected = "a\x00b";
let actual: String = db.query_row("SELECT ?", [expected], |row| row.get(0))?;
let actual: String = db.query_row("SELECT ?1", [expected], |row| row.get(0))?;
assert_eq!(expected, actual);
Ok(())
}
@ -1523,6 +1330,14 @@ mod test {
Ok(())
}
#[test]
fn readonly() -> Result<()> {
let db = Connection::open_in_memory()?;
let stmt = db.prepare("SELECT 1;")?;
assert!(stmt.readonly());
Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
fn test_error_offset() -> Result<()> {
@ -1534,7 +1349,7 @@ mod test {
assert_eq!(error.code, ErrorCode::Unknown);
assert_eq!(offset, 7);
}
err => panic!("Unexpected error {}", err),
err => panic!("Unexpected error {err}"),
}
Ok(())
}

View File

@ -144,13 +144,13 @@ mod test {
let mut db = Connection::open_in_memory()?;
db.trace(Some(tracer));
{
let _ = db.query_row("SELECT ?", [1i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", ["hello"], |_| Ok(()));
let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
}
db.trace(None);
{
let _ = db.query_row("SELECT ?", [2i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(()));
let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
}
let traced_stmts = TRACED_STMTS.lock().unwrap();

View File

@ -91,7 +91,6 @@ pub struct Transaction<'conn> {
pub struct Savepoint<'conn> {
conn: &'conn Connection,
name: String,
depth: u32,
drop_behavior: DropBehavior,
committed: bool,
}
@ -158,13 +157,13 @@ impl Transaction<'_> {
/// ```
#[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, 1)
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_depth_and_name(self.conn, 1, name)
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the transaction when it is
@ -249,50 +248,44 @@ impl Drop for Transaction<'_> {
impl Savepoint<'_> {
#[inline]
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
name: T,
) -> Result<Savepoint<'_>> {
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,
depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
})
}
#[inline]
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
let name = format!("_rusqlite_sp_{depth}");
Savepoint::with_depth_and_name(conn, depth, name)
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::with_depth(conn, 0)
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_depth_and_name(conn, 0, name)
Savepoint::with_name_(conn, name)
}
/// Begin a nested savepoint.
#[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, self.depth + 1)
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_depth_and_name(self.conn, self.depth + 1, name)
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the savepoint when it is
@ -351,8 +344,10 @@ impl Savepoint<'_> {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
DropBehavior::Rollback => self.rollback(),
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."),
}
@ -552,10 +547,7 @@ mod test {
}
{
let tx = db.transaction()?;
assert_eq!(
2i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
}
Ok(())
}
@ -566,7 +558,7 @@ mod test {
assert_eq!(e.code, crate::ErrorCode::Unknown);
assert!(m.contains("transaction"));
} else {
panic!("Unexpected error type: {:?}", e);
panic!("Unexpected error type: {e:?}");
}
}
@ -591,10 +583,7 @@ mod test {
tx.commit()?;
}
assert_eq!(
2i32,
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
Ok(())
}
@ -619,10 +608,7 @@ mod test {
}
{
let tx = db.transaction()?;
assert_eq!(
6i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
}
Ok(())
}
@ -684,6 +670,40 @@ mod test {
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()?;
@ -727,11 +747,11 @@ mod test {
}
fn insert(x: i32, conn: &Connection) -> Result<usize> {
conn.execute("INSERT INTO foo VALUES(?)", [x])
conn.execute("INSERT INTO foo VALUES(?1)", [x])
}
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?;
assert_eq!(x, i);
Ok(())
}

View File

@ -175,12 +175,12 @@ mod test {
#[test]
fn test_naive_date() -> Result<()> {
let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
db.execute("INSERT INTO foo (t) VALUES (?)", [date])?;
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23", s);
let t: NaiveDate = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let t: NaiveDate = db.one_column("SELECT t FROM foo")?;
assert_eq!(date, t);
Ok(())
}
@ -188,12 +188,12 @@ mod test {
#[test]
fn test_naive_time() -> Result<()> {
let db = checked_memory_handle()?;
let time = NaiveTime::from_hms(23, 56, 4);
db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("23:56:04", s);
let v: NaiveTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let v: NaiveTime = db.one_column("SELECT t FROM foo")?;
assert_eq!(time, v);
Ok(())
}
@ -201,19 +201,19 @@ mod test {
#[test]
fn test_naive_date_time() -> Result<()> {
let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms(23, 56, 4);
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 (?)", [dt])?;
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23 23:56:04", s);
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
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.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let hms: NaiveDateTime = db.one_column("SELECT b FROM foo")?;
assert_eq!(dt, hms);
Ok(())
}
@ -221,28 +221,26 @@ mod test {
#[test]
fn test_date_time_utc() -> Result<()> {
let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
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 (?)", [utc])?;
db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
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.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let v1: DateTime<Utc> = db.one_column("SELECT t FROM foo")?;
assert_eq!(utc, v1);
let v2: DateTime<Utc> =
db.query_row("SELECT '2016-02-23 23:56:04.789'", [], |r| r.get(0))?;
let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'")?;
assert_eq!(utc, v2);
let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", [], |r| r.get(0))?;
let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'")?;
assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<Utc> =
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", [], |r| r.get(0))?;
let v4: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'")?;
assert_eq!(utc, v4);
Ok(())
}
@ -250,18 +248,18 @@ mod test {
#[test]
fn test_date_time_local() -> Result<()> {
let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
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 (?)", [local])?;
db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?;
// Stored string should be in UTC
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert!(s.ends_with("+00:00"));
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let v: DateTime<Local> = db.one_column("SELECT t FROM foo")?;
assert_eq!(local, v);
Ok(())
}
@ -271,13 +269,13 @@ mod test {
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 (?)", [time])?;
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
// Stored string should preserve timezone offset
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert!(s.ends_with("+04:00"));
let v: DateTime<FixedOffset> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo")?;
assert_eq!(time.offset(), v.offset());
assert_eq!(time, v);
Ok(())
@ -286,15 +284,13 @@ mod test {
#[test]
fn test_sqlite_functions() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<NaiveTime> = db.query_row("SELECT CURRENT_TIME", [], |r| r.get(0));
let result: Result<NaiveTime> = db.one_column("SELECT CURRENT_TIME");
result.unwrap();
let result: Result<NaiveDate> = db.query_row("SELECT CURRENT_DATE", [], |r| r.get(0));
let result: Result<NaiveDate> = db.one_column("SELECT CURRENT_DATE");
result.unwrap();
let result: Result<NaiveDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
let result: Result<NaiveDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP");
result.unwrap();
let result: Result<DateTime<Utc>> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
let result: Result<DateTime<Utc>> = db.one_column("SELECT CURRENT_TIMESTAMP");
result.unwrap();
Ok(())
}
@ -302,7 +298,7 @@ mod test {
#[test]
fn test_naive_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
result.unwrap();
Ok(())
}
@ -310,7 +306,7 @@ mod test {
#[test]
fn test_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
result.unwrap();
Ok(())
}

View File

@ -59,8 +59,7 @@ impl fmt::Display for FromSqlError {
} => {
write!(
f,
"Cannot read {} byte value out of {} byte blob",
expected_size, blob_size
"Cannot read {expected_size} byte value out of {blob_size} byte blob"
)
}
FromSqlError::Other(ref err) => err.fmt(f),
@ -96,6 +95,15 @@ macro_rules! from_sql_integral(
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))
}
}
)
);
@ -110,6 +118,22 @@ from_sql_integral!(u32);
from_sql_integral!(u64);
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")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_sql_integral!(non_zero std::num::NonZeroI128, i128);
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);
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> {
@ -244,17 +268,17 @@ mod test {
{
for n in out_of_range {
let err = db
.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
.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),
_ => panic!("unexpected error: {err}"),
}
}
for n in in_range {
assert_eq!(
*n,
db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
.unwrap()
.into()
);
@ -273,4 +297,70 @@ mod test {
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]
);
check_ranges!(
std::num::NonZeroU64,
&[0, -2, -1, -4_294_967_296],
&[1, 4_294_967_295, i64::MAX as u64]
);
check_ranges!(
std::num::NonZeroUsize,
&[0, -2, -1, -4_294_967_296],
&[1, 4_294_967_295]
);
Ok(())
}
}

View File

@ -26,7 +26,8 @@
//! [`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`.
//! 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,
@ -102,7 +103,7 @@ mod value_ref;
/// # use rusqlite::types::{Null};
///
/// fn insert_null(conn: &Connection) -> Result<usize> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
/// conn.execute("INSERT INTO people (name) VALUES (?1)", [Null])
/// }
/// ```
#[derive(Copy, Clone)]
@ -153,9 +154,9 @@ mod test {
let db = checked_memory_handle()?;
let v1234 = vec![1u8, 2, 3, 4];
db.execute("INSERT INTO foo(b) VALUES (?)", [&v1234])?;
db.execute("INSERT INTO foo(b) VALUES (?1)", [&v1234])?;
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
assert_eq!(v, v1234);
Ok(())
}
@ -165,9 +166,9 @@ mod test {
let db = checked_memory_handle()?;
let empty = vec![];
db.execute("INSERT INTO foo(b) VALUES (?)", [&empty])?;
db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
assert_eq!(v, empty);
Ok(())
}
@ -177,9 +178,9 @@ mod test {
let db = checked_memory_handle()?;
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: String = db.one_column("SELECT t FROM foo")?;
assert_eq!(from, s);
Ok(())
}
@ -189,9 +190,9 @@ mod test {
let db = checked_memory_handle()?;
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [s.to_owned()])?;
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: String = db.one_column("SELECT t FROM foo")?;
assert_eq!(from, s);
Ok(())
}
@ -200,12 +201,9 @@ mod test {
fn test_value() -> Result<()> {
let db = checked_memory_handle()?;
db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
assert_eq!(
10i64,
db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
);
assert_eq!(10i64, db.one_column::<i64>("SELECT i FROM foo")?);
Ok(())
}
@ -213,11 +211,11 @@ mod test {
fn test_option() -> Result<()> {
let db = checked_memory_handle()?;
let s = Some("hello, world!");
let s = "hello, world!";
let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
db.execute("INSERT INTO foo(b) VALUES (?)", [&b])?;
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([])?;
@ -226,7 +224,7 @@ mod test {
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.unwrap(), s1.unwrap());
assert_eq!(s, s1.unwrap());
assert!(b1.is_none());
}
@ -355,7 +353,7 @@ mod test {
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),
x => panic!("Invalid Value {x:?}"),
}
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
Ok(())

View File

@ -1,24 +1,62 @@
//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
use serde_json::Value;
use serde_json::{Number, Value};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
use crate::{Error, Result};
/// Serialize JSON `Value` to text.
/// 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<'_>> {
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
match self {
Value::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
Value::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
Value::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 text/blob to JSON `Value`.
/// 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> {
let bytes = value.as_bytes()?;
serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
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(Value::Number(Number::from(i))),
ValueRef::Real(f) => {
match Number::from_f64(f) {
Some(n) => Ok(Value::Number(n)),
_ => return Err(FromSqlError::InvalidType), // FIXME
}
}
ValueRef::Null => Ok(Value::Null),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
@ -26,6 +64,7 @@ impl FromSql for Value {
mod 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()?;
@ -38,16 +77,59 @@ mod test {
let db = checked_memory_handle()?;
let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap();
let data: Value = serde_json::from_str(json).unwrap();
db.execute(
"INSERT INTO foo (t, b) VALUES (?, ?)",
"INSERT INTO foo (t, b) VALUES (?1, ?2)",
[&data as &dyn ToSql, &json.as_bytes()],
)?;
let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let t: Value = db.one_column("SELECT t FROM foo")?;
assert_eq!(data, t);
let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
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.query_row("SELECT ?", [Value::Null], |r| r.get(0))?;
assert_eq!(None, v);
let v: String = db.query_row("SELECT ?", [Value::Bool(true)], |r| r.get(0))?;
assert_eq!("true", v);
let v: i64 = db.query_row("SELECT ?", [Value::Number(Number::from(1))], |r| r.get(0))?;
assert_eq!(1, v);
let v: f64 = db.query_row(
"SELECT ?",
[Value::Number(Number::from_f64(0.1).unwrap())],
|r| r.get(0),
)?;
assert_eq!(0.1, v);
let v: String =
db.query_row("SELECT ?", [Value::String("text".to_owned())], |r| r.get(0))?;
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(())
}
}

View File

@ -1,67 +1,155 @@
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::PrimitiveDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::Date`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::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, ValueRef};
use crate::{Error, Result};
use time::format_description::well_known::Rfc3339;
use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
);
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
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<'_>> {
// FIXME keep original offset
let time_string = self
.to_offset(UtcOffset::UTC)
.format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
.format(&OFFSET_DATE_TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_string))
}
}
// Supports parsing formats 2-7 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> {
value.as_str().and_then(|s| {
if s.len() > 10 && s.as_bytes()[10] == b'T' {
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
return OffsetDateTime::parse(s, &Rfc3339)
if let Some(b' ') = s.as_bytes().get(23) {
// legacy
return OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(Box::new(err)));
}
let s = s.strip_suffix('Z').unwrap_or(s);
match s.len() {
len if len <= 19 => {
// TODO YYYY-MM-DDTHH:MM:SS
PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
}
_ if s.as_bytes()[19] == b':' => {
// legacy
OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
}
_ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.or_else(|err| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
.map_err(|_| err)
}),
_ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
if s[8..].contains('+') || s[8..].contains('-') {
// Formats 2-7 with timezone
return OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(Box::new(err)));
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
// Formats 2-7 without timezone
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
.map(|p| p.assume_utc())
.map_err(|err| FromSqlError::Other(Box::new(err)))
})
}
}
/// 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| {
Date::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| {
Time::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| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(err.into()))
})
}
}
@ -69,13 +157,18 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
use crate::{Connection, Result};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
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, f FLOAT, b BLOB)")?;
Ok(db)
}
#[test]
fn test_offset_date_time() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
let db = checked_memory_handle()?;
let mut ts_vec = vec![];
@ -91,9 +184,9 @@ mod test {
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 (?)", [ts])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [ts])?;
let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: OffsetDateTime = db.one_column("SELECT t FROM foo")?;
db.execute("DELETE FROM foo", [])?;
@ -103,47 +196,163 @@ mod test {
}
#[test]
fn test_string_values() -> Result<()> {
let db = Connection::open_in_memory()?;
for (s, t) in vec![
fn test_offset_date_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
// Rfc3339
(
"2013-10-07 08:23:19",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123456789Z",
datetime!(2013-10-07 8:23:19.123456789 UTC),
),
(
"2013-10-07 08:23:19Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"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-07T08:23:19Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"2013-10-07 08:23:19.123Z",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07 08:23:19.120",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"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-07 08:23:19.120Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123Z",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07T08:23:19.120Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123+04:00",
datetime!(2013-10-07 8:23:19.123 +4),
),
// Legacy
(
"2013-10-07 04:23:19-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
"2013-10-07 08:23:12:987 -07:00",
datetime!(2013-10-07 8:23:12.987 -7),
),
(
"2013-10-07 04:23:19.120-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
),
(
"2013-10-07T04:23:19.120-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
),
] {
let result: Result<OffsetDateTime> = db.query_row("SELECT ?", [s], |r| r.get(0));
];
for (s, t) in tests {
let result: OffsetDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
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.query_row("SELECT ?1", ["2013-10-07"], |r| r.get(0))?;
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.query_row("SELECT ?1", [s], |r| r.get(0))?;
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.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t);
}
Ok(())
@ -151,17 +360,66 @@ mod test {
#[test]
fn test_sqlite_functions() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<OffsetDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
let db = checked_memory_handle()?;
db.one_column::<Time>("SELECT CURRENT_TIME").unwrap();
db.one_column::<Date>("SELECT CURRENT_DATE").unwrap();
db.one_column::<PrimitiveDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
db.one_column::<OffsetDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
Ok(())
}
#[test]
fn test_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = OffsetDateTime::now_utc().time();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_param() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
fn test_date_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = OffsetDateTime::now_utc().date();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
[now],
|r| r.get(0),
);
result.unwrap();
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.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_offset_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[OffsetDateTime::now_utc()],
|r| r.get(0),
);
result.unwrap();
Ok(())
}

View File

@ -51,6 +51,12 @@ macro_rules! from_value(
#[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);
@ -68,6 +74,15 @@ 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.
@ -75,6 +90,10 @@ from_value!(Vec<u8>);
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(i128);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(non_zero std::num::NonZeroI128);
#[cfg(feature = "uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
from_value!(uuid::Uuid);
@ -165,10 +184,23 @@ 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")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(i128);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(std::num::NonZeroI128);
#[cfg(feature = "uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
to_sql_self!(uuid::Uuid);
@ -186,12 +218,27 @@ macro_rules! to_sql_self_fallible(
)))
}
}
);
(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.
to_sql_self_fallible!(u64);
to_sql_self_fallible!(usize);
to_sql_self_fallible!(non_zero std::num::NonZeroU64);
to_sql_self_fallible!(non_zero std::num::NonZeroUsize);
impl<T: ?Sized> ToSql for &'_ T
where
@ -267,9 +314,26 @@ mod test {
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>();
is_to_sql::<u64>();
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>();
is_to_sql::<std::num::NonZeroU64>();
is_to_sql::<std::num::NonZeroUsize>();
}
#[test]
@ -368,10 +432,10 @@ mod test {
db.execute(
"
INSERT INTO foo(i128, desc) VALUES
(?, 'zero'),
(?, 'neg one'), (?, 'neg two'),
(?, 'pos one'), (?, 'pos two'),
(?, 'min'), (?, 'max')",
(?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],
)?;
@ -398,6 +462,54 @@ mod test {
Ok(())
}
#[cfg(feature = "i128_blob")]
#[test]
fn test_non_zero_i128() -> crate::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() -> crate::Result<()> {
@ -410,11 +522,11 @@ mod test {
let id = Uuid::new_v4();
db.execute(
"INSERT INTO foo (id, label) VALUES (?, ?)",
"INSERT INTO foo (id, label) VALUES (?1, ?2)",
params![id, "target"],
)?;
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
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();

View File

@ -49,7 +49,7 @@ mod test {
let url2 = "http://www.example2.com/👌";
db.execute(
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
"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"],
@ -74,7 +74,7 @@ mod test {
);
}
e => {
panic!("Expected conversion failure, got {}", e);
panic!("Expected conversion failure, got {e}");
}
}
Ok(())

View File

@ -158,6 +158,7 @@ impl<'a> ValueRef<'a> {
impl From<ValueRef<'_>> for Value {
#[inline]
#[track_caller]
fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed {
ValueRef::Null => Value::Null,

View File

@ -109,8 +109,8 @@ mod test {
tx2.commit().unwrap();
});
assert_eq!(tx.recv().unwrap(), 1);
let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", [], |r| r.get(0));
assert_eq!(42i64, the_answer?);
let the_answer: i64 = db1.one_column("SELECT x FROM foo")?;
assert_eq!(42i64, the_answer);
child.join().unwrap();
Ok(())
}

View File

@ -6,4 +6,4 @@ 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::SqliteMallocString;
pub(crate) use sqlite_string::{alloc, SqliteMallocString};

View File

@ -7,6 +7,12 @@ use std::marker::PhantomData;
use std::os::raw::{c_char, c_int};
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_malloc` is used.
@ -100,7 +106,6 @@ impl SqliteMallocString {
/// This means it's safe to use in extern "C" functions even outside of
/// `catch_unwind`.
pub(crate) fn from_str(s: &str) -> Self {
use std::convert::TryFrom;
let s = if s.as_bytes().contains(&0) {
std::borrow::Cow::Owned(make_nonnull(s))
} else {

View File

@ -17,7 +17,7 @@
//! 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(?);")?;
//! 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?);
@ -206,7 +206,7 @@ mod test {
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(?);")?;
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));

View File

@ -113,10 +113,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
match param {
"filename" => {
if !Path::new(value).exists() {
return Err(Error::ModuleError(format!(
"file '{}' does not exist",
value
)));
return Err(Error::ModuleError(format!("file '{value}' does not exist")));
}
vtab.filename = value.to_owned();
}
@ -137,8 +134,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
n_col = Some(n);
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'columns': {}",
value
"unrecognized argument to 'columns': {value}"
)));
}
}
@ -147,8 +143,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
vtab.has_headers = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'header': {}",
value
"unrecognized argument to 'header': {value}"
)));
}
}
@ -157,8 +152,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
vtab.delimiter = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'delimiter': {}",
value
"unrecognized argument to 'delimiter': {value}"
)));
}
}
@ -171,15 +165,13 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
}
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'quote': {}",
value
"unrecognized argument to 'quote': {value}"
)));
}
}
_ => {
return Err(Error::ModuleError(format!(
"unrecognized parameter '{}'",
param
"unrecognized parameter '{param}'"
)));
}
}
@ -326,8 +318,7 @@ unsafe impl VTabCursor for CsvTabCursor<'_> {
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
"column index out of bounds: {col}"
)));
}
if self.cols.is_empty() {

View File

@ -17,10 +17,11 @@ use std::ptr;
use std::slice;
use crate::context::set_result;
use crate::error::error_from_sqlite_code;
use crate::error::{error_from_sqlite_code, to_sqlite_error};
use crate::ffi;
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use crate::util::alloc;
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// let conn: Connection = ...;
@ -195,6 +196,8 @@ pub enum VTabConfig {
Innocuous = 2,
/// Equivalent to SQLITE_VTAB_DIRECTONLY
DirectOnly = 3,
/// Equivalent to SQLITE_VTAB_USES_ALL_SCHEMAS
UsesAllSchemas = 4,
}
/// `feature = "vtab"`
@ -619,7 +622,7 @@ impl IndexConstraintUsage<'_> {
/// `feature = "vtab"`
pub struct OrderByIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
iter: slice::Iter<'a, ffi::sqlite3_index_orderby>,
}
impl<'a> Iterator for OrderByIter<'a> {
@ -637,7 +640,7 @@ impl<'a> Iterator for OrderByIter<'a> {
}
/// A column of the ORDER BY clause.
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_orderby);
impl OrderBy<'_> {
/// Column number
@ -882,7 +885,7 @@ pub fn dequote(s: &str) -> &str {
return s;
}
match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().next_back() {
Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s)
_ => s,
},
@ -962,8 +965,7 @@ where
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = alloc(&err.to_string());
rc
to_sqlite_error(&err, err_msg)
}
}
Err(err) => {
@ -971,16 +973,7 @@ where
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = alloc(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
Err(err) => to_sqlite_error(&err, err_msg),
}
}
@ -1014,8 +1007,7 @@ where
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = alloc(&err.to_string());
rc
to_sqlite_error(&err, err_msg)
}
}
Err(err) => {
@ -1023,16 +1015,7 @@ where
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = alloc(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
Err(err) => to_sqlite_error(&err, err_msg),
}
}
@ -1309,12 +1292,6 @@ unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) ->
}
}
// Space to hold this string must be obtained
// from an SQLite memory allocation function
fn alloc(s: &str) -> *mut c_char {
crate::util::SqliteMallocString::from_str(s).into_raw()
}
#[cfg(feature = "array")]
#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
pub mod array;

View File

@ -28,6 +28,7 @@ const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3;
bitflags::bitflags! {
#[derive(Clone, Copy)]
#[repr(C)]
struct QueryPlanFlags: ::std::os::raw::c_int {
// start = $value -- constraint exists
@ -41,7 +42,7 @@ bitflags::bitflags! {
// output in ascending order
const ASC = 16;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
}
}
@ -115,7 +116,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
}
if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available.
//#[allow(clippy::bool_to_int_with_if)]
#[allow(clippy::bool_to_int_with_if)]
info.set_estimated_cost(f64::from(
2 - if idx_num.contains(QueryPlanFlags::STEP) {
1
@ -199,19 +200,19 @@ unsafe impl VTabCursor for SeriesTabCursor<'_> {
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(i)?;
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(i)?;
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(i)?;
self.step = args.get::<Option<_>>(i)?.unwrap_or_default();
if self.step == 0 {
self.step = 1;
} else if self.step < 0 {
@ -315,6 +316,26 @@ mod test {
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(())
}
}

View File

@ -1,4 +1,4 @@
///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
use std::default::Default;
use std::marker::PhantomData;
use std::os::raw::c_int;
@ -56,8 +56,7 @@ impl VTabLog {
"schema" => {
if schema.is_some() {
return Err(Error::ModuleError(format!(
"more than one '{}' parameter",
param
"more than one '{param}' parameter"
)));
}
schema = Some(value.to_owned())
@ -65,8 +64,7 @@ impl VTabLog {
"rows" => {
if n_row.is_some() {
return Err(Error::ModuleError(format!(
"more than one '{}' parameter",
param
"more than one '{param}' parameter"
)));
}
if let Ok(n) = i64::from_str(value) {
@ -75,8 +73,7 @@ impl VTabLog {
}
_ => {
return Err(Error::ModuleError(format!(
"unrecognized parameter '{}'",
param
"unrecognized parameter '{param}'"
)));
}
}
@ -286,13 +283,13 @@ mod test {
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 = ?", ["a1"])?;
db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
db.execute(
"INSERT INTO log (a, b, c) VALUES (?, ?, ?)",
"INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
["a", "b", "c"],
)?;
db.execute(
"UPDATE log SET b = ?, c = ? WHERE a = ?",
"UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
["bn", "cn", "a1"],
)?;
Ok(())