mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +08:00
Merge remote-tracking branch 'jgallagher/master' into tail
# Conflicts: # src/inner_connection.rs # src/lib.rs
This commit is contained in:
commit
bcd26ca062
23
.travis.yml
23
.travis.yml
@ -26,22 +26,23 @@ script:
|
|||||||
- cargo build
|
- cargo build
|
||||||
- cargo build --features bundled
|
- cargo build --features bundled
|
||||||
- cargo build --features sqlcipher
|
- cargo build --features sqlcipher
|
||||||
|
- cargo build --features "bundled sqlcipher"
|
||||||
- cargo test
|
- cargo test
|
||||||
- cargo test --features backup
|
- cargo test --features "backup blob"
|
||||||
- cargo test --features blob
|
- cargo test --features "collation functions"
|
||||||
- cargo test --features functions
|
- cargo test --features "hooks limits"
|
||||||
- cargo test --features hooks
|
|
||||||
- cargo test --features limits
|
|
||||||
- cargo test --features load_extension
|
- cargo test --features load_extension
|
||||||
- cargo test --features trace
|
- cargo test --features trace
|
||||||
- cargo test --features chrono
|
- cargo test --features chrono
|
||||||
- cargo test --features serde_json
|
- cargo test --features serde_json
|
||||||
|
- cargo test --features url
|
||||||
- cargo test --features bundled
|
- cargo test --features bundled
|
||||||
- cargo test --features sqlcipher
|
- cargo test --features sqlcipher
|
||||||
- cargo test --features i128_blob
|
- cargo test --features i128_blob
|
||||||
- cargo test --features "unlock_notify bundled"
|
- cargo test --features uuid
|
||||||
- cargo test --features "array bundled csvtab vtab"
|
- cargo test --features "bundled unlock_notify window"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
|
- cargo test --features "array bundled csvtab series vtab"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
|
||||||
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled buildtime_bindgen"
|
||||||
|
22
Cargo.toml
22
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.16.0"
|
version = "0.20.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Ergonomic wrapper for SQLite"
|
description = "Ergonomic wrapper for SQLite"
|
||||||
@ -28,6 +28,7 @@ load_extension = []
|
|||||||
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||||
# sqlite3_blob_reopen: 3.7.4
|
# sqlite3_blob_reopen: 3.7.4
|
||||||
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
|
collation = []
|
||||||
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
||||||
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
# sqlite3_log: 3.6.23 (2010-03-09)
|
# sqlite3_log: 3.6.23 (2010-03-09)
|
||||||
@ -47,7 +48,12 @@ csvtab = ["csv", "vtab"]
|
|||||||
# pointer passing interfaces: 3.20.0
|
# pointer passing interfaces: 3.20.0
|
||||||
array = ["vtab"]
|
array = ["vtab"]
|
||||||
# session extension: 3.13.0
|
# session extension: 3.13.0
|
||||||
session = ["libsqlite3-sys/session", "hooks", "fallible-streaming-iterator"]
|
session = ["libsqlite3-sys/session", "hooks"]
|
||||||
|
# window functions: 3.25.0
|
||||||
|
window = ["functions"]
|
||||||
|
# 3.9.0
|
||||||
|
series = ["vtab"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
time = "0.1.0"
|
time = "0.1.0"
|
||||||
@ -56,18 +62,24 @@ lru-cache = "0.1"
|
|||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
csv = { version = "1.0", optional = true }
|
csv = { version = "1.0", optional = true }
|
||||||
|
url = { version = "2.0", optional = true }
|
||||||
lazy_static = { version = "1.0", optional = true }
|
lazy_static = { version = "1.0", optional = true }
|
||||||
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||||
fallible-streaming-iterator = { version = "0.1", optional = true }
|
fallible-iterator = "0.2"
|
||||||
|
fallible-streaming-iterator = "0.1"
|
||||||
|
memchr = "2.2.0"
|
||||||
|
uuid = { version = "0.7", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
|
uuid = { version = "0.7", features = ["v4"] }
|
||||||
|
unicase = "2.4.0"
|
||||||
|
|
||||||
[dependencies.libsqlite3-sys]
|
[dependencies.libsqlite3-sys]
|
||||||
path = "libsqlite3-sys"
|
path = "libsqlite3-sys"
|
||||||
version = "0.12"
|
version = "0.16"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "config_log"
|
name = "config_log"
|
||||||
@ -80,7 +92,7 @@ name = "deny_single_threaded_sqlite_config"
|
|||||||
name = "vtab"
|
name = "vtab"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "vtab" ]
|
features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window" ]
|
||||||
all-features = false
|
all-features = false
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
60
README.md
60
README.md
@ -11,7 +11,7 @@ an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgre
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::types::ToSql;
|
||||||
use rusqlite::{Connection, NO_PARAMS};
|
use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -22,8 +22,8 @@ struct Person {
|
|||||||
data: Option<Vec<u8>>,
|
data: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let conn = Connection::open_in_memory().unwrap();
|
let conn = Connection::open_in_memory()?;
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE person (
|
"CREATE TABLE person (
|
||||||
@ -33,7 +33,7 @@ fn main() {
|
|||||||
data BLOB
|
data BLOB
|
||||||
)",
|
)",
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
).unwrap();
|
)?;
|
||||||
let me = Person {
|
let me = Person {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "Steven".to_string(),
|
name: "Steven".to_string(),
|
||||||
@ -44,22 +44,22 @@ fn main() {
|
|||||||
"INSERT INTO person (name, time_created, data)
|
"INSERT INTO person (name, time_created, data)
|
||||||
VALUES (?1, ?2, ?3)",
|
VALUES (?1, ?2, ?3)",
|
||||||
&[&me.name as &ToSql, &me.time_created, &me.data],
|
&[&me.name as &ToSql, &me.time_created, &me.data],
|
||||||
).unwrap();
|
)?;
|
||||||
|
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT id, name, time_created, data FROM person")
|
.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||||
.unwrap();
|
|
||||||
let person_iter = stmt
|
let person_iter = stmt
|
||||||
.query_map(NO_PARAMS, |row| Person {
|
.query_map(NO_PARAMS, |row| Ok(Person {
|
||||||
id: row.get(0),
|
id: row.get(0)?,
|
||||||
name: row.get(1),
|
name: row.get(1)?,
|
||||||
time_created: row.get(2),
|
time_created: row.get(2)?,
|
||||||
data: row.get(3),
|
data: row.get(3)?,
|
||||||
}).unwrap();
|
}))?;
|
||||||
|
|
||||||
for person in person_iter {
|
for person in person_iter {
|
||||||
println!("Found person {:?}", person.unwrap());
|
println!("Found person {:?}", person.unwrap());
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,27 +74,30 @@ newer SQLite version; see details below.
|
|||||||
Rusqlite provides several features that are behind [Cargo
|
Rusqlite provides several features that are behind [Cargo
|
||||||
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
||||||
|
|
||||||
* [`load_extension`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.LoadExtensionGuard.html)
|
* [`load_extension`](https://docs.rs/rusqlite/~0/rusqlite/struct.LoadExtensionGuard.html)
|
||||||
allows loading dynamic library-based SQLite extensions.
|
allows loading dynamic library-based SQLite extensions.
|
||||||
* [`backup`](https://docs.rs/rusqlite/0.16.0/rusqlite/backup/index.html)
|
* [`backup`](https://docs.rs/rusqlite/~0/rusqlite/backup/index.html)
|
||||||
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
|
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
|
||||||
* [`functions`](https://docs.rs/rusqlite/0.16.0/rusqlite/functions/index.html)
|
* [`functions`](https://docs.rs/rusqlite/~0/rusqlite/functions/index.html)
|
||||||
allows you to load Rust closures into SQLite connections for use in queries.
|
allows you to load Rust closures into SQLite connections for use in queries.
|
||||||
Note: This feature requires SQLite 3.7.3 or later.
|
Note: This feature requires SQLite 3.7.3 or later.
|
||||||
* [`trace`](https://docs.rs/rusqlite/0.16.0/rusqlite/trace/index.html)
|
* [`trace`](https://docs.rs/rusqlite/~0/rusqlite/trace/index.html)
|
||||||
allows hooks into SQLite's tracing and profiling APIs. Note: This feature
|
allows hooks into SQLite's tracing and profiling APIs. Note: This feature
|
||||||
requires SQLite 3.6.23 or later.
|
requires SQLite 3.6.23 or later.
|
||||||
* [`blob`](https://docs.rs/rusqlite/0.16.0/rusqlite/blob/index.html)
|
* [`blob`](https://docs.rs/rusqlite/~0/rusqlite/blob/index.html)
|
||||||
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
|
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
|
||||||
requires SQLite 3.7.4 or later.
|
requires SQLite 3.7.4 or later.
|
||||||
* [`limits`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.Connection.html#method.limit)
|
* [`limits`](https://docs.rs/rusqlite/~0/rusqlite/struct.Connection.html#method.limit)
|
||||||
allows you to set and retrieve SQLite's per connection limits.
|
allows you to set and retrieve SQLite's per connection limits.
|
||||||
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
|
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for various
|
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
|
||||||
types from the [`chrono` crate](https://crates.io/crates/chrono).
|
types from the [`chrono` crate](https://crates.io/crates/chrono).
|
||||||
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
|
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for the
|
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).
|
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
|
||||||
|
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
|
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
|
||||||
|
`Url` type from the [`url` crate](https://crates.io/crates/url).
|
||||||
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
|
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
|
||||||
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
|
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
|
||||||
* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks.
|
* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks.
|
||||||
@ -103,6 +106,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
|
|||||||
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
||||||
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
|
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
|
||||||
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
|
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
|
||||||
|
* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs.
|
||||||
* [`session`](https://sqlite.org/sessionintro.html), Session module extension.
|
* [`session`](https://sqlite.org/sessionintro.html), Session module extension.
|
||||||
|
|
||||||
## Notes on building rusqlite and libsqlite3-sys
|
## Notes on building rusqlite and libsqlite3-sys
|
||||||
@ -116,11 +120,11 @@ You can adjust this behavior in a number of ways:
|
|||||||
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
||||||
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
|
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
|
||||||
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
||||||
is currently SQLite 3.26.0 (as of `rusqlite` 0.16.0 / `libsqlite3-sys`
|
is currently SQLite 3.29.0 (as of `rusqlite` 0.20.0 / `libsqlite3-sys`
|
||||||
0.11.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
0.16.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
||||||
```
|
```
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.16.0"
|
version = "0.20.0"
|
||||||
features = ["bundled"]
|
features = ["bundled"]
|
||||||
```
|
```
|
||||||
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
||||||
@ -128,7 +132,9 @@ You can adjust this behavior in a number of ways:
|
|||||||
* Installing the sqlite3 development packages will usually be all that is required, but
|
* Installing the sqlite3 development packages will usually be all that is required, but
|
||||||
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
|
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
|
||||||
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
|
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
|
||||||
options. The default when using vcpkg is to dynamically link. `vcpkg install sqlite3:x64-windows` will install the required library.
|
options. The default when using vcpkg is to dynamically link,
|
||||||
|
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
|
||||||
|
`vcpkg install sqlite3:x64-windows` will install the required library.
|
||||||
|
|
||||||
### Binding generation
|
### Binding generation
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ install:
|
|||||||
- rustc -V
|
- rustc -V
|
||||||
- cargo -V
|
- cargo -V
|
||||||
# download SQLite dll (useful only when the `bundled` feature is not set)
|
# download SQLite dll (useful only when the `bundled` feature is not set)
|
||||||
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250000.zip -FileName sqlite-dll-win64-x64.zip
|
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250200.zip -FileName sqlite-dll-win64-x64.zip
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
|
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
|
||||||
# download SQLite headers (useful only when the `bundled` feature is not set)
|
# download SQLite headers (useful only when the `bundled` feature is not set)
|
||||||
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-amalgamation-3250000.zip -FileName sqlite-amalgamation.zip
|
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-amalgamation-3250200.zip -FileName sqlite-amalgamation.zip
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation.zip -y > nul
|
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation.zip -y > nul
|
||||||
# specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
|
# specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER%
|
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER%
|
||||||
@ -33,8 +33,8 @@ build: false
|
|||||||
test_script:
|
test_script:
|
||||||
- cargo test --lib --verbose
|
- cargo test --lib --verbose
|
||||||
- cargo test --lib --verbose --features bundled
|
- cargo test --lib --verbose --features bundled
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
|
- cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.12.0"
|
version = "0.16.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://github.com/jgallagher/rusqlite"
|
repository = "https://github.com/jgallagher/rusqlite"
|
||||||
@ -14,6 +14,7 @@ categories = ["external-ffi-bindings"]
|
|||||||
[features]
|
[features]
|
||||||
default = ["min_sqlite_version_3_6_8"]
|
default = ["min_sqlite_version_3_6_8"]
|
||||||
bundled = ["cc"]
|
bundled = ["cc"]
|
||||||
|
bundled-windows = ["cc"]
|
||||||
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
||||||
sqlcipher = []
|
sqlcipher = []
|
||||||
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
||||||
@ -28,7 +29,7 @@ preupdate_hook = []
|
|||||||
session = ["preupdate_hook"]
|
session = ["preupdate_hook"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = { version = "0.47", optional = true }
|
bindgen = { version = "0.51", optional = true, default-features = false }
|
||||||
pkg-config = { version = "0.3", optional = true }
|
pkg-config = { version = "0.3", optional = true }
|
||||||
cc = { version = "1.0", optional = true }
|
cc = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
@ -4,16 +4,40 @@ use std::path::Path;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
||||||
build::main(&out_dir, &out_path);
|
if cfg!(feature = "sqlcipher") {
|
||||||
|
if cfg!(any(
|
||||||
|
feature = "bundled",
|
||||||
|
all(windows, feature = "bundled-windows")
|
||||||
|
)) {
|
||||||
|
println!(
|
||||||
|
"cargo:warning={}",
|
||||||
|
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
|
||||||
|
This can lead to issues if your version of SQLCipher is not up to date!");
|
||||||
|
}
|
||||||
|
build_linked::main(&out_dir, &out_path)
|
||||||
|
} else {
|
||||||
|
// This can't be `cfg!` without always requiring our `mod build_bundled` (and
|
||||||
|
// thus `cc`)
|
||||||
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
|
{
|
||||||
|
build_bundled::main(&out_dir, &out_path)
|
||||||
|
}
|
||||||
|
#[cfg(not(any(feature = "bundled", all(windows, feature = "bundled-windows"))))]
|
||||||
|
{
|
||||||
|
build_linked::main(&out_dir, &out_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
mod build {
|
mod build_bundled {
|
||||||
use cc;
|
use cc;
|
||||||
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn main(out_dir: &str, out_path: &Path) {
|
pub fn main(out_dir: &str, out_path: &Path) {
|
||||||
if cfg!(feature = "sqlcipher") {
|
if cfg!(feature = "sqlcipher") {
|
||||||
|
// This is just a sanity check, the top level `main` should ensure this.
|
||||||
panic!("Builds with bundled SQLCipher are not supported");
|
panic!("Builds with bundled SQLCipher are not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,11 +70,29 @@ mod build {
|
|||||||
.flag("-DSQLITE_ENABLE_RTREE")
|
.flag("-DSQLITE_ENABLE_RTREE")
|
||||||
.flag("-DSQLITE_ENABLE_STAT2")
|
.flag("-DSQLITE_ENABLE_STAT2")
|
||||||
.flag("-DSQLITE_ENABLE_STAT4")
|
.flag("-DSQLITE_ENABLE_STAT4")
|
||||||
.flag("-DSQLITE_HAVE_ISNAN")
|
|
||||||
.flag("-DSQLITE_SOUNDEX")
|
.flag("-DSQLITE_SOUNDEX")
|
||||||
.flag("-DSQLITE_THREADSAFE=1")
|
.flag("-DSQLITE_THREADSAFE=1")
|
||||||
.flag("-DSQLITE_USE_URI")
|
.flag("-DSQLITE_USE_URI")
|
||||||
.flag("-DHAVE_USLEEP=1");
|
.flag("-DHAVE_USLEEP=1");
|
||||||
|
// Older versions of visual studio don't support c99 (including isnan), which
|
||||||
|
// causes a build failure when the linker fails to find the `isnan`
|
||||||
|
// function. `sqlite` provides its own implmentation, using the fact
|
||||||
|
// that x != x when x is NaN.
|
||||||
|
//
|
||||||
|
// There may be other platforms that don't support `isnan`, they should be
|
||||||
|
// tested for here.
|
||||||
|
if cfg!(target_env = "msvc") {
|
||||||
|
use cc::windows_registry::{find_vs_version, VsVers};
|
||||||
|
let vs_has_nan = match find_vs_version() {
|
||||||
|
Ok(ver) => ver != VsVers::Vs12,
|
||||||
|
Err(_msg) => false,
|
||||||
|
};
|
||||||
|
if vs_has_nan {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
if cfg!(feature = "unlock_notify") {
|
if cfg!(feature = "unlock_notify") {
|
||||||
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
}
|
}
|
||||||
@ -60,6 +102,17 @@ mod build {
|
|||||||
if cfg!(feature = "session") {
|
if cfg!(feature = "session") {
|
||||||
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_VARIABLE_NUMBER") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_VARIABLE_NUMBER={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_VARIABLE_NUMBER");
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_EXPR_DEPTH") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_EXPR_DEPTH={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_EXPR_DEPTH");
|
||||||
|
|
||||||
cfg.compile("libsqlite3.a");
|
cfg.compile("libsqlite3.a");
|
||||||
|
|
||||||
println!("cargo:lib_dir={}", out_dir);
|
println!("cargo:lib_dir={}", out_dir);
|
||||||
@ -98,8 +151,7 @@ impl From<HeaderLocation> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
mod build_linked {
|
||||||
mod build {
|
|
||||||
use pkg_config;
|
use pkg_config;
|
||||||
|
|
||||||
#[cfg(all(feature = "vcpkg", target_env = "msvc"))]
|
#[cfg(all(feature = "vcpkg", target_env = "msvc"))]
|
||||||
@ -111,8 +163,25 @@ mod build {
|
|||||||
|
|
||||||
pub fn main(_out_dir: &str, out_path: &Path) {
|
pub fn main(_out_dir: &str, out_path: &Path) {
|
||||||
let header = find_sqlite();
|
let header = find_sqlite();
|
||||||
|
if cfg!(any(
|
||||||
|
feature = "bundled",
|
||||||
|
all(windows, feature = "bundled-windows")
|
||||||
|
)) && !cfg!(feature = "buildtime_bindgen")
|
||||||
|
{
|
||||||
|
// We can only get here if `bundled` and `sqlcipher` were both
|
||||||
|
// specified (and `builtime_bindgen` was not). In order to keep
|
||||||
|
// `rusqlite` relatively clean we hide the fact that `bundled` can
|
||||||
|
// be ignored in some cases, and just use the bundled bindings, even
|
||||||
|
// though the library we found might not match their version.
|
||||||
|
// Ideally we'd perform a version check here, but doing so is rather
|
||||||
|
// tricky, since we might not have access to executables (and
|
||||||
|
// moreover, we might be cross compiling).
|
||||||
|
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
||||||
|
.expect("Could not copy bindings to output directory");
|
||||||
|
} else {
|
||||||
bindings::write_to_out_dir(header, out_path);
|
bindings::write_to_out_dir(header, out_path);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_link_mode() -> &'static str {
|
fn find_link_mode() -> &'static str {
|
||||||
// If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
|
// If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
|
||||||
@ -132,10 +201,19 @@ mod build {
|
|||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
println!("cargo:rerun-if-env-changed=PATH");
|
println!("cargo:rerun-if-env-changed=PATH");
|
||||||
}
|
}
|
||||||
|
if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
|
||||||
|
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
|
||||||
|
}
|
||||||
// Allow users to specify where to find SQLite.
|
// Allow users to specify where to find SQLite.
|
||||||
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
||||||
|
// Try to use pkg-config to determine link commands
|
||||||
|
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
|
||||||
|
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
|
||||||
|
if let Err(_) = pkg_config::Config::new().probe(link_lib) {
|
||||||
|
// Otherwise just emit the bare minimum link commands.
|
||||||
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||||
println!("cargo:rustc-link-search={}", dir);
|
println!("cargo:rustc-link-search={}", dir);
|
||||||
|
}
|
||||||
return HeaderLocation::FromEnvironment;
|
return HeaderLocation::FromEnvironment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +271,7 @@ mod build {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(not(feature = "buildtime_bindgen"), not(feature = "bundled")))]
|
#[cfg(not(feature = "buildtime_bindgen"))]
|
||||||
mod bindings {
|
mod bindings {
|
||||||
use super::HeaderLocation;
|
use super::HeaderLocation;
|
||||||
|
|
||||||
@ -220,8 +298,8 @@ mod bindings {
|
|||||||
mod bindings {
|
mod bindings {
|
||||||
use bindgen;
|
use bindgen;
|
||||||
|
|
||||||
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
|
|
||||||
use super::HeaderLocation;
|
use super::HeaderLocation;
|
||||||
|
use bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
/* automatically generated by rust-bindgen */
|
/* automatically generated by rust-bindgen */
|
||||||
|
|
||||||
pub const __GNUC_VA_LIST: i32 = 1;
|
pub const __GNUC_VA_LIST: i32 = 1;
|
||||||
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.26.0\0";
|
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.29.0\0";
|
||||||
pub const SQLITE_VERSION_NUMBER: i32 = 3026000;
|
pub const SQLITE_VERSION_NUMBER: i32 = 3029000;
|
||||||
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
||||||
b"2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9\0";
|
b"2019-07-10 17:32:03 fc82b73eaac8b36950e527f12c4b5dc1e147e6f4ad2217ae43ad82882a88bfa6\0";
|
||||||
pub const SQLITE_OK: i32 = 0;
|
pub const SQLITE_OK: i32 = 0;
|
||||||
pub const SQLITE_ERROR: i32 = 1;
|
pub const SQLITE_ERROR: i32 = 1;
|
||||||
pub const SQLITE_INTERNAL: i32 = 2;
|
pub const SQLITE_INTERNAL: i32 = 2;
|
||||||
@ -180,6 +180,7 @@ pub const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: i32 = 32;
|
|||||||
pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33;
|
pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33;
|
||||||
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
|
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
|
||||||
pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35;
|
pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35;
|
||||||
|
pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36;
|
||||||
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
|
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
|
||||||
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
|
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
|
||||||
pub const SQLITE_LAST_ERRNO: i32 = 4;
|
pub const SQLITE_LAST_ERRNO: i32 = 4;
|
||||||
@ -218,6 +219,7 @@ pub const SQLITE_CONFIG_PMASZ: i32 = 25;
|
|||||||
pub const SQLITE_CONFIG_STMTJRNL_SPILL: i32 = 26;
|
pub const SQLITE_CONFIG_STMTJRNL_SPILL: i32 = 26;
|
||||||
pub const SQLITE_CONFIG_SMALL_MALLOC: i32 = 27;
|
pub const SQLITE_CONFIG_SMALL_MALLOC: i32 = 27;
|
||||||
pub const SQLITE_CONFIG_SORTERREF_SIZE: i32 = 28;
|
pub const SQLITE_CONFIG_SORTERREF_SIZE: i32 = 28;
|
||||||
|
pub const SQLITE_CONFIG_MEMDB_MAXSIZE: i32 = 29;
|
||||||
pub const SQLITE_DBCONFIG_MAINDBNAME: i32 = 1000;
|
pub const SQLITE_DBCONFIG_MAINDBNAME: i32 = 1000;
|
||||||
pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001;
|
pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001;
|
||||||
pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002;
|
pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002;
|
||||||
@ -229,7 +231,11 @@ pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007;
|
|||||||
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
|
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
|
||||||
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
||||||
pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
|
pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
|
||||||
pub const SQLITE_DBCONFIG_MAX: i32 = 1010;
|
pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011;
|
||||||
|
pub const SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: i32 = 1012;
|
||||||
|
pub const SQLITE_DBCONFIG_DQS_DML: i32 = 1013;
|
||||||
|
pub const SQLITE_DBCONFIG_DQS_DDL: i32 = 1014;
|
||||||
|
pub const SQLITE_DBCONFIG_MAX: i32 = 1014;
|
||||||
pub const SQLITE_DENY: i32 = 1;
|
pub const SQLITE_DENY: i32 = 1;
|
||||||
pub const SQLITE_IGNORE: i32 = 2;
|
pub const SQLITE_IGNORE: i32 = 2;
|
||||||
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
||||||
@ -284,6 +290,7 @@ pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10;
|
|||||||
pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11;
|
pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11;
|
||||||
pub const SQLITE_PREPARE_PERSISTENT: i32 = 1;
|
pub const SQLITE_PREPARE_PERSISTENT: i32 = 1;
|
||||||
pub const SQLITE_PREPARE_NORMALIZE: i32 = 2;
|
pub const SQLITE_PREPARE_NORMALIZE: i32 = 2;
|
||||||
|
pub const SQLITE_PREPARE_NO_VTAB: i32 = 4;
|
||||||
pub const SQLITE_INTEGER: i32 = 1;
|
pub const SQLITE_INTEGER: i32 = 1;
|
||||||
pub const SQLITE_FLOAT: i32 = 2;
|
pub const SQLITE_FLOAT: i32 = 2;
|
||||||
pub const SQLITE_BLOB: i32 = 4;
|
pub const SQLITE_BLOB: i32 = 4;
|
||||||
@ -356,7 +363,8 @@ pub const SQLITE_TESTCTRL_ISINIT: i32 = 23;
|
|||||||
pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24;
|
pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24;
|
||||||
pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25;
|
pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25;
|
||||||
pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26;
|
pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26;
|
||||||
pub const SQLITE_TESTCTRL_LAST: i32 = 26;
|
pub const SQLITE_TESTCTRL_RESULT_INTREAL: i32 = 27;
|
||||||
|
pub const SQLITE_TESTCTRL_LAST: i32 = 27;
|
||||||
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
||||||
@ -417,7 +425,6 @@ pub const FTS5_TOKEN_COLOCATED: i32 = 1;
|
|||||||
pub type va_list = __builtin_va_list;
|
pub type va_list = __builtin_va_list;
|
||||||
pub type __gnuc_va_list = __builtin_va_list;
|
pub type __gnuc_va_list = __builtin_va_list;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_version"]
|
|
||||||
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
|
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -1645,6 +1652,9 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_stmt_isexplain(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_stmt_busy(arg1: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
pub fn sqlite3_stmt_busy(arg1: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
@ -2086,6 +2096,9 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_value_nochange(arg1: *mut sqlite3_value) -> ::std::os::raw::c_int;
|
pub fn sqlite3_value_nochange(arg1: *mut sqlite3_value) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_value_frombind(arg1: *mut sqlite3_value) -> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_value_subtype(arg1: *mut sqlite3_value) -> ::std::os::raw::c_uint;
|
pub fn sqlite3_value_subtype(arg1: *mut sqlite3_value) -> ::std::os::raw::c_uint;
|
||||||
}
|
}
|
||||||
@ -2322,11 +2335,9 @@ extern "C" {
|
|||||||
pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_temp_directory"]
|
|
||||||
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
|
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_data_directory"]
|
|
||||||
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
|
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
16422
libsqlite3-sys/sqlite3/sqlite3.c
vendored
16422
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
179
libsqlite3-sys/sqlite3/sqlite3.h
vendored
179
libsqlite3-sys/sqlite3/sqlite3.h
vendored
@ -123,9 +123,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.26.0"
|
#define SQLITE_VERSION "3.29.0"
|
||||||
#define SQLITE_VERSION_NUMBER 3026000
|
#define SQLITE_VERSION_NUMBER 3029000
|
||||||
#define SQLITE_SOURCE_ID "2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9"
|
#define SQLITE_SOURCE_ID "2019-07-10 17:32:03 fc82b73eaac8b36950e527f12c4b5dc1e147e6f4ad2217ae43ad82882a88bfa6"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@ -189,6 +189,9 @@ SQLITE_API int sqlite3_libversion_number(void);
|
|||||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||||
SQLITE_API int sqlite3_compileoption_used(const char *zOptName);
|
SQLITE_API int sqlite3_compileoption_used(const char *zOptName);
|
||||||
SQLITE_API const char *sqlite3_compileoption_get(int N);
|
SQLITE_API const char *sqlite3_compileoption_get(int N);
|
||||||
|
#else
|
||||||
|
# define sqlite3_compileoption_used(X) 0
|
||||||
|
# define sqlite3_compileoption_get(X) ((void*)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -823,6 +826,15 @@ struct sqlite3_io_methods {
|
|||||||
** file space based on this hint in order to help writes to the database
|
** file space based on this hint in order to help writes to the database
|
||||||
** file run faster.
|
** file run faster.
|
||||||
**
|
**
|
||||||
|
** <li>[[SQLITE_FCNTL_SIZE_LIMIT]]
|
||||||
|
** The [SQLITE_FCNTL_SIZE_LIMIT] opcode is used by in-memory VFS that
|
||||||
|
** implements [sqlite3_deserialize()] to set an upper bound on the size
|
||||||
|
** of the in-memory database. The argument is a pointer to a [sqlite3_int64].
|
||||||
|
** If the integer pointed to is negative, then it is filled in with the
|
||||||
|
** current limit. Otherwise the limit is set to the larger of the value
|
||||||
|
** of the integer pointed to and the current database size. The integer
|
||||||
|
** pointed to is set to the new limit.
|
||||||
|
**
|
||||||
** <li>[[SQLITE_FCNTL_CHUNK_SIZE]]
|
** <li>[[SQLITE_FCNTL_CHUNK_SIZE]]
|
||||||
** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS
|
** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS
|
||||||
** extends and truncates the database file in chunks of a size specified
|
** extends and truncates the database file in chunks of a size specified
|
||||||
@ -1131,6 +1143,7 @@ struct sqlite3_io_methods {
|
|||||||
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
|
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
|
||||||
#define SQLITE_FCNTL_LOCK_TIMEOUT 34
|
#define SQLITE_FCNTL_LOCK_TIMEOUT 34
|
||||||
#define SQLITE_FCNTL_DATA_VERSION 35
|
#define SQLITE_FCNTL_DATA_VERSION 35
|
||||||
|
#define SQLITE_FCNTL_SIZE_LIMIT 36
|
||||||
|
|
||||||
/* deprecated names */
|
/* deprecated names */
|
||||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||||
@ -1283,8 +1296,14 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
|
|||||||
** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
|
** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
|
||||||
** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to
|
** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to
|
||||||
** test whether a file is readable and writable, or [SQLITE_ACCESS_READ]
|
** test whether a file is readable and writable, or [SQLITE_ACCESS_READ]
|
||||||
** to test whether a file is at least readable. The file can be a
|
** to test whether a file is at least readable. The SQLITE_ACCESS_READ
|
||||||
** directory.
|
** flag is never actually used and is not implemented in the built-in
|
||||||
|
** VFSes of SQLite. The file is named by the second argument and can be a
|
||||||
|
** directory. The xAccess method returns [SQLITE_OK] on success or some
|
||||||
|
** non-zero error code if there is an I/O error or if the name of
|
||||||
|
** the file given in the second argument is illegal. If SQLITE_OK
|
||||||
|
** is returned, then non-zero or zero is written into *pResOut to indicate
|
||||||
|
** whether or not the file is accessible.
|
||||||
**
|
**
|
||||||
** ^SQLite will always allocate at least mxPathname+1 bytes for the
|
** ^SQLite will always allocate at least mxPathname+1 bytes for the
|
||||||
** output buffer xFullPathname. The exact size of the output buffer
|
** output buffer xFullPathname. The exact size of the output buffer
|
||||||
@ -1972,6 +1991,17 @@ struct sqlite3_mem_methods {
|
|||||||
** negative value for this option restores the default behaviour.
|
** negative value for this option restores the default behaviour.
|
||||||
** This option is only available if SQLite is compiled with the
|
** This option is only available if SQLite is compiled with the
|
||||||
** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
|
** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
|
||||||
|
**
|
||||||
|
** [[SQLITE_CONFIG_MEMDB_MAXSIZE]]
|
||||||
|
** <dt>SQLITE_CONFIG_MEMDB_MAXSIZE
|
||||||
|
** <dd>The SQLITE_CONFIG_MEMDB_MAXSIZE option accepts a single parameter
|
||||||
|
** [sqlite3_int64] parameter which is the default maximum size for an in-memory
|
||||||
|
** database created using [sqlite3_deserialize()]. This default maximum
|
||||||
|
** size can be adjusted up or down for individual databases using the
|
||||||
|
** [SQLITE_FCNTL_SIZE_LIMIT] [sqlite3_file_control|file-control]. If this
|
||||||
|
** configuration setting is never used, then the default maximum is determined
|
||||||
|
** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that
|
||||||
|
** compile-time option is not set, then the default maximum is 1073741824.
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
|
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
|
||||||
@ -2002,6 +2032,7 @@ struct sqlite3_mem_methods {
|
|||||||
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
|
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
|
||||||
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
|
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
|
||||||
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
|
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
|
||||||
|
#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Database Connection Configuration Options
|
** CAPI3REF: Database Connection Configuration Options
|
||||||
@ -2064,8 +2095,8 @@ struct sqlite3_mem_methods {
|
|||||||
**
|
**
|
||||||
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
|
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
|
||||||
** <dd> ^This option is used to enable or disable the two-argument
|
** <dd> ^This option is used to enable or disable the
|
||||||
** version of the [fts3_tokenizer()] function which is part of the
|
** [fts3_tokenizer()] function which is part of the
|
||||||
** [FTS3] full-text search engine extension.
|
** [FTS3] full-text search engine extension.
|
||||||
** There should be two additional arguments.
|
** There should be two additional arguments.
|
||||||
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
||||||
@ -2173,10 +2204,50 @@ struct sqlite3_mem_methods {
|
|||||||
** features include but are not limited to the following:
|
** features include but are not limited to the following:
|
||||||
** <ul>
|
** <ul>
|
||||||
** <li> The [PRAGMA writable_schema=ON] statement.
|
** <li> The [PRAGMA writable_schema=ON] statement.
|
||||||
|
** <li> The [PRAGMA journal_mode=OFF] statement.
|
||||||
** <li> Writes to the [sqlite_dbpage] virtual table.
|
** <li> Writes to the [sqlite_dbpage] virtual table.
|
||||||
** <li> Direct writes to [shadow tables].
|
** <li> Direct writes to [shadow tables].
|
||||||
** </ul>
|
** </ul>
|
||||||
** </dd>
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_WRITABLE_SCHEMA]] <dt>SQLITE_DBCONFIG_WRITABLE_SCHEMA</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the
|
||||||
|
** "writable_schema" flag. This has the same effect and is logically equivalent
|
||||||
|
** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF].
|
||||||
|
** The first argument to this setting is an integer which is 0 to disable
|
||||||
|
** the writable_schema, positive to enable writable_schema, or negative to
|
||||||
|
** leave the setting unchanged. The second parameter is a pointer to an
|
||||||
|
** integer into which is written 0 or 1 to indicate whether the writable_schema
|
||||||
|
** is enabled or disabled following this call.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
|
||||||
|
** the legacy behavior of the [ALTER TABLE RENAME] command such it
|
||||||
|
** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
|
||||||
|
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
|
||||||
|
** additional information. This feature can also be turned on and off
|
||||||
|
** using the [PRAGMA legacy_alter_table] statement.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_DQS_DML]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_DQS_DML</td>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
|
||||||
|
** the legacy [double-quoted string literal] misfeature for DML statement
|
||||||
|
** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
|
||||||
|
** default value of this setting is determined by the [-DSQLITE_DQS]
|
||||||
|
** compile-time option.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_DQS_DDL]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
|
||||||
|
** <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
|
||||||
|
** default value of this setting is determined by the [-DSQLITE_DQS]
|
||||||
|
** compile-time option.
|
||||||
|
** </dd>
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
||||||
@ -2190,7 +2261,11 @@ struct sqlite3_mem_methods {
|
|||||||
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
|
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
|
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
|
#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_MAX 1010 /* Largest DBCONFIG */
|
#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_MAX 1014 /* Largest DBCONFIG */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Enable Or Disable Extended Result Codes
|
** CAPI3REF: Enable Or Disable Extended Result Codes
|
||||||
@ -2347,7 +2422,7 @@ SQLITE_API int sqlite3_changes(sqlite3*);
|
|||||||
** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
|
** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
|
||||||
** are not counted.
|
** are not counted.
|
||||||
**
|
**
|
||||||
** This the [sqlite3_total_changes(D)] interface only reports the number
|
** The [sqlite3_total_changes(D)] interface only reports the number
|
||||||
** of rows that changed due to SQL statement run against database
|
** of rows that changed due to SQL statement run against database
|
||||||
** connection D. Any changes by other database connections are ignored.
|
** connection D. Any changes by other database connections are ignored.
|
||||||
** To detect changes against a database file from other database
|
** To detect changes against a database file from other database
|
||||||
@ -2991,9 +3066,9 @@ SQLITE_API int sqlite3_set_authorizer(
|
|||||||
** time is in units of nanoseconds, however the current implementation
|
** time is in units of nanoseconds, however the current implementation
|
||||||
** is only capable of millisecond resolution so the six least significant
|
** is only capable of millisecond resolution so the six least significant
|
||||||
** digits in the time are meaningless. Future versions of SQLite
|
** digits in the time are meaningless. Future versions of SQLite
|
||||||
** might provide greater resolution on the profiler callback. The
|
** might provide greater resolution on the profiler callback. Invoking
|
||||||
** sqlite3_profile() function is considered experimental and is
|
** either [sqlite3_trace()] or [sqlite3_trace_v2()] will cancel the
|
||||||
** subject to change in future versions of SQLite.
|
** profile callback.
|
||||||
*/
|
*/
|
||||||
SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
|
SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
|
||||||
void(*xTrace)(void*,const char*), void*);
|
void(*xTrace)(void*,const char*), void*);
|
||||||
@ -3407,6 +3482,8 @@ SQLITE_API int sqlite3_open_v2(
|
|||||||
** is not a database file pathname pointer that SQLite passed into the xOpen
|
** is not a database file pathname pointer that SQLite passed into the xOpen
|
||||||
** VFS method, then the behavior of this routine is undefined and probably
|
** VFS method, then the behavior of this routine is undefined and probably
|
||||||
** undesirable.
|
** undesirable.
|
||||||
|
**
|
||||||
|
** See the [URI filename] documentation for additional information.
|
||||||
*/
|
*/
|
||||||
SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
|
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 int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
|
||||||
@ -3629,18 +3706,23 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
|
|||||||
** deplete the limited store of lookaside memory. Future versions of
|
** deplete the limited store of lookaside memory. Future versions of
|
||||||
** SQLite may act on this hint differently.
|
** SQLite may act on this hint differently.
|
||||||
**
|
**
|
||||||
** [[SQLITE_PREPARE_NORMALIZE]] ^(<dt>SQLITE_PREPARE_NORMALIZE</dt>
|
** [[SQLITE_PREPARE_NORMALIZE]] <dt>SQLITE_PREPARE_NORMALIZE</dt>
|
||||||
** <dd>The SQLITE_PREPARE_NORMALIZE flag indicates that a normalized
|
** <dd>The SQLITE_PREPARE_NORMALIZE flag is a no-op. This flag used
|
||||||
** representation of the SQL statement should be calculated and then
|
** to be required for any prepared statement that wanted to use the
|
||||||
** associated with the prepared statement, which can be obtained via
|
** [sqlite3_normalized_sql()] interface. However, the
|
||||||
** the [sqlite3_normalized_sql()] interface.)^ The semantics used to
|
** [sqlite3_normalized_sql()] interface is now available to all
|
||||||
** normalize a SQL statement are unspecified and subject to change.
|
** prepared statements, regardless of whether or not they use this
|
||||||
** At a minimum, literal values will be replaced with suitable
|
** flag.
|
||||||
** placeholders.
|
**
|
||||||
|
** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
|
||||||
|
** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
|
||||||
|
** to return an error (error code SQLITE_ERROR) if the statement uses
|
||||||
|
** any virtual tables.
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_PREPARE_PERSISTENT 0x01
|
#define SQLITE_PREPARE_PERSISTENT 0x01
|
||||||
#define SQLITE_PREPARE_NORMALIZE 0x02
|
#define SQLITE_PREPARE_NORMALIZE 0x02
|
||||||
|
#define SQLITE_PREPARE_NO_VTAB 0x04
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Compiling An SQL Statement
|
** CAPI3REF: Compiling An SQL Statement
|
||||||
@ -3865,6 +3947,18 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt);
|
|||||||
*/
|
*/
|
||||||
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
|
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement
|
||||||
|
** METHOD: sqlite3_stmt
|
||||||
|
**
|
||||||
|
** ^The sqlite3_stmt_isexplain(S) interface returns 1 if the
|
||||||
|
** prepared statement S is an EXPLAIN statement, or 2 if the
|
||||||
|
** statement S is an EXPLAIN QUERY PLAN.
|
||||||
|
** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is
|
||||||
|
** an ordinary statement or a NULL pointer.
|
||||||
|
*/
|
||||||
|
SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
|
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
|
||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
@ -4004,7 +4098,9 @@ typedef struct sqlite3_context sqlite3_context;
|
|||||||
** ^The fifth argument to the BLOB and string binding interfaces
|
** ^The fifth argument to the BLOB and string binding interfaces
|
||||||
** is a destructor used to dispose of the BLOB or
|
** is a destructor used to dispose of the BLOB or
|
||||||
** string after SQLite has finished with it. ^The destructor is called
|
** string after SQLite has finished with it. ^The destructor is called
|
||||||
** to dispose of the BLOB or string even if the call to bind API fails.
|
** to dispose of the BLOB or string even if the call to the bind API fails,
|
||||||
|
** except the destructor is not called if the third parameter is a NULL
|
||||||
|
** pointer or the fourth parameter is negative.
|
||||||
** ^If the fifth argument is
|
** ^If the fifth argument is
|
||||||
** the special value [SQLITE_STATIC], then SQLite assumes that the
|
** the special value [SQLITE_STATIC], then SQLite assumes that the
|
||||||
** information is in static, unmanaged space and does not need to be freed.
|
** information is in static, unmanaged space and does not need to be freed.
|
||||||
@ -4921,6 +5017,8 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
|
|||||||
** <tr><td><b>sqlite3_value_nochange </b>
|
** <tr><td><b>sqlite3_value_nochange </b>
|
||||||
** <td>→ <td>True if the column is unchanged in an UPDATE
|
** <td>→ <td>True if the column is unchanged in an UPDATE
|
||||||
** against a virtual table.
|
** against a virtual table.
|
||||||
|
** <tr><td><b>sqlite3_value_frombind </b>
|
||||||
|
** <td>→ <td>True if value originated from a [bound parameter]
|
||||||
** </table></blockquote>
|
** </table></blockquote>
|
||||||
**
|
**
|
||||||
** <b>Details:</b>
|
** <b>Details:</b>
|
||||||
@ -4982,6 +5080,11 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
|
|||||||
** than within an [xUpdate] method call for an UPDATE statement, then
|
** than within an [xUpdate] method call for an UPDATE statement, then
|
||||||
** the return value is arbitrary and meaningless.
|
** the return value is arbitrary and meaningless.
|
||||||
**
|
**
|
||||||
|
** ^The sqlite3_value_frombind(X) interface returns non-zero if the
|
||||||
|
** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()]
|
||||||
|
** interfaces. ^If X comes from an SQL literal value, or a table column,
|
||||||
|
** and expression, then sqlite3_value_frombind(X) returns zero.
|
||||||
|
**
|
||||||
** Please pay particular attention to the fact that the pointer returned
|
** Please pay particular attention to the fact that the pointer returned
|
||||||
** from [sqlite3_value_blob()], [sqlite3_value_text()], or
|
** from [sqlite3_value_blob()], [sqlite3_value_text()], or
|
||||||
** [sqlite3_value_text16()] can be invalidated by a subsequent call to
|
** [sqlite3_value_text16()] can be invalidated by a subsequent call to
|
||||||
@ -5027,6 +5130,7 @@ SQLITE_API int sqlite3_value_bytes16(sqlite3_value*);
|
|||||||
SQLITE_API int sqlite3_value_type(sqlite3_value*);
|
SQLITE_API int sqlite3_value_type(sqlite3_value*);
|
||||||
SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
|
SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
|
||||||
SQLITE_API int sqlite3_value_nochange(sqlite3_value*);
|
SQLITE_API int sqlite3_value_nochange(sqlite3_value*);
|
||||||
|
SQLITE_API int sqlite3_value_frombind(sqlite3_value*);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Finding The Subtype Of SQL Values
|
** CAPI3REF: Finding The Subtype Of SQL Values
|
||||||
@ -5762,7 +5866,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
|||||||
** associated with database N of connection D. ^The main database file
|
** associated with database N of connection D. ^The main database file
|
||||||
** has the name "main". If there is no attached database N on the database
|
** has the name "main". If there is no attached database N on the database
|
||||||
** connection D, or if database N is a temporary or in-memory database, then
|
** connection D, or if database N is a temporary or in-memory database, then
|
||||||
** a NULL pointer is returned.
|
** this function will return either a NULL pointer or an empty string.
|
||||||
**
|
**
|
||||||
** ^The filename returned by this function is the output of the
|
** ^The filename returned by this function is the output of the
|
||||||
** xFullPathname method of the [VFS]. ^In other words, the filename
|
** xFullPathname method of the [VFS]. ^In other words, the filename
|
||||||
@ -7253,7 +7357,8 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||||||
#define SQLITE_TESTCTRL_SORTER_MMAP 24
|
#define SQLITE_TESTCTRL_SORTER_MMAP 24
|
||||||
#define SQLITE_TESTCTRL_IMPOSTER 25
|
#define SQLITE_TESTCTRL_IMPOSTER 25
|
||||||
#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
|
#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
|
||||||
#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */
|
#define SQLITE_TESTCTRL_RESULT_INTREAL 27
|
||||||
|
#define SQLITE_TESTCTRL_LAST 27 /* Largest TESTCTRL */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: SQL Keyword Checking
|
** CAPI3REF: SQL Keyword Checking
|
||||||
@ -9996,7 +10101,7 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
|
|||||||
** sqlite3changeset_next() is called on the iterator or until the
|
** sqlite3changeset_next() is called on the iterator or until the
|
||||||
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
|
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
|
||||||
** set to the number of columns in the table affected by the change. If
|
** set to the number of columns in the table affected by the change. If
|
||||||
** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
|
** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
|
||||||
** is an indirect change, or false (0) otherwise. See the documentation for
|
** is an indirect change, or false (0) otherwise. See the documentation for
|
||||||
** [sqlite3session_indirect()] for a description of direct and indirect
|
** [sqlite3session_indirect()] for a description of direct and indirect
|
||||||
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
|
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
|
||||||
@ -10863,7 +10968,7 @@ SQLITE_API int sqlite3rebaser_configure(
|
|||||||
** in size. This function allocates and populates a buffer with a copy
|
** in size. This function allocates and populates a buffer with a copy
|
||||||
** of the changeset rebased rebased according to the configuration of the
|
** of the changeset rebased rebased according to the configuration of the
|
||||||
** rebaser object passed as the first argument. If successful, (*ppOut)
|
** rebaser object passed as the first argument. If successful, (*ppOut)
|
||||||
** is set to point to the new buffer containing the rebased changset and
|
** is set to point to the new buffer containing the rebased changeset and
|
||||||
** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
|
** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
|
||||||
** responsibility of the caller to eventually free the new buffer using
|
** responsibility of the caller to eventually free the new buffer using
|
||||||
** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
|
** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
|
||||||
@ -11230,12 +11335,8 @@ struct Fts5PhraseIter {
|
|||||||
**
|
**
|
||||||
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
|
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
|
||||||
** to the column in which it occurs and *piOff the token offset of the
|
** to the column in which it occurs and *piOff the token offset of the
|
||||||
** first token of the phrase. The exception is if the table was created
|
** first token of the phrase. Returns SQLITE_OK if successful, or an error
|
||||||
** with the offsets=0 option specified. In this case *piOff is always
|
** code (i.e. SQLITE_NOMEM) if an error occurs.
|
||||||
** set to -1.
|
|
||||||
**
|
|
||||||
** Returns SQLITE_OK if successful, or an error code (i.e. SQLITE_NOMEM)
|
|
||||||
** if an error occurs.
|
|
||||||
**
|
**
|
||||||
** This API can be quite slow if used with an FTS5 table created with the
|
** This API can be quite slow if used with an FTS5 table created with the
|
||||||
** "detail=none" or "detail=column" option.
|
** "detail=none" or "detail=column" option.
|
||||||
@ -11276,7 +11377,7 @@ struct Fts5PhraseIter {
|
|||||||
** Save the pointer passed as the second argument as the extension functions
|
** Save the pointer passed as the second argument as the extension functions
|
||||||
** "auxiliary data". The pointer may then be retrieved by the current or any
|
** "auxiliary data". The pointer may then be retrieved by the current or any
|
||||||
** future invocation of the same fts5 extension function made as part of
|
** future invocation of the same fts5 extension function made as part of
|
||||||
** of the same MATCH query using the xGetAuxdata() API.
|
** the same MATCH query using the xGetAuxdata() API.
|
||||||
**
|
**
|
||||||
** Each extension function is allocated a single auxiliary data slot for
|
** Each extension function is allocated a single auxiliary data slot for
|
||||||
** each FTS query (MATCH expression). If the extension function is invoked
|
** each FTS query (MATCH expression). If the extension function is invoked
|
||||||
@ -11291,7 +11392,7 @@ struct Fts5PhraseIter {
|
|||||||
** The xDelete callback, if one is specified, is also invoked on the
|
** The xDelete callback, if one is specified, is also invoked on the
|
||||||
** auxiliary data pointer after the FTS5 query has finished.
|
** auxiliary data pointer after the FTS5 query has finished.
|
||||||
**
|
**
|
||||||
** If an error (e.g. an OOM condition) occurs within this function, an
|
** If an error (e.g. an OOM condition) occurs within this function,
|
||||||
** the auxiliary data is set to NULL and an error code returned. If the
|
** the auxiliary data is set to NULL and an error code returned. If the
|
||||||
** xDelete parameter was not NULL, it is invoked on the auxiliary data
|
** xDelete parameter was not NULL, it is invoked on the auxiliary data
|
||||||
** pointer before returning.
|
** pointer before returning.
|
||||||
@ -11524,11 +11625,11 @@ struct Fts5ExtensionApi {
|
|||||||
** the tokenizer substitutes "first" for "1st" and the query works
|
** the tokenizer substitutes "first" for "1st" and the query works
|
||||||
** as expected.
|
** as expected.
|
||||||
**
|
**
|
||||||
** <li> By adding multiple synonyms for a single term to the FTS index.
|
** <li> By querying the index for all synonyms of each query term
|
||||||
** In this case, when tokenizing query text, the tokenizer may
|
** separately. In this case, when tokenizing query text, the
|
||||||
** provide multiple synonyms for a single term within the document.
|
** tokenizer may provide multiple synonyms for a single term
|
||||||
** FTS5 then queries the index for each synonym individually. For
|
** within the document. FTS5 then queries the index for each
|
||||||
** example, faced with the query:
|
** synonym individually. For example, faced with the query:
|
||||||
**
|
**
|
||||||
** <codeblock>
|
** <codeblock>
|
||||||
** ... MATCH 'first place'</codeblock>
|
** ... MATCH 'first place'</codeblock>
|
||||||
@ -11552,7 +11653,7 @@ struct Fts5ExtensionApi {
|
|||||||
** "place".
|
** "place".
|
||||||
**
|
**
|
||||||
** This way, even if the tokenizer does not provide synonyms
|
** This way, even if the tokenizer does not provide synonyms
|
||||||
** when tokenizing query text (it should not - to do would be
|
** when tokenizing query text (it should not - to do so would be
|
||||||
** inefficient), it doesn't matter if the user queries for
|
** inefficient), it doesn't matter if the user queries for
|
||||||
** 'first + place' or '1st + place', as there are entries in the
|
** 'first + place' or '1st + place', as there are entries in the
|
||||||
** FTS index corresponding to both forms of the first token.
|
** FTS index corresponding to both forms of the first token.
|
||||||
|
6
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
6
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -319,6 +319,9 @@ struct sqlite3_api_routines {
|
|||||||
void(*xDestroy)(void*));
|
void(*xDestroy)(void*));
|
||||||
/* Version 3.26.0 and later */
|
/* Version 3.26.0 and later */
|
||||||
const char *(*normalized_sql)(sqlite3_stmt*);
|
const char *(*normalized_sql)(sqlite3_stmt*);
|
||||||
|
/* Version 3.28.0 and later */
|
||||||
|
int (*stmt_isexplain)(sqlite3_stmt*);
|
||||||
|
int (*value_frombind)(sqlite3_value*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -608,6 +611,9 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
||||||
/* Version 3.26.0 and later */
|
/* Version 3.26.0 and later */
|
||||||
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
|
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
|
||||||
|
/* Version 3.28.0 and later */
|
||||||
|
#define sqlite3_stmt_isexplain sqlite3_api->isexplain
|
||||||
|
#define sqlite3_value_frombind sqlite3_api->frombind
|
||||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||||
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||||
|
@ -4,8 +4,8 @@ cd $SCRIPT_DIR
|
|||||||
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
||||||
|
|
||||||
# Download and extract amalgamation
|
# Download and extract amalgamation
|
||||||
SQLITE=sqlite-amalgamation-3260000
|
SQLITE=sqlite-amalgamation-3290000
|
||||||
curl -O http://sqlite.org/2018/$SQLITE.zip
|
curl -O http://sqlite.org/2019/$SQLITE.zip
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
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/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
||||||
@ -16,9 +16,9 @@ rm -f $SQLITE3_LIB_DIR/bindgen_bundled_version.rs
|
|||||||
export SQLITE3_INCLUDE_DIR=$SQLITE3_LIB_DIR
|
export SQLITE3_INCLUDE_DIR=$SQLITE3_LIB_DIR
|
||||||
cargo update
|
cargo update
|
||||||
# Just to make sure there is only one bindgen.rs file in target dir
|
# Just to make sure there is only one bindgen.rs file in target dir
|
||||||
find $SCRIPT_DIR/target -type f -name bindgen.rs -exec rm {} \;
|
find $SCRIPT_DIR/../target -type f -name bindgen.rs -exec rm {} \;
|
||||||
cargo build --features "buildtime_bindgen" --no-default-features
|
cargo build --features "buildtime_bindgen" --no-default-features
|
||||||
find $SCRIPT_DIR/target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bindgen_bundled_version.rs \;
|
find $SCRIPT_DIR/../target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bindgen_bundled_version.rs \;
|
||||||
# Sanity check
|
# Sanity check
|
||||||
cd $SCRIPT_DIR/..
|
cd $SCRIPT_DIR/..
|
||||||
cargo update
|
cargo update
|
||||||
|
@ -167,7 +167,7 @@ pub struct Backup<'a, 'b> {
|
|||||||
b: *mut ffi::sqlite3_backup,
|
b: *mut ffi::sqlite3_backup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Backup<'a, 'b> {
|
impl Backup<'_, '_> {
|
||||||
/// Attempt to create a new handle that will allow backups from `from` to
|
/// Attempt to create a new handle that will allow backups from `from` to
|
||||||
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
|
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
|
||||||
/// API calls on the destination of a backup while the backup is taking
|
/// API calls on the destination of a backup while the backup is taking
|
||||||
@ -177,7 +177,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
||||||
/// `NULL`.
|
/// `NULL`.
|
||||||
pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
||||||
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
|
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
||||||
/// `NULL`.
|
/// `NULL`.
|
||||||
pub fn new_with_names(
|
pub fn new_with_names<'a, 'b>(
|
||||||
from: &'a Connection,
|
from: &'a Connection,
|
||||||
from_name: DatabaseName<'_>,
|
from_name: DatabaseName<'_>,
|
||||||
to: &'b mut Connection,
|
to: &'b mut Connection,
|
||||||
@ -294,7 +294,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Drop for Backup<'a, 'b> {
|
impl Drop for Backup<'_, '_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { ffi::sqlite3_backup_finish(self.b) };
|
unsafe { ffi::sqlite3_backup_finish(self.b) };
|
||||||
}
|
}
|
||||||
|
37
src/blob.rs
37
src/blob.rs
@ -16,43 +16,40 @@
|
|||||||
//! ```rust
|
//! ```rust
|
||||||
//! use rusqlite::blob::ZeroBlob;
|
//! use rusqlite::blob::ZeroBlob;
|
||||||
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
|
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
|
||||||
|
//! use std::error::Error;
|
||||||
//! use std::io::{Read, Seek, SeekFrom, Write};
|
//! use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() -> Result<(), Box<Error>> {
|
||||||
//! let db = Connection::open_in_memory().unwrap();
|
//! let db = Connection::open_in_memory()?;
|
||||||
//! db.execute_batch("CREATE TABLE test (content BLOB);")
|
//! db.execute_batch("CREATE TABLE test (content BLOB);")?;
|
||||||
//! .unwrap();
|
|
||||||
//! db.execute(
|
//! db.execute(
|
||||||
//! "INSERT INTO test (content) VALUES (ZEROBLOB(10))",
|
//! "INSERT INTO test (content) VALUES (ZEROBLOB(10))",
|
||||||
//! NO_PARAMS,
|
//! NO_PARAMS,
|
||||||
//! )
|
//! )?;
|
||||||
//! .unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! let rowid = db.last_insert_rowid();
|
//! let rowid = db.last_insert_rowid();
|
||||||
//! let mut blob = db
|
//! let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
|
||||||
//! .blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
|
||||||
//! .unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! // Make sure to test that the number of bytes written matches what you expect;
|
//! // Make sure to test that the number of bytes written matches what you expect;
|
||||||
//! // if you try to write too much, the data will be truncated to the size of the
|
//! // if you try to write too much, the data will be truncated to the size of the
|
||||||
//! // BLOB.
|
//! // BLOB.
|
||||||
//! let bytes_written = blob.write(b"01234567").unwrap();
|
//! let bytes_written = blob.write(b"01234567")?;
|
||||||
//! assert_eq!(bytes_written, 8);
|
//! assert_eq!(bytes_written, 8);
|
||||||
//!
|
//!
|
||||||
//! // Same guidance - make sure you check the number of bytes read!
|
//! // Same guidance - make sure you check the number of bytes read!
|
||||||
//! blob.seek(SeekFrom::Start(0)).unwrap();
|
//! blob.seek(SeekFrom::Start(0))?;
|
||||||
//! let mut buf = [0u8; 20];
|
//! let mut buf = [0u8; 20];
|
||||||
//! let bytes_read = blob.read(&mut buf[..]).unwrap();
|
//! let bytes_read = blob.read(&mut buf[..])?;
|
||||||
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
||||||
//!
|
//!
|
||||||
//! db.execute("INSERT INTO test (content) VALUES (?)", &[ZeroBlob(64)])
|
//! db.execute("INSERT INTO test (content) VALUES (?)", &[ZeroBlob(64)])?;
|
||||||
//! .unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! // given a new row ID, we can reopen the blob on that row
|
//! // given a new row ID, we can reopen the blob on that row
|
||||||
//! let rowid = db.last_insert_rowid();
|
//! let rowid = db.last_insert_rowid();
|
||||||
//! blob.reopen(rowid).unwrap();
|
//! blob.reopen(rowid)?;
|
||||||
//!
|
//!
|
||||||
//! assert_eq!(blob.size(), 64);
|
//! assert_eq!(blob.size(), 64);
|
||||||
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
@ -111,7 +108,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Blob<'conn> {
|
impl Blob<'_> {
|
||||||
/// Move a BLOB handle to a new row.
|
/// Move a BLOB handle to a new row.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
@ -151,7 +148,7 @@ impl<'conn> Blob<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Read for Blob<'conn> {
|
impl io::Read for Blob<'_> {
|
||||||
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
|
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
|
||||||
/// the blob has been reached.
|
/// the blob has been reached.
|
||||||
///
|
///
|
||||||
@ -175,7 +172,7 @@ impl<'conn> io::Read for Blob<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Write for Blob<'conn> {
|
impl io::Write for Blob<'_> {
|
||||||
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
|
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
|
||||||
/// the blob has been reached; consider using `Write::write_all(buf)`
|
/// the blob has been reached; consider using `Write::write_all(buf)`
|
||||||
/// if you want to get an error if the entirety of the buffer cannot be
|
/// if you want to get an error if the entirety of the buffer cannot be
|
||||||
@ -208,7 +205,7 @@ impl<'conn> io::Write for Blob<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Seek for Blob<'conn> {
|
impl io::Seek for Blob<'_> {
|
||||||
/// Seek to an offset, in bytes, in BLOB.
|
/// Seek to an offset, in bytes, in BLOB.
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
let pos = match pos {
|
let pos = match pos {
|
||||||
@ -235,7 +232,7 @@ impl<'conn> io::Seek for Blob<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Blob<'conn> {
|
impl Drop for Blob<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.close_();
|
self.close_();
|
||||||
}
|
}
|
||||||
|
13
src/busy.rs
13
src/busy.rs
@ -75,14 +75,13 @@ impl InnerConnection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use self::tempdir::TempDir;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::sync_channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tempdir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
use crate::{Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS};
|
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_busy() {
|
fn test_default_busy() {
|
||||||
@ -94,7 +93,7 @@ mod test {
|
|||||||
.transaction_with_behavior(TransactionBehavior::Exclusive)
|
.transaction_with_behavior(TransactionBehavior::Exclusive)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let db2 = Connection::open(&path).unwrap();
|
let db2 = Connection::open(&path).unwrap();
|
||||||
let r = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
|
let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
|
||||||
match r.unwrap_err() {
|
match r.unwrap_err() {
|
||||||
Error::SqliteFailure(err, _) => {
|
Error::SqliteFailure(err, _) => {
|
||||||
assert_eq!(err.code, ErrorCode::DatabaseBusy);
|
assert_eq!(err.code, ErrorCode::DatabaseBusy);
|
||||||
@ -127,7 +126,7 @@ mod test {
|
|||||||
assert_eq!(tx.recv().unwrap(), 1);
|
assert_eq!(tx.recv().unwrap(), 1);
|
||||||
let _ = db2
|
let _ = db2
|
||||||
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
||||||
row.get_checked::<_, i32>(0)
|
row.get::<_, i32>(0)
|
||||||
})
|
})
|
||||||
.expect("unexpected error");
|
.expect("unexpected error");
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore] // FIXME: unstable
|
#[ignore] // FIXME: unstable
|
||||||
fn test_busy_handler() {
|
fn test_busy_handler() {
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
fn busy_handler(_: i32) -> bool {
|
fn busy_handler(_: i32) -> bool {
|
||||||
@ -166,7 +165,7 @@ mod test {
|
|||||||
assert_eq!(tx.recv().unwrap(), 1);
|
assert_eq!(tx.recv().unwrap(), 1);
|
||||||
let _ = db2
|
let _ = db2
|
||||||
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
||||||
row.get_checked::<_, i32>(0)
|
row.get::<_, i32>(0)
|
||||||
})
|
})
|
||||||
.expect("unexpected error");
|
.expect("unexpected error");
|
||||||
assert_eq!(CALLED.load(Ordering::Relaxed), true);
|
assert_eq!(CALLED.load(Ordering::Relaxed), true);
|
||||||
|
22
src/cache.rs
22
src/cache.rs
@ -79,7 +79,7 @@ impl<'conn> DerefMut for CachedStatement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Drop for CachedStatement<'conn> {
|
impl Drop for CachedStatement<'_> {
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(stmt) = self.stmt.take() {
|
if let Some(stmt) = self.stmt.take() {
|
||||||
@ -88,8 +88,8 @@ impl<'conn> Drop for CachedStatement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> CachedStatement<'conn> {
|
impl CachedStatement<'_> {
|
||||||
fn new(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
|
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
|
||||||
CachedStatement {
|
CachedStatement {
|
||||||
stmt: Some(stmt),
|
stmt: Some(stmt),
|
||||||
cache,
|
cache,
|
||||||
@ -153,6 +153,7 @@ impl StatementCache {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::StatementCache;
|
use super::StatementCache;
|
||||||
use crate::{Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
impl StatementCache {
|
impl StatementCache {
|
||||||
fn clear(&self) {
|
fn clear(&self) {
|
||||||
@ -277,12 +278,8 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1i32,
|
Ok(Some(1i32)),
|
||||||
stmt.query_map::<i32, _, _>(NO_PARAMS, |r| r.get(0))
|
stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
|
||||||
.unwrap()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,12 +294,11 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(1i32, 2i32),
|
Ok(Some((1i32, 2i32))),
|
||||||
stmt.query_map(NO_PARAMS, |r| (r.get(0), r.get(1)))
|
stmt.query(NO_PARAMS)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.map(|r| Ok((r.get(0)?, r.get(1)?)))
|
||||||
.next()
|
.next()
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
207
src/collation.rs
Normal file
207
src/collation.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
//! Add, remove, or modify a collation
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::panic::{catch_unwind, UnwindSafe};
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::{str_to_cstring, Connection, InnerConnection, Result};
|
||||||
|
|
||||||
|
// FIXME copy/paste from function.rs
|
||||||
|
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||||
|
drop(Box::from_raw(p as *mut T));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Add or modify a collation.
|
||||||
|
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
self.db
|
||||||
|
.borrow_mut()
|
||||||
|
.create_collation(collation_name, x_compare)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collation needed callback
|
||||||
|
pub fn collation_needed(
|
||||||
|
&self,
|
||||||
|
x_coll_needed: fn(&Connection, &str) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.db.borrow_mut().collation_needed(x_coll_needed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove collation.
|
||||||
|
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
|
||||||
|
self.db.borrow_mut().remove_collation(collation_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<C>(
|
||||||
|
arg1: *mut c_void,
|
||||||
|
arg2: c_int,
|
||||||
|
arg3: *const c_void,
|
||||||
|
arg4: c_int,
|
||||||
|
arg5: *const c_void,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering,
|
||||||
|
{
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let r = catch_unwind(|| {
|
||||||
|
let boxed_f: *mut C = arg1 as *mut C;
|
||||||
|
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||||
|
let s1 = {
|
||||||
|
let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
let s2 = {
|
||||||
|
let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
(*boxed_f)(s1, s2)
|
||||||
|
});
|
||||||
|
let t = match r {
|
||||||
|
Err(_) => {
|
||||||
|
return -1; // FIXME How ?
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
|
};
|
||||||
|
|
||||||
|
match t {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let boxed_f: *mut C = Box::into_raw(Box::new(x_compare));
|
||||||
|
let c_name = str_to_cstring(collation_name)?;
|
||||||
|
let flags = ffi::SQLITE_UTF8;
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_collation_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
flags,
|
||||||
|
boxed_f as *mut c_void,
|
||||||
|
Some(call_boxed_closure::<C>),
|
||||||
|
Some(free_boxed_value::<C>),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collation_needed(
|
||||||
|
&mut self,
|
||||||
|
x_coll_needed: fn(&Connection, &str) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
use std::mem;
|
||||||
|
unsafe extern "C" fn collation_needed_callback(
|
||||||
|
arg1: *mut c_void,
|
||||||
|
arg2: *mut ffi::sqlite3,
|
||||||
|
e_text_rep: c_int,
|
||||||
|
arg3: *const c_char,
|
||||||
|
) {
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
if e_text_rep != ffi::SQLITE_UTF8 {
|
||||||
|
// TODO: validate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1);
|
||||||
|
if catch_unwind(|| {
|
||||||
|
let conn = Connection::from_handle(arg2).unwrap();
|
||||||
|
let collation_name = {
|
||||||
|
let c_slice = CStr::from_ptr(arg3).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
callback(&conn, collation_name)
|
||||||
|
}).is_err() {
|
||||||
|
return; // FIXME How ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_collation_needed(
|
||||||
|
self.db(),
|
||||||
|
mem::transmute(x_coll_needed),
|
||||||
|
Some(collation_needed_callback),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
|
||||||
|
let c_name = str_to_cstring(collation_name)?;
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_collation_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
ffi::SQLITE_UTF8,
|
||||||
|
ptr::null_mut(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use unicase::UniCase;
|
||||||
|
|
||||||
|
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
||||||
|
UniCase::new(s1).cmp(&UniCase::new(s2))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unicase() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
db.create_collation("unicase", unicase_compare).unwrap();
|
||||||
|
|
||||||
|
collate(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collate(db: Connection) {
|
||||||
|
db.execute_batch(
|
||||||
|
"CREATE TABLE foo (bar);
|
||||||
|
INSERT INTO foo (bar) VALUES ('Maße');
|
||||||
|
INSERT INTO foo (bar) VALUES ('MASSE');",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")
|
||||||
|
.unwrap();
|
||||||
|
let rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
assert_eq!(rows.count().unwrap(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
|
||||||
|
if "unicase" == collation_name {
|
||||||
|
db.create_collation(collation_name, unicase_compare)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collation_needed() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.collation_needed(collation_needed).unwrap();
|
||||||
|
collate(db);
|
||||||
|
}
|
||||||
|
}
|
171
src/column.rs
Normal file
171
src/column.rs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
use std::str;
|
||||||
|
|
||||||
|
use crate::{Error, Result, Row, Rows, Statement};
|
||||||
|
|
||||||
|
/// Information about a column of a SQLite query.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Column<'stmt> {
|
||||||
|
name: &'stmt str,
|
||||||
|
decl_type: Option<&'stmt str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Column<'_> {
|
||||||
|
/// Returns the name of the column.
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of the column (`None` for expression).
|
||||||
|
pub fn decl_type(&self) -> Option<&str> {
|
||||||
|
self.decl_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Statement<'_> {
|
||||||
|
/// Get all the column names in the result set of the prepared statement.
|
||||||
|
pub fn column_names(&self) -> Vec<&str> {
|
||||||
|
let n = self.column_count();
|
||||||
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
|
for i in 0..n {
|
||||||
|
let s = self.column_name(i);
|
||||||
|
cols.push(s);
|
||||||
|
}
|
||||||
|
cols
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of columns in the result set returned by the prepared
|
||||||
|
/// statement.
|
||||||
|
pub fn column_count(&self) -> usize {
|
||||||
|
self.stmt.column_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn column_name(&self, col: usize) -> &str {
|
||||||
|
// Just panic if the bounds are wrong for now, we never call this
|
||||||
|
// without checking first.
|
||||||
|
let slice = self.stmt.column_name(col).expect("Column out of bounds");
|
||||||
|
str::from_utf8(slice.to_bytes()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the column index in the result set for a given column name.
|
||||||
|
///
|
||||||
|
/// If there is no AS clause then the name of the column is unspecified and
|
||||||
|
/// may change from one release of SQLite to the next.
|
||||||
|
///
|
||||||
|
/// # Failure
|
||||||
|
///
|
||||||
|
/// Will return an `Error::InvalidColumnName` when there is no column with
|
||||||
|
/// the specified `name`.
|
||||||
|
pub fn column_index(&self, name: &str) -> Result<usize> {
|
||||||
|
let bytes = name.as_bytes();
|
||||||
|
let n = self.column_count();
|
||||||
|
for i in 0..n {
|
||||||
|
// Note: `column_name` is only fallible if `i` is out of bounds,
|
||||||
|
// which we've already checked.
|
||||||
|
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
|
||||||
|
return Ok(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::InvalidColumnName(String::from(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the result of the query.
|
||||||
|
pub fn columns<'stmt>(&'stmt self) -> Vec<Column<'stmt>> {
|
||||||
|
let n = self.column_count();
|
||||||
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
|
for i in 0..n {
|
||||||
|
let name = self.column_name(i);
|
||||||
|
let slice = self.stmt.column_decltype(i);
|
||||||
|
let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap());
|
||||||
|
cols.push(Column { name, decl_type });
|
||||||
|
}
|
||||||
|
cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Rows<'stmt> {
|
||||||
|
/// Get all the column names.
|
||||||
|
pub fn column_names(&self) -> Option<Vec<&str>> {
|
||||||
|
self.stmt.map(Statement::column_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of columns.
|
||||||
|
pub fn column_count(&self) -> Option<usize> {
|
||||||
|
self.stmt.map(Statement::column_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the Rows.
|
||||||
|
pub fn columns(&self) -> Option<Vec<Column<'stmt>>> {
|
||||||
|
self.stmt.map(Statement::columns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Row<'stmt> {
|
||||||
|
/// Return the number of columns in the current row.
|
||||||
|
pub fn column_count(&self) -> usize {
|
||||||
|
self.stmt.column_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the Row.
|
||||||
|
pub fn columns(&self) -> Vec<Column<'stmt>> {
|
||||||
|
self.stmt.columns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Column;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_columns() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let query = db.prepare("SELECT * FROM sqlite_master").unwrap();
|
||||||
|
let columns = query.columns();
|
||||||
|
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
|
||||||
|
assert_eq!(
|
||||||
|
column_names.as_slice(),
|
||||||
|
&["type", "name", "tbl_name", "rootpage", "sql"]
|
||||||
|
);
|
||||||
|
let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
|
||||||
|
assert_eq!(
|
||||||
|
&column_types[..3],
|
||||||
|
&[Some("text"), Some("text"), Some("text"),]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_column_name_in_error() {
|
||||||
|
use crate::{types::Type, Error};
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch(
|
||||||
|
"BEGIN;
|
||||||
|
CREATE TABLE foo(x INTEGER, y TEXT);
|
||||||
|
INSERT INTO foo VALUES(4, NULL);
|
||||||
|
END;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
|
||||||
|
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
match row.get::<_, String>(0).unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 0);
|
||||||
|
assert_eq!(name, "renamed");
|
||||||
|
assert_eq!(ty, Type::Integer);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match row.get::<_, String>("y").unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 1);
|
||||||
|
assert_eq!(name, "y");
|
||||||
|
assert_eq!(ty, Type::Null);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/config.rs
Normal file
114
src/config.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
//! Configure database connections
|
||||||
|
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
|
/// Database Connection Configuration Options
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(non_snake_case, non_camel_case_types)]
|
||||||
|
pub enum DbConfig {
|
||||||
|
//SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
|
||||||
|
//SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */
|
||||||
|
SQLITE_DBCONFIG_ENABLE_FKEY = 1002,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
|
||||||
|
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
||||||
|
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
||||||
|
SQLITE_DBCONFIG_TRIGGER_EQP = 1008,
|
||||||
|
//SQLITE_DBCONFIG_RESET_DATABASE = 1009,
|
||||||
|
SQLITE_DBCONFIG_DEFENSIVE = 1010,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Returns the current value of a `config`.
|
||||||
|
///
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FKEY: return `false` or `true` to indicate
|
||||||
|
/// whether FK enforcement is off or on
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: return `false` or `true` to indicate
|
||||||
|
/// whether triggers are disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return `false` or `true` to
|
||||||
|
/// indicate whether fts3_tokenizer are disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return `false` to indicate
|
||||||
|
/// checkpoints-on-close are not disabled or `true` if they are
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_QPSG: return `false` or `true` to indicate
|
||||||
|
/// whether the QPSG is disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_TRIGGER_EQP: return `false` to indicate
|
||||||
|
/// output-for-trigger are not disabled or `true` if it is
|
||||||
|
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
|
||||||
|
let c = self.db.borrow();
|
||||||
|
unsafe {
|
||||||
|
let mut val = 0;
|
||||||
|
check!(ffi::sqlite3_db_config(
|
||||||
|
c.db(),
|
||||||
|
config as c_int,
|
||||||
|
-1,
|
||||||
|
&mut val
|
||||||
|
));
|
||||||
|
Ok(val != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make configuration changes to a database connection
|
||||||
|
///
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FKEY: `false` to disable FK enforcement, `true`
|
||||||
|
/// to enable FK enforcement
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: `false` to disable triggers, `true` to
|
||||||
|
/// enable triggers
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: `false` to disable
|
||||||
|
/// fts3_tokenizer(), `true` to enable fts3_tokenizer()
|
||||||
|
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: `false` (the default) to enable
|
||||||
|
/// checkpoints-on-close, `true` to disable them
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_QPSG: `false` to disable the QPSG, `true` to
|
||||||
|
/// enable QPSG
|
||||||
|
/// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
|
||||||
|
/// programs, `true` to enable it
|
||||||
|
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
|
||||||
|
let c = self.db.borrow_mut();
|
||||||
|
unsafe {
|
||||||
|
let mut val = 0;
|
||||||
|
check!(ffi::sqlite3_db_config(
|
||||||
|
c.db(),
|
||||||
|
config as c_int,
|
||||||
|
if new_val { 1 } else { 0 },
|
||||||
|
&mut val
|
||||||
|
));
|
||||||
|
Ok(val != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::DbConfig;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_db_config() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
|
||||||
|
let opposite = !db
|
||||||
|
.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ use std::rc::Rc;
|
|||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
use crate::ffi::sqlite3_context;
|
use crate::ffi::sqlite3_context;
|
||||||
|
|
||||||
use crate::str_to_cstring;
|
use crate::str_for_sqlite;
|
||||||
use crate::types::{ToSqlOutput, ValueRef};
|
use crate::types::{ToSqlOutput, ValueRef};
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
use crate::vtab::array::{free_array, ARRAY_TYPE};
|
use crate::vtab::array::{free_array, ARRAY_TYPE};
|
||||||
@ -38,25 +38,20 @@ pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
|||||||
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
|
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
|
||||||
ValueRef::Text(s) => {
|
ValueRef::Text(s) => {
|
||||||
let length = s.len();
|
let length = s.len();
|
||||||
if length > ::std::i32::MAX as usize {
|
if length > c_int::max_value() as usize {
|
||||||
ffi::sqlite3_result_error_toobig(ctx);
|
ffi::sqlite3_result_error_toobig(ctx);
|
||||||
} else {
|
} else {
|
||||||
let c_str = match str_to_cstring(s) {
|
let (c_str, len, destructor) = match str_for_sqlite(s) {
|
||||||
Ok(c_str) => c_str,
|
Ok(c_str) => c_str,
|
||||||
// TODO sqlite3_result_error
|
// TODO sqlite3_result_error
|
||||||
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
|
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
|
||||||
};
|
};
|
||||||
let destructor = if length > 0 {
|
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
|
||||||
ffi::SQLITE_TRANSIENT()
|
|
||||||
} else {
|
|
||||||
ffi::SQLITE_STATIC()
|
|
||||||
};
|
|
||||||
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ValueRef::Blob(b) => {
|
ValueRef::Blob(b) => {
|
||||||
let length = b.len();
|
let length = b.len();
|
||||||
if length > ::std::i32::MAX as usize {
|
if length > c_int::max_value() as usize {
|
||||||
ffi::sqlite3_result_error_toobig(ctx);
|
ffi::sqlite3_result_error_toobig(ctx);
|
||||||
} else if length == 0 {
|
} else if length == 0 {
|
||||||
ffi::sqlite3_result_zeroblob(ctx, 0)
|
ffi::sqlite3_result_zeroblob(ctx, 0)
|
||||||
|
61
src/error.rs
61
src/error.rs
@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::FromSqlError;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
use crate::{errmsg_to_string, ffi};
|
use crate::{errmsg_to_string, ffi};
|
||||||
use std::error;
|
use std::error;
|
||||||
@ -59,7 +60,7 @@ pub enum Error {
|
|||||||
/// Error when the value of a particular column is requested, but the type
|
/// Error when the value of a particular column is requested, but the type
|
||||||
/// of the result in that column cannot be converted to the requested
|
/// of the result in that column cannot be converted to the requested
|
||||||
/// Rust type.
|
/// Rust type.
|
||||||
InvalidColumnType(usize, Type),
|
InvalidColumnType(usize, String, Type),
|
||||||
|
|
||||||
/// Error when a query that was expected to insert one row did not insert
|
/// Error when a query that was expected to insert one row did not insert
|
||||||
/// any or insert many.
|
/// any or insert many.
|
||||||
@ -119,8 +120,8 @@ impl PartialEq for Error {
|
|||||||
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
||||||
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
||||||
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
||||||
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
|
(Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
|
||||||
i1 == i2 && t1 == t2
|
i1 == i2 && t1 == t2 && n1 == n2
|
||||||
}
|
}
|
||||||
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
@ -157,6 +158,32 @@ impl From<::std::ffi::NulError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNKNOWN_COLUMN: usize = std::usize::MAX;
|
||||||
|
|
||||||
|
/// The conversion isn't precise, but it's convenient to have it
|
||||||
|
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
|
||||||
|
impl From<FromSqlError> for Error {
|
||||||
|
fn from(err: FromSqlError) -> Error {
|
||||||
|
// The error type requires index and type fields, but they aren't known in this
|
||||||
|
// context.
|
||||||
|
match err {
|
||||||
|
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
FromSqlError::Other(source) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
|
||||||
|
}
|
||||||
|
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
@ -166,13 +193,23 @@ impl fmt::Display for Error {
|
|||||||
f,
|
f,
|
||||||
"SQLite was compiled or configured for single-threaded use only"
|
"SQLite was compiled or configured for single-threaded use only"
|
||||||
),
|
),
|
||||||
Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
|
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||||
|
if i != UNKNOWN_COLUMN {
|
||||||
|
write!(
|
||||||
f,
|
f,
|
||||||
"Conversion error from type {} at index: {}, {}",
|
"Conversion error from type {} at index: {}, {}",
|
||||||
t, i, err
|
t, i, err
|
||||||
),
|
)
|
||||||
|
} else {
|
||||||
|
err.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
Error::IntegralValueOutOfRange(col, val) => {
|
Error::IntegralValueOutOfRange(col, val) => {
|
||||||
|
if col != UNKNOWN_COLUMN {
|
||||||
write!(f, "Integer {} out of range at index {}", val, col)
|
write!(f, "Integer {} out of range at index {}", val, col)
|
||||||
|
} else {
|
||||||
|
write!(f, "Integer {} out of range", val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Error::Utf8Error(ref err) => err.fmt(f),
|
Error::Utf8Error(ref err) => err.fmt(f),
|
||||||
Error::NulError(ref err) => err.fmt(f),
|
Error::NulError(ref err) => err.fmt(f),
|
||||||
@ -184,9 +221,11 @@ impl fmt::Display for Error {
|
|||||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
||||||
Error::InvalidColumnType(i, ref t) => {
|
Error::InvalidColumnType(i, ref name, ref t) => write!(
|
||||||
write!(f, "Invalid column type {} at index: {}", t, i)
|
f,
|
||||||
}
|
"Invalid column type {} at index: {}, name: {}",
|
||||||
|
t, i, name
|
||||||
|
),
|
||||||
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
@ -232,7 +271,7 @@ impl error::Error for Error {
|
|||||||
Error::QueryReturnedNoRows => "query returned no rows",
|
Error::QueryReturnedNoRows => "query returned no rows",
|
||||||
Error::InvalidColumnIndex(_) => "invalid column index",
|
Error::InvalidColumnIndex(_) => "invalid column index",
|
||||||
Error::InvalidColumnName(_) => "invalid column name",
|
Error::InvalidColumnName(_) => "invalid column name",
|
||||||
Error::InvalidColumnType(_, _) => "invalid column type",
|
Error::InvalidColumnType(_, _, _) => "invalid column type",
|
||||||
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
@ -266,7 +305,7 @@ impl error::Error for Error {
|
|||||||
| Error::QueryReturnedNoRows
|
| Error::QueryReturnedNoRows
|
||||||
| Error::InvalidColumnIndex(_)
|
| Error::InvalidColumnIndex(_)
|
||||||
| Error::InvalidColumnName(_)
|
| Error::InvalidColumnName(_)
|
||||||
| Error::InvalidColumnType(_, _)
|
| Error::InvalidColumnType(_, _, _)
|
||||||
| Error::InvalidPath(_)
|
| Error::InvalidPath(_)
|
||||||
| Error::StatementChangedRows(_)
|
| Error::StatementChangedRows(_)
|
||||||
| Error::InvalidQuery
|
| Error::InvalidQuery
|
||||||
@ -314,7 +353,7 @@ macro_rules! check {
|
|||||||
($funcall:expr) => {{
|
($funcall:expr) => {{
|
||||||
let rc = $funcall;
|
let rc = $funcall;
|
||||||
if rc != crate::ffi::SQLITE_OK {
|
if rc != crate::ffi::SQLITE_OK {
|
||||||
Err(crate::error::error_from_sqlite_code(rc, None))?;
|
return Err(crate::error::error_from_sqlite_code(rc, None).into());
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
425
src/functions.rs
425
src/functions.rs
@ -11,42 +11,54 @@
|
|||||||
//! ```rust
|
//! ```rust
|
||||||
//! use regex::Regex;
|
//! use regex::Regex;
|
||||||
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
||||||
//! use std::collections::HashMap;
|
|
||||||
//!
|
//!
|
||||||
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
||||||
//! let mut cached_regexes = HashMap::new();
|
|
||||||
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
||||||
//! let regex_s = ctx.get::<String>(0)?;
|
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
//! let entry = cached_regexes.entry(regex_s.clone());
|
//!
|
||||||
//! let regex = {
|
//! let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
||||||
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
|
//! let new_re = match saved_re {
|
||||||
//! match entry {
|
//! None => {
|
||||||
//! Occupied(occ) => occ.into_mut(),
|
//! let s = ctx.get::<String>(0)?;
|
||||||
//! Vacant(vac) => match Regex::new(®ex_s) {
|
//! match Regex::new(&s) {
|
||||||
//! Ok(r) => vac.insert(r),
|
//! Ok(r) => Some(r),
|
||||||
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||||
//! },
|
|
||||||
//! }
|
//! }
|
||||||
|
//! }
|
||||||
|
//! Some(_) => None,
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! let text = ctx.get::<String>(1)?;
|
//! let is_match = {
|
||||||
//! Ok(regex.is_match(&text))
|
//! let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
|
||||||
|
//!
|
||||||
|
//! let text = ctx
|
||||||
|
//! .get_raw(1)
|
||||||
|
//! .as_str()
|
||||||
|
//! .map_err(|e| Error::UserFunctionError(e.into()))?;
|
||||||
|
//!
|
||||||
|
//! re.is_match(text)
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! if let Some(re) = new_re {
|
||||||
|
//! ctx.set_aux(0, re);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Ok(is_match)
|
||||||
//! })
|
//! })
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() -> Result<()> {
|
||||||
//! let db = Connection::open_in_memory().unwrap();
|
//! let db = Connection::open_in_memory()?;
|
||||||
//! add_regexp_function(&db).unwrap();
|
//! add_regexp_function(&db)?;
|
||||||
//!
|
//!
|
||||||
//! let is_match: bool = db
|
//! let is_match: bool = db.query_row(
|
||||||
//! .query_row(
|
|
||||||
//! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
|
//! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
|
||||||
//! NO_PARAMS,
|
//! NO_PARAMS,
|
||||||
//! |row| row.get(0),
|
//! |row| row.get(0),
|
||||||
//! )
|
//! )?;
|
||||||
//! .unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! assert!(is_match);
|
//! assert!(is_match);
|
||||||
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
@ -104,7 +116,7 @@ pub struct Context<'a> {
|
|||||||
args: &'a [*mut sqlite3_value],
|
args: &'a [*mut sqlite3_value],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
impl Context<'_> {
|
||||||
/// Returns the number of arguments to the function.
|
/// Returns the number of arguments to the function.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.args.len()
|
self.args.len()
|
||||||
@ -138,6 +150,10 @@ impl<'a> Context<'a> {
|
|||||||
FromSqlError::InvalidI128Size(_) => {
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +162,7 @@ impl<'a> Context<'a> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will panic if `idx` is greater than or equal to `self.len()`.
|
/// Will panic if `idx` is greater than or equal to `self.len()`.
|
||||||
pub fn get_raw(&self, idx: usize) -> ValueRef<'a> {
|
pub fn get_raw(&self, idx: usize) -> ValueRef<'_> {
|
||||||
let arg = self.args[idx];
|
let arg = self.args[idx];
|
||||||
unsafe { ValueRef::from_value(arg) }
|
unsafe { ValueRef::from_value(arg) }
|
||||||
}
|
}
|
||||||
@ -210,6 +226,22 @@ where
|
|||||||
fn finalize(&self, _: Option<A>) -> Result<T>;
|
fn finalize(&self, _: Option<A>) -> Result<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// WindowAggregate is the callback interface for user-defined aggregate window
|
||||||
|
/// function.
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
pub trait WindowAggregate<A, T>: Aggregate<A, T>
|
||||||
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
/// Returns the current value of the aggregate. Unlike xFinal, the
|
||||||
|
/// implementation should not delete any context.
|
||||||
|
fn value(&self, _: Option<&A>) -> Result<T>;
|
||||||
|
|
||||||
|
/// Removes a row from the current window.
|
||||||
|
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Attach a user-defined scalar function to this database connection.
|
/// Attach a user-defined scalar function to this database connection.
|
||||||
///
|
///
|
||||||
@ -278,6 +310,24 @@ impl Connection {
|
|||||||
.create_aggregate_function(fn_name, n_arg, deterministic, aggr)
|
.create_aggregate_function(fn_name, n_arg, deterministic, aggr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
pub fn create_window_function<A, W, T>(
|
||||||
|
&self,
|
||||||
|
fn_name: &str,
|
||||||
|
n_arg: c_int,
|
||||||
|
deterministic: bool,
|
||||||
|
aggr: W,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
W: WindowAggregate<A, T>,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
self.db
|
||||||
|
.borrow_mut()
|
||||||
|
.create_window_function(fn_name, n_arg, deterministic, aggr)
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes a user-defined function from this database connection.
|
/// Removes a user-defined function from this database connection.
|
||||||
///
|
///
|
||||||
/// `fn_name` and `n_arg` should match the name and number of arguments
|
/// `fn_name` and `n_arg` should match the name and number of arguments
|
||||||
@ -370,26 +420,100 @@ impl InnerConnection {
|
|||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
unsafe fn aggregate_context<A>(
|
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
||||||
ctx: *mut sqlite3_context,
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
bytes: usize,
|
let mut flags = ffi::SQLITE_UTF8;
|
||||||
) -> Option<*mut *mut A> {
|
if deterministic {
|
||||||
|
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||||
|
}
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_function_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
n_arg,
|
||||||
|
flags,
|
||||||
|
boxed_aggr as *mut c_void,
|
||||||
|
None,
|
||||||
|
Some(call_boxed_step::<A, D, T>),
|
||||||
|
Some(call_boxed_final::<A, D, T>),
|
||||||
|
Some(free_boxed_value::<D>),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
fn create_window_function<A, W, T>(
|
||||||
|
&mut self,
|
||||||
|
fn_name: &str,
|
||||||
|
n_arg: c_int,
|
||||||
|
deterministic: bool,
|
||||||
|
aggr: W,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
W: WindowAggregate<A, T>,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
|
||||||
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
|
let mut flags = ffi::SQLITE_UTF8;
|
||||||
|
if deterministic {
|
||||||
|
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||||
|
}
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_window_function(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
n_arg,
|
||||||
|
flags,
|
||||||
|
boxed_aggr as *mut c_void,
|
||||||
|
Some(call_boxed_step::<A, W, T>),
|
||||||
|
Some(call_boxed_final::<A, W, T>),
|
||||||
|
Some(call_boxed_value::<A, W, T>),
|
||||||
|
Some(call_boxed_inverse::<A, W, T>),
|
||||||
|
Some(free_boxed_value::<W>),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
|
||||||
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_function_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
n_arg,
|
||||||
|
ffi::SQLITE_UTF8,
|
||||||
|
ptr::null_mut(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, bytes: usize) -> Option<*mut *mut A> {
|
||||||
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
|
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
|
||||||
if pac.is_null() {
|
if pac.is_null() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(pac)
|
Some(pac)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn call_boxed_step<A, D, T>(
|
unsafe extern "C" fn call_boxed_step<A, D, T>(
|
||||||
ctx: *mut sqlite3_context,
|
ctx: *mut sqlite3_context,
|
||||||
argc: c_int,
|
argc: c_int,
|
||||||
argv: *mut *mut sqlite3_value,
|
argv: *mut *mut sqlite3_value,
|
||||||
) where
|
) where
|
||||||
A: RefUnwindSafe + UnwindSafe,
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
|
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
|
||||||
Some(pac) => pac,
|
Some(pac) => pac,
|
||||||
None => {
|
None => {
|
||||||
@ -424,14 +548,57 @@ impl InnerConnection {
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => report_error(ctx, &err),
|
Err(err) => report_error(ctx, &err),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
|
#[cfg(feature = "window")]
|
||||||
where
|
unsafe extern "C" fn call_boxed_inverse<A, W, T>(
|
||||||
|
ctx: *mut sqlite3_context,
|
||||||
|
argc: c_int,
|
||||||
|
argv: *mut *mut sqlite3_value,
|
||||||
|
) where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
W: WindowAggregate<A, T>,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
|
||||||
|
Some(pac) => pac,
|
||||||
|
None => {
|
||||||
|
ffi::sqlite3_result_error_nomem(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let r = catch_unwind(|| {
|
||||||
|
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
|
||||||
|
assert!(
|
||||||
|
!boxed_aggr.is_null(),
|
||||||
|
"Internal error - null aggregate pointer"
|
||||||
|
);
|
||||||
|
let mut ctx = Context {
|
||||||
|
ctx,
|
||||||
|
args: slice::from_raw_parts(argv, argc as usize),
|
||||||
|
};
|
||||||
|
(*boxed_aggr).inverse(&mut ctx, &mut **pac)
|
||||||
|
});
|
||||||
|
let r = match r {
|
||||||
|
Err(_) => {
|
||||||
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
|
};
|
||||||
|
match r {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => report_error(ctx, &err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
|
||||||
|
where
|
||||||
A: RefUnwindSafe + UnwindSafe,
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
// Within the xFinal callback, it is customary to set N=0 in calls to
|
// Within the xFinal callback, it is customary to set N=0 in calls to
|
||||||
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||||
let a: Option<A> = match aggregate_context(ctx, 0) {
|
let a: Option<A> = match aggregate_context(ctx, 0) {
|
||||||
@ -467,58 +634,60 @@ impl InnerConnection {
|
|||||||
Ok(Err(err)) => report_error(ctx, &err),
|
Ok(Err(err)) => report_error(ctx, &err),
|
||||||
Err(err) => report_error(ctx, err),
|
Err(err) => report_error(ctx, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
#[cfg(feature = "window")]
|
||||||
let c_name = str_to_cstring(fn_name)?;
|
unsafe extern "C" fn call_boxed_value<A, W, T>(ctx: *mut sqlite3_context)
|
||||||
let mut flags = ffi::SQLITE_UTF8;
|
where
|
||||||
if deterministic {
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
W: WindowAggregate<A, T>,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
// Within the xValue callback, it is customary to set N=0 in calls to
|
||||||
|
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||||
|
let a: Option<&A> = match aggregate_context(ctx, 0) {
|
||||||
|
Some(pac) => {
|
||||||
|
if (*pac as *mut A).is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let a = &**pac;
|
||||||
|
Some(a)
|
||||||
}
|
}
|
||||||
let r = unsafe {
|
}
|
||||||
ffi::sqlite3_create_function_v2(
|
None => None,
|
||||||
self.db(),
|
|
||||||
c_name.as_ptr(),
|
|
||||||
n_arg,
|
|
||||||
flags,
|
|
||||||
boxed_aggr as *mut c_void,
|
|
||||||
None,
|
|
||||||
Some(call_boxed_step::<A, D, T>),
|
|
||||||
Some(call_boxed_final::<A, D, T>),
|
|
||||||
Some(free_boxed_value::<D>),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
|
let r = catch_unwind(|| {
|
||||||
let c_name = str_to_cstring(fn_name)?;
|
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
|
||||||
let r = unsafe {
|
assert!(
|
||||||
ffi::sqlite3_create_function_v2(
|
!boxed_aggr.is_null(),
|
||||||
self.db(),
|
"Internal error - null aggregate pointer"
|
||||||
c_name.as_ptr(),
|
);
|
||||||
n_arg,
|
(*boxed_aggr).value(a)
|
||||||
ffi::SQLITE_UTF8,
|
});
|
||||||
ptr::null_mut(),
|
let t = match r {
|
||||||
None,
|
Err(_) => {
|
||||||
None,
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
None,
|
return;
|
||||||
None,
|
}
|
||||||
)
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
self.decode_result(r)
|
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||||
|
match t {
|
||||||
|
Ok(Ok(ref value)) => set_result(ctx, value),
|
||||||
|
Ok(Err(err)) => report_error(ctx, &err),
|
||||||
|
Err(err) => report_error(ctx, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use self::regex::Regex;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::f64::EPSILON;
|
use std::f64::EPSILON;
|
||||||
use std::os::raw::c_double;
|
use std::os::raw::c_double;
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
use crate::functions::WindowAggregate;
|
||||||
use crate::functions::{Aggregate, Context};
|
use crate::functions::{Aggregate, Context};
|
||||||
use crate::{Connection, Error, Result, NO_PARAMS};
|
use crate::{Connection, Error, Result, NO_PARAMS};
|
||||||
|
|
||||||
@ -616,60 +785,6 @@ mod test {
|
|||||||
assert_eq!(2, result.unwrap());
|
assert_eq!(2, result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_function_regexp_with_hashmap_cache() {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
db.execute_batch(
|
|
||||||
"BEGIN;
|
|
||||||
CREATE TABLE foo (x string);
|
|
||||||
INSERT INTO foo VALUES ('lisa');
|
|
||||||
INSERT INTO foo VALUES ('lXsi');
|
|
||||||
INSERT INTO foo VALUES ('lisX');
|
|
||||||
END;",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// This implementation of a regexp scalar function uses a captured HashMap
|
|
||||||
// to keep cached regular expressions around (even across multiple queries)
|
|
||||||
// until the function is removed.
|
|
||||||
let mut cached_regexes = HashMap::new();
|
|
||||||
db.create_scalar_function("regexp", 2, true, move |ctx| {
|
|
||||||
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
|
||||||
|
|
||||||
let regex_s = ctx.get::<String>(0)?;
|
|
||||||
let entry = cached_regexes.entry(regex_s.clone());
|
|
||||||
let regex = {
|
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|
||||||
match entry {
|
|
||||||
Occupied(occ) => occ.into_mut(),
|
|
||||||
Vacant(vac) => match Regex::new(®ex_s) {
|
|
||||||
Ok(r) => vac.insert(r),
|
|
||||||
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let text = ctx.get::<String>(1)?;
|
|
||||||
Ok(regex.is_match(&text))
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let result: Result<bool> =
|
|
||||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
|
||||||
r.get(0)
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(true, result.unwrap());
|
|
||||||
|
|
||||||
let result: Result<i64> = db.query_row(
|
|
||||||
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
|
|
||||||
NO_PARAMS,
|
|
||||||
|r| r.get(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(2, result.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_varargs_function() {
|
fn test_varargs_function() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -771,7 +886,7 @@ mod test {
|
|||||||
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
|
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
|
||||||
2, 1)";
|
2, 1)";
|
||||||
let result: (i64, i64) = db
|
let result: (i64, i64) = db
|
||||||
.query_row(dual_sum, NO_PARAMS, |r| (r.get(0), r.get(1)))
|
.query_row(dual_sum, NO_PARAMS, |r| Ok((r.get(0)?, r.get(1)?)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!((4, 2), result);
|
assert_eq!((4, 2), result);
|
||||||
}
|
}
|
||||||
@ -791,4 +906,58 @@ mod test {
|
|||||||
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
|
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
|
||||||
assert_eq!(2, result);
|
assert_eq!(2, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
impl WindowAggregate<i64, Option<i64>> for Sum {
|
||||||
|
fn inverse(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
|
||||||
|
*sum -= ctx.get::<i64>(0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self, sum: Option<&i64>) -> Result<Option<i64>> {
|
||||||
|
Ok(sum.copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
fn test_window() {
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.create_window_function("sumint", 1, true, Sum).unwrap();
|
||||||
|
db.execute_batch(
|
||||||
|
"CREATE TABLE t3(x, y);
|
||||||
|
INSERT INTO t3 VALUES('a', 4),
|
||||||
|
('b', 5),
|
||||||
|
('c', 3),
|
||||||
|
('d', 8),
|
||||||
|
('e', 1);",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT x, sumint(y) OVER (
|
||||||
|
ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||||||
|
) AS sum_y
|
||||||
|
FROM t3 ORDER BY x;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let results: Vec<(String, i64)> = stmt
|
||||||
|
.query(NO_PARAMS)
|
||||||
|
.unwrap()
|
||||||
|
.map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
|
||||||
|
.collect()
|
||||||
|
.unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
("a".to_owned(), 9),
|
||||||
|
("b".to_owned(), 12),
|
||||||
|
("c".to_owned(), 16),
|
||||||
|
("d".to_owned(), 12),
|
||||||
|
("e".to_owned(), 9),
|
||||||
|
];
|
||||||
|
assert_eq!(expected, results);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,7 @@ fn free_boxed_hook<F>(p: *mut c_void) {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::Action;
|
use super::Action;
|
||||||
use crate::Connection;
|
use crate::Connection;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
|
use std::sync::{Arc, Mutex, Once};
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::str_to_cstring;
|
use super::{str_for_sqlite, str_to_cstring};
|
||||||
use super::{Connection, InterruptHandle, OpenFlags, Result};
|
use super::{Connection, InterruptHandle, OpenFlags, Result};
|
||||||
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
|
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
|
||||||
use crate::raw_statement::RawStatement;
|
use crate::raw_statement::RawStatement;
|
||||||
@ -78,8 +78,10 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
let mut db = MaybeUninit::uninit();
|
||||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
|
let r =
|
||||||
|
ffi::sqlite3_open_v2(c_path.as_ptr(), db.as_mut_ptr(), flags.bits(), ptr::null());
|
||||||
|
let db: *mut ffi::sqlite3 = db.assume_init();
|
||||||
if r != ffi::SQLITE_OK {
|
if r != ffi::SQLITE_OK {
|
||||||
let e = if db.is_null() {
|
let e = if db.is_null() {
|
||||||
error_from_sqlite_code(r, None)
|
error_from_sqlite_code(r, None)
|
||||||
@ -180,21 +182,27 @@ impl InnerConnection {
|
|||||||
|
|
||||||
let dylib_str = super::path_to_cstring(dylib_path)?;
|
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
let mut errmsg = MaybeUninit::uninit();
|
||||||
let r = if let Some(entry_point) = entry_point {
|
let r = if let Some(entry_point) = entry_point {
|
||||||
let c_entry = str_to_cstring(entry_point)?;
|
let c_entry = str_to_cstring(entry_point)?;
|
||||||
ffi::sqlite3_load_extension(
|
ffi::sqlite3_load_extension(
|
||||||
self.db,
|
self.db,
|
||||||
dylib_str.as_ptr(),
|
dylib_str.as_ptr(),
|
||||||
c_entry.as_ptr(),
|
c_entry.as_ptr(),
|
||||||
&mut errmsg,
|
errmsg.as_mut_ptr(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
|
ffi::sqlite3_load_extension(
|
||||||
|
self.db,
|
||||||
|
dylib_str.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
errmsg.as_mut_ptr(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
if r == ffi::SQLITE_OK {
|
if r == ffi::SQLITE_OK {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
let message = super::errmsg_to_string(&*errmsg);
|
let message = super::errmsg_to_string(&*errmsg);
|
||||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
Err(error_from_sqlite_code(r, Some(message)))
|
Err(error_from_sqlite_code(r, Some(message)))
|
||||||
@ -207,12 +215,8 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
||||||
if sql.len() >= ::std::i32::MAX as usize {
|
let mut c_stmt = MaybeUninit::uninit();
|
||||||
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
|
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||||
}
|
|
||||||
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
|
||||||
let c_sql = str_to_cstring(sql)?;
|
|
||||||
let len_with_nul = (sql.len() + 1) as c_int;
|
|
||||||
let mut c_tail = ptr::null();
|
let mut c_tail = ptr::null();
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
if cfg!(feature = "unlock_notify") {
|
if cfg!(feature = "unlock_notify") {
|
||||||
@ -220,9 +224,9 @@ impl InnerConnection {
|
|||||||
loop {
|
loop {
|
||||||
rc = ffi::sqlite3_prepare_v2(
|
rc = ffi::sqlite3_prepare_v2(
|
||||||
self.db(),
|
self.db(),
|
||||||
c_sql.as_ptr(),
|
c_sql,
|
||||||
len_with_nul,
|
len,
|
||||||
&mut c_stmt,
|
c_stmt.as_mut_ptr(),
|
||||||
&mut c_tail,
|
&mut c_tail,
|
||||||
);
|
);
|
||||||
if !unlock_notify::is_locked(self.db, rc) {
|
if !unlock_notify::is_locked(self.db, rc) {
|
||||||
@ -235,15 +239,10 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
rc
|
rc
|
||||||
} else {
|
} else {
|
||||||
ffi::sqlite3_prepare_v2(
|
ffi::sqlite3_prepare_v2(self.db(), c_sql, len, c_stmt.as_mut_ptr(), &mut c_tail)
|
||||||
self.db(),
|
|
||||||
c_sql.as_ptr(),
|
|
||||||
len_with_nul,
|
|
||||||
&mut c_stmt,
|
|
||||||
&mut c_tail,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let c_stmt: *mut ffi::sqlite3_stmt = unsafe { c_stmt.assume_init() };
|
||||||
if !c_tail.is_null() && unsafe { *c_tail == 0 } {
|
if !c_tail.is_null() && unsafe { *c_tail == 0 } {
|
||||||
// '\0' when there is no ';' at the end
|
// '\0' when there is no ';' at the end
|
||||||
c_tail = ptr::null(); // TODO ignore spaces, comments, ... at the end
|
c_tail = ptr::null(); // TODO ignore spaces, comments, ... at the end
|
||||||
@ -295,9 +294,9 @@ impl Drop for InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
static SQLITE_VERSION_CHECK: Once = Once::new();
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
pub static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
|
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
fn ensure_valid_sqlite_version() {
|
fn ensure_valid_sqlite_version() {
|
||||||
@ -343,8 +342,8 @@ rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static SQLITE_INIT: Once = ONCE_INIT;
|
static SQLITE_INIT: Once = Once::new();
|
||||||
pub static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
|
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||||
// Ensure SQLite was compiled in thredsafe mode.
|
// Ensure SQLite was compiled in thredsafe mode.
|
||||||
|
205
src/lib.rs
205
src/lib.rs
@ -3,7 +3,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use rusqlite::types::ToSql;
|
//! use rusqlite::types::ToSql;
|
||||||
//! use rusqlite::{params, Connection};
|
//! use rusqlite::{params, Connection, Result};
|
||||||
//! use time::Timespec;
|
//! use time::Timespec;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Debug)]
|
//! #[derive(Debug)]
|
||||||
@ -14,8 +14,8 @@
|
|||||||
//! data: Option<Vec<u8>>,
|
//! data: Option<Vec<u8>>,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() -> Result<()> {
|
||||||
//! let conn = Connection::open_in_memory().unwrap();
|
//! let conn = Connection::open_in_memory()?;
|
||||||
//!
|
//!
|
||||||
//! conn.execute(
|
//! conn.execute(
|
||||||
//! "CREATE TABLE person (
|
//! "CREATE TABLE person (
|
||||||
@ -25,8 +25,7 @@
|
|||||||
//! data BLOB
|
//! data BLOB
|
||||||
//! )",
|
//! )",
|
||||||
//! params![],
|
//! params![],
|
||||||
//! )
|
//! )?;
|
||||||
//! .unwrap();
|
|
||||||
//! let me = Person {
|
//! let me = Person {
|
||||||
//! id: 0,
|
//! id: 0,
|
||||||
//! name: "Steven".to_string(),
|
//! name: "Steven".to_string(),
|
||||||
@ -37,36 +36,28 @@
|
|||||||
//! "INSERT INTO person (name, time_created, data)
|
//! "INSERT INTO person (name, time_created, data)
|
||||||
//! VALUES (?1, ?2, ?3)",
|
//! VALUES (?1, ?2, ?3)",
|
||||||
//! params![me.name, me.time_created, me.data],
|
//! params![me.name, me.time_created, me.data],
|
||||||
//! )
|
//! )?;
|
||||||
//! .unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! let mut stmt = conn
|
//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||||
//! .prepare("SELECT id, name, time_created, data FROM person")
|
//! let person_iter = stmt.query_map(params![], |row| {
|
||||||
//! .unwrap();
|
//! Ok(Person {
|
||||||
//! let person_iter = stmt
|
//! id: row.get(0)?,
|
||||||
//! .query_map(params![], |row| Person {
|
//! name: row.get(1)?,
|
||||||
//! id: row.get(0),
|
//! time_created: row.get(2)?,
|
||||||
//! name: row.get(1),
|
//! data: row.get(3)?,
|
||||||
//! time_created: row.get(2),
|
|
||||||
//! data: row.get(3),
|
|
||||||
//! })
|
//! })
|
||||||
//! .unwrap();
|
//! })?;
|
||||||
//!
|
//!
|
||||||
//! for person in person_iter {
|
//! for person in person_iter {
|
||||||
//! println!("Found person {:?}", person.unwrap());
|
//! println!("Found person {:?}", person.unwrap());
|
||||||
//! }
|
//! }
|
||||||
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
#![allow(unknown_lints)]
|
#![allow(unknown_lints)]
|
||||||
|
|
||||||
pub use libsqlite3_sys as ffi;
|
pub use libsqlite3_sys as ffi;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
#[cfg(any(test, feature = "vtab"))]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert;
|
use std::convert;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
@ -86,10 +77,11 @@ use crate::raw_statement::RawStatement;
|
|||||||
use crate::types::ValueRef;
|
use crate::types::ValueRef;
|
||||||
|
|
||||||
pub use crate::cache::CachedStatement;
|
pub use crate::cache::CachedStatement;
|
||||||
|
pub use crate::column::Column;
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
pub use crate::ffi::ErrorCode;
|
pub use crate::ffi::ErrorCode;
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
pub use crate::hooks::*;
|
pub use crate::hooks::Action;
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
pub use crate::load_extension_guard::LoadExtensionGuard;
|
pub use crate::load_extension_guard::LoadExtensionGuard;
|
||||||
pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
|
pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
|
||||||
@ -98,16 +90,21 @@ pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBe
|
|||||||
pub use crate::types::ToSql;
|
pub use crate::types::ToSql;
|
||||||
pub use crate::version::*;
|
pub use crate::version::*;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod error;
|
||||||
|
|
||||||
#[cfg(feature = "backup")]
|
#[cfg(feature = "backup")]
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
pub mod blob;
|
pub mod blob;
|
||||||
mod busy;
|
mod busy;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
#[cfg(feature = "collation")]
|
||||||
|
mod collation;
|
||||||
|
mod column;
|
||||||
|
pub mod config;
|
||||||
#[cfg(any(feature = "functions", feature = "vtab"))]
|
#[cfg(any(feature = "functions", feature = "vtab"))]
|
||||||
mod context;
|
mod context;
|
||||||
#[macro_use]
|
|
||||||
mod error;
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
pub mod functions;
|
pub mod functions;
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
@ -117,6 +114,7 @@ mod inner_connection;
|
|||||||
pub mod limits;
|
pub mod limits;
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
mod load_extension_guard;
|
mod load_extension_guard;
|
||||||
|
mod pragma;
|
||||||
mod raw_statement;
|
mod raw_statement;
|
||||||
mod row;
|
mod row;
|
||||||
#[cfg(feature = "session")]
|
#[cfg(feature = "session")]
|
||||||
@ -238,6 +236,42 @@ fn str_to_cstring(s: &str) -> Result<CString> {
|
|||||||
Ok(CString::new(s)?)
|
Ok(CString::new(s)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok((string ptr, len as c_int, SQLITE_STATIC | SQLITE_TRANSIENT))`
|
||||||
|
/// normally.
|
||||||
|
/// Returns errors if the string has embedded nuls or is too large for sqlite.
|
||||||
|
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
|
||||||
|
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
|
||||||
|
/// static).
|
||||||
|
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
|
||||||
|
let len = len_as_c_int(s.len())?;
|
||||||
|
if memchr::memchr(0, s).is_none() {
|
||||||
|
let (ptr, dtor_info) = if len != 0 {
|
||||||
|
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
|
||||||
|
} else {
|
||||||
|
// Return a pointer guaranteed to live forever
|
||||||
|
("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
|
||||||
|
};
|
||||||
|
Ok((ptr, len, dtor_info))
|
||||||
|
} else {
|
||||||
|
// There's an embedded nul, so we fabricate a NulError.
|
||||||
|
let e = CString::new(s);
|
||||||
|
Err(Error::NulError(e.unwrap_err()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to cast to c_int safely, returning the correct error type if the cast
|
||||||
|
// failed.
|
||||||
|
fn len_as_c_int(len: usize) -> Result<c_int> {
|
||||||
|
if len >= (c_int::max_value() as usize) {
|
||||||
|
Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_TOOBIG),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(len as c_int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn path_to_cstring(p: &Path) -> Result<CString> {
|
fn path_to_cstring(p: &Path) -> Result<CString> {
|
||||||
let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
|
let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
|
||||||
str_to_cstring(s)
|
str_to_cstring(s)
|
||||||
@ -264,7 +298,7 @@ pub enum DatabaseName<'a> {
|
|||||||
feature = "session",
|
feature = "session",
|
||||||
feature = "bundled"
|
feature = "bundled"
|
||||||
))]
|
))]
|
||||||
impl<'a> DatabaseName<'a> {
|
impl DatabaseName<'_> {
|
||||||
fn to_cstring(&self) -> Result<CString> {
|
fn to_cstring(&self) -> Result<CString> {
|
||||||
use self::DatabaseName::{Attached, Main, Temp};
|
use self::DatabaseName::{Attached, Main, Temp};
|
||||||
match *self {
|
match *self {
|
||||||
@ -298,6 +332,16 @@ impl Connection {
|
|||||||
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
||||||
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
||||||
///
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use rusqlite::{Connection, Result};
|
||||||
|
/// fn open_my_db() -> Result<()> {
|
||||||
|
/// let path = "./my_db.db3";
|
||||||
|
/// let db = Connection::open(&path)?;
|
||||||
|
/// println!("{}", db.is_autocommit());
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
||||||
@ -477,7 +521,7 @@ impl Connection {
|
|||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnOnce(&Row<'_, '_>) -> T,
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
stmt.check_no_tail()?;
|
stmt.check_no_tail()?;
|
||||||
@ -500,13 +544,11 @@ impl Connection {
|
|||||||
/// or if the underlying SQLite call fails.
|
/// or if the underlying SQLite call fails.
|
||||||
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||||
where
|
where
|
||||||
F: FnOnce(&Row<'_, '_>) -> T,
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
stmt.check_no_tail()?;
|
stmt.check_no_tail()?;
|
||||||
let mut rows = stmt.query_named(params)?;
|
stmt.query_row_named(params, f)
|
||||||
|
|
||||||
rows.get_expected_row().map(|r| f(&r))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method to execute a query that is expected to return a
|
/// Convenience method to execute a query that is expected to return a
|
||||||
@ -522,7 +564,7 @@ impl Connection {
|
|||||||
/// conn.query_row_and_then(
|
/// conn.query_row_and_then(
|
||||||
/// "SELECT value FROM preferences WHERE name='locale'",
|
/// "SELECT value FROM preferences WHERE name='locale'",
|
||||||
/// NO_PARAMS,
|
/// NO_PARAMS,
|
||||||
/// |row| row.get_checked(0),
|
/// |row| row.get(0),
|
||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@ -538,7 +580,7 @@ impl Connection {
|
|||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnOnce(&Row<'_, '_>) -> result::Result<T, E>,
|
F: FnOnce(&Row<'_>) -> result::Result<T, E>,
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
@ -694,7 +736,7 @@ impl Connection {
|
|||||||
/// Return the number of rows modified, inserted or deleted by the most
|
/// Return the number of rows modified, inserted or deleted by the most
|
||||||
/// recently completed INSERT, UPDATE or DELETE statement on the database
|
/// recently completed INSERT, UPDATE or DELETE statement on the database
|
||||||
/// connection.
|
/// connection.
|
||||||
pub fn changes(&self) -> usize {
|
fn changes(&self) -> usize {
|
||||||
self.db.borrow_mut().changes()
|
self.db.borrow_mut().changes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,7 +761,7 @@ impl fmt::Debug for Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
#[doc = "Flags for opening SQLite database connections."]
|
#[doc = "Flags for opening SQLite database connections."]
|
||||||
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -814,12 +856,12 @@ unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use self::tempdir::TempDir;
|
use super::*;
|
||||||
pub use super::*;
|
|
||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
pub use std::error::Error as StdError;
|
use fallible_iterator::FallibleIterator;
|
||||||
pub use std::fmt;
|
use std::error::Error as StdError;
|
||||||
use tempdir;
|
use std::fmt;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
// this function is never called, but is still type checked; in
|
// this function is never called, but is still type checked; in
|
||||||
// particular, calls with specific instantiations will require
|
// particular, calls with specific instantiations will require
|
||||||
@ -854,8 +896,8 @@ mod test {
|
|||||||
)
|
)
|
||||||
.expect("create temp db");
|
.expect("create temp db");
|
||||||
|
|
||||||
let mut db1 = Connection::open(&path).unwrap();
|
let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
|
||||||
let mut db2 = Connection::open(&path).unwrap();
|
let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
|
||||||
|
|
||||||
db1.busy_timeout(Duration::from_millis(0)).unwrap();
|
db1.busy_timeout(Duration::from_millis(0)).unwrap();
|
||||||
db2.busy_timeout(Duration::from_millis(0)).unwrap();
|
db2.busy_timeout(Duration::from_millis(0)).unwrap();
|
||||||
@ -865,9 +907,9 @@ mod test {
|
|||||||
let tx2 = db2.transaction().unwrap();
|
let tx2 = db2.transaction().unwrap();
|
||||||
|
|
||||||
// SELECT first makes sqlite lock with a shared lock
|
// SELECT first makes sqlite lock with a shared lock
|
||||||
tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| ())
|
tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| ())
|
tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap();
|
tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap();
|
||||||
@ -923,23 +965,24 @@ mod test {
|
|||||||
// statement first.
|
// statement first.
|
||||||
let raw_stmt = {
|
let raw_stmt = {
|
||||||
use super::str_to_cstring;
|
use super::str_to_cstring;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
let raw_db = db.db.borrow_mut().db;
|
let raw_db = db.db.borrow_mut().db;
|
||||||
let sql = "SELECT 1";
|
let sql = "SELECT 1";
|
||||||
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
let mut raw_stmt = MaybeUninit::uninit();
|
||||||
let rc = unsafe {
|
let rc = unsafe {
|
||||||
ffi::sqlite3_prepare_v2(
|
ffi::sqlite3_prepare_v2(
|
||||||
raw_db,
|
raw_db,
|
||||||
str_to_cstring(sql).unwrap().as_ptr(),
|
str_to_cstring(sql).unwrap().as_ptr(),
|
||||||
(sql.len() + 1) as c_int,
|
(sql.len() + 1) as c_int,
|
||||||
&mut raw_stmt,
|
raw_stmt.as_mut_ptr(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
assert_eq!(rc, ffi::SQLITE_OK);
|
assert_eq!(rc, ffi::SQLITE_OK);
|
||||||
|
let raw_stmt: *mut ffi::sqlite3_stmt = unsafe { raw_stmt.assume_init() };
|
||||||
raw_stmt
|
raw_stmt
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1081,8 +1124,8 @@ mod test {
|
|||||||
let mut rows = query.query(&[4i32]).unwrap();
|
let mut rows = query.query(&[4i32]).unwrap();
|
||||||
let mut v = Vec::<i32>::new();
|
let mut v = Vec::<i32>::new();
|
||||||
|
|
||||||
while let Some(row) = rows.next() {
|
while let Some(row) = rows.next().unwrap() {
|
||||||
v.push(row.unwrap().get(0));
|
v.push(row.get(0).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(v, [3i32, 2, 1]);
|
assert_eq!(v, [3i32, 2, 1]);
|
||||||
@ -1092,8 +1135,8 @@ mod test {
|
|||||||
let mut rows = query.query(&[3i32]).unwrap();
|
let mut rows = query.query(&[3i32]).unwrap();
|
||||||
let mut v = Vec::<i32>::new();
|
let mut v = Vec::<i32>::new();
|
||||||
|
|
||||||
while let Some(row) = rows.next() {
|
while let Some(row) = rows.next().unwrap() {
|
||||||
v.push(row.unwrap().get(0));
|
v.push(row.get(0).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(v, [2i32, 1]);
|
assert_eq!(v, [2i32, 1]);
|
||||||
@ -1114,8 +1157,9 @@ mod test {
|
|||||||
|
|
||||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||||
let results: Result<Vec<String>> = query
|
let results: Result<Vec<String>> = query
|
||||||
.query_map(NO_PARAMS, |row| row.get(1))
|
.query(NO_PARAMS)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.map(|row| row.get(1))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
assert_eq!(results.unwrap().concat(), "hello, world!");
|
assert_eq!(results.unwrap().concat(), "hello, world!");
|
||||||
@ -1146,7 +1190,7 @@ mod test {
|
|||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| ());
|
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| Ok(()));
|
||||||
|
|
||||||
assert!(bad_query_result.is_err());
|
assert!(bad_query_result.is_err());
|
||||||
}
|
}
|
||||||
@ -1235,7 +1279,7 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
assert!(!db.is_busy());
|
assert!(!db.is_busy());
|
||||||
let row = rows.next();
|
let row = rows.next().unwrap();
|
||||||
assert!(db.is_busy());
|
assert!(db.is_busy());
|
||||||
assert!(row.is_some());
|
assert!(row.is_some());
|
||||||
}
|
}
|
||||||
@ -1304,12 +1348,11 @@ mod test {
|
|||||||
.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")
|
.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let result: Result<Vec<i32>> = stmt.query_map(NO_PARAMS, |r| r.get(0)).unwrap().collect();
|
let result: Result<Vec<i32>> = stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).collect();
|
||||||
|
|
||||||
match result.unwrap_err() {
|
match result.unwrap_err() {
|
||||||
Error::SqliteFailure(err, _) => {
|
Error::SqliteFailure(err, _) => {
|
||||||
assert_eq!(err.code, ErrorCode::OperationInterrupted);
|
assert_eq!(err.code, ErrorCode::OperationInterrupted);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
err => {
|
err => {
|
||||||
panic!("Unexpected error {}", err);
|
panic!("Unexpected error {}", err);
|
||||||
@ -1347,8 +1390,7 @@ mod test {
|
|||||||
let mut query = db.prepare("SELECT i, x FROM foo").unwrap();
|
let mut query = db.prepare("SELECT i, x FROM foo").unwrap();
|
||||||
let mut rows = query.query(NO_PARAMS).unwrap();
|
let mut rows = query.query(NO_PARAMS).unwrap();
|
||||||
|
|
||||||
while let Some(res) = rows.next() {
|
while let Some(row) = rows.next().unwrap() {
|
||||||
let row = res.unwrap();
|
|
||||||
let i = row.get_raw(0).as_i64().unwrap();
|
let i = row.get_raw(0).as_i64().unwrap();
|
||||||
let expect = vals[i as usize];
|
let expect = vals[i as usize];
|
||||||
let x = row.get_raw("x").as_str().unwrap();
|
let x = row.get_raw("x").as_str().unwrap();
|
||||||
@ -1421,7 +1463,7 @@ mod test {
|
|||||||
|
|
||||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||||
let results: Result<Vec<String>> = query
|
let results: Result<Vec<String>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| row.get_checked(1))
|
.query_and_then(NO_PARAMS, |row| row.get(1))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -1442,17 +1484,17 @@ mod test {
|
|||||||
|
|
||||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||||
let bad_type: Result<Vec<f64>> = query
|
let bad_type: Result<Vec<f64>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| row.get_checked(1))
|
.query_and_then(NO_PARAMS, |row| row.get(1))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
Error::InvalidColumnType(_, _) => (),
|
Error::InvalidColumnType(_, _, _) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
let bad_idx: Result<Vec<String>> = query
|
let bad_idx: Result<Vec<String>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| row.get_checked(3))
|
.query_and_then(NO_PARAMS, |row| row.get(3))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -1476,9 +1518,7 @@ mod test {
|
|||||||
|
|
||||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||||
let results: CustomResult<Vec<String>> = query
|
let results: CustomResult<Vec<String>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| {
|
.query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
|
||||||
row.get_checked(1).map_err(CustomError::Sqlite)
|
|
||||||
})
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -1499,21 +1539,17 @@ mod test {
|
|||||||
|
|
||||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||||
let bad_type: CustomResult<Vec<f64>> = query
|
let bad_type: CustomResult<Vec<f64>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| {
|
.query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
|
||||||
row.get_checked(1).map_err(CustomError::Sqlite)
|
|
||||||
})
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
let bad_idx: CustomResult<Vec<String>> = query
|
let bad_idx: CustomResult<Vec<String>> = query
|
||||||
.query_and_then(NO_PARAMS, |row| {
|
.query_and_then(NO_PARAMS, |row| row.get(3).map_err(CustomError::Sqlite))
|
||||||
row.get_checked(3).map_err(CustomError::Sqlite)
|
|
||||||
})
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -1544,7 +1580,7 @@ mod test {
|
|||||||
|
|
||||||
let query = "SELECT x, y FROM foo ORDER BY x DESC";
|
let query = "SELECT x, y FROM foo ORDER BY x DESC";
|
||||||
let results: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
let results: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
||||||
row.get_checked(1).map_err(CustomError::Sqlite)
|
row.get(1).map_err(CustomError::Sqlite)
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(results.unwrap(), "hello");
|
assert_eq!(results.unwrap(), "hello");
|
||||||
@ -1561,16 +1597,16 @@ mod test {
|
|||||||
|
|
||||||
let query = "SELECT x, y FROM foo ORDER BY x DESC";
|
let query = "SELECT x, y FROM foo ORDER BY x DESC";
|
||||||
let bad_type: CustomResult<f64> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
let bad_type: CustomResult<f64> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
||||||
row.get_checked(1).map_err(CustomError::Sqlite)
|
row.get(1).map_err(CustomError::Sqlite)
|
||||||
});
|
});
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
let bad_idx: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
let bad_idx: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
|
||||||
row.get_checked(3).map_err(CustomError::Sqlite)
|
row.get(3).map_err(CustomError::Sqlite)
|
||||||
});
|
});
|
||||||
|
|
||||||
match bad_idx.unwrap_err() {
|
match bad_idx.unwrap_err() {
|
||||||
@ -1597,7 +1633,20 @@ mod test {
|
|||||||
db.execute_batch(sql).unwrap();
|
db.execute_batch(sql).unwrap();
|
||||||
|
|
||||||
db.query_row("SELECT * FROM foo", params![], |r| {
|
db.query_row("SELECT * FROM foo", params![], |r| {
|
||||||
assert_eq!(2, r.column_count())
|
assert_eq!(2, r.column_count());
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_dyn_box() {
|
||||||
|
let db = checked_memory_handle();
|
||||||
|
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
|
||||||
|
let b: Box<dyn ToSql> = Box::new(5);
|
||||||
|
db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap();
|
||||||
|
db.query_row("SELECT x FROM foo", params![], |r| {
|
||||||
|
assert_eq!(5, r.get_unwrap::<_, i32>(0));
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ pub struct LoadExtensionGuard<'conn> {
|
|||||||
conn: &'conn Connection,
|
conn: &'conn Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> LoadExtensionGuard<'conn> {
|
impl LoadExtensionGuard<'_> {
|
||||||
/// Attempt to enable loading extensions. Loading extensions will be
|
/// Attempt to enable loading extensions. Loading extensions will be
|
||||||
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
||||||
/// nested.
|
/// nested.
|
||||||
@ -28,7 +28,7 @@ impl<'conn> LoadExtensionGuard<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for LoadExtensionGuard<'conn> {
|
impl Drop for LoadExtensionGuard<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.conn.load_extension_disable();
|
self.conn.load_extension_disable();
|
||||||
}
|
}
|
||||||
|
433
src/pragma.rs
Normal file
433
src/pragma.rs
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
//! Pragma helpers
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::types::{ToSql, ToSqlOutput, ValueRef};
|
||||||
|
use crate::{Connection, DatabaseName, Result, Row, NO_PARAMS};
|
||||||
|
|
||||||
|
pub struct Sql {
|
||||||
|
buf: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sql {
|
||||||
|
pub fn new() -> Sql {
|
||||||
|
Sql { buf: String::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_pragma(
|
||||||
|
&mut self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.push_keyword("PRAGMA")?;
|
||||||
|
self.push_space();
|
||||||
|
if let Some(schema_name) = schema_name {
|
||||||
|
self.push_schema_name(schema_name);
|
||||||
|
self.push_dot();
|
||||||
|
}
|
||||||
|
self.push_keyword(pragma_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_keyword(&mut self, keyword: &str) -> Result<()> {
|
||||||
|
if !keyword.is_empty() && is_identifier(keyword) {
|
||||||
|
self.buf.push_str(keyword);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Invalid keyword \"{}\"", keyword)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_schema_name(&mut self, schema_name: DatabaseName<'_>) {
|
||||||
|
match schema_name {
|
||||||
|
DatabaseName::Main => self.buf.push_str("main"),
|
||||||
|
DatabaseName::Temp => self.buf.push_str("temp"),
|
||||||
|
DatabaseName::Attached(s) => self.push_identifier(s),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_identifier(&mut self, s: &str) {
|
||||||
|
if is_identifier(s) {
|
||||||
|
self.buf.push_str(s);
|
||||||
|
} else {
|
||||||
|
self.wrap_and_escape(s, '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> {
|
||||||
|
let value = value.to_sql()?;
|
||||||
|
let value = match value {
|
||||||
|
ToSqlOutput::Borrowed(v) => v,
|
||||||
|
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
||||||
|
#[cfg(feature = "blob")]
|
||||||
|
ToSqlOutput::ZeroBlob(_) => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
ToSqlOutput::Array(_) => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
ValueRef::Integer(i) => {
|
||||||
|
self.push_int(i);
|
||||||
|
}
|
||||||
|
ValueRef::Real(r) => {
|
||||||
|
self.push_real(r);
|
||||||
|
}
|
||||||
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s)?;
|
||||||
|
self.push_string_literal(s);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_string_literal(&mut self, s: &str) {
|
||||||
|
self.wrap_and_escape(s, '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_int(&mut self, i: i64) {
|
||||||
|
self.buf.push_str(&i.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_real(&mut self, f: f64) {
|
||||||
|
self.buf.push_str(&f.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_space(&mut self) {
|
||||||
|
self.buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_dot(&mut self) {
|
||||||
|
self.buf.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_equal_sign(&mut self) {
|
||||||
|
self.buf.push('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_brace(&mut self) {
|
||||||
|
self.buf.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_brace(&mut self) {
|
||||||
|
self.buf.push(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_and_escape(&mut self, s: &str, quote: char) {
|
||||||
|
self.buf.push(quote);
|
||||||
|
let chars = s.chars();
|
||||||
|
for ch in chars {
|
||||||
|
// escape `quote` by doubling it
|
||||||
|
if ch == quote {
|
||||||
|
self.buf.push(ch);
|
||||||
|
}
|
||||||
|
self.buf.push(ch)
|
||||||
|
}
|
||||||
|
self.buf.push(quote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Sql {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Query the current value of `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Some pragmas will return multiple rows/values which cannot be retrieved
|
||||||
|
/// with this method.
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT user_version FROM pragma_user_version;`
|
||||||
|
pub fn pragma_query_value<T, F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut query = Sql::new();
|
||||||
|
query.push_pragma(schema_name, pragma_name)?;
|
||||||
|
self.query_row(&query, NO_PARAMS, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current rows/values of `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT * FROM pragma_collation_list;`
|
||||||
|
pub fn pragma_query<F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<()>,
|
||||||
|
{
|
||||||
|
let mut query = Sql::new();
|
||||||
|
query.push_pragma(schema_name, pragma_name)?;
|
||||||
|
let mut stmt = self.prepare(&query)?;
|
||||||
|
let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
|
while let Some(result_row) = rows.next()? {
|
||||||
|
let row = result_row;
|
||||||
|
f(&row)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current value(s) of `pragma_name` associated to
|
||||||
|
/// `pragma_value`.
|
||||||
|
///
|
||||||
|
/// This method can be used with query-only pragmas which need an argument
|
||||||
|
/// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)
|
||||||
|
/// (e.g. `integrity_check`).
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT * FROM pragma_table_info(?);`
|
||||||
|
pub fn pragma<F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<()>,
|
||||||
|
{
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.open_brace();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
sql.close_brace();
|
||||||
|
let mut stmt = self.prepare(&sql)?;
|
||||||
|
let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
|
while let Some(result_row) = rows.next()? {
|
||||||
|
let row = result_row;
|
||||||
|
f(&row)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new value to `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Some pragmas will return the updated value which cannot be retrieved
|
||||||
|
/// with this method.
|
||||||
|
pub fn pragma_update(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.push_equal_sign();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
self.execute_batch(&sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new value to `pragma_name` and return the updated value.
|
||||||
|
///
|
||||||
|
/// Only few pragmas automatically return the updated value.
|
||||||
|
pub fn pragma_update_and_check<F, T>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.push_equal_sign();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
self.query_row(&sql, NO_PARAMS, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier(s: &str) -> bool {
|
||||||
|
let chars = s.char_indices();
|
||||||
|
for (i, ch) in chars {
|
||||||
|
if i == 0 {
|
||||||
|
if !is_identifier_start(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if !is_identifier_continue(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_start(c: char) -> bool {
|
||||||
|
(c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_continue(c: char) -> bool {
|
||||||
|
c == '$'
|
||||||
|
|| (c >= '0' && c <= '9')
|
||||||
|
|| (c >= 'A' && c <= 'Z')
|
||||||
|
|| c == '_'
|
||||||
|
|| (c >= 'a' && c <= 'z')
|
||||||
|
|| c > '\x7F'
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Sql;
|
||||||
|
use crate::pragma;
|
||||||
|
use crate::{Connection, DatabaseName};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_value() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let user_version: i32 = db
|
||||||
|
.pragma_query_value(None, "user_version", |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
fn pragma_func_query_value() {
|
||||||
|
use crate::NO_PARAMS;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let user_version: i32 = db
|
||||||
|
.query_row(
|
||||||
|
"SELECT user_version FROM pragma_user_version",
|
||||||
|
NO_PARAMS,
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_no_schema() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut user_version = -1;
|
||||||
|
db.pragma_query(None, "user_version", |row| {
|
||||||
|
user_version = row.get(0)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_with_schema() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut user_version = -1;
|
||||||
|
db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
|
||||||
|
user_version = row.get(0)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut columns = Vec::new();
|
||||||
|
db.pragma(None, "table_info", &"sqlite_master", |row| {
|
||||||
|
let column: String = row.get(1)?;
|
||||||
|
columns.push(column);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(5, columns.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
fn pragma_func() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
|
||||||
|
let mut columns = Vec::new();
|
||||||
|
let mut rows = table_info.query(&["sqlite_master"]).unwrap();
|
||||||
|
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
let row = row;
|
||||||
|
let column: String = row.get(1).unwrap();
|
||||||
|
columns.push(column);
|
||||||
|
}
|
||||||
|
assert_eq!(5, columns.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_update() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.pragma_update(None, "user_version", &1).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_update_and_check() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let journal_mode: String = db
|
||||||
|
.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!("off", &journal_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_identifier() {
|
||||||
|
assert!(pragma::is_identifier("full"));
|
||||||
|
assert!(pragma::is_identifier("r2d2"));
|
||||||
|
assert!(!pragma::is_identifier("sp ce"));
|
||||||
|
assert!(!pragma::is_identifier("semi;colon"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_quote() {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_schema_name(DatabaseName::Attached(r#"schema";--"#));
|
||||||
|
assert_eq!(r#""schema"";--""#, sql.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrap_and_escape() {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_string_literal("value'; --");
|
||||||
|
assert_eq!("'value''; --'", sql.as_str());
|
||||||
|
}
|
||||||
|
}
|
@ -26,8 +26,32 @@ impl RawStatement {
|
|||||||
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
|
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_name(&self, idx: usize) -> &CStr {
|
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
|
||||||
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
|
unsafe {
|
||||||
|
let decltype = ffi::sqlite3_column_decltype(self.0, idx as c_int);
|
||||||
|
if decltype.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(CStr::from_ptr(decltype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
|
||||||
|
let idx = idx as c_int;
|
||||||
|
if idx < 0 || idx >= self.column_count() as c_int {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let ptr = ffi::sqlite3_column_name(self.0, idx);
|
||||||
|
// If ptr is null here, it's an OOM, so there's probably nothing
|
||||||
|
// meaningful we can do. Just assert instead of returning None.
|
||||||
|
assert!(
|
||||||
|
!ptr.is_null(),
|
||||||
|
"Null pointer from sqlite3_column_name: Out of memory?"
|
||||||
|
);
|
||||||
|
Some(CStr::from_ptr(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&self) -> c_int {
|
pub fn step(&self) -> c_int {
|
||||||
@ -90,9 +114,9 @@ impl RawStatement {
|
|||||||
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `CStr` must be freed
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "bundled")]
|
||||||
pub fn expanded_sql(&self) -> Option<&CStr> {
|
pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
|
||||||
unsafe {
|
|
||||||
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
None
|
None
|
||||||
@ -100,7 +124,6 @@ impl RawStatement {
|
|||||||
Some(CStr::from_ptr(ptr))
|
Some(CStr::from_ptr(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
||||||
assert!(!self.0.is_null());
|
assert!(!self.0.is_null());
|
||||||
|
168
src/row.rs
168
src/row.rs
@ -1,4 +1,5 @@
|
|||||||
use std::marker::PhantomData;
|
use fallible_iterator::FallibleIterator;
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
use std::{convert, result};
|
use std::{convert, result};
|
||||||
|
|
||||||
use super::{Error, Result, Statement};
|
use super::{Error, Result, Statement};
|
||||||
@ -6,7 +7,8 @@ use crate::types::{FromSql, FromSqlError, ValueRef};
|
|||||||
|
|
||||||
/// An handle for the resulting rows of a query.
|
/// An handle for the resulting rows of a query.
|
||||||
pub struct Rows<'stmt> {
|
pub struct Rows<'stmt> {
|
||||||
stmt: Option<&'stmt Statement<'stmt>>,
|
pub(crate) stmt: Option<&'stmt Statement<'stmt>>,
|
||||||
|
row: Option<Row<'stmt>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Rows<'stmt> {
|
impl<'stmt> Rows<'stmt> {
|
||||||
@ -16,55 +18,73 @@ impl<'stmt> Rows<'stmt> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to get the next row from the query. Returns `Some(Ok(Row))` if
|
/// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if
|
||||||
/// there is another row, `Some(Err(...))` if there was an error
|
/// there is another row, `Err(...)` if there was an error
|
||||||
/// getting the next row, and `None` if all rows have been retrieved.
|
/// getting the next row, and `Ok(None)` if all rows have been retrieved.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// This interface is not compatible with Rust's `Iterator` trait, because
|
/// This interface is not compatible with Rust's `Iterator` trait, because
|
||||||
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
||||||
/// This is a "streaming iterator". For a more natural interface,
|
/// This is a fallible "streaming iterator". For a more natural interface,
|
||||||
/// consider using `query_map` or `query_and_then` instead, which
|
/// consider using `query_map` or `query_and_then` instead, which
|
||||||
/// return types that implement `Iterator`.
|
/// return types that implement `Iterator`.
|
||||||
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
||||||
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
|
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
|
||||||
self.stmt.and_then(|stmt| match stmt.step() {
|
self.advance()?;
|
||||||
Ok(true) => Some(Ok(Row {
|
Ok((*self).get())
|
||||||
stmt,
|
|
||||||
phantom: PhantomData,
|
|
||||||
})),
|
|
||||||
Ok(false) => {
|
|
||||||
self.reset();
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
self.reset();
|
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
|
||||||
Some(Err(err))
|
where
|
||||||
}
|
F: FnMut(&Row<'_>) -> Result<B>,
|
||||||
})
|
{
|
||||||
|
Map { rows: self, f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Rows<'stmt> {
|
impl<'stmt> Rows<'stmt> {
|
||||||
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
|
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
|
||||||
Rows { stmt: Some(stmt) }
|
Rows {
|
||||||
|
stmt: Some(stmt),
|
||||||
|
row: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
|
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
|
||||||
match self.next() {
|
match self.next()? {
|
||||||
Some(row) => row,
|
Some(row) => Ok(row),
|
||||||
None => Err(Error::QueryReturnedNoRows),
|
None => Err(Error::QueryReturnedNoRows),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Drop for Rows<'stmt> {
|
impl Drop for Rows<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Map<'stmt, F> {
|
||||||
|
rows: Rows<'stmt>,
|
||||||
|
f: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> FallibleIterator for Map<'_, F>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<B>,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
type Item = B;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<Option<B>> {
|
||||||
|
match self.rows.next()? {
|
||||||
|
Some(v) => Ok(Some((self.f)(v)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An iterator over the mapped resulting rows of a query.
|
/// An iterator over the mapped resulting rows of a query.
|
||||||
pub struct MappedRows<'stmt, F> {
|
pub struct MappedRows<'stmt, F> {
|
||||||
rows: Rows<'stmt>,
|
rows: Rows<'stmt>,
|
||||||
@ -73,16 +93,16 @@ pub struct MappedRows<'stmt, F> {
|
|||||||
|
|
||||||
impl<'stmt, T, F> MappedRows<'stmt, F>
|
impl<'stmt, T, F> MappedRows<'stmt, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row<'_, '_>) -> T,
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
||||||
MappedRows { rows, map: f }
|
MappedRows { rows, map: f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
|
impl<T, F> Iterator for MappedRows<'_, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row<'_, '_>) -> T,
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
type Item = Result<T>;
|
type Item = Result<T>;
|
||||||
|
|
||||||
@ -90,7 +110,8 @@ where
|
|||||||
let map = &mut self.map;
|
let map = &mut self.map;
|
||||||
self.rows
|
self.rows
|
||||||
.next()
|
.next()
|
||||||
.map(|row_result| row_result.map(|row| (map)(&row)))
|
.transpose()
|
||||||
|
.map(|row_result| row_result.and_then(|row| (map)(&row)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,17 +124,17 @@ pub struct AndThenRows<'stmt, F> {
|
|||||||
|
|
||||||
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
|
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
||||||
AndThenRows { rows, map: f }
|
AndThenRows { rows, map: f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
|
impl<T, E, F> Iterator for AndThenRows<'_, F>
|
||||||
where
|
where
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
type Item = result::Result<T, E>;
|
type Item = result::Result<T, E>;
|
||||||
|
|
||||||
@ -121,31 +142,65 @@ where
|
|||||||
let map = &mut self.map;
|
let map = &mut self.map;
|
||||||
self.rows
|
self.rows
|
||||||
.next()
|
.next()
|
||||||
|
.transpose()
|
||||||
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
|
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single result row of a query.
|
impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
|
||||||
pub struct Row<'a, 'stmt: 'a> {
|
type Error = Error;
|
||||||
stmt: &'stmt Statement<'stmt>,
|
type Item = Row<'stmt>;
|
||||||
phantom: PhantomData<&'a ()>,
|
|
||||||
|
fn advance(&mut self) -> Result<()> {
|
||||||
|
match self.stmt {
|
||||||
|
Some(ref stmt) => match stmt.step() {
|
||||||
|
Ok(true) => {
|
||||||
|
self.row = Some(Row { stmt });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
self.reset();
|
||||||
|
self.row = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.reset();
|
||||||
|
self.row = None;
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.row = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> Option<&Row<'stmt>> {
|
||||||
|
self.row.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'stmt> Row<'a, 'stmt> {
|
/// A single result row of a query.
|
||||||
|
pub struct Row<'stmt> {
|
||||||
|
pub(crate) stmt: &'stmt Statement<'stmt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Row<'stmt> {
|
||||||
/// Get the value of a particular column of the result row.
|
/// Get the value of a particular column of the result row.
|
||||||
///
|
///
|
||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Panics if calling `row.get_checked(idx)` would return an error,
|
/// Panics if calling `row.get(idx)` would return an error,
|
||||||
/// including:
|
/// including:
|
||||||
///
|
///
|
||||||
/// * If the underlying SQLite column type is not a valid type as a
|
/// * If the underlying SQLite column type is not a valid type as a source
|
||||||
/// source for `T`
|
/// for `T`
|
||||||
/// * If the underlying SQLite integral value is outside the range
|
/// * If the underlying SQLite integral value is outside the range
|
||||||
/// representable by `T`
|
/// representable by `T`
|
||||||
/// * If `idx` is outside the range of columns in the returned query
|
/// * If `idx` is outside the range of columns in the returned query
|
||||||
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
||||||
self.get_checked(idx).unwrap()
|
self.get(idx).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a particular column of the result row.
|
/// Get the value of a particular column of the result row.
|
||||||
@ -164,17 +219,25 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
/// If the result type is i128 (which requires the `i128_blob` feature to be
|
/// 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
|
/// enabled), and the underlying SQLite column is a blob whose size is not
|
||||||
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
||||||
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||||
let idx = idx.idx(self.stmt)?;
|
let idx = idx.idx(self.stmt)?;
|
||||||
let value = self.stmt.value_ref(idx);
|
let value = self.stmt.value_ref(idx);
|
||||||
FromSql::column_result(value).map_err(|err| match err {
|
FromSql::column_result(value).map_err(|err| match err {
|
||||||
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidType => {
|
||||||
|
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
|
||||||
|
}
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
FromSqlError::Other(err) => {
|
FromSqlError::Other(err) => {
|
||||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +247,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
||||||
/// it's lifetime. This means that while this method is completely safe,
|
/// it's lifetime. This means that while this method is completely safe,
|
||||||
/// it can be somewhat difficult to use, and most callers will be better
|
/// it can be somewhat difficult to use, and most callers will be better
|
||||||
/// served by `get` or `get_checked`.
|
/// served by `get` or `get`.
|
||||||
///
|
///
|
||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
@ -193,7 +256,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
///
|
///
|
||||||
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
||||||
/// name for this row.
|
/// name for this row.
|
||||||
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'a>> {
|
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
|
||||||
let idx = idx.idx(self.stmt)?;
|
let idx = idx.idx(self.stmt)?;
|
||||||
// Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
|
// Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
|
||||||
// returns) to `ValueRef<'a>` is needed because it's only valid until
|
// returns) to `ValueRef<'a>` is needed because it's only valid until
|
||||||
@ -208,7 +271,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
||||||
/// it's lifetime. This means that while this method is completely safe,
|
/// it's lifetime. This means that while this method is completely safe,
|
||||||
/// it can be difficult to use, and most callers will be better served by
|
/// it can be difficult to use, and most callers will be better served by
|
||||||
/// `get` or `get_checked`.
|
/// `get` or `get`.
|
||||||
///
|
///
|
||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
@ -217,14 +280,9 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
///
|
///
|
||||||
/// * If `idx` is outside the range of columns in the returned query.
|
/// * If `idx` is outside the range of columns in the returned query.
|
||||||
/// * If `idx` is not a valid column name for this row.
|
/// * If `idx` is not a valid column name for this row.
|
||||||
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'a> {
|
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
|
||||||
self.get_raw_checked(idx).unwrap()
|
self.get_raw_checked(idx).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the number of columns in the current row.
|
|
||||||
pub fn column_count(&self) -> usize {
|
|
||||||
self.stmt.column_count()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait implemented by types that can index into columns of a row.
|
/// A trait implemented by types that can index into columns of a row.
|
||||||
@ -245,7 +303,7 @@ impl RowIndex for usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RowIndex for &'a str {
|
impl RowIndex for &'_ str {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||||
stmt.column_index(*self)
|
stmt.column_index(*self)
|
||||||
|
110
src/session.rs
110
src/session.rs
@ -4,7 +4,7 @@
|
|||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::{c_char, c_int, c_uchar, c_void};
|
use std::os::raw::{c_char, c_int, c_uchar, c_void};
|
||||||
use std::panic::{catch_unwind, RefUnwindSafe};
|
use std::panic::{catch_unwind, RefUnwindSafe};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@ -28,20 +28,24 @@ pub struct Session<'conn> {
|
|||||||
filter: Option<Box<dyn Fn(&str) -> bool>>,
|
filter: Option<Box<dyn Fn(&str) -> bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Session<'conn> {
|
impl Session<'_> {
|
||||||
/// Create a new session object
|
/// Create a new session object
|
||||||
pub fn new(db: &'conn Connection) -> Result<Session<'conn>> {
|
pub fn new<'conn>(db: &'conn Connection) -> Result<Session<'conn>> {
|
||||||
Session::new_with_name(db, DatabaseName::Main)
|
Session::new_with_name(db, DatabaseName::Main)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new session object
|
/// Create a new session object
|
||||||
pub fn new_with_name(db: &'conn Connection, name: DatabaseName<'_>) -> Result<Session<'conn>> {
|
pub fn new_with_name<'conn>(
|
||||||
|
db: &'conn Connection,
|
||||||
|
name: DatabaseName<'_>,
|
||||||
|
) -> Result<Session<'conn>> {
|
||||||
let name = name.to_cstring()?;
|
let name = name.to_cstring()?;
|
||||||
|
|
||||||
let db = db.db.borrow_mut().db;
|
let db = db.db.borrow_mut().db;
|
||||||
|
|
||||||
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
|
let mut s = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
|
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), s.as_mut_ptr()) });
|
||||||
|
let s: *mut ffi::sqlite3_session = unsafe { s.assume_init() };
|
||||||
|
|
||||||
Ok(Session {
|
Ok(Session {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -62,7 +66,6 @@ impl<'conn> Session<'conn> {
|
|||||||
where
|
where
|
||||||
F: Fn(&str) -> bool + RefUnwindSafe,
|
F: Fn(&str) -> bool + RefUnwindSafe,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
let boxed_filter: *mut F = p_arg as *mut F;
|
let boxed_filter: *mut F = p_arg as *mut F;
|
||||||
@ -110,8 +113,9 @@ impl<'conn> Session<'conn> {
|
|||||||
/// Generate a Changeset
|
/// Generate a Changeset
|
||||||
pub fn changeset(&mut self) -> Result<Changeset> {
|
pub fn changeset(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
|
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +135,9 @@ impl<'conn> Session<'conn> {
|
|||||||
/// Generate a Patchset
|
/// Generate a Patchset
|
||||||
pub fn patchset(&mut self) -> Result<Changeset> {
|
pub fn patchset(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
|
let mut ps = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
|
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, ps.as_mut_ptr()) });
|
||||||
|
let ps: *mut c_void = unsafe { ps.assume_init() };
|
||||||
// TODO Validate: same struct
|
// TODO Validate: same struct
|
||||||
Ok(Changeset { cs: ps, n })
|
Ok(Changeset { cs: ps, n })
|
||||||
}
|
}
|
||||||
@ -155,9 +160,10 @@ impl<'conn> Session<'conn> {
|
|||||||
let from = from.to_cstring()?;
|
let from = from.to_cstring()?;
|
||||||
let table = str_to_cstring(table)?.as_ptr();
|
let table = str_to_cstring(table)?.as_ptr();
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
let mut errmsg = MaybeUninit::uninit();
|
||||||
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
|
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
|
||||||
if r != ffi::SQLITE_OK {
|
if r != ffi::SQLITE_OK {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
let message = errmsg_to_string(&*errmsg);
|
let message = errmsg_to_string(&*errmsg);
|
||||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
return Err(error_from_sqlite_code(r, Some(message)));
|
return Err(error_from_sqlite_code(r, Some(message)));
|
||||||
@ -196,7 +202,7 @@ impl<'conn> Session<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Drop for Session<'conn> {
|
impl Drop for Session<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.filter.is_some() {
|
if self.filter.is_some() {
|
||||||
self.table_filter(None::<fn(&str) -> bool>);
|
self.table_filter(None::<fn(&str) -> bool>);
|
||||||
@ -252,15 +258,17 @@ impl Changeset {
|
|||||||
/// Invert a changeset
|
/// Invert a changeset
|
||||||
pub fn invert(&self) -> Result<Changeset> {
|
pub fn invert(&self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
|
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an iterator to traverse a changeset
|
/// Create an iterator to traverse a changeset
|
||||||
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
||||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
let mut it = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
|
check!(unsafe { ffi::sqlite3changeset_start(it.as_mut_ptr(), self.n, self.cs) });
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
Ok(ChangesetIter {
|
Ok(ChangesetIter {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
it,
|
it,
|
||||||
@ -271,8 +279,11 @@ impl Changeset {
|
|||||||
/// Concatenate two changeset objects
|
/// Concatenate two changeset objects
|
||||||
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, cs.as_mut_ptr())
|
||||||
|
});
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,18 +303,19 @@ pub struct ChangesetIter<'changeset> {
|
|||||||
item: Option<ChangesetItem>,
|
item: Option<ChangesetItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'changeset> ChangesetIter<'changeset> {
|
impl ChangesetIter<'_> {
|
||||||
/// Create an iterator on `input`
|
/// Create an iterator on `input`
|
||||||
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||||
let input_ref = &input;
|
let input_ref = &input;
|
||||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
let mut it = MaybeUninit::uninit();
|
||||||
check!(unsafe {
|
check!(unsafe {
|
||||||
ffi::sqlite3changeset_start_strm(
|
ffi::sqlite3changeset_start_strm(
|
||||||
&mut it,
|
it.as_mut_ptr(),
|
||||||
Some(x_input),
|
Some(x_input),
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
Ok(ChangesetIter {
|
Ok(ChangesetIter {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
it,
|
it,
|
||||||
@ -312,7 +324,7 @@ impl<'changeset> ChangesetIter<'changeset> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'changeset> FallibleStreamingIterator for ChangesetIter<'changeset> {
|
impl FallibleStreamingIterator for ChangesetIter<'_> {
|
||||||
type Error = crate::error::Error;
|
type Error = crate::error::Error;
|
||||||
type Item = ChangesetItem;
|
type Item = ChangesetItem;
|
||||||
|
|
||||||
@ -343,7 +355,7 @@ pub struct Operation<'item> {
|
|||||||
indirect: bool,
|
indirect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'item> Operation<'item> {
|
impl Operation<'_> {
|
||||||
pub fn table_name(&self) -> &str {
|
pub fn table_name(&self) -> &str {
|
||||||
self.table_name
|
self.table_name
|
||||||
}
|
}
|
||||||
@ -361,7 +373,7 @@ impl<'item> Operation<'item> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'changeset> Drop for ChangesetIter<'changeset> {
|
impl Drop for ChangesetIter<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::sqlite3changeset_finalize(self.it);
|
ffi::sqlite3changeset_finalize(self.it);
|
||||||
@ -383,12 +395,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
||||||
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_conflict(
|
check!(ffi::sqlite3changeset_conflict(
|
||||||
self.it,
|
self.it,
|
||||||
col as i32,
|
col as i32,
|
||||||
&mut p_value
|
p_value.as_mut_ptr()
|
||||||
));
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,8 +424,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_INSERT`.
|
/// `SQLITE_INSERT`.
|
||||||
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
|
check!(ffi::sqlite3changeset_new(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,8 +441,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_UPDATE`.
|
/// `SQLITE_UPDATE`.
|
||||||
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
|
check!(ffi::sqlite3changeset_old(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,14 +458,15 @@ impl ChangesetItem {
|
|||||||
let mut code = 0;
|
let mut code = 0;
|
||||||
let mut indirect = 0;
|
let mut indirect = 0;
|
||||||
let tab = unsafe {
|
let tab = unsafe {
|
||||||
let mut pz_tab: *const c_char = mem::uninitialized();
|
let mut pz_tab = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_op(
|
check!(ffi::sqlite3changeset_op(
|
||||||
self.it,
|
self.it,
|
||||||
&mut pz_tab,
|
pz_tab.as_mut_ptr(),
|
||||||
&mut number_of_columns,
|
&mut number_of_columns,
|
||||||
&mut code,
|
&mut code,
|
||||||
&mut indirect
|
&mut indirect
|
||||||
));
|
));
|
||||||
|
let pz_tab: *const c_char = pz_tab.assume_init();
|
||||||
CStr::from_ptr(pz_tab)
|
CStr::from_ptr(pz_tab)
|
||||||
};
|
};
|
||||||
let table_name = tab.to_str()?;
|
let table_name = tab.to_str()?;
|
||||||
@ -458,12 +482,13 @@ impl ChangesetItem {
|
|||||||
pub fn pk(&self) -> Result<&[u8]> {
|
pub fn pk(&self) -> Result<&[u8]> {
|
||||||
let mut number_of_columns = 0;
|
let mut number_of_columns = 0;
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut pks: *mut c_uchar = mem::uninitialized();
|
let mut pks = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_pk(
|
check!(ffi::sqlite3changeset_pk(
|
||||||
self.it,
|
self.it,
|
||||||
&mut pks,
|
pks.as_mut_ptr(),
|
||||||
&mut number_of_columns
|
&mut number_of_columns
|
||||||
));
|
));
|
||||||
|
let pks: *mut c_uchar = pks.assume_init();
|
||||||
Ok(from_raw_parts(pks, number_of_columns as usize))
|
Ok(from_raw_parts(pks, number_of_columns as usize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,8 +502,9 @@ pub struct Changegroup {
|
|||||||
|
|
||||||
impl Changegroup {
|
impl Changegroup {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
|
let mut cg = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
|
check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
|
||||||
|
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
|
||||||
Ok(Changegroup { cg })
|
Ok(Changegroup { cg })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,8 +530,9 @@ impl Changegroup {
|
|||||||
/// Obtain a composite Changeset
|
/// Obtain a composite Changeset
|
||||||
pub fn output(&mut self) -> Result<Changeset> {
|
pub fn output(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut output: *mut c_void = unsafe { mem::uninitialized() };
|
let mut output = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
|
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, output.as_mut_ptr()) });
|
||||||
|
let output: *mut c_void = unsafe { output.assume_init() };
|
||||||
Ok(Changeset { cs: output, n })
|
Ok(Changeset { cs: output, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +672,6 @@ where
|
|||||||
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
||||||
@ -796,7 +822,7 @@ mod test {
|
|||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
db.apply(
|
db.apply(
|
||||||
|
181
src/statement.rs
181
src/statement.rs
@ -7,7 +7,7 @@ use std::slice::from_raw_parts;
|
|||||||
use std::{convert, fmt, mem, ptr, result, str};
|
use std::{convert, fmt, mem, ptr, result, str};
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::str_to_cstring;
|
use super::{len_as_c_int, str_for_sqlite, str_to_cstring};
|
||||||
use super::{
|
use super::{
|
||||||
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
||||||
};
|
};
|
||||||
@ -18,48 +18,10 @@ use crate::vtab::array::{free_array, ARRAY_TYPE};
|
|||||||
/// A prepared statement.
|
/// A prepared statement.
|
||||||
pub struct Statement<'conn> {
|
pub struct Statement<'conn> {
|
||||||
conn: &'conn Connection,
|
conn: &'conn Connection,
|
||||||
stmt: RawStatement,
|
pub(crate) stmt: RawStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Statement<'conn> {
|
impl Statement<'_> {
|
||||||
/// Get all the column names in the result set of the prepared statement.
|
|
||||||
pub fn column_names(&self) -> Vec<&str> {
|
|
||||||
let n = self.column_count();
|
|
||||||
let mut cols = Vec::with_capacity(n as usize);
|
|
||||||
for i in 0..n {
|
|
||||||
let slice = self.stmt.column_name(i);
|
|
||||||
let s = str::from_utf8(slice.to_bytes()).unwrap();
|
|
||||||
cols.push(s);
|
|
||||||
}
|
|
||||||
cols
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the number of columns in the result set returned by the prepared
|
|
||||||
/// statement.
|
|
||||||
pub fn column_count(&self) -> usize {
|
|
||||||
self.stmt.column_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the column index in the result set for a given column name.
|
|
||||||
///
|
|
||||||
/// If there is no AS clause then the name of the column is unspecified and
|
|
||||||
/// may change from one release of SQLite to the next.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return an `Error::InvalidColumnName` when there is no column with
|
|
||||||
/// the specified `name`.
|
|
||||||
pub fn column_index(&self, name: &str) -> Result<usize> {
|
|
||||||
let bytes = name.as_bytes();
|
|
||||||
let n = self.column_count();
|
|
||||||
for i in 0..n {
|
|
||||||
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) {
|
|
||||||
return Ok(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::InvalidColumnName(String::from(name)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute the prepared statement.
|
/// Execute the prepared statement.
|
||||||
///
|
///
|
||||||
/// On success, returns the number of rows that were changed or inserted or
|
/// On success, returns the number of rows that were changed or inserted or
|
||||||
@ -174,9 +136,8 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// let mut rows = stmt.query(NO_PARAMS)?;
|
/// let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
///
|
///
|
||||||
/// let mut names = Vec::new();
|
/// let mut names = Vec::new();
|
||||||
/// while let Some(result_row) = rows.next() {
|
/// while let Some(row) = rows.next()? {
|
||||||
/// let row = result_row?;
|
/// names.push(row.get(0)?);
|
||||||
/// names.push(row.get(0));
|
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// Ok(names)
|
/// Ok(names)
|
||||||
@ -209,7 +170,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// fn query(conn: &Connection) -> Result<()> {
|
/// fn query(conn: &Connection) -> Result<()> {
|
||||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
||||||
/// let mut rows = stmt.query_named(&[(":name", &"one")])?;
|
/// let mut rows = stmt.query_named(&[(":name", &"one")])?;
|
||||||
/// while let Some(row) = rows.next() {
|
/// while let Some(row) = rows.next()? {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
@ -224,7 +185,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// fn query(conn: &Connection) -> Result<()> {
|
/// fn query(conn: &Connection) -> Result<()> {
|
||||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
||||||
/// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
|
/// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
|
||||||
/// while let Some(row) = rows.next() {
|
/// while let Some(row) = rows.next()? {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
@ -234,7 +195,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_named<'a>(&'a mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'a>> {
|
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
|
||||||
self.check_readonly()?;
|
self.check_readonly()?;
|
||||||
self.bind_parameters_named(params)?;
|
self.bind_parameters_named(params)?;
|
||||||
Ok(Rows::new(self))
|
Ok(Rows::new(self))
|
||||||
@ -267,7 +228,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnMut(&Row<'_, '_>) -> T,
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let rows = self.query(params)?;
|
let rows = self.query(params)?;
|
||||||
Ok(MappedRows::new(rows, f))
|
Ok(MappedRows::new(rows, f))
|
||||||
@ -300,13 +261,13 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_map_named<'a, T, F>(
|
pub fn query_map_named<T, F>(
|
||||||
&'a mut self,
|
&mut self,
|
||||||
params: &[(&str, &dyn ToSql)],
|
params: &[(&str, &dyn ToSql)],
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<MappedRows<'a, F>>
|
) -> Result<MappedRows<'_, F>>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row<'_, '_>) -> T,
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let rows = self.query_named(params)?;
|
let rows = self.query_named(params)?;
|
||||||
Ok(MappedRows::new(rows, f))
|
Ok(MappedRows::new(rows, f))
|
||||||
@ -324,7 +285,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
let rows = self.query(params)?;
|
let rows = self.query(params)?;
|
||||||
Ok(AndThenRows::new(rows, f))
|
Ok(AndThenRows::new(rows, f))
|
||||||
@ -354,7 +315,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
|
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
|
||||||
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
|
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
|
||||||
/// let rows =
|
/// let rows =
|
||||||
/// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)))?;
|
/// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?;
|
||||||
///
|
///
|
||||||
/// let mut persons = Vec::new();
|
/// let mut persons = Vec::new();
|
||||||
/// for person_result in rows {
|
/// for person_result in rows {
|
||||||
@ -368,14 +329,14 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_and_then_named<'a, T, E, F>(
|
pub fn query_and_then_named<T, E, F>(
|
||||||
&'a mut self,
|
&mut self,
|
||||||
params: &[(&str, &dyn ToSql)],
|
params: &[(&str, &dyn ToSql)],
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<AndThenRows<'a, F>>
|
) -> Result<AndThenRows<'_, F>>
|
||||||
where
|
where
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
let rows = self.query_named(params)?;
|
let rows = self.query_named(params)?;
|
||||||
Ok(AndThenRows::new(rows, f))
|
Ok(AndThenRows::new(rows, f))
|
||||||
@ -389,7 +350,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
let mut rows = self.query(params)?;
|
let mut rows = self.query(params)?;
|
||||||
let exists = rows.next().is_some();
|
let exists = rows.next()?.is_some();
|
||||||
Ok(exists)
|
Ok(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,11 +371,34 @@ impl<'conn> Statement<'conn> {
|
|||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnOnce(&Row<'_, '_>) -> T,
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let mut rows = self.query(params)?;
|
let mut rows = self.query(params)?;
|
||||||
|
|
||||||
rows.get_expected_row().map(|r| f(&r))
|
rows.get_expected_row().and_then(|r| f(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut rows = self.query_named(params)?;
|
||||||
|
|
||||||
|
rows.get_expected_row().and_then(|r| f(&r))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the statement.
|
/// Consumes the statement.
|
||||||
@ -506,37 +490,19 @@ impl<'conn> Statement<'conn> {
|
|||||||
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
|
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
|
||||||
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
|
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
|
||||||
ValueRef::Text(s) => unsafe {
|
ValueRef::Text(s) => unsafe {
|
||||||
let length = s.len();
|
let (c_str, len, destructor) = str_for_sqlite(s)?;
|
||||||
if length > ::std::i32::MAX as usize {
|
ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
|
||||||
ffi::SQLITE_TOOBIG
|
|
||||||
} else {
|
|
||||||
let c_str = str_to_cstring(s)?;
|
|
||||||
let destructor = if length > 0 {
|
|
||||||
ffi::SQLITE_TRANSIENT()
|
|
||||||
} else {
|
|
||||||
ffi::SQLITE_STATIC()
|
|
||||||
};
|
|
||||||
ffi::sqlite3_bind_text(
|
|
||||||
ptr,
|
|
||||||
col as c_int,
|
|
||||||
c_str.as_ptr(),
|
|
||||||
length as c_int,
|
|
||||||
destructor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ValueRef::Blob(b) => unsafe {
|
ValueRef::Blob(b) => unsafe {
|
||||||
let length = b.len();
|
let length = len_as_c_int(b.len())?;
|
||||||
if length > ::std::i32::MAX as usize {
|
if length == 0 {
|
||||||
ffi::SQLITE_TOOBIG
|
|
||||||
} else if length == 0 {
|
|
||||||
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
|
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
|
||||||
} else {
|
} else {
|
||||||
ffi::sqlite3_bind_blob(
|
ffi::sqlite3_bind_blob(
|
||||||
ptr,
|
ptr,
|
||||||
col as c_int,
|
col as c_int,
|
||||||
b.as_ptr() as *const c_void,
|
b.as_ptr() as *const c_void,
|
||||||
length as c_int,
|
length,
|
||||||
ffi::SQLITE_TRANSIENT(),
|
ffi::SQLITE_TRANSIENT(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -584,11 +550,16 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// Returns a string containing the SQL text of prepared statement with
|
/// Returns a string containing the SQL text of prepared statement with
|
||||||
/// bound parameters expanded.
|
/// bound parameters expanded.
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "bundled")]
|
||||||
pub fn expanded_sql(&self) -> Option<&str> {
|
pub fn expanded_sql(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.stmt
|
match self.stmt.expanded_sql() {
|
||||||
.expanded_sql()
|
Some(s) => {
|
||||||
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
|
let sql = str::from_utf8_unchecked(s.to_bytes()).to_owned();
|
||||||
|
ffi::sqlite3_free(s.as_ptr() as *mut _);
|
||||||
|
Some(sql)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,7 +583,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Into<RawStatement> for Statement<'conn> {
|
impl Into<RawStatement> for Statement<'_> {
|
||||||
fn into(mut self) -> RawStatement {
|
fn into(mut self) -> RawStatement {
|
||||||
let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null());
|
let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null());
|
||||||
mem::swap(&mut stmt, &mut self.stmt);
|
mem::swap(&mut stmt, &mut self.stmt);
|
||||||
@ -620,7 +591,7 @@ impl<'conn> Into<RawStatement> for Statement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> fmt::Debug for Statement<'conn> {
|
impl fmt::Debug for Statement<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let sql = str::from_utf8(self.stmt.sql().to_bytes());
|
let sql = str::from_utf8(self.stmt.sql().to_bytes());
|
||||||
f.debug_struct("Statement")
|
f.debug_struct("Statement")
|
||||||
@ -631,14 +602,14 @@ impl<'conn> fmt::Debug for Statement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Drop for Statement<'conn> {
|
impl Drop for Statement<'_> {
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.finalize_();
|
self.finalize_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Statement<'conn> {
|
impl Statement<'_> {
|
||||||
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
|
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
|
||||||
Statement { conn, stmt }
|
Statement { conn, stmt }
|
||||||
}
|
}
|
||||||
@ -664,10 +635,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
CStr::from_ptr(text as *const c_char)
|
CStr::from_ptr(text as *const c_char)
|
||||||
};
|
};
|
||||||
|
|
||||||
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
|
let s = s.to_bytes();
|
||||||
let s = s
|
|
||||||
.to_str()
|
|
||||||
.expect("sqlite3_column_text returned invalid UTF-8");
|
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s)
|
||||||
}
|
}
|
||||||
ffi::SQLITE_BLOB => {
|
ffi::SQLITE_BLOB => {
|
||||||
@ -781,13 +749,12 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
stmt.execute_named(&[(":name", &"one")]).unwrap();
|
stmt.execute_named(&[(":name", &"one")]).unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT COUNT(*) FROM test WHERE name = :name")
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1i32,
|
1i32,
|
||||||
db.query_row_named::<i32, _>(
|
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
|
||||||
"SELECT COUNT(*) FROM test WHERE name = :name",
|
|
||||||
&[(":name", &"one")],
|
|
||||||
|r| r.get(0)
|
|
||||||
)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -806,8 +773,8 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
|
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
|
||||||
|
|
||||||
let id: i32 = rows.next().unwrap().unwrap().get(0);
|
let id: Result<i32> = rows.next().unwrap().unwrap().get(0);
|
||||||
assert_eq!(1, id);
|
assert_eq!(Ok(1), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -824,8 +791,8 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let mut rows = stmt
|
let mut rows = stmt
|
||||||
.query_map_named(&[(":name", &"one")], |row| {
|
.query_map_named(&[(":name", &"one")], |row| {
|
||||||
let id: i32 = row.get(0);
|
let id: Result<i32> = row.get(0);
|
||||||
2 * id
|
id.map(|i| 2 * i)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -848,7 +815,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let mut rows = stmt
|
let mut rows = stmt
|
||||||
.query_and_then_named(&[(":name", &"one")], |row| {
|
.query_and_then_named(&[(":name", &"one")], |row| {
|
||||||
let id: i32 = row.get(0);
|
let id: i32 = row.get(0)?;
|
||||||
if id == 1 {
|
if id == 1 {
|
||||||
Ok(id)
|
Ok(id)
|
||||||
} else {
|
} else {
|
||||||
@ -1021,7 +988,7 @@ mod test {
|
|||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
let stmt = db.prepare("SELECT ?").unwrap();
|
let stmt = db.prepare("SELECT ?").unwrap();
|
||||||
stmt.bind_parameter(&1, 1).unwrap();
|
stmt.bind_parameter(&1, 1).unwrap();
|
||||||
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
|
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -122,6 +122,7 @@ impl Connection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -140,13 +141,13 @@ mod test {
|
|||||||
let mut db = Connection::open_in_memory().unwrap();
|
let mut db = Connection::open_in_memory().unwrap();
|
||||||
db.trace(Some(tracer));
|
db.trace(Some(tracer));
|
||||||
{
|
{
|
||||||
let _ = db.query_row("SELECT ?", &[1i32], |_| {});
|
let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
|
||||||
let _ = db.query_row("SELECT ?", &["hello"], |_| {});
|
let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
|
||||||
}
|
}
|
||||||
db.trace(None);
|
db.trace(None);
|
||||||
{
|
{
|
||||||
let _ = db.query_row("SELECT ?", &[2i32], |_| {});
|
let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
|
||||||
let _ = db.query_row("SELECT ?", &["goodbye"], |_| {});
|
let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
||||||
|
@ -87,7 +87,7 @@ pub struct Savepoint<'conn> {
|
|||||||
committed: bool,
|
committed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Transaction<'conn> {
|
impl Transaction<'_> {
|
||||||
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
||||||
/// transactions.
|
/// transactions.
|
||||||
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
||||||
@ -195,7 +195,7 @@ impl<'conn> Transaction<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Deref for Transaction<'conn> {
|
impl Deref for Transaction<'_> {
|
||||||
type Target = Connection;
|
type Target = Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Connection {
|
fn deref(&self) -> &Connection {
|
||||||
@ -204,13 +204,13 @@ impl<'conn> Deref for Transaction<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Transaction<'conn> {
|
impl Drop for Transaction<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.finish_();
|
self.finish_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Savepoint<'conn> {
|
impl Savepoint<'_> {
|
||||||
fn with_depth_and_name<T: Into<String>>(
|
fn with_depth_and_name<T: Into<String>>(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
@ -308,7 +308,7 @@ impl<'conn> Savepoint<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Deref for Savepoint<'conn> {
|
impl Deref for Savepoint<'_> {
|
||||||
type Target = Connection;
|
type Target = Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Connection {
|
fn deref(&self) -> &Connection {
|
||||||
@ -317,7 +317,7 @@ impl<'conn> Deref for Savepoint<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Savepoint<'conn> {
|
impl Drop for Savepoint<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.finish_();
|
self.finish_();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
||||||
use chrono;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@ -54,7 +53,7 @@ impl FromSql for NaiveTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone =>
|
/// ISO 8601 combined date and time without timezone =>
|
||||||
/// "YYYY-MM-DD HH:MM:SS.SSS"
|
/// "YYYY-MM-DDTHH:MM:SS.SSS"
|
||||||
impl ToSql for NaiveDateTime {
|
impl ToSql for NaiveDateTime {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
||||||
@ -129,10 +128,8 @@ impl FromSql for DateTime<Local> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::chrono::{
|
|
||||||
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
|
||||||
};
|
|
||||||
use crate::{Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -18,6 +18,11 @@ pub enum FromSqlError {
|
|||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
InvalidI128Size(usize),
|
InvalidI128Size(usize),
|
||||||
|
|
||||||
|
/// Error returned when reading a `uuid` from a blob with a size
|
||||||
|
/// other than 16. Only available when the `uuid` feature is enabled.
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
InvalidUuidSize(usize),
|
||||||
|
|
||||||
/// An error case available for implementors of the `FromSql` trait.
|
/// An error case available for implementors of the `FromSql` trait.
|
||||||
Other(Box<dyn Error + Send + Sync>),
|
Other(Box<dyn Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
@ -29,6 +34,8 @@ impl PartialEq for FromSqlError {
|
|||||||
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
|
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
|
||||||
(_, _) => false,
|
(_, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,6 +50,10 @@ impl fmt::Display for FromSqlError {
|
|||||||
FromSqlError::InvalidI128Size(s) => {
|
FromSqlError::InvalidI128Size(s) => {
|
||||||
write!(f, "Cannot read 128bit value out of {} byte blob", s)
|
write!(f, "Cannot read 128bit value out of {} byte blob", s)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(s) => {
|
||||||
|
write!(f, "Cannot read UUID value out of {} byte blob", s)
|
||||||
|
}
|
||||||
FromSqlError::Other(ref err) => err.fmt(f),
|
FromSqlError::Other(ref err) => err.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,6 +66,8 @@ impl Error for FromSqlError {
|
|||||||
FromSqlError::OutOfRange(_) => "value out of range",
|
FromSqlError::OutOfRange(_) => "value out of range",
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value",
|
FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value",
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => "unexpected blob size for UUID value",
|
||||||
FromSqlError::Other(ref err) => err.description(),
|
FromSqlError::Other(ref err) => err.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,9 +77,7 @@ impl Error for FromSqlError {
|
|||||||
fn cause(&self) -> Option<&dyn Error> {
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
match *self {
|
match *self {
|
||||||
FromSqlError::Other(ref err) => err.cause(),
|
FromSqlError::Other(ref err) => err.cause(),
|
||||||
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
|
_ => None,
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
FromSqlError::InvalidI128Size(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +162,7 @@ impl FromSql for bool {
|
|||||||
|
|
||||||
impl FromSql for String {
|
impl FromSql for String {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_str().map(|s| s.to_string())
|
value.as_str().map(ToString::to_string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +187,19 @@ impl FromSql for i128 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
impl FromSql for uuid::Uuid {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
value
|
||||||
|
.as_blob()
|
||||||
|
.and_then(|bytes| {
|
||||||
|
uuid::Builder::from_slice(bytes)
|
||||||
|
.map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
|
||||||
|
})
|
||||||
|
.map(|mut builder| builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: FromSql> FromSql for Option<T> {
|
impl<T: FromSql> FromSql for Option<T> {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
@ -210,8 +234,7 @@ mod test {
|
|||||||
{
|
{
|
||||||
for n in out_of_range {
|
for n in out_of_range {
|
||||||
let err = db
|
let err = db
|
||||||
.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
|
.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
|
||||||
.unwrap()
|
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
match err {
|
match err {
|
||||||
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
||||||
|
117
src/types/mod.rs
117
src/types/mod.rs
@ -66,6 +66,8 @@ mod from_sql;
|
|||||||
mod serde_json;
|
mod serde_json;
|
||||||
mod time;
|
mod time;
|
||||||
mod to_sql;
|
mod to_sql;
|
||||||
|
#[cfg(feature = "url")]
|
||||||
|
mod url;
|
||||||
mod value;
|
mod value;
|
||||||
mod value_ref;
|
mod value_ref;
|
||||||
|
|
||||||
@ -207,27 +209,27 @@ mod test {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let row1 = rows.next().unwrap().unwrap();
|
let row1 = rows.next().unwrap().unwrap();
|
||||||
let s1: Option<String> = row1.get(0);
|
let s1: Option<String> = row1.get_unwrap(0);
|
||||||
let b1: Option<Vec<u8>> = row1.get(1);
|
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
|
||||||
assert_eq!(s.unwrap(), s1.unwrap());
|
assert_eq!(s.unwrap(), s1.unwrap());
|
||||||
assert!(b1.is_none());
|
assert!(b1.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let row2 = rows.next().unwrap().unwrap();
|
let row2 = rows.next().unwrap().unwrap();
|
||||||
let s2: Option<String> = row2.get(0);
|
let s2: Option<String> = row2.get_unwrap(0);
|
||||||
let b2: Option<Vec<u8>> = row2.get(1);
|
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
|
||||||
assert!(s2.is_none());
|
assert!(s2.is_none());
|
||||||
assert_eq!(b, b2);
|
assert_eq!(b, b2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cyclomatic_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn test_mismatched_types() {
|
fn test_mismatched_types() {
|
||||||
fn is_invalid_column_type(err: Error) -> bool {
|
fn is_invalid_column_type(err: Error) -> bool {
|
||||||
match err {
|
match err {
|
||||||
Error::InvalidColumnType(_, _) => true,
|
Error::InvalidColumnType(_, _, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,102 +248,94 @@ mod test {
|
|||||||
let row = rows.next().unwrap().unwrap();
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
|
||||||
// check the correct types come back as expected
|
// check the correct types come back as expected
|
||||||
assert_eq!(vec![1, 2], row.get_checked::<_, Vec<u8>>(0).unwrap());
|
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
|
||||||
assert_eq!("text", row.get_checked::<_, String>(1).unwrap());
|
assert_eq!("text", row.get::<_, String>(1).unwrap());
|
||||||
assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap());
|
assert_eq!(1, row.get::<_, c_int>(2).unwrap());
|
||||||
assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON);
|
assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
|
||||||
assert!(row.get_checked::<_, Option<c_int>>(4).unwrap().is_none());
|
assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
|
||||||
assert!(row.get_checked::<_, Option<c_double>>(4).unwrap().is_none());
|
assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
|
||||||
assert!(row.get_checked::<_, Option<String>>(4).unwrap().is_none());
|
assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
|
||||||
|
|
||||||
// check some invalid types
|
// check some invalid types
|
||||||
|
|
||||||
// 0 is actually a blob (Vec<u8>)
|
// 0 is actually a blob (Vec<u8>)
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_int>(0).err().unwrap()
|
row.get::<_, c_int>(0).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_int>(0).err().unwrap()
|
row.get::<_, c_int>(0).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, c_double>(0).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, i64>(0).err().unwrap()
|
row.get::<_, String>(0).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_double>(0).err().unwrap()
|
row.get::<_, time::Timespec>(0).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, String>(0).err().unwrap()
|
row.get::<_, Option<c_int>>(0).err().unwrap()
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, time::Timespec>(0).err().unwrap()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Option<c_int>>(0).err().unwrap()
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// 1 is actually a text (String)
|
// 1 is actually a text (String)
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_int>(1).err().unwrap()
|
row.get::<_, c_int>(1).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, c_double>(1).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, i64>(1).err().unwrap()
|
row.get::<_, Vec<u8>>(1).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_double>(1).err().unwrap()
|
row.get::<_, Option<c_int>>(1).err().unwrap()
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Vec<u8>>(1).err().unwrap()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Option<c_int>>(1).err().unwrap()
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// 2 is actually an integer
|
// 2 is actually an integer
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, String>(2).err().unwrap()
|
row.get::<_, String>(2).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, Vec<u8>>(2).err().unwrap()
|
row.get::<_, Vec<u8>>(2).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, Option<String>>(2).err().unwrap()
|
row.get::<_, Option<String>>(2).err().unwrap()
|
||||||
));
|
));
|
||||||
|
|
||||||
// 3 is actually a float (c_double)
|
// 3 is actually a float (c_double)
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_int>(3).err().unwrap()
|
row.get::<_, c_int>(3).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, String>(3).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, i64>(3).err().unwrap()
|
row.get::<_, Vec<u8>>(3).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, String>(3).err().unwrap()
|
row.get::<_, Option<c_int>>(3).err().unwrap()
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Vec<u8>>(3).err().unwrap()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Option<c_int>>(3).err().unwrap()
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// 4 is actually NULL
|
// 4 is actually NULL
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_int>(4).err().unwrap()
|
row.get::<_, c_int>(4).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, c_double>(4).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, i64>(4).err().unwrap()
|
row.get::<_, String>(4).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, c_double>(4).err().unwrap()
|
row.get::<_, Vec<u8>>(4).err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_invalid_column_type(
|
assert!(is_invalid_column_type(
|
||||||
row.get_checked::<_, String>(4).err().unwrap()
|
row.get::<_, time::Timespec>(4).err().unwrap()
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, Vec<u8>>(4).err().unwrap()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get_checked::<_, time::Timespec>(4).err().unwrap()
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,19 +354,16 @@ mod test {
|
|||||||
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
|
||||||
let row = rows.next().unwrap().unwrap();
|
let row = rows.next().unwrap().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
|
||||||
Value::Blob(vec![1, 2]),
|
|
||||||
row.get_checked::<_, Value>(0).unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::Text(String::from("text")),
|
Value::Text(String::from("text")),
|
||||||
row.get_checked::<_, Value>(1).unwrap()
|
row.get::<_, Value>(1).unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(Value::Integer(1), row.get_checked::<_, Value>(2).unwrap());
|
assert_eq!(Value::Integer(1), row.get::<_, Value>(2).unwrap());
|
||||||
match row.get_checked::<_, Value>(3).unwrap() {
|
match row.get::<_, Value>(3).unwrap() {
|
||||||
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
|
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
|
||||||
x => panic!("Invalid Value {:?}", x),
|
x => panic!("Invalid Value {:?}", x),
|
||||||
}
|
}
|
||||||
assert_eq!(Value::Null, row.get_checked::<_, Value>(4).unwrap());
|
assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use self::serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@ -17,7 +16,7 @@ impl ToSql for Value {
|
|||||||
impl FromSql for Value {
|
impl FromSql for Value {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Text(s) => serde_json::from_str(s),
|
ValueRef::Text(s) => serde_json::from_slice(s),
|
||||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||||
_ => return Err(FromSqlError::InvalidType),
|
_ => return Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
@ -27,9 +26,9 @@ impl FromSql for Value {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::serde_json;
|
|
||||||
use crate::types::ToSql;
|
use crate::types::ToSql;
|
||||||
use crate::{Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -36,8 +36,8 @@ impl FromSql for time::Timespec {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::time;
|
|
||||||
use crate::{Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use time;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -40,7 +40,7 @@ where
|
|||||||
// be converted into Values.
|
// be converted into Values.
|
||||||
macro_rules! from_value(
|
macro_rules! from_value(
|
||||||
($t:ty) => (
|
($t:ty) => (
|
||||||
impl<'a> From<$t> for ToSqlOutput<'a> {
|
impl From<$t> for ToSqlOutput<'_> {
|
||||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -65,7 +65,10 @@ from_value!(Vec<u8>);
|
|||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
from_value!(i128);
|
from_value!(i128);
|
||||||
|
|
||||||
impl<'a> ToSql for ToSqlOutput<'a> {
|
#[cfg(feature = "uuid")]
|
||||||
|
from_value!(uuid::Uuid);
|
||||||
|
|
||||||
|
impl ToSql for ToSqlOutput<'_> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(match *self {
|
Ok(match *self {
|
||||||
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
||||||
@ -84,6 +87,13 @@ pub trait ToSql {
|
|||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToSql for Box<dyn ToSql> {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
let derefed: &dyn ToSql = &**self;
|
||||||
|
derefed.to_sql()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We should be able to use a generic impl like this:
|
// We should be able to use a generic impl like this:
|
||||||
//
|
//
|
||||||
// impl<T: Copy> ToSql for T where T: Into<Value> {
|
// impl<T: Copy> ToSql for T where T: Into<Value> {
|
||||||
@ -121,7 +131,10 @@ to_sql_self!(f64);
|
|||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
to_sql_self!(i128);
|
to_sql_self!(i128);
|
||||||
|
|
||||||
impl<'a, T: ?Sized> ToSql for &'a T
|
#[cfg(feature = "uuid")]
|
||||||
|
to_sql_self!(uuid::Uuid);
|
||||||
|
|
||||||
|
impl<T: ?Sized> ToSql for &'_ T
|
||||||
where
|
where
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
@ -169,7 +182,7 @@ impl<T: ToSql> ToSql for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ToSql for Cow<'a, str> {
|
impl ToSql for Cow<'_, str> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self.as_ref()))
|
Ok(ToSqlOutput::from(self.as_ref()))
|
||||||
}
|
}
|
||||||
@ -229,7 +242,7 @@ mod test {
|
|||||||
|
|
||||||
let res = stmt
|
let res = stmt
|
||||||
.query_map(NO_PARAMS, |row| {
|
.query_map(NO_PARAMS, |row| {
|
||||||
(row.get::<_, i128>(0), row.get::<_, String>(1))
|
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
@ -248,4 +261,36 @@ mod test {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
#[test]
|
||||||
|
fn test_uuid() {
|
||||||
|
use crate::{params, Connection};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO foo (id, label) VALUES (?, ?)",
|
||||||
|
params![id, "target"],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT id, label FROM foo WHERE id = ?")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut rows = stmt.query(params![id]).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
|
||||||
|
let found_id: Uuid = row.get_unwrap(0);
|
||||||
|
let found_label: String = row.get_unwrap(1);
|
||||||
|
|
||||||
|
assert_eq!(found_id, id);
|
||||||
|
assert_eq!(found_label, "target");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
81
src/types/url.rs
Normal file
81
src/types/url.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
//! `ToSql` and `FromSql` implementation for [`url::Url`].
|
||||||
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
|
use crate::Result;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Serialize `Url` to text.
|
||||||
|
impl ToSql for Url {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(ToSqlOutput::from(self.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize text to `Url`.
|
||||||
|
impl FromSql for Url {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
match value {
|
||||||
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
|
||||||
|
Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
|
_ => Err(FromSqlError::InvalidType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{params, Connection, Error, Result};
|
||||||
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
fn checked_memory_handle() -> Connection {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")
|
||||||
|
.unwrap();
|
||||||
|
db
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(db: &Connection, id: i64) -> Result<Url> {
|
||||||
|
db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sql_url() {
|
||||||
|
let db = &checked_memory_handle();
|
||||||
|
|
||||||
|
let url0 = Url::parse("http://www.example1.com").unwrap();
|
||||||
|
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
|
||||||
|
let url2 = "http://www.example2.com/👌";
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
|
||||||
|
// also insert a non-hex encoded url (which might be present if it was
|
||||||
|
// inserted separately)
|
||||||
|
params![url0, url1, url2, "illegal"],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(get_url(db, 0).unwrap(), url0);
|
||||||
|
|
||||||
|
assert_eq!(get_url(db, 1).unwrap(), url1);
|
||||||
|
|
||||||
|
// Should successfully read it, even though it wasn't inserted as an
|
||||||
|
// escaped url.
|
||||||
|
let out_url2: Url = get_url(db, 2).unwrap();
|
||||||
|
assert_eq!(out_url2, Url::parse(url2).unwrap());
|
||||||
|
|
||||||
|
// Make sure the conversion error comes through correctly.
|
||||||
|
let err = get_url(db, 3).unwrap_err();
|
||||||
|
match err {
|
||||||
|
Error::FromSqlConversionFailure(_, _, e) => {
|
||||||
|
assert_eq!(
|
||||||
|
*e.downcast::<ParseError>().unwrap(),
|
||||||
|
ParseError::RelativeUrlWithoutBase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Expected conversion failure, got {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,13 @@ impl From<i128> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
impl From<uuid::Uuid> for Value {
|
||||||
|
fn from(id: uuid::Uuid) -> Value {
|
||||||
|
Value::Blob(id.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! from_i64(
|
macro_rules! from_i64(
|
||||||
($t:ty) => (
|
($t:ty) => (
|
||||||
impl From<$t> for Value {
|
impl From<$t> for Value {
|
||||||
|
@ -14,12 +14,12 @@ pub enum ValueRef<'a> {
|
|||||||
/// The value is a floating point number.
|
/// The value is a floating point number.
|
||||||
Real(f64),
|
Real(f64),
|
||||||
/// The value is a text string.
|
/// The value is a text string.
|
||||||
Text(&'a str),
|
Text(&'a [u8]),
|
||||||
/// The value is a blob of data
|
/// The value is a blob of data
|
||||||
Blob(&'a [u8]),
|
Blob(&'a [u8]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ValueRef<'a> {
|
impl ValueRef<'_> {
|
||||||
pub fn data_type(&self) -> Type {
|
pub fn data_type(&self) -> Type {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Null => Type::Null,
|
ValueRef::Null => Type::Null,
|
||||||
@ -54,7 +54,9 @@ impl<'a> ValueRef<'a> {
|
|||||||
/// `Err(Error::InvalidColumnType)`.
|
/// `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Text(t) => Ok(t),
|
ValueRef::Text(t) => {
|
||||||
|
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
_ => Err(FromSqlError::InvalidType),
|
_ => Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,13 +71,16 @@ impl<'a> ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<ValueRef<'a>> for Value {
|
impl From<ValueRef<'_>> for Value {
|
||||||
fn from(borrowed: ValueRef<'_>) -> Value {
|
fn from(borrowed: ValueRef<'_>) -> Value {
|
||||||
match borrowed {
|
match borrowed {
|
||||||
ValueRef::Null => Value::Null,
|
ValueRef::Null => Value::Null,
|
||||||
ValueRef::Integer(i) => Value::Integer(i),
|
ValueRef::Integer(i) => Value::Integer(i),
|
||||||
ValueRef::Real(r) => Value::Real(r),
|
ValueRef::Real(r) => Value::Real(r),
|
||||||
ValueRef::Text(s) => Value::Text(s.to_string()),
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s).expect("invalid UTF-8");
|
||||||
|
Value::Text(s.to_string())
|
||||||
|
}
|
||||||
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +88,7 @@ impl<'a> From<ValueRef<'a>> for Value {
|
|||||||
|
|
||||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||||
fn from(s: &str) -> ValueRef<'_> {
|
fn from(s: &str) -> ValueRef<'_> {
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +104,7 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
|
|||||||
Value::Null => ValueRef::Null,
|
Value::Null => ValueRef::Null,
|
||||||
Value::Integer(i) => ValueRef::Integer(i),
|
Value::Integer(i) => ValueRef::Integer(i),
|
||||||
Value::Real(r) => ValueRef::Real(r),
|
Value::Real(r) => ValueRef::Real(r),
|
||||||
Value::Text(ref s) => ValueRef::Text(s),
|
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
|
||||||
Value::Blob(ref b) => ValueRef::Blob(b),
|
Value::Blob(ref b) => ValueRef::Blob(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,10 +130,7 @@ impl<'a> ValueRef<'a> {
|
|||||||
);
|
);
|
||||||
let s = CStr::from_ptr(text as *const c_char);
|
let s = CStr::from_ptr(text as *const c_char);
|
||||||
|
|
||||||
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
let s = s.to_bytes();
|
||||||
let s = s
|
|
||||||
.to_str()
|
|
||||||
.expect("sqlite3_value_text returned invalid UTF-8");
|
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s)
|
||||||
}
|
}
|
||||||
ffi::SQLITE_BLOB => {
|
ffi::SQLITE_BLOB => {
|
||||||
|
@ -35,7 +35,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("csv", &CSV_MODULE, aux)
|
conn.create_module("csv", &CSV_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +347,7 @@ impl From<csv::Error> for Error {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::vtab::csvtab;
|
use crate::vtab::csvtab;
|
||||||
use crate::{Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_csv_module() {
|
fn test_csv_module() {
|
||||||
@ -363,8 +364,9 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ids: Result<Vec<i32>> = s
|
let ids: Result<Vec<i32>> = s
|
||||||
.query_map(NO_PARAMS, |row| row.get::<_, i32>(0))
|
.query(NO_PARAMS)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.map(|row| row.get::<_, i32>(0))
|
||||||
.collect();
|
.collect();
|
||||||
let sum = ids.unwrap().iter().sum::<i32>();
|
let sum = ids.unwrap().iter().sum::<i32>();
|
||||||
assert_eq!(sum, 15);
|
assert_eq!(sum, 15);
|
||||||
@ -389,7 +391,7 @@ mod test {
|
|||||||
|
|
||||||
let mut rows = s.query(NO_PARAMS).unwrap();
|
let mut rows = s.query(NO_PARAMS).unwrap();
|
||||||
let row = rows.next().unwrap().unwrap();
|
let row = rows.next().unwrap().unwrap();
|
||||||
assert_eq!(row.get::<_, i32>(0), 2);
|
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
|
||||||
}
|
}
|
||||||
db.execute_batch("DROP TABLE vtab").unwrap();
|
db.execute_batch("DROP TABLE vtab").unwrap();
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ pub struct Module<T: VTab> {
|
|||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: VTab> Send for Module<T> {}
|
||||||
unsafe impl<T: VTab> Sync for Module<T> {}
|
unsafe impl<T: VTab> Sync for Module<T> {}
|
||||||
|
|
||||||
/// Create a read-only virtual table implementation.
|
/// Create a read-only virtual table implementation.
|
||||||
@ -233,7 +234,7 @@ pub trait CreateVTab: VTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
#[doc = "Index constraint operator."]
|
#[doc = "Index constraint operator."]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
|
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
|
||||||
@ -337,7 +338,7 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
|
|||||||
/// WHERE clause constraint
|
/// WHERE clause constraint
|
||||||
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
|
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
|
||||||
|
|
||||||
impl<'a> IndexConstraint<'a> {
|
impl IndexConstraint<'_> {
|
||||||
/// Column constrained. -1 for ROWID
|
/// Column constrained. -1 for ROWID
|
||||||
pub fn column(&self) -> c_int {
|
pub fn column(&self) -> c_int {
|
||||||
self.0.iColumn
|
self.0.iColumn
|
||||||
@ -357,7 +358,7 @@ impl<'a> IndexConstraint<'a> {
|
|||||||
/// Information about what parameters to pass to `VTabCursor.filter`.
|
/// Information about what parameters to pass to `VTabCursor.filter`.
|
||||||
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
|
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
|
||||||
|
|
||||||
impl<'a> IndexConstraintUsage<'a> {
|
impl IndexConstraintUsage<'_> {
|
||||||
/// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
|
/// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
|
||||||
pub fn set_argv_index(&mut self, argv_index: c_int) {
|
pub fn set_argv_index(&mut self, argv_index: c_int) {
|
||||||
self.0.argvIndex = argv_index;
|
self.0.argvIndex = argv_index;
|
||||||
@ -388,7 +389,7 @@ impl<'a> Iterator for OrderByIter<'a> {
|
|||||||
/// A column of the ORDER BY clause.
|
/// 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_info_sqlite3_index_orderby);
|
||||||
|
|
||||||
impl<'a> OrderBy<'a> {
|
impl OrderBy<'_> {
|
||||||
/// Column number
|
/// Column number
|
||||||
pub fn column(&self) -> c_int {
|
pub fn column(&self) -> c_int {
|
||||||
self.0.iColumn
|
self.0.iColumn
|
||||||
@ -453,7 +454,7 @@ pub struct Values<'a> {
|
|||||||
args: &'a [*mut ffi::sqlite3_value],
|
args: &'a [*mut ffi::sqlite3_value],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Values<'a> {
|
impl Values<'_> {
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.args.len()
|
self.args.len()
|
||||||
}
|
}
|
||||||
@ -472,7 +473,13 @@ impl<'a> Values<'a> {
|
|||||||
}
|
}
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,7 +648,6 @@ where
|
|||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
let mut conn = VTabConnection(db);
|
let mut conn = VTabConnection(db);
|
||||||
let aux = aux as *mut T::Aux;
|
let aux = aux as *mut T::Aux;
|
||||||
@ -695,7 +701,6 @@ where
|
|||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
let mut conn = VTabConnection(db);
|
let mut conn = VTabConnection(db);
|
||||||
let aux = aux as *mut T::Aux;
|
let aux = aux as *mut T::Aux;
|
||||||
@ -848,7 +853,6 @@ where
|
|||||||
C: VTabCursor,
|
C: VTabCursor,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
let idx_name = if idx_str.is_null() {
|
let idx_name = if idx_str.is_null() {
|
||||||
None
|
None
|
||||||
@ -981,7 +985,7 @@ fn mprintf(err_msg: &str) -> *mut c_char {
|
|||||||
pub mod array;
|
pub mod array;
|
||||||
#[cfg(feature = "csvtab")]
|
#[cfg(feature = "csvtab")]
|
||||||
pub mod csvtab;
|
pub mod csvtab;
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "series")]
|
||||||
pub mod series; // SQLite >= 3.9.0
|
pub mod series; // SQLite >= 3.9.0
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -18,7 +18,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("generate_series", &SERIES_MODULE, aux)
|
conn.create_module("generate_series", &SERIES_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
|
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ const SERIES_COLUMN_START: c_int = 1;
|
|||||||
const SERIES_COLUMN_STOP: c_int = 2;
|
const SERIES_COLUMN_STOP: c_int = 2;
|
||||||
const SERIES_COLUMN_STEP: c_int = 3;
|
const SERIES_COLUMN_STEP: c_int = 3;
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct QueryPlanFlags: ::std::os::raw::c_int {
|
struct QueryPlanFlags: ::std::os::raw::c_int {
|
||||||
// start = $value -- constraint exists
|
// start = $value -- constraint exists
|
||||||
|
@ -2,12 +2,9 @@
|
|||||||
//! function affects SQLite process-wide and so is not safe to run as a normal
|
//! function affects SQLite process-wide and so is not safe to run as a normal
|
||||||
//! #[test] in the library.
|
//! #[test] in the library.
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user