mirror of
https://github.com/isar/rusqlite.git
synced 2026-03-29 09:39:11 +08:00
Merge branch 'master' into sub_type
This commit is contained in:
@@ -336,7 +336,7 @@ mod test {
|
||||
backup.step(-1)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
|
||||
assert_eq!(42, the_answer);
|
||||
|
||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
||||
@@ -346,7 +346,7 @@ mod test {
|
||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
|
||||
assert_eq!(42 + 43, the_answer);
|
||||
Ok(())
|
||||
}
|
||||
@@ -368,7 +368,7 @@ mod test {
|
||||
backup.step(-1)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
|
||||
assert_eq!(42, the_answer);
|
||||
|
||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
||||
@@ -379,7 +379,7 @@ mod test {
|
||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
|
||||
assert_eq!(42 + 43, the_answer);
|
||||
Ok(())
|
||||
}
|
||||
@@ -406,7 +406,7 @@ mod test {
|
||||
backup.step(-1)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
|
||||
assert_eq!(42, the_answer);
|
||||
|
||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
||||
@@ -421,7 +421,7 @@ mod test {
|
||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
||||
}
|
||||
|
||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
|
||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
|
||||
assert_eq!(42 + 43, the_answer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
//! // Insert another BLOB, this time using a parameter passed in from
|
||||
//! // rust (potentially with a dynamic size).
|
||||
//! db.execute(
|
||||
//! "INSERT INTO test_table (content) VALUES (?)",
|
||||
//! "INSERT INTO test_table (content) VALUES (?1)",
|
||||
//! [ZeroBlob(64)],
|
||||
//! )?;
|
||||
//!
|
||||
@@ -175,7 +175,7 @@
|
||||
//! // Insert another blob, this time using a parameter passed in from
|
||||
//! // rust (potentially with a dynamic size).
|
||||
//! db.execute(
|
||||
//! "INSERT INTO test_table (content) VALUES (?)",
|
||||
//! "INSERT INTO test_table (content) VALUES (?1)",
|
||||
//! [ZeroBlob(64)],
|
||||
//! )?;
|
||||
//!
|
||||
@@ -274,7 +274,6 @@ impl Blob<'_> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
use std::convert::TryInto;
|
||||
self.size().try_into().unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ mod test {
|
||||
));
|
||||
blob.raw_read_at_exact(&mut s2, 5).unwrap_err();
|
||||
|
||||
let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
|
||||
let end_pos = blob.stream_position().unwrap();
|
||||
assert_eq!(end_pos, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///! Busy handler (when the database is locked)
|
||||
//! Busy handler (when the database is locked)
|
||||
use std::convert::TryInto;
|
||||
use std::mem;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Prepared statements cache for faster execution.
|
||||
|
||||
use crate::raw_statement::RawStatement;
|
||||
use crate::{Connection, Result, Statement};
|
||||
use crate::{Connection, PrepFlags, Result, Statement};
|
||||
use hashlink::LruCache;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -17,13 +17,13 @@ impl Connection {
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn insert_new_people(conn: &Connection) -> Result<()> {
|
||||
/// {
|
||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
|
||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
|
||||
/// stmt.execute(["Joe Smith"])?;
|
||||
/// }
|
||||
/// {
|
||||
/// // This will return the same underlying SQLite statement handle without
|
||||
/// // having to prepare it again.
|
||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
|
||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
|
||||
/// stmt.execute(["Bob Jones"])?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
@@ -144,7 +144,7 @@ impl StatementCache {
|
||||
let mut cache = self.0.borrow_mut();
|
||||
let stmt = match cache.remove(trimmed) {
|
||||
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
|
||||
None => conn.prepare(trimmed),
|
||||
None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT),
|
||||
};
|
||||
stmt.map(|mut stmt| {
|
||||
stmt.stmt.set_statement_cache_key(trimmed);
|
||||
|
||||
@@ -203,7 +203,7 @@ mod test {
|
||||
assert_eq!(ty, Type::Integer);
|
||||
}
|
||||
e => {
|
||||
panic!("Unexpected error type: {:?}", e);
|
||||
panic!("Unexpected error type: {e:?}");
|
||||
}
|
||||
}
|
||||
match row.get::<_, String>("y").unwrap_err() {
|
||||
@@ -213,7 +213,7 @@ mod test {
|
||||
assert_eq!(ty, Type::Null);
|
||||
}
|
||||
e => {
|
||||
panic!("Unexpected error type: {:?}", e);
|
||||
panic!("Unexpected error type: {e:?}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -61,6 +61,13 @@ pub enum DbConfig {
|
||||
/// sqlite_master tables) are untainted by malicious content.
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0
|
||||
/// Sets or clears a flag that enables collection of the
|
||||
/// sqlite3_stmt_scanstatus_v2() statistics
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018, // 3.42.0
|
||||
/// Changes the default order in which tables and indexes are scanned
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019, // 3.42.0
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
|
||||
40
src/error.rs
40
src/error.rs
@@ -252,11 +252,7 @@ impl fmt::Display for Error {
|
||||
),
|
||||
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||
if i != UNKNOWN_COLUMN {
|
||||
write!(
|
||||
f,
|
||||
"Conversion error from type {} at index: {}, {}",
|
||||
t, i, err
|
||||
)
|
||||
write!(f, "Conversion error from type {t} at index: {i}, {err}")
|
||||
} else {
|
||||
err.fmt(f)
|
||||
}
|
||||
@@ -278,15 +274,12 @@ impl fmt::Display for Error {
|
||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
|
||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
|
||||
Error::InvalidColumnType(i, ref name, ref t) => write!(
|
||||
f,
|
||||
"Invalid column type {} at index: {}, name: {}",
|
||||
t, i, name
|
||||
),
|
||||
Error::InvalidColumnType(i, ref name, ref t) => {
|
||||
write!(f, "Invalid column type {t} at index: {i}, name: {name}")
|
||||
}
|
||||
Error::InvalidParameterCount(i1, n1) => write!(
|
||||
f,
|
||||
"Wrong number of parameters passed to query. Got {}, needed {}",
|
||||
i1, n1
|
||||
"Wrong number of parameters passed to query. Got {i1}, needed {n1}"
|
||||
),
|
||||
Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
|
||||
|
||||
@@ -393,7 +386,6 @@ impl Error {
|
||||
|
||||
#[cold]
|
||||
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
||||
// TODO sqlite3_error_offset // 3.38.0, #1130
|
||||
Error::SqliteFailure(ffi::Error::new(code), message)
|
||||
}
|
||||
|
||||
@@ -443,3 +435,25 @@ pub fn check(code: c_int) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform Rust error to SQLite error (message and code).
|
||||
/// # Safety
|
||||
/// This function is unsafe because it uses raw pointer
|
||||
pub unsafe fn to_sqlite_error(
|
||||
e: &Error,
|
||||
err_msg: *mut *mut std::os::raw::c_char,
|
||||
) -> std::os::raw::c_int {
|
||||
use crate::util::alloc;
|
||||
match e {
|
||||
Error::SqliteFailure(err, s) => {
|
||||
if let Some(s) = s {
|
||||
*err_msg = alloc(s);
|
||||
}
|
||||
err.extended_code
|
||||
}
|
||||
err => {
|
||||
*err_msg = alloc(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,21 +71,13 @@ use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
|
||||
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
|
||||
|
||||
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
|
||||
// Extended constraint error codes were added in SQLite 3.7.16. We don't have
|
||||
// an explicit feature check for that, and this doesn't really warrant one.
|
||||
// We'll use the extended code if we're on the bundled version (since it's
|
||||
// at least 3.17.0) and the normal constraint error code if not.
|
||||
fn constraint_error_code() -> i32 {
|
||||
ffi::SQLITE_CONSTRAINT_FUNCTION
|
||||
}
|
||||
|
||||
if let Error::SqliteFailure(ref err, ref s) = *err {
|
||||
ffi::sqlite3_result_error_code(ctx, err.extended_code);
|
||||
if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
|
||||
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
||||
}
|
||||
} else {
|
||||
ffi::sqlite3_result_error_code(ctx, constraint_error_code());
|
||||
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
|
||||
if let Ok(cstr) = str_to_cstring(&err.to_string()) {
|
||||
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
||||
}
|
||||
@@ -833,9 +825,9 @@ mod test {
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
half,
|
||||
)?;
|
||||
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
|
||||
let result: f64 = db.one_column("SELECT half(6)")?;
|
||||
|
||||
assert!((3f64 - result?).abs() < f64::EPSILON);
|
||||
assert!((3f64 - result).abs() < f64::EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -848,11 +840,11 @@ mod test {
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
half,
|
||||
)?;
|
||||
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
|
||||
assert!((3f64 - result?).abs() < f64::EPSILON);
|
||||
let result: f64 = db.one_column("SELECT half(6)")?;
|
||||
assert!((3f64 - result).abs() < f64::EPSILON);
|
||||
|
||||
db.remove_function("half", 1)?;
|
||||
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
|
||||
let result: Result<f64> = db.one_column("SELECT half(6)");
|
||||
result.unwrap_err();
|
||||
Ok(())
|
||||
}
|
||||
@@ -860,7 +852,7 @@ mod test {
|
||||
// This implementation of a regexp scalar function uses SQLite's auxiliary data
|
||||
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
|
||||
// expression multiple times within one query.
|
||||
fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<(bool, SubType)> {
|
||||
fn regexp_with_auxiliary(ctx: &Context<'_>) -> Result<(bool, SubType)> {
|
||||
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
let regexp: std::sync::Arc<Regex> = ctx
|
||||
@@ -881,7 +873,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_regexp_with_auxilliary() -> Result<()> {
|
||||
fn test_function_regexp_with_auxiliary() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch(
|
||||
"BEGIN;
|
||||
@@ -895,21 +887,17 @@ mod test {
|
||||
"regexp",
|
||||
2,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
regexp_with_auxilliary,
|
||||
regexp_with_auxiliary,
|
||||
)?;
|
||||
|
||||
let result: Result<bool> =
|
||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
|
||||
let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?;
|
||||
|
||||
assert!(result?);
|
||||
assert!(result);
|
||||
|
||||
let result: Result<i64> = db.query_row(
|
||||
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
);
|
||||
let result: i64 =
|
||||
db.one_column("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1")?;
|
||||
|
||||
assert_eq!(2, result?);
|
||||
assert_eq!(2, result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -937,7 +925,7 @@ mod test {
|
||||
("onetwo", "SELECT my_concat('one', 'two')"),
|
||||
("abc", "SELECT my_concat('a', 'b', 'c')"),
|
||||
] {
|
||||
let result: String = db.query_row(query, [], |r| r.get(0))?;
|
||||
let result: String = db.one_column(query)?;
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
Ok(())
|
||||
@@ -956,11 +944,8 @@ mod test {
|
||||
Ok((true, None))
|
||||
})?;
|
||||
|
||||
let res: bool = db.query_row(
|
||||
"SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
let res: bool =
|
||||
db.one_column("SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)")?;
|
||||
// Doesn't actually matter, we'll assert in the function if there's a problem.
|
||||
assert!(res);
|
||||
Ok(())
|
||||
@@ -1015,11 +1000,11 @@ mod test {
|
||||
|
||||
// sum should return NULL when given no columns (contrast with count below)
|
||||
let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
|
||||
let result: Option<i64> = db.query_row(no_result, [], |r| r.get(0))?;
|
||||
let result: Option<i64> = db.one_column(no_result)?;
|
||||
assert!(result.is_none());
|
||||
|
||||
let single_sum = "SELECT my_sum(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
|
||||
let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
|
||||
let result: i64 = db.one_column(single_sum)?;
|
||||
assert_eq!(4, result);
|
||||
|
||||
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
|
||||
@@ -1041,11 +1026,11 @@ mod test {
|
||||
|
||||
// count should return 0 when given no columns (contrast with sum above)
|
||||
let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
|
||||
let result: i64 = db.query_row(no_result, [], |r| r.get(0))?;
|
||||
let result: i64 = db.one_column(no_result)?;
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let single_sum = "SELECT my_count(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
|
||||
let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
|
||||
let result: i64 = db.one_column(single_sum)?;
|
||||
assert_eq!(2, result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ unsafe fn free_boxed_hook<F>(p: *mut c_void) {
|
||||
|
||||
unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
|
||||
expect_optional_utf8(p_str, description)
|
||||
.unwrap_or_else(|| panic!("received empty {}", description))
|
||||
.unwrap_or_else(|| panic!("received empty {description}"))
|
||||
}
|
||||
|
||||
unsafe fn expect_optional_utf8<'a>(
|
||||
@@ -667,7 +667,7 @@ unsafe fn expect_optional_utf8<'a>(
|
||||
return None;
|
||||
}
|
||||
std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
|
||||
.unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
|
||||
.unwrap_or_else(|_| panic!("received non-utf8 string as {description}"))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::ffi;
|
||||
use super::str_for_sqlite;
|
||||
use super::{Connection, InterruptHandle, OpenFlags, Result};
|
||||
use super::{Connection, InterruptHandle, OpenFlags, PrepFlags, Result};
|
||||
use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error};
|
||||
use crate::raw_statement::RawStatement;
|
||||
use crate::statement::Statement;
|
||||
@@ -69,13 +69,13 @@ impl InnerConnection {
|
||||
|
||||
// Replicate the check for sane open flags from SQLite, because the check in
|
||||
// SQLite itself wasn't added until version 3.7.3.
|
||||
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
|
||||
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
|
||||
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits(), 0x02);
|
||||
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits(), 0x04);
|
||||
debug_assert_eq!(
|
||||
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
|
||||
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits(),
|
||||
0x40
|
||||
);
|
||||
if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
|
||||
if (1 << (flags.bits() & 0x7)) & 0x46 == 0 {
|
||||
return Err(Error::SqliteFailure(
|
||||
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||
None,
|
||||
@@ -218,33 +218,24 @@ impl InnerConnection {
|
||||
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
|
||||
}
|
||||
|
||||
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
||||
let mut c_stmt = ptr::null_mut();
|
||||
pub fn prepare<'a>(
|
||||
&mut self,
|
||||
conn: &'a Connection,
|
||||
sql: &str,
|
||||
flags: PrepFlags,
|
||||
) -> Result<Statement<'a>> {
|
||||
let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
|
||||
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||
let mut c_tail = ptr::null();
|
||||
let mut c_tail: *const c_char = ptr::null();
|
||||
// TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728
|
||||
#[cfg(not(feature = "unlock_notify"))]
|
||||
let r = unsafe {
|
||||
ffi::sqlite3_prepare_v2(
|
||||
self.db(),
|
||||
c_sql,
|
||||
len,
|
||||
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
|
||||
&mut c_tail as *mut *const c_char,
|
||||
)
|
||||
};
|
||||
let r = unsafe { self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail) };
|
||||
#[cfg(feature = "unlock_notify")]
|
||||
let r = unsafe {
|
||||
use crate::unlock_notify;
|
||||
let mut rc;
|
||||
loop {
|
||||
rc = ffi::sqlite3_prepare_v2(
|
||||
self.db(),
|
||||
c_sql,
|
||||
len,
|
||||
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
|
||||
&mut c_tail as *mut *const c_char,
|
||||
);
|
||||
rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail);
|
||||
if !unlock_notify::is_locked(self.db, rc) {
|
||||
break;
|
||||
}
|
||||
@@ -261,8 +252,6 @@ impl InnerConnection {
|
||||
}
|
||||
// If the input text contains no SQL (if the input is an empty string or a
|
||||
// comment) then *ppStmt is set to NULL.
|
||||
let c_stmt: *mut ffi::sqlite3_stmt = c_stmt;
|
||||
let c_tail: *const c_char = c_tail;
|
||||
let tail = if c_tail.is_null() {
|
||||
0
|
||||
} else {
|
||||
@@ -278,6 +267,32 @@ impl InnerConnection {
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(feature = "modern_sqlite"))]
|
||||
unsafe fn prepare_(
|
||||
&self,
|
||||
z_sql: *const c_char,
|
||||
n_byte: c_int,
|
||||
_: PrepFlags,
|
||||
pp_stmt: *mut *mut ffi::sqlite3_stmt,
|
||||
pz_tail: *mut *const c_char,
|
||||
) -> c_int {
|
||||
ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
unsafe fn prepare_(
|
||||
&self,
|
||||
z_sql: *const c_char,
|
||||
n_byte: c_int,
|
||||
flags: PrepFlags,
|
||||
pp_stmt: *mut *mut ffi::sqlite3_stmt,
|
||||
pz_tail: *mut *const c_char,
|
||||
) -> c_int {
|
||||
ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn changes(&self) -> u64 {
|
||||
#[cfg(not(feature = "modern_sqlite"))]
|
||||
@@ -371,15 +386,7 @@ impl Drop for InnerConnection {
|
||||
#[allow(unused_must_use)]
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
use std::thread::panicking;
|
||||
|
||||
if let Err(e) = self.close() {
|
||||
if panicking() {
|
||||
eprintln!("Error while closing SQLite connection: {e:?}");
|
||||
} else {
|
||||
panic!("Error while closing SQLite connection: {:?}", e);
|
||||
}
|
||||
}
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +397,7 @@ pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// threading mode checks are not necessary (and do not work) on target
|
||||
// platforms that do not have threading (such as webassembly)
|
||||
#[cfg(any(target_arch = "wasm32"))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
276
src/lib.rs
276
src/lib.rs
@@ -62,7 +62,7 @@ use std::ffi::{CStr, CString};
|
||||
use std::fmt;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::str;
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -75,7 +75,7 @@ use crate::types::ValueRef;
|
||||
|
||||
pub use crate::cache::CachedStatement;
|
||||
pub use crate::column::Column;
|
||||
pub use crate::error::Error;
|
||||
pub use crate::error::{to_sqlite_error, Error};
|
||||
pub use crate::ffi::ErrorCode;
|
||||
#[cfg(feature = "load_extension")]
|
||||
pub use crate::load_extension_guard::LoadExtensionGuard;
|
||||
@@ -119,6 +119,9 @@ mod params;
|
||||
mod pragma;
|
||||
mod raw_statement;
|
||||
mod row;
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
pub mod serialize;
|
||||
#[cfg(feature = "session")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
|
||||
pub mod session;
|
||||
@@ -140,13 +143,6 @@ pub(crate) use util::SmallCString;
|
||||
|
||||
// Number of cached prepared statements we'll hold on to.
|
||||
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
|
||||
/// To be used when your statement has no [parameter][sqlite-varparam].
|
||||
///
|
||||
/// [sqlite-varparam]: https://sqlite.org/lang_expr.html#varparam
|
||||
///
|
||||
/// This is deprecated in favor of using an empty array literal.
|
||||
#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
|
||||
pub const NO_PARAMS: &[&dyn ToSql] = &[];
|
||||
|
||||
/// A macro making it more convenient to longer lists of
|
||||
/// parameters as a `&[&dyn ToSql]`.
|
||||
@@ -330,7 +326,6 @@ impl DatabaseName<'_> {
|
||||
pub struct Connection {
|
||||
db: RefCell<InnerConnection>,
|
||||
cache: StatementCache,
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Connection {}
|
||||
@@ -427,7 +422,6 @@ impl Connection {
|
||||
InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Connection {
|
||||
db: RefCell::new(db),
|
||||
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
|
||||
path: Some(path.as_ref().to_path_buf()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -452,7 +446,6 @@ impl Connection {
|
||||
InnerConnection::open_with_flags(&c_path, flags, Some(&c_vfs)).map(|db| Connection {
|
||||
db: RefCell::new(db),
|
||||
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
|
||||
path: Some(path.as_ref().to_path_buf()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -534,7 +527,7 @@ impl Connection {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection};
|
||||
/// fn update_rows(conn: &Connection) {
|
||||
/// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
|
||||
/// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
|
||||
/// Ok(updated) => println!("{} rows were updated", updated),
|
||||
/// Err(err) => println!("update failed: {}", err),
|
||||
/// }
|
||||
@@ -580,12 +573,23 @@ impl Connection {
|
||||
|
||||
/// Returns the path to the database file, if one exists and is known.
|
||||
///
|
||||
/// Returns `Some("")` for a temporary or in-memory database.
|
||||
///
|
||||
/// Note that in some cases [PRAGMA
|
||||
/// database_list](https://sqlite.org/pragma.html#pragma_database_list) is
|
||||
/// likely to be more robust.
|
||||
#[inline]
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
self.path.as_deref()
|
||||
pub fn path(&self) -> Option<&str> {
|
||||
unsafe {
|
||||
let db = self.handle();
|
||||
let db_name = DatabaseName::Main.as_cstring().unwrap();
|
||||
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
|
||||
if db_filename.is_null() {
|
||||
None
|
||||
} else {
|
||||
CStr::from_ptr(db_filename).to_str().ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to free as much heap memory as possible from the database
|
||||
@@ -598,26 +602,6 @@ impl Connection {
|
||||
self.db.borrow_mut().release_memory()
|
||||
}
|
||||
|
||||
/// Convenience method to prepare and execute a single SQL statement with
|
||||
/// named parameter(s).
|
||||
///
|
||||
/// On success, returns the number of rows that were changed or inserted or
|
||||
/// deleted (via `sqlite3_changes`).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[deprecated = "You can use `execute` with named params now."]
|
||||
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||
// This function itself is deprecated, so it's fine
|
||||
#![allow(deprecated)]
|
||||
self.prepare(sql).and_then(|mut stmt| {
|
||||
stmt.check_no_tail()
|
||||
.and_then(|_| stmt.execute_named(params))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the SQLite rowid of the most recent successful INSERT.
|
||||
///
|
||||
/// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under
|
||||
@@ -665,26 +649,10 @@ impl Connection {
|
||||
stmt.query_row(params, f)
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query with named parameter(s) that is
|
||||
/// expected to return a single row.
|
||||
///
|
||||
/// If the query returns more than one row, all rows except the first are
|
||||
/// ignored.
|
||||
///
|
||||
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
|
||||
/// query truly is optional, you can call `.optional()` on the result of
|
||||
/// this to get a `Result<Option<T>>`.
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[deprecated = "You can use `query_row` with named params now."]
|
||||
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
self.query_row(sql, params, f)
|
||||
// https://sqlite.org/tclsqlite.html#onecolumn
|
||||
#[cfg(test)]
|
||||
pub(crate) fn one_column<T: crate::types::FromSql>(&self, sql: &str) -> Result<T> {
|
||||
self.query_row(sql, [], |r| r.get(0))
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query that is expected to return a
|
||||
@@ -733,7 +701,7 @@ impl Connection {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn insert_new_people(conn: &Connection) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?)")?;
|
||||
/// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
|
||||
/// stmt.execute(["Joe Smith"])?;
|
||||
/// stmt.execute(["Bob Jones"])?;
|
||||
/// Ok(())
|
||||
@@ -746,7 +714,18 @@ impl Connection {
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[inline]
|
||||
pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
|
||||
self.db.borrow_mut().prepare(self, sql)
|
||||
self.prepare_with_flags(sql, PrepFlags::default())
|
||||
}
|
||||
|
||||
/// Prepare a SQL statement for execution.
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[inline]
|
||||
pub fn prepare_with_flags(&self, sql: &str, flags: PrepFlags) -> Result<Statement<'_>> {
|
||||
self.db.borrow_mut().prepare(self, sql, flags)
|
||||
}
|
||||
|
||||
/// Close the SQLite connection.
|
||||
@@ -915,12 +894,31 @@ impl Connection {
|
||||
/// This function is unsafe because improper use may impact the Connection.
|
||||
#[inline]
|
||||
pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result<Connection> {
|
||||
let db_path = db_filename(db);
|
||||
let db = InnerConnection::new(db, false);
|
||||
Ok(Connection {
|
||||
db: RefCell::new(db),
|
||||
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
|
||||
path: db_path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Connection` from a raw owned handle.
|
||||
///
|
||||
/// The returned connection will attempt to close the inner connection
|
||||
/// when dropped/closed. This function should only be called on connections
|
||||
/// owned by the caller.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because improper use may impact the Connection.
|
||||
/// In particular, it should only be called on connections created
|
||||
/// and owned by the caller, e.g. as a result of calling
|
||||
/// ffi::sqlite3_open().
|
||||
#[inline]
|
||||
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
|
||||
let db = InnerConnection::new(db, true);
|
||||
Ok(Connection {
|
||||
db: RefCell::new(db),
|
||||
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -973,7 +971,7 @@ impl Connection {
|
||||
impl fmt::Debug for Connection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Connection")
|
||||
.field("path", &self.path)
|
||||
.field("path", &self.path())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1041,11 +1039,12 @@ impl<'conn> Iterator for Batch<'conn, '_> {
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for opening SQLite database connections. See
|
||||
/// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
|
||||
/// [sqlite3_open_v2](https://www.sqlite.org/c3ref/open.html) for details.
|
||||
///
|
||||
/// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
|
||||
/// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
|
||||
/// some discussion about these flags.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct OpenFlags: ::std::os::raw::c_int {
|
||||
/// The database is opened in read-only mode.
|
||||
@@ -1122,6 +1121,19 @@ impl Default for OpenFlags {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Prepare flags. See
|
||||
/// [sqlite3_prepare_v3](https://sqlite.org/c3ref/c_prepare_normalize.html) for details.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct PrepFlags: ::std::os::raw::c_uint {
|
||||
/// A hint to the query planner that the prepared statement will be retained for a long time and probably reused many times.
|
||||
const SQLITE_PREPARE_PERSISTENT = 0x01;
|
||||
/// Causes the SQL compiler to return an error (error code SQLITE_ERROR) if the statement uses any virtual tables.
|
||||
const SQLITE_PREPARE_NO_VTAB = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
/// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or
|
||||
/// later. If you are running against a SQLite older than that, rusqlite
|
||||
/// attempts to ensure safety by performing configuration and initialization of
|
||||
@@ -1164,16 +1176,6 @@ impl InterruptHandle {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
||||
let db_name = DatabaseName::Main.as_cstring().unwrap();
|
||||
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
|
||||
if db_filename.is_null() {
|
||||
None
|
||||
} else {
|
||||
CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(doctest)]
|
||||
doc_comment::doctest!("../README.md");
|
||||
|
||||
@@ -1188,13 +1190,21 @@ mod test {
|
||||
// this function is never called, but is still type checked; in
|
||||
// particular, calls with specific instantiations will require
|
||||
// that those types are `Send`.
|
||||
#[allow(dead_code, unconditional_recursion)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
unconditional_recursion,
|
||||
clippy::extra_unused_type_parameters
|
||||
)]
|
||||
fn ensure_send<T: Send>() {
|
||||
ensure_send::<Connection>();
|
||||
ensure_send::<InterruptHandle>();
|
||||
}
|
||||
|
||||
#[allow(dead_code, unconditional_recursion)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
unconditional_recursion,
|
||||
clippy::extra_unused_type_parameters
|
||||
)]
|
||||
fn ensure_sync<T: Sync>() {
|
||||
ensure_sync::<InterruptHandle>();
|
||||
}
|
||||
@@ -1261,9 +1271,9 @@ mod test {
|
||||
|
||||
let path_string = path.to_str().unwrap();
|
||||
let db = Connection::open(path_string)?;
|
||||
let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
|
||||
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
|
||||
|
||||
assert_eq!(42i64, the_answer?);
|
||||
assert_eq!(42i64, the_answer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1275,6 +1285,21 @@ mod test {
|
||||
db.close().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path() -> Result<()> {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let db = Connection::open("")?;
|
||||
assert_eq!(Some(""), db.path());
|
||||
let db = Connection::open_in_memory()?;
|
||||
assert_eq!(Some(""), db.path());
|
||||
let db = Connection::open("file:dummy.db?mode=memory&cache=shared")?;
|
||||
assert_eq!(Some(""), db.path());
|
||||
let path = tmp.path().join("file.db");
|
||||
let db = Connection::open(path)?;
|
||||
assert!(db.path().map(|p| p.ends_with("file.db")).unwrap_or(false));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_failure() {
|
||||
let filename = "no_such_file.db";
|
||||
@@ -1285,9 +1310,7 @@ mod test {
|
||||
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
|
||||
assert!(
|
||||
msg.contains(filename),
|
||||
"error message '{}' does not contain '{}'",
|
||||
msg,
|
||||
filename
|
||||
"error message '{msg}' does not contain '{filename}'"
|
||||
);
|
||||
} else {
|
||||
panic!("SqliteFailure expected");
|
||||
@@ -1318,9 +1341,9 @@ mod test {
|
||||
}
|
||||
|
||||
let db = Connection::open(&db_path)?;
|
||||
let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
|
||||
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
|
||||
|
||||
assert_eq!(42i64, the_answer?);
|
||||
assert_eq!(42i64, the_answer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1400,13 +1423,10 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
|
||||
|
||||
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
|
||||
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [2i32])?);
|
||||
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [1i32])?);
|
||||
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [2i32])?);
|
||||
|
||||
assert_eq!(
|
||||
3i32,
|
||||
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(3i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1414,12 +1434,11 @@ mod test {
|
||||
#[cfg(feature = "extra_check")]
|
||||
fn test_execute_select() {
|
||||
let db = checked_memory_handle();
|
||||
let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
|
||||
let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
Error::ExecuteReturnedResults,
|
||||
"Unexpected error: {}",
|
||||
err
|
||||
"Unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1435,7 +1454,7 @@ mod test {
|
||||
.unwrap_err();
|
||||
match err {
|
||||
Error::MultipleStatement => (),
|
||||
_ => panic!("Unexpected error: {}", err),
|
||||
_ => panic!("Unexpected error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1459,7 +1478,7 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
|
||||
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
|
||||
assert_eq!(insert_stmt.execute([1i32])?, 1);
|
||||
assert_eq!(insert_stmt.execute([2i32])?, 1);
|
||||
assert_eq!(insert_stmt.execute([3i32])?, 1);
|
||||
@@ -1468,7 +1487,7 @@ mod test {
|
||||
assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
|
||||
assert_eq!(insert_stmt.execute([types::Null])?, 1);
|
||||
|
||||
let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
|
||||
let mut update_stmt = db.prepare("UPDATE foo SET x=?1 WHERE x<?2")?;
|
||||
assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
|
||||
assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
|
||||
assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
|
||||
@@ -1480,12 +1499,12 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
|
||||
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
|
||||
assert_eq!(insert_stmt.execute([1i32])?, 1);
|
||||
assert_eq!(insert_stmt.execute([2i32])?, 1);
|
||||
assert_eq!(insert_stmt.execute([3i32])?, 1);
|
||||
|
||||
let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
|
||||
let mut query = db.prepare("SELECT x FROM foo WHERE x < ?1 ORDER BY x DESC")?;
|
||||
{
|
||||
let mut rows = query.query([4i32])?;
|
||||
let mut v = Vec::<i32>::new();
|
||||
@@ -1541,15 +1560,12 @@ mod test {
|
||||
END;";
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
assert_eq!(
|
||||
10i64,
|
||||
db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(10i64, db.one_column::<i64>("SELECT SUM(x) FROM foo")?);
|
||||
|
||||
let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
|
||||
let result: Result<i64> = db.one_column("SELECT x FROM foo WHERE x > 5");
|
||||
match result.unwrap_err() {
|
||||
Error::QueryReturnedNoRows => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
|
||||
@@ -1562,21 +1578,21 @@ mod test {
|
||||
fn test_optional() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
|
||||
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 <> 0");
|
||||
let result = result.optional();
|
||||
match result? {
|
||||
None => (),
|
||||
_ => panic!("Unexpected result"),
|
||||
}
|
||||
|
||||
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0));
|
||||
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 == 0");
|
||||
let result = result.optional();
|
||||
match result? {
|
||||
Some(1) => (),
|
||||
_ => panic!("Unexpected result"),
|
||||
}
|
||||
|
||||
let bad_query_result: Result<i64> = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0));
|
||||
let bad_query_result: Result<i64> = db.one_column("NOT A PROPER QUERY");
|
||||
let bad_query_result = bad_query_result.optional();
|
||||
bad_query_result.unwrap_err();
|
||||
Ok(())
|
||||
@@ -1585,11 +1601,8 @@ mod test {
|
||||
#[test]
|
||||
fn test_pragma_query_row() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
assert_eq!(
|
||||
"memory",
|
||||
db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
|
||||
);
|
||||
let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
|
||||
assert_eq!("memory", db.one_column::<String>("PRAGMA journal_mode")?);
|
||||
let mode = db.one_column::<String>("PRAGMA journal_mode=off")?;
|
||||
if cfg!(features = "bundled") {
|
||||
assert_eq!(mode, "off");
|
||||
} else {
|
||||
@@ -1604,7 +1617,7 @@ mod test {
|
||||
// > MEMORY or OFF and can not be changed to a different value. An
|
||||
// > attempt to change the journal_mode of an in-memory database to
|
||||
// > any setting other than MEMORY or OFF is ignored.
|
||||
assert!(mode == "memory" || mode == "off", "Got mode {:?}", mode);
|
||||
assert!(mode == "memory" || mode == "off", "Got mode {mode:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1691,7 +1704,7 @@ mod test {
|
||||
assert_eq!(err.code, ErrorCode::ConstraintViolation);
|
||||
check_extended_code(err.extended_code);
|
||||
}
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1756,7 +1769,7 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(i, x);")?;
|
||||
let vals = ["foobar", "1234", "qwerty"];
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
|
||||
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?1, ?2)")?;
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
let i_to_insert = i as i64;
|
||||
assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1);
|
||||
@@ -1796,6 +1809,16 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_handle_owned() -> Result<()> {
|
||||
let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut();
|
||||
let r = unsafe { ffi::sqlite3_open(":memory:\0".as_ptr() as *const i8, &mut handle) };
|
||||
assert_eq!(r, ffi::SQLITE_OK);
|
||||
let db = unsafe { Connection::from_handle_owned(handle) }?;
|
||||
db.execute_batch("PRAGMA VACUUM")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod query_and_then_tests {
|
||||
|
||||
use super::*;
|
||||
@@ -1873,7 +1896,7 @@ mod test {
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
Error::InvalidColumnType(..) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let bad_idx: Result<Vec<String>> =
|
||||
@@ -1881,7 +1904,7 @@ mod test {
|
||||
|
||||
match bad_idx.unwrap_err() {
|
||||
Error::InvalidColumnIndex(_) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1926,7 +1949,7 @@ mod test {
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let bad_idx: CustomResult<Vec<String>> = query
|
||||
@@ -1935,7 +1958,7 @@ mod test {
|
||||
|
||||
match bad_idx.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let non_sqlite_err: CustomResult<Vec<String>> = query
|
||||
@@ -1944,7 +1967,7 @@ mod test {
|
||||
|
||||
match non_sqlite_err.unwrap_err() {
|
||||
CustomError::SomeError => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1981,7 +2004,7 @@ mod test {
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let bad_idx: CustomResult<String> =
|
||||
@@ -1989,7 +2012,7 @@ mod test {
|
||||
|
||||
match bad_idx.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
|
||||
let non_sqlite_err: CustomResult<String> =
|
||||
@@ -1997,7 +2020,7 @@ mod test {
|
||||
|
||||
match non_sqlite_err.unwrap_err() {
|
||||
CustomError::SomeError => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2022,7 +2045,7 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
|
||||
let b: Box<dyn ToSql> = Box::new(5);
|
||||
db.execute("INSERT INTO foo VALUES(?)", [b])?;
|
||||
db.execute("INSERT INTO foo VALUES(?1)", [b])?;
|
||||
db.query_row("SELECT x FROM foo", [], |r| {
|
||||
assert_eq!(5, r.get_unwrap::<_, i32>(0));
|
||||
Ok(())
|
||||
@@ -2034,10 +2057,10 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.query_row(
|
||||
"SELECT
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?;",
|
||||
?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10,
|
||||
?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20,
|
||||
?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30,
|
||||
?31, ?32, ?33, ?34;",
|
||||
params![
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
@@ -2079,10 +2102,7 @@ mod test {
|
||||
fn test_returning() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
|
||||
let row_id =
|
||||
db.query_row::<i64, _, _>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [], |r| {
|
||||
r.get(0)
|
||||
})?;
|
||||
let row_id = db.one_column::<i64>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID")?;
|
||||
assert_eq!(row_id, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ use sealed::Sealed;
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result, params};
|
||||
/// fn update_rows(conn: &Connection) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
|
||||
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?;
|
||||
///
|
||||
/// // Using a tuple:
|
||||
/// stmt.execute((0, "foobar"))?;
|
||||
@@ -138,9 +138,7 @@ use sealed::Sealed;
|
||||
/// ## No parameters
|
||||
///
|
||||
/// You can just use an empty tuple or the empty array literal to run a query
|
||||
/// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
|
||||
/// common in previous versions of this library is no longer needed, and is now
|
||||
/// deprecated).
|
||||
/// that accepts no parameters.
|
||||
///
|
||||
/// ### Example (no parameters)
|
||||
///
|
||||
@@ -192,8 +190,7 @@ pub trait Params: Sealed {
|
||||
}
|
||||
|
||||
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
|
||||
// unambiguous, this must be the *only* implementation for an empty array. This
|
||||
// avoids `NO_PARAMS` being a necessary part of the API.
|
||||
// unambiguous, this must be the *only* implementation for an empty array.
|
||||
//
|
||||
// This sadly prevents `impl<T: ToSql, const N: usize> Params for [T; N]`, which
|
||||
// forces people to use `params![...]` or `rusqlite::params_from_iter` for long
|
||||
@@ -357,7 +354,7 @@ impl_for_array_ref!(
|
||||
/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
|
||||
/// assert_eq!(ids.len(), 3, "Unrealistic sample code");
|
||||
///
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?;
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?1, ?2, ?3)")?;
|
||||
/// let _rows = stmt.query(params_from_iter(ids.iter()))?;
|
||||
///
|
||||
/// // use _rows...
|
||||
|
||||
@@ -211,7 +211,7 @@ impl Connection {
|
||||
/// (e.g. `integrity_check`).
|
||||
///
|
||||
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||
/// `SELECT * FROM pragma_table_info(?);`
|
||||
/// `SELECT * FROM pragma_table_info(?1);`
|
||||
pub fn pragma<F, V>(
|
||||
&self,
|
||||
schema_name: Option<DatabaseName<'_>>,
|
||||
@@ -303,15 +303,15 @@ fn is_identifier(s: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_identifier_start(c: char) -> bool {
|
||||
('A'..='Z').contains(&c) || c == '_' || ('a'..='z').contains(&c) || c > '\x7F'
|
||||
c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F'
|
||||
}
|
||||
|
||||
fn is_identifier_continue(c: char) -> bool {
|
||||
c == '$'
|
||||
|| ('0'..='9').contains(&c)
|
||||
|| ('A'..='Z').contains(&c)
|
||||
|| c.is_ascii_digit()
|
||||
|| c.is_ascii_uppercase()
|
||||
|| c == '_'
|
||||
|| ('a'..='z').contains(&c)
|
||||
|| c.is_ascii_lowercase()
|
||||
|| c > '\x7F'
|
||||
}
|
||||
|
||||
@@ -333,10 +333,7 @@ mod test {
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn pragma_func_query_value() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let user_version: i32 =
|
||||
db.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
|
||||
row.get(0)
|
||||
})?;
|
||||
let user_version: i32 = db.one_column("SELECT user_version FROM pragma_user_version")?;
|
||||
assert_eq!(0, user_version);
|
||||
Ok(())
|
||||
}
|
||||
@@ -382,7 +379,7 @@ mod test {
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn pragma_func() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)")?;
|
||||
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?1)")?;
|
||||
let mut columns = Vec::new();
|
||||
let mut rows = table_info.query(["sqlite_master"])?;
|
||||
|
||||
@@ -408,18 +405,17 @@ mod test {
|
||||
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
|
||||
assert!(
|
||||
journal_mode == "off" || journal_mode == "memory",
|
||||
"mode: {:?}",
|
||||
journal_mode,
|
||||
"mode: {journal_mode:?}"
|
||||
);
|
||||
// Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
|
||||
let mode =
|
||||
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get::<_, String>(0))?;
|
||||
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||
assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
|
||||
|
||||
let param: &dyn crate::ToSql = &"OFF";
|
||||
let mode =
|
||||
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
|
||||
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||
assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -169,8 +169,10 @@ impl RawStatement {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_bindings(&self) -> c_int {
|
||||
unsafe { ffi::sqlite3_clear_bindings(self.ptr) }
|
||||
pub fn clear_bindings(&self) {
|
||||
unsafe {
|
||||
ffi::sqlite3_clear_bindings(self.ptr);
|
||||
} // rc is always SQLITE_OK
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -195,7 +197,6 @@ impl RawStatement {
|
||||
}
|
||||
|
||||
// does not work for PRAGMA
|
||||
#[cfg(feature = "extra_check")]
|
||||
#[inline]
|
||||
pub fn readonly(&self) -> bool {
|
||||
unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 }
|
||||
|
||||
57
src/row.rs
57
src/row.rs
@@ -257,6 +257,7 @@ impl<'stmt> Row<'stmt> {
|
||||
/// * If the underlying SQLite integral value is outside the range
|
||||
/// representable by `T`
|
||||
/// * If `idx` is outside the range of columns in the returned query
|
||||
#[track_caller]
|
||||
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
||||
self.get(idx).unwrap()
|
||||
}
|
||||
@@ -277,6 +278,7 @@ impl<'stmt> Row<'stmt> {
|
||||
/// If the result type is i128 (which requires the `i128_blob` feature to be
|
||||
/// enabled), and the underlying SQLite column is a blob whose size is not
|
||||
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
||||
#[track_caller]
|
||||
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||
let idx = idx.idx(self.stmt)?;
|
||||
let value = self.stmt.value_ref(idx);
|
||||
@@ -335,23 +337,10 @@ impl<'stmt> Row<'stmt> {
|
||||
///
|
||||
/// * If `idx` is outside the range of columns in the returned query.
|
||||
/// * If `idx` is not a valid column name for this row.
|
||||
#[track_caller]
|
||||
pub fn get_ref_unwrap<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
|
||||
self.get_ref(idx).unwrap()
|
||||
}
|
||||
|
||||
/// Renamed to [`get_ref`](Row::get_ref).
|
||||
#[deprecated = "Use [`get_ref`](Row::get_ref) instead."]
|
||||
#[inline]
|
||||
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
|
||||
self.get_ref(idx)
|
||||
}
|
||||
|
||||
/// Renamed to [`get_ref_unwrap`](Row::get_ref_unwrap).
|
||||
#[deprecated = "Use [`get_ref_unwrap`](Row::get_ref_unwrap) instead."]
|
||||
#[inline]
|
||||
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
|
||||
self.get_ref_unwrap(idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
|
||||
@@ -360,6 +349,46 @@ impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug `Row` like an ordered `Map<Result<&str>, Result<(Type, ValueRef)>>`
|
||||
/// with column name as key except that for `Type::Blob` only its size is
|
||||
/// printed (not its content).
|
||||
impl<'stmt> std::fmt::Debug for Row<'stmt> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut dm = f.debug_map();
|
||||
for c in 0..self.stmt.column_count() {
|
||||
let name = self.stmt.column_name(c);
|
||||
dm.key(&name);
|
||||
let value = self.get_ref(c);
|
||||
match value {
|
||||
Ok(value) => {
|
||||
let dt = value.data_type();
|
||||
match value {
|
||||
ValueRef::Null => {
|
||||
dm.value(&(dt, ()));
|
||||
}
|
||||
ValueRef::Integer(i) => {
|
||||
dm.value(&(dt, i));
|
||||
}
|
||||
ValueRef::Real(f) => {
|
||||
dm.value(&(dt, f));
|
||||
}
|
||||
ValueRef::Text(s) => {
|
||||
dm.value(&(dt, String::from_utf8_lossy(s)));
|
||||
}
|
||||
ValueRef::Blob(b) => {
|
||||
dm.value(&(dt, b.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ref _err) => {
|
||||
dm.value(&value);
|
||||
}
|
||||
}
|
||||
}
|
||||
dm.finish()
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
/// This trait exists just to ensure that the only impls of `trait Params`
|
||||
/// that are allowed are ones in this crate.
|
||||
|
||||
162
src/serialize.rs
Normal file
162
src/serialize.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! Serialize a database.
|
||||
use std::convert::TryInto;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::error::error_from_handle;
|
||||
use crate::ffi;
|
||||
use crate::{Connection, DatabaseName, Result};
|
||||
|
||||
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
|
||||
pub struct SharedData<'conn> {
|
||||
phantom: PhantomData<&'conn Connection>,
|
||||
ptr: NonNull<u8>,
|
||||
sz: usize,
|
||||
}
|
||||
|
||||
/// Owned serialized database
|
||||
pub struct OwnedData {
|
||||
ptr: NonNull<u8>,
|
||||
sz: usize,
|
||||
}
|
||||
|
||||
impl OwnedData {
|
||||
/// SAFETY: Caller must be certain that `ptr` is allocated by
|
||||
/// `sqlite3_malloc`.
|
||||
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self {
|
||||
Self { ptr, sz }
|
||||
}
|
||||
|
||||
fn into_raw(self) -> (*mut u8, usize) {
|
||||
let raw = (self.ptr.as_ptr(), self.sz);
|
||||
std::mem::forget(self);
|
||||
raw
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OwnedData {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi::sqlite3_free(self.ptr.as_ptr().cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialized database
|
||||
pub enum Data<'conn> {
|
||||
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
|
||||
Shared(SharedData<'conn>),
|
||||
/// Owned serialized database
|
||||
Owned(OwnedData),
|
||||
}
|
||||
|
||||
impl<'conn> Deref for Data<'conn> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] {
|
||||
let (ptr, sz) = match self {
|
||||
Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz),
|
||||
Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz),
|
||||
};
|
||||
unsafe { std::slice::from_raw_parts(ptr, sz) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Serialize a database.
|
||||
pub fn serialize<'conn>(&'conn self, schema: DatabaseName<'_>) -> Result<Data<'conn>> {
|
||||
let schema = schema.as_cstring()?;
|
||||
let mut sz = 0;
|
||||
let mut ptr: *mut u8 = unsafe {
|
||||
ffi::sqlite3_serialize(
|
||||
self.handle(),
|
||||
schema.as_ptr(),
|
||||
&mut sz,
|
||||
ffi::SQLITE_SERIALIZE_NOCOPY,
|
||||
)
|
||||
};
|
||||
Ok(if ptr.is_null() {
|
||||
ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) };
|
||||
if ptr.is_null() {
|
||||
return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) });
|
||||
}
|
||||
Data::Owned(OwnedData {
|
||||
ptr: NonNull::new(ptr).unwrap(),
|
||||
sz: sz.try_into().unwrap(),
|
||||
})
|
||||
} else {
|
||||
// shared buffer
|
||||
Data::Shared(SharedData {
|
||||
ptr: NonNull::new(ptr).unwrap(),
|
||||
sz: sz.try_into().unwrap(),
|
||||
phantom: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Deserialize a database.
|
||||
pub fn deserialize(
|
||||
&mut self,
|
||||
schema: DatabaseName<'_>,
|
||||
data: OwnedData,
|
||||
read_only: bool,
|
||||
) -> Result<()> {
|
||||
let schema = schema.as_cstring()?;
|
||||
let (data, sz) = data.into_raw();
|
||||
let sz = sz.try_into().unwrap();
|
||||
let flags = if read_only {
|
||||
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY
|
||||
} else {
|
||||
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE
|
||||
};
|
||||
let rc = unsafe {
|
||||
ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags)
|
||||
};
|
||||
if rc != ffi::SQLITE_OK {
|
||||
// TODO sqlite3_free(data) ?
|
||||
return Err(unsafe { error_from_handle(self.handle(), rc) });
|
||||
}
|
||||
/* TODO
|
||||
if let Some(mxSize) = mxSize {
|
||||
unsafe {
|
||||
ffi::sqlite3_file_control(
|
||||
self.handle(),
|
||||
schema.as_ptr(),
|
||||
ffi::SQLITE_FCNTL_SIZE_LIMIT,
|
||||
&mut mxSize,
|
||||
)
|
||||
};
|
||||
}*/
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{Connection, DatabaseName, Result};
|
||||
|
||||
#[test]
|
||||
fn serialize() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
||||
let data = db.serialize(DatabaseName::Main)?;
|
||||
let Data::Owned(data) = data else { panic!("expected OwnedData")};
|
||||
assert!(data.sz > 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize() -> Result<()> {
|
||||
let src = Connection::open_in_memory()?;
|
||||
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
||||
let data = src.serialize(DatabaseName::Main)?;
|
||||
let Data::Owned(data) = data else { panic!("expected OwnedData")};
|
||||
|
||||
let mut dst = Connection::open_in_memory()?;
|
||||
dst.deserialize(DatabaseName::Main, data, false)?;
|
||||
dst.execute("DELETE FROM x", [])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -75,13 +75,10 @@ impl Session<'_> {
|
||||
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
|
||||
str::from_utf8(c_slice)
|
||||
};
|
||||
if let Ok(true) =
|
||||
c_int::from(
|
||||
catch_unwind(|| (*boxed_filter)(tbl_name.expect("non-utf8 table name")))
|
||||
{
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
match filter {
|
||||
@@ -193,7 +190,7 @@ impl Session<'_> {
|
||||
#[inline]
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
unsafe {
|
||||
ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
|
||||
ffi::sqlite3session_enable(self.s, c_int::from(enabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +204,7 @@ impl Session<'_> {
|
||||
#[inline]
|
||||
pub fn set_indirect(&mut self, indirect: bool) {
|
||||
unsafe {
|
||||
ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
|
||||
ffi::sqlite3session_indirect(self.s, c_int::from(indirect));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,13 +705,9 @@ where
|
||||
str::from_utf8(c_slice)
|
||||
};
|
||||
match *tuple {
|
||||
(Some(ref filter), _) => {
|
||||
if let Ok(true) = catch_unwind(|| filter(tbl_name.expect("illegal table name"))) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
(Some(ref filter), _) => c_int::from(
|
||||
catch_unwind(|| filter(tbl_name.expect("illegal table name"))).unwrap_or_default(),
|
||||
),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
@@ -786,7 +779,7 @@ mod test {
|
||||
assert!(session.is_empty());
|
||||
|
||||
session.attach(None)?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
||||
|
||||
session.changeset()
|
||||
}
|
||||
@@ -799,7 +792,7 @@ mod test {
|
||||
assert!(session.is_empty());
|
||||
|
||||
session.attach(None)?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
||||
|
||||
let mut output = Vec::new();
|
||||
session.changeset_strm(&mut output)?;
|
||||
@@ -859,7 +852,7 @@ mod test {
|
||||
)?;
|
||||
|
||||
assert!(!CALLED.load(Ordering::Relaxed));
|
||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
|
||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
|
||||
row.get::<_, i32>(0)
|
||||
})?;
|
||||
assert_eq!(1, check);
|
||||
@@ -894,7 +887,7 @@ mod test {
|
||||
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
||||
)?;
|
||||
|
||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
|
||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
|
||||
row.get::<_, i32>(0)
|
||||
})?;
|
||||
assert_eq!(1, check);
|
||||
@@ -910,7 +903,7 @@ mod test {
|
||||
assert!(session.is_empty());
|
||||
|
||||
session.attach(None)?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
||||
|
||||
assert!(!session.is_empty());
|
||||
Ok(())
|
||||
|
||||
293
src/statement.rs
293
src/statement.rs
@@ -33,7 +33,7 @@ impl Statement<'_> {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result, params};
|
||||
/// fn update_rows(conn: &Connection) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("UPDATE foo SET bar = ? WHERE qux = ?")?;
|
||||
/// let mut stmt = conn.prepare("UPDATE foo SET bar = ?1 WHERE qux = ?2")?;
|
||||
/// // For a single parameter, or a parameter where all the values have
|
||||
/// // the same type, just passing an array is simplest.
|
||||
/// stmt.execute([2i32])?;
|
||||
@@ -58,7 +58,7 @@ impl Statement<'_> {
|
||||
/// fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> {
|
||||
/// # // no need to do it for real.
|
||||
/// # fn sha256(_: &[u8]) -> [u8; 32] { [0; 32] }
|
||||
/// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?, ?, ?)";
|
||||
/// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?1, ?2, ?3)";
|
||||
/// let mut stmt = conn.prepare_cached(query)?;
|
||||
/// let hash: [u8; 32] = sha256(data);
|
||||
/// // The easiest way to pass positional parameters of have several
|
||||
@@ -114,31 +114,6 @@ impl Statement<'_> {
|
||||
self.execute_with_bound_parameters()
|
||||
}
|
||||
|
||||
/// Execute the prepared statement with named parameter(s).
|
||||
///
|
||||
/// Note: This function is deprecated in favor of [`Statement::execute`],
|
||||
/// which can now take named parameters directly.
|
||||
///
|
||||
/// If any parameters that were in the prepared statement are not included
|
||||
/// in `params`, they will continue to use the most-recently bound value
|
||||
/// from a previous call to `execute_named`, or `NULL` if they have never
|
||||
/// been bound.
|
||||
///
|
||||
/// On success, returns the number of rows that were changed or inserted or
|
||||
/// deleted (via `sqlite3_changes`).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails, the executed statement
|
||||
/// returns rows (in which case `query` should be used instead), or the
|
||||
/// underlying SQLite call fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `execute` with named params now."]
|
||||
#[inline]
|
||||
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||
self.execute(params)
|
||||
}
|
||||
|
||||
/// Execute an INSERT and return the ROWID.
|
||||
///
|
||||
/// # Note
|
||||
@@ -193,7 +168,7 @@ impl Statement<'_> {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn query(conn: &Connection, name: &str) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
|
||||
/// let mut rows = stmt.query(rusqlite::params![name])?;
|
||||
/// while let Some(row) = rows.next()? {
|
||||
/// // ...
|
||||
@@ -207,7 +182,7 @@ impl Statement<'_> {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn query(conn: &Connection, name: &str) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
|
||||
/// let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
|
||||
/// let mut rows = stmt.query([name])?;
|
||||
/// while let Some(row) = rows.next()? {
|
||||
/// // ...
|
||||
@@ -254,26 +229,6 @@ impl Statement<'_> {
|
||||
Ok(Rows::new(self))
|
||||
}
|
||||
|
||||
/// Execute the prepared statement with named parameter(s), returning a
|
||||
/// handle for the resulting rows.
|
||||
///
|
||||
/// Note: This function is deprecated in favor of [`Statement::query`],
|
||||
/// which can now take named parameters directly.
|
||||
///
|
||||
/// If any parameters that were in the prepared statement are not included
|
||||
/// in `params`, they will continue to use the most-recently bound value
|
||||
/// from a previous call to `query_named`, or `NULL` if they have never been
|
||||
/// bound.
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query` with named params now."]
|
||||
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
|
||||
self.query(params)
|
||||
}
|
||||
|
||||
/// Executes the prepared statement and maps a function over the resulting
|
||||
/// rows, returning an iterator over the mapped function results.
|
||||
///
|
||||
@@ -328,37 +283,6 @@ impl Statement<'_> {
|
||||
self.query(params).map(|rows| rows.mapped(f))
|
||||
}
|
||||
|
||||
/// Execute the prepared statement with named parameter(s), returning an
|
||||
/// iterator over the result of calling the mapping function over the
|
||||
/// query's rows.
|
||||
///
|
||||
/// Note: This function is deprecated in favor of [`Statement::query_map`],
|
||||
/// which can now take named parameters directly.
|
||||
///
|
||||
/// If any parameters that were in the prepared statement
|
||||
/// are not included in `params`, they will continue to use the
|
||||
/// most-recently bound value from a previous call to `query_named`,
|
||||
/// or `NULL` if they have never been bound.
|
||||
///
|
||||
/// `f` is used to transform the _streaming_ iterator into a _standard_
|
||||
/// iterator.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query_map` with named params now."]
|
||||
pub fn query_map_named<T, F>(
|
||||
&mut self,
|
||||
params: &[(&str, &dyn ToSql)],
|
||||
f: F,
|
||||
) -> Result<MappedRows<'_, F>>
|
||||
where
|
||||
F: FnMut(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
self.query_map(params, f)
|
||||
}
|
||||
|
||||
/// Executes the prepared statement and maps a function over the resulting
|
||||
/// rows, where the function returns a `Result` with `Error` type
|
||||
/// implementing `std::convert::From<Error>` (so errors can be unified).
|
||||
@@ -398,7 +322,7 @@ impl Statement<'_> {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
||||
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?")?;
|
||||
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?1")?;
|
||||
/// let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
|
||||
///
|
||||
/// let mut persons = Vec::new();
|
||||
@@ -423,36 +347,6 @@ impl Statement<'_> {
|
||||
self.query(params).map(|rows| rows.and_then(f))
|
||||
}
|
||||
|
||||
/// Execute the prepared statement with named parameter(s), returning an
|
||||
/// iterator over the result of calling the mapping function over the
|
||||
/// query's rows.
|
||||
///
|
||||
/// Note: This function is deprecated in favor of
|
||||
/// [`Statement::query_and_then`], which can now take named parameters
|
||||
/// directly.
|
||||
///
|
||||
/// If any parameters that were in the prepared statement are not included
|
||||
/// in `params`, they will continue to use the most-recently bound value
|
||||
/// from a previous call to `query_named`, or `NULL` if they have never been
|
||||
/// bound.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query_and_then` with named params now."]
|
||||
pub fn query_and_then_named<T, E, F>(
|
||||
&mut self,
|
||||
params: &[(&str, &dyn ToSql)],
|
||||
f: F,
|
||||
) -> Result<AndThenRows<'_, F>>
|
||||
where
|
||||
E: From<Error>,
|
||||
F: FnMut(&Row<'_>) -> Result<T, E>,
|
||||
{
|
||||
self.query_and_then(params, f)
|
||||
}
|
||||
|
||||
/// Return `true` if a query in the SQL statement it executes returns one
|
||||
/// or more rows and `false` if the SQL returns an empty set.
|
||||
#[inline]
|
||||
@@ -487,35 +381,6 @@ impl Statement<'_> {
|
||||
rows.get_expected_row().and_then(f)
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query with named parameter(s) that is
|
||||
/// expected to return a single row.
|
||||
///
|
||||
/// Note: This function is deprecated in favor of
|
||||
/// [`Statement::query_and_then`], which can now take named parameters
|
||||
/// directly.
|
||||
///
|
||||
/// If the query returns more than one row, all rows except the first are
|
||||
/// ignored.
|
||||
///
|
||||
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
|
||||
/// query truly is optional, you can call
|
||||
/// [`.optional()`](crate::OptionalExtension::optional) on the result of
|
||||
/// this to get a `Result<Option<T>>` (requires that the trait
|
||||
/// `rusqlite::OptionalExtension` is imported).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query_row` with named params now."]
|
||||
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
self.query_row(params, f)
|
||||
}
|
||||
|
||||
/// Consumes the statement.
|
||||
///
|
||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||
@@ -844,6 +709,12 @@ impl Statement<'_> {
|
||||
self.stmt.is_explain()
|
||||
}
|
||||
|
||||
/// Returns true if the statement is read only.
|
||||
#[inline]
|
||||
pub fn readonly(&self) -> bool {
|
||||
self.stmt.readonly()
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_check")]
|
||||
#[inline]
|
||||
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||
@@ -870,6 +741,11 @@ impl Statement<'_> {
|
||||
mem::swap(&mut stmt, &mut self.stmt);
|
||||
stmt
|
||||
}
|
||||
|
||||
/// Reset all bindings
|
||||
pub fn clear_bindings(&mut self) {
|
||||
self.stmt.clear_bindings()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Statement<'_> {
|
||||
@@ -1009,13 +885,12 @@ mod test {
|
||||
use crate::{params_from_iter, Connection, Error, Result};
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_execute_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
|
||||
|
||||
assert_eq!(
|
||||
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
|
||||
db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1032,7 +907,7 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
6i32,
|
||||
db.query_row_named::<i32, _>(
|
||||
db.query_row::<i32, _, _>(
|
||||
"SELECT SUM(x) FROM foo WHERE x > :x",
|
||||
&[(":x", &0i32)],
|
||||
|r| r.get(0)
|
||||
@@ -1050,7 +925,6 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_stmt_execute_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
|
||||
@@ -1058,13 +932,9 @@ mod test {
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?;
|
||||
stmt.execute_named(&[(":name", &"one")])?;
|
||||
stmt.execute(&[(":name", &"one")])?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?;
|
||||
assert_eq!(
|
||||
1i32,
|
||||
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(
|
||||
1i32,
|
||||
stmt.query_row::<i32, _, _>(&[(":name", "one")], |r| r.get(0))?
|
||||
@@ -1073,7 +943,6 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_query_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = r#"
|
||||
@@ -1083,24 +952,13 @@ mod test {
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
|
||||
// legacy `_named` api
|
||||
{
|
||||
let mut rows = stmt.query_named(&[(":name", &"one")])?;
|
||||
let id: Result<i32> = rows.next()?.unwrap().get(0);
|
||||
assert_eq!(Ok(1), id);
|
||||
}
|
||||
|
||||
// plain api
|
||||
{
|
||||
let mut rows = stmt.query(&[(":name", "one")])?;
|
||||
let id: Result<i32> = rows.next()?.unwrap().get(0);
|
||||
assert_eq!(Ok(1), id);
|
||||
}
|
||||
let mut rows = stmt.query(&[(":name", "one")])?;
|
||||
let id: Result<i32> = rows.next()?.unwrap().get(0);
|
||||
assert_eq!(Ok(1), id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_query_map_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = r#"
|
||||
@@ -1110,61 +968,13 @@ mod test {
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
|
||||
// legacy `_named` api
|
||||
{
|
||||
let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| {
|
||||
let id: Result<i32> = row.get(0);
|
||||
id.map(|i| 2 * i)
|
||||
})?;
|
||||
|
||||
let doubled_id: i32 = rows.next().unwrap()?;
|
||||
assert_eq!(2, doubled_id);
|
||||
}
|
||||
// plain api
|
||||
{
|
||||
let mut rows = stmt.query_map(&[(":name", "one")], |row| {
|
||||
let id: Result<i32> = row.get(0);
|
||||
id.map(|i| 2 * i)
|
||||
})?;
|
||||
|
||||
let doubled_id: i32 = rows.next().unwrap()?;
|
||||
assert_eq!(2, doubled_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_query_and_then_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = r#"
|
||||
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
|
||||
INSERT INTO test(id, name) VALUES (1, "one");
|
||||
INSERT INTO test(id, name) VALUES (2, "one");
|
||||
"#;
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
|
||||
let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| {
|
||||
let id: i32 = row.get(0)?;
|
||||
if id == 1 {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(Error::SqliteSingleThreadedMode)
|
||||
}
|
||||
let mut rows = stmt.query_map(&[(":name", "one")], |row| {
|
||||
let id: Result<i32> = row.get(0);
|
||||
id.map(|i| 2 * i)
|
||||
})?;
|
||||
|
||||
// first row should be Ok
|
||||
let doubled_id: i32 = rows.next().unwrap()?;
|
||||
assert_eq!(1, doubled_id);
|
||||
|
||||
// second row should be Err
|
||||
#[allow(clippy::match_wild_err_arm)]
|
||||
match rows.next().unwrap() {
|
||||
Ok(_) => panic!("invalid Ok"),
|
||||
Err(Error::SqliteSingleThreadedMode) => (),
|
||||
Err(_) => panic!("invalid Err"),
|
||||
}
|
||||
assert_eq!(2, doubled_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1203,17 +1013,15 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_unbound_parameters_are_null() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
|
||||
db.execute_batch(sql)?;
|
||||
|
||||
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
|
||||
stmt.execute_named(&[(":x", &"one")])?;
|
||||
stmt.execute(&[(":x", &"one")])?;
|
||||
|
||||
let result: Option<String> =
|
||||
db.query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0))?;
|
||||
let result: Option<String> = db.one_column("SELECT y FROM test WHERE x = 'one'")?;
|
||||
assert!(result.is_none());
|
||||
Ok(())
|
||||
}
|
||||
@@ -1259,8 +1067,7 @@ mod test {
|
||||
stmt.execute(&[(":x", "one")])?;
|
||||
stmt.execute(&[(":y", "two")])?;
|
||||
|
||||
let result: String =
|
||||
db.query_row("SELECT x FROM test WHERE y = 'two'", [], |row| row.get(0))?;
|
||||
let result: String = db.one_column("SELECT x FROM test WHERE y = 'two'")?;
|
||||
assert_eq!(result, "one");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1269,17 +1076,17 @@ mod test {
|
||||
fn test_insert() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")?;
|
||||
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")?;
|
||||
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?1)")?;
|
||||
assert_eq!(stmt.insert([1i32])?, 1);
|
||||
assert_eq!(stmt.insert([2i32])?, 2);
|
||||
match stmt.insert([1i32]).unwrap_err() {
|
||||
Error::StatementChangedRows(0) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")?;
|
||||
match multi.insert([]).unwrap_err() {
|
||||
Error::StatementChangedRows(2) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1309,7 +1116,7 @@ mod test {
|
||||
INSERT INTO foo VALUES(2);
|
||||
END;";
|
||||
db.execute_batch(sql)?;
|
||||
let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?")?;
|
||||
let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?1")?;
|
||||
assert!(stmt.exists([1i32])?);
|
||||
assert!(stmt.exists([2i32])?);
|
||||
assert!(!stmt.exists([0i32])?);
|
||||
@@ -1318,18 +1125,18 @@ mod test {
|
||||
#[test]
|
||||
fn test_tuple_params() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let s = db.query_row("SELECT printf('[%s]', ?)", ("abc",), |r| {
|
||||
let s = db.query_row("SELECT printf('[%s]', ?1)", ("abc",), |r| {
|
||||
r.get::<_, String>(0)
|
||||
})?;
|
||||
assert_eq!(s, "[abc]");
|
||||
let s = db.query_row(
|
||||
"SELECT printf('%d %s %d', ?, ?, ?)",
|
||||
"SELECT printf('%d %s %d', ?1, ?2, ?3)",
|
||||
(1i32, "abc", 2i32),
|
||||
|r| r.get::<_, String>(0),
|
||||
)?;
|
||||
assert_eq!(s, "1 abc 2");
|
||||
let s = db.query_row(
|
||||
"SELECT printf('%d %s %d %d', ?, ?, ?, ?)",
|
||||
"SELECT printf('%d %s %d %d', ?1, ?2, ?3, ?4)",
|
||||
(1, "abc", 2i32, 4i64),
|
||||
|r| r.get::<_, String>(0),
|
||||
)?;
|
||||
@@ -1341,10 +1148,10 @@ mod test {
|
||||
);
|
||||
let query = "SELECT printf(
|
||||
'%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s',
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?
|
||||
?1, ?2, ?3, ?4,
|
||||
?5, ?6, ?7, ?8,
|
||||
?9, ?10, ?11, ?12,
|
||||
?13, ?14, ?15, ?16
|
||||
)";
|
||||
let s = db.query_row(query, bigtup, |r| r.get::<_, String>(0))?;
|
||||
assert_eq!(s, "0 a | 1 b | 2 c | 3 d || 4 e | 5 f | 6 g | 7 h");
|
||||
@@ -1360,7 +1167,7 @@ mod test {
|
||||
INSERT INTO foo VALUES(2, 4);
|
||||
END;";
|
||||
db.execute_batch(sql)?;
|
||||
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?")?;
|
||||
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?;
|
||||
let y: Result<i64> = stmt.query_row([1i32], |r| r.get(0));
|
||||
assert_eq!(3i64, y?);
|
||||
Ok(())
|
||||
@@ -1397,7 +1204,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_expanded_sql() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let stmt = db.prepare("SELECT ?")?;
|
||||
let stmt = db.prepare("SELECT ?1")?;
|
||||
stmt.bind_parameter(&1, 1)?;
|
||||
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
|
||||
Ok(())
|
||||
@@ -1499,8 +1306,8 @@ mod test {
|
||||
assert_eq!("UTF-16le", encoding);
|
||||
db.execute_batch("CREATE TABLE foo(x TEXT)")?;
|
||||
let expected = "テスト";
|
||||
db.execute("INSERT INTO foo(x) VALUES (?)", [&expected])?;
|
||||
let actual: String = db.query_row("SELECT x FROM foo", [], |row| row.get(0))?;
|
||||
db.execute("INSERT INTO foo(x) VALUES (?1)", [&expected])?;
|
||||
let actual: String = db.one_column("SELECT x FROM foo")?;
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1509,7 +1316,7 @@ mod test {
|
||||
fn test_nul_byte() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let expected = "a\x00b";
|
||||
let actual: String = db.query_row("SELECT ?", [expected], |row| row.get(0))?;
|
||||
let actual: String = db.query_row("SELECT ?1", [expected], |row| row.get(0))?;
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1523,6 +1330,14 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readonly() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let stmt = db.prepare("SELECT 1;")?;
|
||||
assert!(stmt.readonly());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
|
||||
fn test_error_offset() -> Result<()> {
|
||||
@@ -1534,7 +1349,7 @@ mod test {
|
||||
assert_eq!(error.code, ErrorCode::Unknown);
|
||||
assert_eq!(offset, 7);
|
||||
}
|
||||
err => panic!("Unexpected error {}", err),
|
||||
err => panic!("Unexpected error {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -144,13 +144,13 @@ mod test {
|
||||
let mut db = Connection::open_in_memory()?;
|
||||
db.trace(Some(tracer));
|
||||
{
|
||||
let _ = db.query_row("SELECT ?", [1i32], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?", ["hello"], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
|
||||
}
|
||||
db.trace(None);
|
||||
{
|
||||
let _ = db.query_row("SELECT ?", [2i32], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
|
||||
let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
|
||||
}
|
||||
|
||||
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
||||
|
||||
@@ -91,7 +91,6 @@ pub struct Transaction<'conn> {
|
||||
pub struct Savepoint<'conn> {
|
||||
conn: &'conn Connection,
|
||||
name: String,
|
||||
depth: u32,
|
||||
drop_behavior: DropBehavior,
|
||||
committed: bool,
|
||||
}
|
||||
@@ -158,13 +157,13 @@ impl Transaction<'_> {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth(self.conn, 1)
|
||||
Savepoint::new_(self.conn)
|
||||
}
|
||||
|
||||
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
||||
#[inline]
|
||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth_and_name(self.conn, 1, name)
|
||||
Savepoint::with_name_(self.conn, name)
|
||||
}
|
||||
|
||||
/// Get the current setting for what happens to the transaction when it is
|
||||
@@ -249,50 +248,44 @@ impl Drop for Transaction<'_> {
|
||||
|
||||
impl Savepoint<'_> {
|
||||
#[inline]
|
||||
fn with_depth_and_name<T: Into<String>>(
|
||||
conn: &Connection,
|
||||
depth: u32,
|
||||
name: T,
|
||||
) -> Result<Savepoint<'_>> {
|
||||
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
let name = name.into();
|
||||
conn.execute_batch(&format!("SAVEPOINT {name}"))
|
||||
.map(|_| Savepoint {
|
||||
conn,
|
||||
name,
|
||||
depth,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
committed: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
|
||||
let name = format!("_rusqlite_sp_{depth}");
|
||||
Savepoint::with_depth_and_name(conn, depth, name)
|
||||
fn new_(conn: &Connection) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_name_(conn, "_rusqlite_sp")
|
||||
}
|
||||
|
||||
/// Begin a new savepoint. Can be nested.
|
||||
#[inline]
|
||||
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth(conn, 0)
|
||||
Savepoint::new_(conn)
|
||||
}
|
||||
|
||||
/// Begin a new savepoint with a user-provided savepoint name.
|
||||
#[inline]
|
||||
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth_and_name(conn, 0, name)
|
||||
Savepoint::with_name_(conn, name)
|
||||
}
|
||||
|
||||
/// Begin a nested savepoint.
|
||||
#[inline]
|
||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth(self.conn, self.depth + 1)
|
||||
Savepoint::new_(self.conn)
|
||||
}
|
||||
|
||||
/// Begin a nested savepoint with a user-provided savepoint name.
|
||||
#[inline]
|
||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
|
||||
Savepoint::with_name_(self.conn, name)
|
||||
}
|
||||
|
||||
/// Get the current setting for what happens to the savepoint when it is
|
||||
@@ -351,8 +344,10 @@ impl Savepoint<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
match self.drop_behavior() {
|
||||
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
|
||||
DropBehavior::Rollback => self.rollback(),
|
||||
DropBehavior::Commit => self
|
||||
.commit_()
|
||||
.or_else(|_| self.rollback().and_then(|_| self.commit_())),
|
||||
DropBehavior::Rollback => self.rollback().and_then(|_| self.commit_()),
|
||||
DropBehavior::Ignore => Ok(()),
|
||||
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
||||
}
|
||||
@@ -552,10 +547,7 @@ mod test {
|
||||
}
|
||||
{
|
||||
let tx = db.transaction()?;
|
||||
assert_eq!(
|
||||
2i32,
|
||||
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -566,7 +558,7 @@ mod test {
|
||||
assert_eq!(e.code, crate::ErrorCode::Unknown);
|
||||
assert!(m.contains("transaction"));
|
||||
} else {
|
||||
panic!("Unexpected error type: {:?}", e);
|
||||
panic!("Unexpected error type: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,10 +583,7 @@ mod test {
|
||||
tx.commit()?;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
2i32,
|
||||
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -619,10 +608,7 @@ mod test {
|
||||
}
|
||||
{
|
||||
let tx = db.transaction()?;
|
||||
assert_eq!(
|
||||
6i32,
|
||||
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -684,6 +670,40 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_savepoint_drop_behavior_releases() -> Result<()> {
|
||||
let mut db = checked_memory_handle()?;
|
||||
|
||||
{
|
||||
let mut sp = db.savepoint()?;
|
||||
sp.set_drop_behavior(DropBehavior::Commit);
|
||||
}
|
||||
assert!(db.is_autocommit());
|
||||
{
|
||||
let mut sp = db.savepoint()?;
|
||||
sp.set_drop_behavior(DropBehavior::Rollback);
|
||||
}
|
||||
assert!(db.is_autocommit());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_savepoint_release_error() -> Result<()> {
|
||||
let mut db = checked_memory_handle()?;
|
||||
|
||||
db.pragma_update(None, "foreign_keys", true)?;
|
||||
db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
|
||||
{
|
||||
let mut sp = db.savepoint()?;
|
||||
sp.execute("INSERT INTO f VALUES (0)", [])?;
|
||||
sp.set_drop_behavior(DropBehavior::Commit);
|
||||
}
|
||||
assert!(db.is_autocommit());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_savepoint_names() -> Result<()> {
|
||||
let mut db = checked_memory_handle()?;
|
||||
@@ -727,11 +747,11 @@ mod test {
|
||||
}
|
||||
|
||||
fn insert(x: i32, conn: &Connection) -> Result<usize> {
|
||||
conn.execute("INSERT INTO foo VALUES(?)", [x])
|
||||
conn.execute("INSERT INTO foo VALUES(?1)", [x])
|
||||
}
|
||||
|
||||
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
|
||||
let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
|
||||
let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?;
|
||||
assert_eq!(x, i);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -175,12 +175,12 @@ mod test {
|
||||
#[test]
|
||||
fn test_naive_date() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = NaiveDate::from_ymd(2016, 2, 23);
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [date])?;
|
||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
|
||||
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("2016-02-23", s);
|
||||
let t: NaiveDate = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let t: NaiveDate = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(date, t);
|
||||
Ok(())
|
||||
}
|
||||
@@ -188,12 +188,12 @@ mod test {
|
||||
#[test]
|
||||
fn test_naive_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let time = NaiveTime::from_hms(23, 56, 4);
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
|
||||
let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
||||
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("23:56:04", s);
|
||||
let v: NaiveTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let v: NaiveTime = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(time, v);
|
||||
Ok(())
|
||||
}
|
||||
@@ -201,19 +201,19 @@ mod test {
|
||||
#[test]
|
||||
fn test_naive_date_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = NaiveDate::from_ymd(2016, 2, 23);
|
||||
let time = NaiveTime::from_hms(23, 56, 4);
|
||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
||||
let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
|
||||
let dt = NaiveDateTime::new(date, time);
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
||||
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("2016-02-23 23:56:04", s);
|
||||
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let v: NaiveDateTime = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(dt, v);
|
||||
|
||||
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
|
||||
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
|
||||
let hms: NaiveDateTime = db.one_column("SELECT b FROM foo")?;
|
||||
assert_eq!(dt, hms);
|
||||
Ok(())
|
||||
}
|
||||
@@ -221,28 +221,26 @@ mod test {
|
||||
#[test]
|
||||
fn test_date_time_utc() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = NaiveDate::from_ymd(2016, 2, 23);
|
||||
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
|
||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
||||
let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
|
||||
let dt = NaiveDateTime::new(date, time);
|
||||
let utc = Utc.from_utc_datetime(&dt);
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?;
|
||||
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("2016-02-23 23:56:04.789+00:00", s);
|
||||
|
||||
let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let v1: DateTime<Utc> = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(utc, v1);
|
||||
|
||||
let v2: DateTime<Utc> =
|
||||
db.query_row("SELECT '2016-02-23 23:56:04.789'", [], |r| r.get(0))?;
|
||||
let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'")?;
|
||||
assert_eq!(utc, v2);
|
||||
|
||||
let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", [], |r| r.get(0))?;
|
||||
let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'")?;
|
||||
assert_eq!(utc - Duration::milliseconds(789), v3);
|
||||
|
||||
let v4: DateTime<Utc> =
|
||||
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", [], |r| r.get(0))?;
|
||||
let v4: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'")?;
|
||||
assert_eq!(utc, v4);
|
||||
Ok(())
|
||||
}
|
||||
@@ -250,18 +248,18 @@ mod test {
|
||||
#[test]
|
||||
fn test_date_time_local() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = NaiveDate::from_ymd(2016, 2, 23);
|
||||
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
|
||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
||||
let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
|
||||
let dt = NaiveDateTime::new(date, time);
|
||||
let local = Local.from_local_datetime(&dt).single().unwrap();
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [local])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?;
|
||||
|
||||
// Stored string should be in UTC
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert!(s.ends_with("+00:00"));
|
||||
|
||||
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let v: DateTime<Local> = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(local, v);
|
||||
Ok(())
|
||||
}
|
||||
@@ -271,13 +269,13 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
||||
|
||||
// Stored string should preserve timezone offset
|
||||
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert!(s.ends_with("+04:00"));
|
||||
|
||||
let v: DateTime<FixedOffset> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(time.offset(), v.offset());
|
||||
assert_eq!(time, v);
|
||||
Ok(())
|
||||
@@ -286,15 +284,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_sqlite_functions() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Result<NaiveTime> = db.query_row("SELECT CURRENT_TIME", [], |r| r.get(0));
|
||||
let result: Result<NaiveTime> = db.one_column("SELECT CURRENT_TIME");
|
||||
result.unwrap();
|
||||
let result: Result<NaiveDate> = db.query_row("SELECT CURRENT_DATE", [], |r| r.get(0));
|
||||
let result: Result<NaiveDate> = db.one_column("SELECT CURRENT_DATE");
|
||||
result.unwrap();
|
||||
let result: Result<NaiveDateTime> =
|
||||
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
|
||||
let result: Result<NaiveDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP");
|
||||
result.unwrap();
|
||||
let result: Result<DateTime<Utc>> =
|
||||
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
|
||||
let result: Result<DateTime<Utc>> = db.one_column("SELECT CURRENT_TIMESTAMP");
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@@ -302,7 +298,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_naive_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
|
||||
let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@@ -310,7 +306,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
|
||||
let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -59,8 +59,7 @@ impl fmt::Display for FromSqlError {
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Cannot read {} byte value out of {} byte blob",
|
||||
expected_size, blob_size
|
||||
"Cannot read {expected_size} byte value out of {blob_size} byte blob"
|
||||
)
|
||||
}
|
||||
FromSqlError::Other(ref err) => err.fmt(f),
|
||||
@@ -96,6 +95,15 @@ macro_rules! from_sql_integral(
|
||||
i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
|
||||
}
|
||||
}
|
||||
);
|
||||
(non_zero $nz:ty, $z:ty) => (
|
||||
impl FromSql for $nz {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
let i = <$z>::column_result(value)?;
|
||||
<$nz>::new(i).ok_or(FromSqlError::OutOfRange(0))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -110,6 +118,22 @@ from_sql_integral!(u32);
|
||||
from_sql_integral!(u64);
|
||||
from_sql_integral!(usize);
|
||||
|
||||
from_sql_integral!(non_zero std::num::NonZeroIsize, isize);
|
||||
from_sql_integral!(non_zero std::num::NonZeroI8, i8);
|
||||
from_sql_integral!(non_zero std::num::NonZeroI16, i16);
|
||||
from_sql_integral!(non_zero std::num::NonZeroI32, i32);
|
||||
from_sql_integral!(non_zero std::num::NonZeroI64, i64);
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
|
||||
from_sql_integral!(non_zero std::num::NonZeroI128, i128);
|
||||
|
||||
from_sql_integral!(non_zero std::num::NonZeroUsize, usize);
|
||||
from_sql_integral!(non_zero std::num::NonZeroU8, u8);
|
||||
from_sql_integral!(non_zero std::num::NonZeroU16, u16);
|
||||
from_sql_integral!(non_zero std::num::NonZeroU32, u32);
|
||||
from_sql_integral!(non_zero std::num::NonZeroU64, u64);
|
||||
// std::num::NonZeroU128 is not supported since u128 isn't either
|
||||
|
||||
impl FromSql for i64 {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
@@ -244,17 +268,17 @@ mod test {
|
||||
{
|
||||
for n in out_of_range {
|
||||
let err = db
|
||||
.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
|
||||
.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
|
||||
.unwrap_err();
|
||||
match err {
|
||||
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
||||
_ => panic!("unexpected error: {}", err),
|
||||
_ => panic!("unexpected error: {err}"),
|
||||
}
|
||||
}
|
||||
for n in in_range {
|
||||
assert_eq!(
|
||||
*n,
|
||||
db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
|
||||
db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
|
||||
.unwrap()
|
||||
.into()
|
||||
);
|
||||
@@ -273,4 +297,70 @@ mod test {
|
||||
check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonzero_ranges() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
macro_rules! check_ranges {
|
||||
($nz:ty, $out_of_range:expr, $in_range:expr) => {
|
||||
for &n in $out_of_range {
|
||||
assert_eq!(
|
||||
db.query_row("SELECT ?1", [n], |r| r.get::<_, $nz>(0)),
|
||||
Err(Error::IntegralValueOutOfRange(0, n)),
|
||||
"{}",
|
||||
std::any::type_name::<$nz>()
|
||||
);
|
||||
}
|
||||
for &n in $in_range {
|
||||
let non_zero = <$nz>::new(n).unwrap();
|
||||
assert_eq!(
|
||||
Ok(non_zero),
|
||||
db.query_row("SELECT ?1", [non_zero], |r| r.get::<_, $nz>(0))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check_ranges!(std::num::NonZeroI8, &[0, -129, 128], &[-128, 1, 127]);
|
||||
check_ranges!(
|
||||
std::num::NonZeroI16,
|
||||
&[0, -32769, 32768],
|
||||
&[-32768, -1, 1, 32767]
|
||||
);
|
||||
check_ranges!(
|
||||
std::num::NonZeroI32,
|
||||
&[0, -2_147_483_649, 2_147_483_648],
|
||||
&[-2_147_483_648, -1, 1, 2_147_483_647]
|
||||
);
|
||||
check_ranges!(
|
||||
std::num::NonZeroI64,
|
||||
&[0],
|
||||
&[-2_147_483_648, -1, 1, 2_147_483_647, i64::MAX, i64::MIN]
|
||||
);
|
||||
check_ranges!(
|
||||
std::num::NonZeroIsize,
|
||||
&[0],
|
||||
&[-2_147_483_648, -1, 1, 2_147_483_647]
|
||||
);
|
||||
check_ranges!(std::num::NonZeroU8, &[0, -2, -1, 256], &[1, 255]);
|
||||
check_ranges!(std::num::NonZeroU16, &[0, -2, -1, 65536], &[1, 65535]);
|
||||
check_ranges!(
|
||||
std::num::NonZeroU32,
|
||||
&[0, -2, -1, 4_294_967_296],
|
||||
&[1, 4_294_967_295]
|
||||
);
|
||||
check_ranges!(
|
||||
std::num::NonZeroU64,
|
||||
&[0, -2, -1, -4_294_967_296],
|
||||
&[1, 4_294_967_295, i64::MAX as u64]
|
||||
);
|
||||
check_ranges!(
|
||||
std::num::NonZeroUsize,
|
||||
&[0, -2, -1, -4_294_967_296],
|
||||
&[1, 4_294_967_295]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
|
||||
//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
|
||||
//! types, so if you store an `i64` in a column with type `REAL` it will be
|
||||
//! stored as an `INTEGER`, not a `REAL`.
|
||||
//! stored as an `INTEGER`, not a `REAL` (unless the column is part of a
|
||||
//! [STRICT table](https://www.sqlite.org/stricttables.html)).
|
||||
//!
|
||||
//! If the `time` feature is enabled, implementations are
|
||||
//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
|
||||
@@ -102,7 +103,7 @@ mod value_ref;
|
||||
/// # use rusqlite::types::{Null};
|
||||
///
|
||||
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
||||
/// conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
|
||||
/// conn.execute("INSERT INTO people (name) VALUES (?1)", [Null])
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -153,9 +154,9 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let v1234 = vec![1u8, 2, 3, 4];
|
||||
db.execute("INSERT INTO foo(b) VALUES (?)", [&v1234])?;
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&v1234])?;
|
||||
|
||||
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
|
||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
|
||||
assert_eq!(v, v1234);
|
||||
Ok(())
|
||||
}
|
||||
@@ -165,9 +166,9 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let empty = vec![];
|
||||
db.execute("INSERT INTO foo(b) VALUES (?)", [&empty])?;
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
|
||||
|
||||
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
|
||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
|
||||
assert_eq!(v, empty);
|
||||
Ok(())
|
||||
}
|
||||
@@ -177,9 +178,9 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = "hello, world!";
|
||||
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
|
||||
|
||||
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let from: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(from, s);
|
||||
Ok(())
|
||||
}
|
||||
@@ -189,9 +190,9 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = "hello, world!";
|
||||
db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()])?;
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [s.to_owned()])?;
|
||||
|
||||
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let from: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(from, s);
|
||||
Ok(())
|
||||
}
|
||||
@@ -200,12 +201,9 @@ mod test {
|
||||
fn test_value() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
|
||||
db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
|
||||
|
||||
assert_eq!(
|
||||
10i64,
|
||||
db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(10i64, db.one_column::<i64>("SELECT i FROM foo")?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -213,11 +211,11 @@ mod test {
|
||||
fn test_option() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let s = Some("hello, world!");
|
||||
let s = "hello, world!";
|
||||
let b = Some(vec![1u8, 2, 3, 4]);
|
||||
|
||||
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
|
||||
db.execute("INSERT INTO foo(b) VALUES (?)", [&b])?;
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [Some(s)])?;
|
||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&b])?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
@@ -226,7 +224,7 @@ mod test {
|
||||
let row1 = rows.next()?.unwrap();
|
||||
let s1: Option<String> = row1.get_unwrap(0);
|
||||
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
|
||||
assert_eq!(s.unwrap(), s1.unwrap());
|
||||
assert_eq!(s, s1.unwrap());
|
||||
assert!(b1.is_none());
|
||||
}
|
||||
|
||||
@@ -355,7 +353,7 @@ mod test {
|
||||
assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
|
||||
match row.get::<_, Value>(3)? {
|
||||
Value::Real(val) => assert!((1.5 - val).abs() < f64::EPSILON),
|
||||
x => panic!("Invalid Value {:?}", x),
|
||||
x => panic!("Invalid Value {x:?}"),
|
||||
}
|
||||
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
|
||||
Ok(())
|
||||
|
||||
@@ -1,24 +1,62 @@
|
||||
//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
|
||||
|
||||
use serde_json::Value;
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use crate::Result;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Serialize JSON `Value` to text.
|
||||
/// Serialize JSON `Value` to text:
|
||||
///
|
||||
///
|
||||
/// | JSON | SQLite |
|
||||
/// |----------|---------|
|
||||
/// | Null | NULL |
|
||||
/// | Bool | 'true' / 'false' |
|
||||
/// | Number | INT or REAL except u64 |
|
||||
/// | _ | TEXT |
|
||||
impl ToSql for Value {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
|
||||
match self {
|
||||
Value::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
|
||||
Value::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
|
||||
Value::Number(n) if n.is_f64() => Ok(ToSqlOutput::from(n.as_f64().unwrap())),
|
||||
_ => serde_json::to_string(self)
|
||||
.map(ToSqlOutput::from)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize text/blob to JSON `Value`.
|
||||
/// Deserialize SQLite value to JSON `Value`:
|
||||
///
|
||||
/// | SQLite | JSON |
|
||||
/// |----------|---------|
|
||||
/// | NULL | Null |
|
||||
/// | 'null' | Null |
|
||||
/// | 'true' | Bool |
|
||||
/// | 1 | Number |
|
||||
/// | 0.1 | Number |
|
||||
/// | '"text"' | String |
|
||||
/// | 'text' | _Error_ |
|
||||
/// | '[0, 1]' | Array |
|
||||
/// | '{"x": 1}' | Object |
|
||||
impl FromSql for Value {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
let bytes = value.as_bytes()?;
|
||||
serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||
match value {
|
||||
ValueRef::Text(s) => serde_json::from_slice(s), // KO for b"text"
|
||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||
ValueRef::Integer(i) => Ok(Value::Number(Number::from(i))),
|
||||
ValueRef::Real(f) => {
|
||||
match Number::from_f64(f) {
|
||||
Some(n) => Ok(Value::Number(n)),
|
||||
_ => return Err(FromSqlError::InvalidType), // FIXME
|
||||
}
|
||||
}
|
||||
ValueRef::Null => Ok(Value::Null),
|
||||
}
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +64,7 @@ impl FromSql for Value {
|
||||
mod test {
|
||||
use crate::types::ToSql;
|
||||
use crate::{Connection, Result};
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
@@ -38,16 +77,59 @@ mod test {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let json = r#"{"foo": 13, "bar": "baz"}"#;
|
||||
let data: serde_json::Value = serde_json::from_str(json).unwrap();
|
||||
let data: Value = serde_json::from_str(json).unwrap();
|
||||
db.execute(
|
||||
"INSERT INTO foo (t, b) VALUES (?, ?)",
|
||||
"INSERT INTO foo (t, b) VALUES (?1, ?2)",
|
||||
[&data as &dyn ToSql, &json.as_bytes()],
|
||||
)?;
|
||||
|
||||
let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let t: Value = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(data, t);
|
||||
let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
|
||||
let b: Value = db.one_column("SELECT b FROM foo")?;
|
||||
assert_eq!(data, b);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_sql() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
let v: Option<String> = db.query_row("SELECT ?", [Value::Null], |r| r.get(0))?;
|
||||
assert_eq!(None, v);
|
||||
let v: String = db.query_row("SELECT ?", [Value::Bool(true)], |r| r.get(0))?;
|
||||
assert_eq!("true", v);
|
||||
let v: i64 = db.query_row("SELECT ?", [Value::Number(Number::from(1))], |r| r.get(0))?;
|
||||
assert_eq!(1, v);
|
||||
let v: f64 = db.query_row(
|
||||
"SELECT ?",
|
||||
[Value::Number(Number::from_f64(0.1).unwrap())],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
assert_eq!(0.1, v);
|
||||
let v: String =
|
||||
db.query_row("SELECT ?", [Value::String("text".to_owned())], |r| r.get(0))?;
|
||||
assert_eq!("\"text\"", v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_sql() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
let v: Value = db.one_column("SELECT NULL")?;
|
||||
assert_eq!(Value::Null, v);
|
||||
let v: Value = db.one_column("SELECT 'null'")?;
|
||||
assert_eq!(Value::Null, v);
|
||||
let v: Value = db.one_column("SELECT 'true'")?;
|
||||
assert_eq!(Value::Bool(true), v);
|
||||
let v: Value = db.one_column("SELECT 1")?;
|
||||
assert_eq!(Value::Number(Number::from(1)), v);
|
||||
let v: Value = db.one_column("SELECT 0.1")?;
|
||||
assert_eq!(Value::Number(Number::from_f64(0.1).unwrap()), v);
|
||||
let v: Value = db.one_column("SELECT '\"text\"'")?;
|
||||
assert_eq!(Value::String("text".to_owned()), v);
|
||||
let v: Result<Value> = db.one_column("SELECT 'text'");
|
||||
assert!(v.is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,155 @@
|
||||
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`time::PrimitiveDateTime`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`time::Date`].
|
||||
//! [`ToSql`] and [`FromSql`] implementation for [`time::Time`].
|
||||
//! Time Strings in:
|
||||
//! - Format 2: "YYYY-MM-DD HH:MM"
|
||||
//! - Format 5: "YYYY-MM-DDTHH:MM"
|
||||
//! - Format 8: "HH:MM"
|
||||
//! without an explicit second value will assume 0 seconds.
|
||||
//! Time String that contain an optional timezone without an explicit date are unsupported.
|
||||
//! All other assumptions described in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) section are unsupported.
|
||||
|
||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use crate::{Error, Result};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::format_description::FormatItem;
|
||||
use time::macros::format_description;
|
||||
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
|
||||
const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
|
||||
const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const PRIMITIVE_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
|
||||
);
|
||||
const TIME_ENCODING: &[FormatItem<'_>] =
|
||||
format_description!(version = 2, "[hour]:[minute]:[second].[subsecond]");
|
||||
|
||||
const DATE_FORMAT: &[FormatItem<'_>] = format_description!(version = 2, "[year]-[month]-[day]");
|
||||
const TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
||||
);
|
||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
||||
);
|
||||
const UTC_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][optional [Z]]"
|
||||
);
|
||||
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
|
||||
/// OffsetDatetime => RFC3339 format ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM")
|
||||
impl ToSql for OffsetDateTime {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
// FIXME keep original offset
|
||||
let time_string = self
|
||||
.to_offset(UtcOffset::UTC)
|
||||
.format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
|
||||
.format(&OFFSET_DATE_TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(time_string))
|
||||
}
|
||||
}
|
||||
|
||||
// Supports parsing formats 2-7 from https://www.sqlite.org/lang_datefunc.html
|
||||
// Formats 2-7 without a timezone assumes UTC
|
||||
impl FromSql for OffsetDateTime {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
if s.len() > 10 && s.as_bytes()[10] == b'T' {
|
||||
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
|
||||
return OffsetDateTime::parse(s, &Rfc3339)
|
||||
if let Some(b' ') = s.as_bytes().get(23) {
|
||||
// legacy
|
||||
return OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)));
|
||||
}
|
||||
let s = s.strip_suffix('Z').unwrap_or(s);
|
||||
match s.len() {
|
||||
len if len <= 19 => {
|
||||
// TODO YYYY-MM-DDTHH:MM:SS
|
||||
PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
|
||||
.map(PrimitiveDateTime::assume_utc)
|
||||
}
|
||||
_ if s.as_bytes()[19] == b':' => {
|
||||
// legacy
|
||||
OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
|
||||
}
|
||||
_ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
|
||||
.or_else(|err| {
|
||||
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
|
||||
.map(PrimitiveDateTime::assume_utc)
|
||||
.map_err(|_| err)
|
||||
}),
|
||||
_ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
|
||||
if s[8..].contains('+') || s[8..].contains('-') {
|
||||
// Formats 2-7 with timezone
|
||||
return OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)));
|
||||
}
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||
// Formats 2-7 without timezone
|
||||
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
|
||||
.map(|p| p.assume_utc())
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
||||
impl ToSql for Date {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let date_str = self
|
||||
.format(&DATE_FORMAT)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(date_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
||||
impl FromSql for Date {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
Date::parse(s, &DATE_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
||||
impl ToSql for Time {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let time_str = self
|
||||
.format(&TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(time_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
||||
impl FromSql for Time {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
Time::parse(s, &TIME_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
|
||||
impl ToSql for PrimitiveDateTime {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
let date_time_str = self
|
||||
.format(&PRIMITIVE_DATE_TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
Ok(ToSqlOutput::from(date_time_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// YYYY-MM-DD HH:MM
|
||||
/// YYYY-MM-DDTHH:MM
|
||||
/// YYYY-MM-DD HH:MM:SS
|
||||
/// YYYY-MM-DDTHH:MM:SS
|
||||
/// YYYY-MM-DD HH:MM:SS.SSS
|
||||
/// YYYY-MM-DDTHH:MM:SS.SSS
|
||||
/// => ISO 8601 combined date and time with timezone
|
||||
impl FromSql for PrimitiveDateTime {
|
||||
#[inline]
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(err.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -69,13 +157,18 @@ impl FromSql for OffsetDateTime {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Connection, Result};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use time::macros::{date, datetime, time};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let mut ts_vec = vec![];
|
||||
|
||||
@@ -91,9 +184,9 @@ mod test {
|
||||
ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
|
||||
|
||||
for ts in ts_vec {
|
||||
db.execute("INSERT INTO foo(t) VALUES (?)", [ts])?;
|
||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [ts])?;
|
||||
|
||||
let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
|
||||
let from: OffsetDateTime = db.one_column("SELECT t FROM foo")?;
|
||||
|
||||
db.execute("DELETE FROM foo", [])?;
|
||||
|
||||
@@ -103,47 +196,163 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_values() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
for (s, t) in vec![
|
||||
fn test_offset_date_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let tests = vec![
|
||||
// Rfc3339
|
||||
(
|
||||
"2013-10-07 08:23:19",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123456789Z",
|
||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123456789Z",
|
||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
||||
),
|
||||
// Format 2
|
||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07 08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07 08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
||||
// Format 3
|
||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
("2013-10-07 08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
(
|
||||
"2013-10-07 08:23:19+04:00",
|
||||
datetime!(2013-10-07 8:23:19 +4),
|
||||
),
|
||||
// Format 4
|
||||
(
|
||||
"2013-10-07 08:23:19.123",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07T08:23:19Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.120",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123+04:00",
|
||||
datetime!(2013-10-07 8:23:19.123 +4),
|
||||
),
|
||||
// Format 5
|
||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07T08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
||||
("2013-10-07T08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
||||
// Format 6
|
||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
("2013-10-07T08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
||||
(
|
||||
"2013-10-07T08:23:19+04:00",
|
||||
datetime!(2013-10-07 8:23:19 +4),
|
||||
),
|
||||
// Format 7
|
||||
(
|
||||
"2013-10-07T08:23:19.123",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.120Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07T08:23:19.120Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123+04:00",
|
||||
datetime!(2013-10-07 8:23:19.123 +4),
|
||||
),
|
||||
// Legacy
|
||||
(
|
||||
"2013-10-07 04:23:19-04:00",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:12:987 -07:00",
|
||||
datetime!(2013-10-07 8:23:12.987 -7),
|
||||
),
|
||||
(
|
||||
"2013-10-07 04:23:19.120-04:00",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
|
||||
),
|
||||
(
|
||||
"2013-10-07T04:23:19.120-04:00",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
|
||||
),
|
||||
] {
|
||||
let result: Result<OffsetDateTime> = db.query_row("SELECT ?", [s], |r| r.get(0));
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: OffsetDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let date = date!(2016 - 02 - 23);
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("2016-02-23", s);
|
||||
let t: Date = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(date, t);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let time = time!(23:56:04.00001);
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("23:56:04.00001", s);
|
||||
let v: Time = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(time, v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let dt = date!(2016 - 02 - 23).with_time(time!(23:56:04));
|
||||
|
||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
||||
|
||||
let s: String = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!("2016-02-23 23:56:04.0", s);
|
||||
let v: PrimitiveDateTime = db.one_column("SELECT t FROM foo")?;
|
||||
assert_eq!(dt, v);
|
||||
|
||||
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
|
||||
let hms: PrimitiveDateTime = db.one_column("SELECT b FROM foo")?;
|
||||
assert_eq!(dt, hms);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Date = db.query_row("SELECT ?1", ["2013-10-07"], |r| r.get(0))?;
|
||||
assert_eq!(result, date!(2013 - 10 - 07));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let tests = vec![
|
||||
("08:23", time!(08:23)),
|
||||
("08:23:19", time!(08:23:19)),
|
||||
("08:23:19.111", time!(08:23:19.111)),
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: Time = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let tests = vec![
|
||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23)),
|
||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19)),
|
||||
("2013-10-07T08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23)),
|
||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19)),
|
||||
("2013-10-07 08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: PrimitiveDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
@@ -151,17 +360,66 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_functions() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let result: Result<OffsetDateTime> =
|
||||
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
|
||||
let db = checked_memory_handle()?;
|
||||
db.one_column::<Time>("SELECT CURRENT_TIME").unwrap();
|
||||
db.one_column::<Date>("SELECT CURRENT_DATE").unwrap();
|
||||
db.one_column::<PrimitiveDateTime>("SELECT CURRENT_TIMESTAMP")
|
||||
.unwrap();
|
||||
db.one_column::<OffsetDateTime>("SELECT CURRENT_TIMESTAMP")
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = OffsetDateTime::now_utc().time();
|
||||
let result: Result<bool> = db.query_row(
|
||||
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
|
||||
[now],
|
||||
|r| r.get(0),
|
||||
);
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
|
||||
fn test_date_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = OffsetDateTime::now_utc().date();
|
||||
let result: Result<bool> = db.query_row(
|
||||
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
|
||||
[now],
|
||||
|r| r.get(0),
|
||||
);
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let now = PrimitiveDateTime::new(
|
||||
OffsetDateTime::now_utc().date(),
|
||||
OffsetDateTime::now_utc().time(),
|
||||
);
|
||||
let result: Result<bool> = db.query_row(
|
||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
||||
[now],
|
||||
|r| r.get(0),
|
||||
);
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let result: Result<bool> = db.query_row(
|
||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
||||
[OffsetDateTime::now_utc()],
|
||||
|r| r.get(0),
|
||||
);
|
||||
result.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -51,6 +51,12 @@ macro_rules! from_value(
|
||||
#[inline]
|
||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
||||
}
|
||||
);
|
||||
(non_zero $t:ty) => (
|
||||
impl From<$t> for ToSqlOutput<'_> {
|
||||
#[inline]
|
||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.get().into())}
|
||||
}
|
||||
)
|
||||
);
|
||||
from_value!(String);
|
||||
@@ -68,6 +74,15 @@ from_value!(f32);
|
||||
from_value!(f64);
|
||||
from_value!(Vec<u8>);
|
||||
|
||||
from_value!(non_zero std::num::NonZeroI8);
|
||||
from_value!(non_zero std::num::NonZeroI16);
|
||||
from_value!(non_zero std::num::NonZeroI32);
|
||||
from_value!(non_zero std::num::NonZeroI64);
|
||||
from_value!(non_zero std::num::NonZeroIsize);
|
||||
from_value!(non_zero std::num::NonZeroU8);
|
||||
from_value!(non_zero std::num::NonZeroU16);
|
||||
from_value!(non_zero std::num::NonZeroU32);
|
||||
|
||||
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
|
||||
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
|
||||
// worth adding another case to Value.
|
||||
@@ -75,6 +90,10 @@ from_value!(Vec<u8>);
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
|
||||
from_value!(i128);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
|
||||
from_value!(non_zero std::num::NonZeroI128);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
|
||||
from_value!(uuid::Uuid);
|
||||
@@ -165,10 +184,23 @@ to_sql_self!(u32);
|
||||
to_sql_self!(f32);
|
||||
to_sql_self!(f64);
|
||||
|
||||
to_sql_self!(std::num::NonZeroI8);
|
||||
to_sql_self!(std::num::NonZeroI16);
|
||||
to_sql_self!(std::num::NonZeroI32);
|
||||
to_sql_self!(std::num::NonZeroI64);
|
||||
to_sql_self!(std::num::NonZeroIsize);
|
||||
to_sql_self!(std::num::NonZeroU8);
|
||||
to_sql_self!(std::num::NonZeroU16);
|
||||
to_sql_self!(std::num::NonZeroU32);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
|
||||
to_sql_self!(i128);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
|
||||
to_sql_self!(std::num::NonZeroI128);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
|
||||
to_sql_self!(uuid::Uuid);
|
||||
@@ -186,12 +218,27 @@ macro_rules! to_sql_self_fallible(
|
||||
)))
|
||||
}
|
||||
}
|
||||
);
|
||||
(non_zero $t:ty) => (
|
||||
impl ToSql for $t {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(
|
||||
i64::try_from(self.get()).map_err(
|
||||
// TODO: Include the values in the error message.
|
||||
|err| Error::ToSqlConversionFailure(err.into())
|
||||
)?
|
||||
)))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Special implementations for usize and u64 because these conversions can fail.
|
||||
to_sql_self_fallible!(u64);
|
||||
to_sql_self_fallible!(usize);
|
||||
to_sql_self_fallible!(non_zero std::num::NonZeroU64);
|
||||
to_sql_self_fallible!(non_zero std::num::NonZeroUsize);
|
||||
|
||||
impl<T: ?Sized> ToSql for &'_ T
|
||||
where
|
||||
@@ -267,9 +314,26 @@ mod test {
|
||||
is_to_sql::<i16>();
|
||||
is_to_sql::<i32>();
|
||||
is_to_sql::<i64>();
|
||||
is_to_sql::<isize>();
|
||||
is_to_sql::<u8>();
|
||||
is_to_sql::<u16>();
|
||||
is_to_sql::<u32>();
|
||||
is_to_sql::<u64>();
|
||||
is_to_sql::<usize>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonzero_types() {
|
||||
is_to_sql::<std::num::NonZeroI8>();
|
||||
is_to_sql::<std::num::NonZeroI16>();
|
||||
is_to_sql::<std::num::NonZeroI32>();
|
||||
is_to_sql::<std::num::NonZeroI64>();
|
||||
is_to_sql::<std::num::NonZeroIsize>();
|
||||
is_to_sql::<std::num::NonZeroU8>();
|
||||
is_to_sql::<std::num::NonZeroU16>();
|
||||
is_to_sql::<std::num::NonZeroU32>();
|
||||
is_to_sql::<std::num::NonZeroU64>();
|
||||
is_to_sql::<std::num::NonZeroUsize>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -368,10 +432,10 @@ mod test {
|
||||
db.execute(
|
||||
"
|
||||
INSERT INTO foo(i128, desc) VALUES
|
||||
(?, 'zero'),
|
||||
(?, 'neg one'), (?, 'neg two'),
|
||||
(?, 'pos one'), (?, 'pos two'),
|
||||
(?, 'min'), (?, 'max')",
|
||||
(?1, 'zero'),
|
||||
(?2, 'neg one'), (?3, 'neg two'),
|
||||
(?4, 'pos one'), (?5, 'pos two'),
|
||||
(?6, 'min'), (?7, 'max')",
|
||||
[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
|
||||
)?;
|
||||
|
||||
@@ -398,6 +462,54 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
#[test]
|
||||
fn test_non_zero_i128() -> crate::Result<()> {
|
||||
use std::num::NonZeroI128;
|
||||
macro_rules! nz {
|
||||
($x:expr) => {
|
||||
NonZeroI128::new($x).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let db = crate::Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
|
||||
db.execute(
|
||||
"INSERT INTO foo(i128, desc) VALUES
|
||||
(?1, 'neg one'), (?2, 'neg two'),
|
||||
(?3, 'pos one'), (?4, 'pos two'),
|
||||
(?5, 'min'), (?6, 'max')",
|
||||
[
|
||||
nz!(-1),
|
||||
nz!(-2),
|
||||
nz!(1),
|
||||
nz!(2),
|
||||
nz!(i128::MIN),
|
||||
nz!(i128::MAX),
|
||||
],
|
||||
)?;
|
||||
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
|
||||
|
||||
let res = stmt
|
||||
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
|
||||
.collect::<Result<Vec<(NonZeroI128, String)>, _>>()?;
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
&[
|
||||
(nz!(i128::MIN), "min".to_owned()),
|
||||
(nz!(-2), "neg two".to_owned()),
|
||||
(nz!(-1), "neg one".to_owned()),
|
||||
(nz!(1), "pos one".to_owned()),
|
||||
(nz!(2), "pos two".to_owned()),
|
||||
(nz!(i128::MAX), "max".to_owned()),
|
||||
]
|
||||
);
|
||||
let err = db.query_row("SELECT ?1", [0i128], |row| row.get::<_, NonZeroI128>(0));
|
||||
assert_eq!(err, Err(crate::Error::IntegralValueOutOfRange(0, 0)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
#[test]
|
||||
fn test_uuid() -> crate::Result<()> {
|
||||
@@ -410,11 +522,11 @@ mod test {
|
||||
let id = Uuid::new_v4();
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO foo (id, label) VALUES (?, ?)",
|
||||
"INSERT INTO foo (id, label) VALUES (?1, ?2)",
|
||||
params![id, "target"],
|
||||
)?;
|
||||
|
||||
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
|
||||
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?;
|
||||
|
||||
let mut rows = stmt.query(params![id])?;
|
||||
let row = rows.next()?.unwrap();
|
||||
|
||||
@@ -49,7 +49,7 @@ mod test {
|
||||
let url2 = "http://www.example2.com/👌";
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
|
||||
"INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
|
||||
// also insert a non-hex encoded url (which might be present if it was
|
||||
// inserted separately)
|
||||
params![url0, url1, url2, "illegal"],
|
||||
@@ -74,7 +74,7 @@ mod test {
|
||||
);
|
||||
}
|
||||
e => {
|
||||
panic!("Expected conversion failure, got {}", e);
|
||||
panic!("Expected conversion failure, got {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -158,6 +158,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
impl From<ValueRef<'_>> for Value {
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn from(borrowed: ValueRef<'_>) -> Value {
|
||||
match borrowed {
|
||||
ValueRef::Null => Value::Null,
|
||||
|
||||
@@ -109,8 +109,8 @@ mod test {
|
||||
tx2.commit().unwrap();
|
||||
});
|
||||
assert_eq!(tx.recv().unwrap(), 1);
|
||||
let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", [], |r| r.get(0));
|
||||
assert_eq!(42i64, the_answer?);
|
||||
let the_answer: i64 = db1.one_column("SELECT x FROM foo")?;
|
||||
assert_eq!(42i64, the_answer);
|
||||
child.join().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ pub(crate) use small_cstr::SmallCString;
|
||||
|
||||
// Doesn't use any modern features or vtab stuff, but is only used by them.
|
||||
mod sqlite_string;
|
||||
pub(crate) use sqlite_string::SqliteMallocString;
|
||||
pub(crate) use sqlite_string::{alloc, SqliteMallocString};
|
||||
|
||||
@@ -7,6 +7,12 @@ use std::marker::PhantomData;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// Space to hold this string must be obtained
|
||||
// from an SQLite memory allocation function
|
||||
pub(crate) fn alloc(s: &str) -> *mut c_char {
|
||||
SqliteMallocString::from_str(s).into_raw()
|
||||
}
|
||||
|
||||
/// A string we own that's allocated on the SQLite heap. Automatically calls
|
||||
/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
|
||||
/// on it. If constructed from a rust string, `sqlite3_malloc` is used.
|
||||
@@ -100,7 +106,6 @@ impl SqliteMallocString {
|
||||
/// This means it's safe to use in extern "C" functions even outside of
|
||||
/// `catch_unwind`.
|
||||
pub(crate) fn from_str(s: &str) -> Self {
|
||||
use std::convert::TryFrom;
|
||||
let s = if s.as_bytes().contains(&0) {
|
||||
std::borrow::Cow::Owned(make_nonnull(s))
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! let v = [1i64, 2, 3, 4];
|
||||
//! // Note: A `Rc<Vec<Value>>` must be used as the parameter.
|
||||
//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
|
||||
//! let mut stmt = db.prepare("SELECT value from rarray(?);")?;
|
||||
//! let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
||||
//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
|
||||
//! for value in rows {
|
||||
//! println!("{}", value?);
|
||||
@@ -206,7 +206,7 @@ mod test {
|
||||
let values: Vec<Value> = v.into_iter().map(Value::from).collect();
|
||||
let ptr = Rc::new(values);
|
||||
{
|
||||
let mut stmt = db.prepare("SELECT value from rarray(?);")?;
|
||||
let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
||||
|
||||
let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
|
||||
assert_eq!(2, Rc::strong_count(&ptr));
|
||||
|
||||
@@ -113,10 +113,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
match param {
|
||||
"filename" => {
|
||||
if !Path::new(value).exists() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"file '{}' does not exist",
|
||||
value
|
||||
)));
|
||||
return Err(Error::ModuleError(format!("file '{value}' does not exist")));
|
||||
}
|
||||
vtab.filename = value.to_owned();
|
||||
}
|
||||
@@ -137,8 +134,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
n_col = Some(n);
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'columns': {}",
|
||||
value
|
||||
"unrecognized argument to 'columns': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -147,8 +143,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
vtab.has_headers = b;
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'header': {}",
|
||||
value
|
||||
"unrecognized argument to 'header': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -157,8 +152,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
vtab.delimiter = b;
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'delimiter': {}",
|
||||
value
|
||||
"unrecognized argument to 'delimiter': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -171,15 +165,13 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
}
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'quote': {}",
|
||||
value
|
||||
"unrecognized argument to 'quote': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized parameter '{}'",
|
||||
param
|
||||
"unrecognized parameter '{param}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -326,8 +318,7 @@ unsafe impl VTabCursor for CsvTabCursor<'_> {
|
||||
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
|
||||
if col < 0 || col as usize >= self.cols.len() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"column index out of bounds: {}",
|
||||
col
|
||||
"column index out of bounds: {col}"
|
||||
)));
|
||||
}
|
||||
if self.cols.is_empty() {
|
||||
|
||||
@@ -17,10 +17,11 @@ use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use crate::context::set_result;
|
||||
use crate::error::error_from_sqlite_code;
|
||||
use crate::error::{error_from_sqlite_code, to_sqlite_error};
|
||||
use crate::ffi;
|
||||
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
|
||||
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
|
||||
use crate::util::alloc;
|
||||
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
|
||||
|
||||
// let conn: Connection = ...;
|
||||
@@ -195,6 +196,8 @@ pub enum VTabConfig {
|
||||
Innocuous = 2,
|
||||
/// Equivalent to SQLITE_VTAB_DIRECTONLY
|
||||
DirectOnly = 3,
|
||||
/// Equivalent to SQLITE_VTAB_USES_ALL_SCHEMAS
|
||||
UsesAllSchemas = 4,
|
||||
}
|
||||
|
||||
/// `feature = "vtab"`
|
||||
@@ -619,7 +622,7 @@ impl IndexConstraintUsage<'_> {
|
||||
|
||||
/// `feature = "vtab"`
|
||||
pub struct OrderByIter<'a> {
|
||||
iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
|
||||
iter: slice::Iter<'a, ffi::sqlite3_index_orderby>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for OrderByIter<'a> {
|
||||
@@ -637,7 +640,7 @@ impl<'a> Iterator for OrderByIter<'a> {
|
||||
}
|
||||
|
||||
/// A column of the ORDER BY clause.
|
||||
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
|
||||
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_orderby);
|
||||
|
||||
impl OrderBy<'_> {
|
||||
/// Column number
|
||||
@@ -882,7 +885,7 @@ pub fn dequote(s: &str) -> &str {
|
||||
return s;
|
||||
}
|
||||
match s.bytes().next() {
|
||||
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
|
||||
Some(b) if b == b'"' || b == b'\'' => match s.bytes().next_back() {
|
||||
Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s)
|
||||
_ => s,
|
||||
},
|
||||
@@ -962,8 +965,7 @@ where
|
||||
ffi::SQLITE_OK
|
||||
} else {
|
||||
let err = error_from_sqlite_code(rc, None);
|
||||
*err_msg = alloc(&err.to_string());
|
||||
rc
|
||||
to_sqlite_error(&err, err_msg)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -971,16 +973,7 @@ where
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
},
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
if let Some(s) = s {
|
||||
*err_msg = alloc(&s);
|
||||
}
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = alloc(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
Err(err) => to_sqlite_error(&err, err_msg),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,8 +1007,7 @@ where
|
||||
ffi::SQLITE_OK
|
||||
} else {
|
||||
let err = error_from_sqlite_code(rc, None);
|
||||
*err_msg = alloc(&err.to_string());
|
||||
rc
|
||||
to_sqlite_error(&err, err_msg)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1023,16 +1015,7 @@ where
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
},
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
if let Some(s) = s {
|
||||
*err_msg = alloc(&s);
|
||||
}
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = alloc(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
Err(err) => to_sqlite_error(&err, err_msg),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1309,12 +1292,6 @@ unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) ->
|
||||
}
|
||||
}
|
||||
|
||||
// Space to hold this string must be obtained
|
||||
// from an SQLite memory allocation function
|
||||
fn alloc(s: &str) -> *mut c_char {
|
||||
crate::util::SqliteMallocString::from_str(s).into_raw()
|
||||
}
|
||||
|
||||
#[cfg(feature = "array")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
|
||||
pub mod array;
|
||||
|
||||
@@ -28,6 +28,7 @@ const SERIES_COLUMN_STOP: c_int = 2;
|
||||
const SERIES_COLUMN_STEP: c_int = 3;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct QueryPlanFlags: ::std::os::raw::c_int {
|
||||
// start = $value -- constraint exists
|
||||
@@ -41,7 +42,7 @@ bitflags::bitflags! {
|
||||
// output in ascending order
|
||||
const ASC = 16;
|
||||
// Both start and stop
|
||||
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
|
||||
const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +116,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::BOTH) {
|
||||
// Both start= and stop= boundaries are available.
|
||||
//#[allow(clippy::bool_to_int_with_if)]
|
||||
#[allow(clippy::bool_to_int_with_if)]
|
||||
info.set_estimated_cost(f64::from(
|
||||
2 - if idx_num.contains(QueryPlanFlags::STEP) {
|
||||
1
|
||||
@@ -199,19 +200,19 @@ unsafe impl VTabCursor for SeriesTabCursor<'_> {
|
||||
let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
|
||||
let mut i = 0;
|
||||
if idx_num.contains(QueryPlanFlags::START) {
|
||||
self.min_value = args.get(i)?;
|
||||
self.min_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.min_value = 0;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STOP) {
|
||||
self.max_value = args.get(i)?;
|
||||
self.max_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.max_value = 0xffff_ffff;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STEP) {
|
||||
self.step = args.get(i)?;
|
||||
self.step = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
if self.step == 0 {
|
||||
self.step = 1;
|
||||
} else if self.step < 0 {
|
||||
@@ -315,6 +316,26 @@ mod test {
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
|
||||
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
let empty = Vec::<i32>::new();
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,10,NULL)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL,10,2)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL,2)")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL) ORDER BY value DESC")?;
|
||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
||||
//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
||||
use std::default::Default;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_int;
|
||||
@@ -56,8 +56,7 @@ impl VTabLog {
|
||||
"schema" => {
|
||||
if schema.is_some() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"more than one '{}' parameter",
|
||||
param
|
||||
"more than one '{param}' parameter"
|
||||
)));
|
||||
}
|
||||
schema = Some(value.to_owned())
|
||||
@@ -65,8 +64,7 @@ impl VTabLog {
|
||||
"rows" => {
|
||||
if n_row.is_some() {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"more than one '{}' parameter",
|
||||
param
|
||||
"more than one '{param}' parameter"
|
||||
)));
|
||||
}
|
||||
if let Ok(n) = i64::from_str(value) {
|
||||
@@ -75,8 +73,7 @@ impl VTabLog {
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized parameter '{}'",
|
||||
param
|
||||
"unrecognized parameter '{param}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -286,13 +283,13 @@ mod test {
|
||||
let mut stmt = db.prepare("SELECT * FROM log;")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
while rows.next()?.is_some() {}
|
||||
db.execute("DELETE FROM log WHERE a = ?", ["a1"])?;
|
||||
db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
|
||||
db.execute(
|
||||
"INSERT INTO log (a, b, c) VALUES (?, ?, ?)",
|
||||
"INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
|
||||
["a", "b", "c"],
|
||||
)?;
|
||||
db.execute(
|
||||
"UPDATE log SET b = ?, c = ? WHERE a = ?",
|
||||
"UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
|
||||
["bn", "cn", "a1"],
|
||||
)?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user