Merge branch 'master' into sub_type

This commit is contained in:
gwenn
2023-08-19 12:46:57 +02:00
committed by GitHub
55 changed files with 13369 additions and 6451 deletions

View File

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

View File

@@ -134,7 +134,7 @@
//! // Insert another BLOB, this time using a parameter passed in from
//! // rust (potentially with a dynamic size).
//! db.execute(
//! "INSERT INTO test_table (content) VALUES (?)",
//! "INSERT INTO test_table (content) VALUES (?1)",
//! [ZeroBlob(64)],
//! )?;
//!
@@ -175,7 +175,7 @@
//! // Insert another blob, this time using a parameter passed in from
//! // rust (potentially with a dynamic size).
//! db.execute(
//! "INSERT INTO test_table (content) VALUES (?)",
//! "INSERT INTO test_table (content) VALUES (?1)",
//! [ZeroBlob(64)],
//! )?;
//!
@@ -274,7 +274,6 @@ impl Blob<'_> {
#[inline]
#[must_use]
pub fn len(&self) -> usize {
use std::convert::TryInto;
self.size().try_into().unwrap()
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
//! Prepared statements cache for faster execution.
use crate::raw_statement::RawStatement;
use crate::{Connection, Result, Statement};
use crate::{Connection, PrepFlags, Result, Statement};
use hashlink::LruCache;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
@@ -17,13 +17,13 @@ impl Connection {
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// {
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
/// stmt.execute(["Joe Smith"])?;
/// }
/// {
/// // This will return the same underlying SQLite statement handle without
/// // having to prepare it again.
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
/// stmt.execute(["Bob Jones"])?;
/// }
/// Ok(())
@@ -144,7 +144,7 @@ impl StatementCache {
let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(trimmed) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
None => conn.prepare(trimmed),
None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT),
};
stmt.map(|mut stmt| {
stmt.stmt.set_statement_cache_key(trimmed);

View File

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

View File

@@ -61,6 +61,13 @@ pub enum DbConfig {
/// sqlite_master tables) are untainted by malicious content.
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0
/// Sets or clears a flag that enables collection of the
/// sqlite3_stmt_scanstatus_v2() statistics
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018, // 3.42.0
/// Changes the default order in which tables and indexes are scanned
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019, // 3.42.0
}
impl Connection {

View File

@@ -252,11 +252,7 @@ impl fmt::Display for Error {
),
Error::FromSqlConversionFailure(i, ref t, ref err) => {
if i != UNKNOWN_COLUMN {
write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
)
write!(f, "Conversion error from type {t} at index: {i}, {err}")
} else {
err.fmt(f)
}
@@ -278,15 +274,12 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
Error::InvalidColumnType(i, ref name, ref t) => write!(
f,
"Invalid column type {} at index: {}, name: {}",
t, i, name
),
Error::InvalidColumnType(i, ref name, ref t) => {
write!(f, "Invalid column type {t} at index: {i}, name: {name}")
}
Error::InvalidParameterCount(i1, n1) => write!(
f,
"Wrong number of parameters passed to query. Got {}, needed {}",
i1, n1
"Wrong number of parameters passed to query. Got {i1}, needed {n1}"
),
Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
@@ -393,7 +386,6 @@ impl Error {
#[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
// TODO sqlite3_error_offset // 3.38.0, #1130
Error::SqliteFailure(ffi::Error::new(code), message)
}
@@ -443,3 +435,25 @@ pub fn check(code: c_int) -> Result<()> {
Ok(())
}
}
/// Transform Rust error to SQLite error (message and code).
/// # Safety
/// This function is unsafe because it uses raw pointer
pub unsafe fn to_sqlite_error(
e: &Error,
err_msg: *mut *mut std::os::raw::c_char,
) -> std::os::raw::c_int {
use crate::util::alloc;
match e {
Error::SqliteFailure(err, s) => {
if let Some(s) = s {
*err_msg = alloc(s);
}
err.extended_code
}
err => {
*err_msg = alloc(&err.to_string());
ffi::SQLITE_ERROR
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -70,7 +70,7 @@ use sealed::Sealed;
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, params};
/// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?;
///
/// // Using a tuple:
/// stmt.execute((0, "foobar"))?;
@@ -138,9 +138,7 @@ use sealed::Sealed;
/// ## No parameters
///
/// You can just use an empty tuple or the empty array literal to run a query
/// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
/// common in previous versions of this library is no longer needed, and is now
/// deprecated).
/// that accepts no parameters.
///
/// ### Example (no parameters)
///
@@ -192,8 +190,7 @@ pub trait Params: Sealed {
}
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API.
// unambiguous, this must be the *only* implementation for an empty array.
//
// This sadly prevents `impl<T: ToSql, const N: usize> Params for [T; N]`, which
// forces people to use `params![...]` or `rusqlite::params_from_iter` for long
@@ -357,7 +354,7 @@ impl_for_array_ref!(
/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
/// assert_eq!(ids.len(), 3, "Unrealistic sample code");
///
/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?;
/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?1, ?2, ?3)")?;
/// let _rows = stmt.query(params_from_iter(ids.iter()))?;
///
/// // use _rows...

View File

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

View File

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

View File

@@ -257,6 +257,7 @@ impl<'stmt> Row<'stmt> {
/// * If the underlying SQLite integral value is outside the range
/// representable by `T`
/// * If `idx` is outside the range of columns in the returned query
#[track_caller]
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get(idx).unwrap()
}
@@ -277,6 +278,7 @@ impl<'stmt> Row<'stmt> {
/// If the result type is i128 (which requires the `i128_blob` feature to be
/// enabled), and the underlying SQLite column is a blob whose size is not
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
#[track_caller]
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
let idx = idx.idx(self.stmt)?;
let value = self.stmt.value_ref(idx);
@@ -335,23 +337,10 @@ impl<'stmt> Row<'stmt> {
///
/// * If `idx` is outside the range of columns in the returned query.
/// * If `idx` is not a valid column name for this row.
#[track_caller]
pub fn get_ref_unwrap<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
self.get_ref(idx).unwrap()
}
/// Renamed to [`get_ref`](Row::get_ref).
#[deprecated = "Use [`get_ref`](Row::get_ref) instead."]
#[inline]
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
self.get_ref(idx)
}
/// Renamed to [`get_ref_unwrap`](Row::get_ref_unwrap).
#[deprecated = "Use [`get_ref_unwrap`](Row::get_ref_unwrap) instead."]
#[inline]
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
self.get_ref_unwrap(idx)
}
}
impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
@@ -360,6 +349,46 @@ impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
}
}
/// Debug `Row` like an ordered `Map<Result<&str>, Result<(Type, ValueRef)>>`
/// with column name as key except that for `Type::Blob` only its size is
/// printed (not its content).
impl<'stmt> std::fmt::Debug for Row<'stmt> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dm = f.debug_map();
for c in 0..self.stmt.column_count() {
let name = self.stmt.column_name(c);
dm.key(&name);
let value = self.get_ref(c);
match value {
Ok(value) => {
let dt = value.data_type();
match value {
ValueRef::Null => {
dm.value(&(dt, ()));
}
ValueRef::Integer(i) => {
dm.value(&(dt, i));
}
ValueRef::Real(f) => {
dm.value(&(dt, f));
}
ValueRef::Text(s) => {
dm.value(&(dt, String::from_utf8_lossy(s)));
}
ValueRef::Blob(b) => {
dm.value(&(dt, b.len()));
}
}
}
Err(ref _err) => {
dm.value(&value);
}
}
}
dm.finish()
}
}
mod sealed {
/// This trait exists just to ensure that the only impls of `trait Params`
/// that are allowed are ones in this crate.

162
src/serialize.rs Normal file
View File

@@ -0,0 +1,162 @@
//! Serialize a database.
use std::convert::TryInto;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use crate::error::error_from_handle;
use crate::ffi;
use crate::{Connection, DatabaseName, Result};
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
pub struct SharedData<'conn> {
phantom: PhantomData<&'conn Connection>,
ptr: NonNull<u8>,
sz: usize,
}
/// Owned serialized database
pub struct OwnedData {
ptr: NonNull<u8>,
sz: usize,
}
impl OwnedData {
/// SAFETY: Caller must be certain that `ptr` is allocated by
/// `sqlite3_malloc`.
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self {
Self { ptr, sz }
}
fn into_raw(self) -> (*mut u8, usize) {
let raw = (self.ptr.as_ptr(), self.sz);
std::mem::forget(self);
raw
}
}
impl Drop for OwnedData {
fn drop(&mut self) {
unsafe {
ffi::sqlite3_free(self.ptr.as_ptr().cast());
}
}
}
/// Serialized database
pub enum Data<'conn> {
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
Shared(SharedData<'conn>),
/// Owned serialized database
Owned(OwnedData),
}
impl<'conn> Deref for Data<'conn> {
type Target = [u8];
fn deref(&self) -> &[u8] {
let (ptr, sz) = match self {
Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz),
Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz),
};
unsafe { std::slice::from_raw_parts(ptr, sz) }
}
}
impl Connection {
/// Serialize a database.
pub fn serialize<'conn>(&'conn self, schema: DatabaseName<'_>) -> Result<Data<'conn>> {
let schema = schema.as_cstring()?;
let mut sz = 0;
let mut ptr: *mut u8 = unsafe {
ffi::sqlite3_serialize(
self.handle(),
schema.as_ptr(),
&mut sz,
ffi::SQLITE_SERIALIZE_NOCOPY,
)
};
Ok(if ptr.is_null() {
ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) };
if ptr.is_null() {
return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) });
}
Data::Owned(OwnedData {
ptr: NonNull::new(ptr).unwrap(),
sz: sz.try_into().unwrap(),
})
} else {
// shared buffer
Data::Shared(SharedData {
ptr: NonNull::new(ptr).unwrap(),
sz: sz.try_into().unwrap(),
phantom: PhantomData,
})
})
}
/// Deserialize a database.
pub fn deserialize(
&mut self,
schema: DatabaseName<'_>,
data: OwnedData,
read_only: bool,
) -> Result<()> {
let schema = schema.as_cstring()?;
let (data, sz) = data.into_raw();
let sz = sz.try_into().unwrap();
let flags = if read_only {
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY
} else {
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE
};
let rc = unsafe {
ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags)
};
if rc != ffi::SQLITE_OK {
// TODO sqlite3_free(data) ?
return Err(unsafe { error_from_handle(self.handle(), rc) });
}
/* TODO
if let Some(mxSize) = mxSize {
unsafe {
ffi::sqlite3_file_control(
self.handle(),
schema.as_ptr(),
ffi::SQLITE_FCNTL_SIZE_LIMIT,
&mut mxSize,
)
};
}*/
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Connection, DatabaseName, Result};
#[test]
fn serialize() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = db.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else { panic!("expected OwnedData")};
assert!(data.sz > 0);
Ok(())
}
#[test]
fn deserialize() -> Result<()> {
let src = Connection::open_in_memory()?;
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = src.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else { panic!("expected OwnedData")};
let mut dst = Connection::open_in_memory()?;
dst.deserialize(DatabaseName::Main, data, false)?;
dst.execute("DELETE FROM x", [])?;
Ok(())
}
}

View File

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

View File

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

View File

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

View File

@@ -91,7 +91,6 @@ pub struct Transaction<'conn> {
pub struct Savepoint<'conn> {
conn: &'conn Connection,
name: String,
depth: u32,
drop_behavior: DropBehavior,
committed: bool,
}
@@ -158,13 +157,13 @@ impl Transaction<'_> {
/// ```
#[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, 1)
Savepoint::new_(self.conn)
}
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
#[inline]
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(self.conn, 1, name)
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the transaction when it is
@@ -249,50 +248,44 @@ impl Drop for Transaction<'_> {
impl Savepoint<'_> {
#[inline]
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
name: T,
) -> Result<Savepoint<'_>> {
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {name}"))
.map(|_| Savepoint {
conn,
name,
depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
})
}
#[inline]
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
let name = format!("_rusqlite_sp_{depth}");
Savepoint::with_depth_and_name(conn, depth, name)
fn new_(conn: &Connection) -> Result<Savepoint<'_>> {
Savepoint::with_name_(conn, "_rusqlite_sp")
}
/// Begin a new savepoint. Can be nested.
#[inline]
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
Savepoint::with_depth(conn, 0)
Savepoint::new_(conn)
}
/// Begin a new savepoint with a user-provided savepoint name.
#[inline]
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(conn, 0, name)
Savepoint::with_name_(conn, name)
}
/// Begin a nested savepoint.
#[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, self.depth + 1)
Savepoint::new_(self.conn)
}
/// Begin a nested savepoint with a user-provided savepoint name.
#[inline]
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the savepoint when it is
@@ -351,8 +344,10 @@ impl Savepoint<'_> {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
DropBehavior::Rollback => self.rollback(),
DropBehavior::Commit => self
.commit_()
.or_else(|_| self.rollback().and_then(|_| self.commit_())),
DropBehavior::Rollback => self.rollback().and_then(|_| self.commit_()),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
}
@@ -552,10 +547,7 @@ mod test {
}
{
let tx = db.transaction()?;
assert_eq!(
2i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
}
Ok(())
}
@@ -566,7 +558,7 @@ mod test {
assert_eq!(e.code, crate::ErrorCode::Unknown);
assert!(m.contains("transaction"));
} else {
panic!("Unexpected error type: {:?}", e);
panic!("Unexpected error type: {e:?}");
}
}
@@ -591,10 +583,7 @@ mod test {
tx.commit()?;
}
assert_eq!(
2i32,
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
Ok(())
}
@@ -619,10 +608,7 @@ mod test {
}
{
let tx = db.transaction()?;
assert_eq!(
6i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
}
Ok(())
}
@@ -684,6 +670,40 @@ mod test {
Ok(())
}
#[test]
fn test_savepoint_drop_behavior_releases() -> Result<()> {
let mut db = checked_memory_handle()?;
{
let mut sp = db.savepoint()?;
sp.set_drop_behavior(DropBehavior::Commit);
}
assert!(db.is_autocommit());
{
let mut sp = db.savepoint()?;
sp.set_drop_behavior(DropBehavior::Rollback);
}
assert!(db.is_autocommit());
Ok(())
}
#[test]
fn test_savepoint_release_error() -> Result<()> {
let mut db = checked_memory_handle()?;
db.pragma_update(None, "foreign_keys", true)?;
db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
{
let mut sp = db.savepoint()?;
sp.execute("INSERT INTO f VALUES (0)", [])?;
sp.set_drop_behavior(DropBehavior::Commit);
}
assert!(db.is_autocommit());
Ok(())
}
#[test]
fn test_savepoint_names() -> Result<()> {
let mut db = checked_memory_handle()?;
@@ -727,11 +747,11 @@ mod test {
}
fn insert(x: i32, conn: &Connection) -> Result<usize> {
conn.execute("INSERT INTO foo VALUES(?)", [x])
conn.execute("INSERT INTO foo VALUES(?1)", [x])
}
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?;
assert_eq!(x, i);
Ok(())
}

View File

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

View File

@@ -59,8 +59,7 @@ impl fmt::Display for FromSqlError {
} => {
write!(
f,
"Cannot read {} byte value out of {} byte blob",
expected_size, blob_size
"Cannot read {expected_size} byte value out of {blob_size} byte blob"
)
}
FromSqlError::Other(ref err) => err.fmt(f),
@@ -96,6 +95,15 @@ macro_rules! from_sql_integral(
i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
}
}
);
(non_zero $nz:ty, $z:ty) => (
impl FromSql for $nz {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let i = <$z>::column_result(value)?;
<$nz>::new(i).ok_or(FromSqlError::OutOfRange(0))
}
}
)
);
@@ -110,6 +118,22 @@ from_sql_integral!(u32);
from_sql_integral!(u64);
from_sql_integral!(usize);
from_sql_integral!(non_zero std::num::NonZeroIsize, isize);
from_sql_integral!(non_zero std::num::NonZeroI8, i8);
from_sql_integral!(non_zero std::num::NonZeroI16, i16);
from_sql_integral!(non_zero std::num::NonZeroI32, i32);
from_sql_integral!(non_zero std::num::NonZeroI64, i64);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_sql_integral!(non_zero std::num::NonZeroI128, i128);
from_sql_integral!(non_zero std::num::NonZeroUsize, usize);
from_sql_integral!(non_zero std::num::NonZeroU8, u8);
from_sql_integral!(non_zero std::num::NonZeroU16, u16);
from_sql_integral!(non_zero std::num::NonZeroU32, u32);
from_sql_integral!(non_zero std::num::NonZeroU64, u64);
// std::num::NonZeroU128 is not supported since u128 isn't either
impl FromSql for i64 {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
@@ -244,17 +268,17 @@ mod test {
{
for n in out_of_range {
let err = db
.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
.unwrap_err();
match err {
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
_ => panic!("unexpected error: {}", err),
_ => panic!("unexpected error: {err}"),
}
}
for n in in_range {
assert_eq!(
*n,
db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0))
db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
.unwrap()
.into()
);
@@ -273,4 +297,70 @@ mod test {
check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
Ok(())
}
#[test]
fn test_nonzero_ranges() -> Result<()> {
let db = Connection::open_in_memory()?;
macro_rules! check_ranges {
($nz:ty, $out_of_range:expr, $in_range:expr) => {
for &n in $out_of_range {
assert_eq!(
db.query_row("SELECT ?1", [n], |r| r.get::<_, $nz>(0)),
Err(Error::IntegralValueOutOfRange(0, n)),
"{}",
std::any::type_name::<$nz>()
);
}
for &n in $in_range {
let non_zero = <$nz>::new(n).unwrap();
assert_eq!(
Ok(non_zero),
db.query_row("SELECT ?1", [non_zero], |r| r.get::<_, $nz>(0))
);
}
};
}
check_ranges!(std::num::NonZeroI8, &[0, -129, 128], &[-128, 1, 127]);
check_ranges!(
std::num::NonZeroI16,
&[0, -32769, 32768],
&[-32768, -1, 1, 32767]
);
check_ranges!(
std::num::NonZeroI32,
&[0, -2_147_483_649, 2_147_483_648],
&[-2_147_483_648, -1, 1, 2_147_483_647]
);
check_ranges!(
std::num::NonZeroI64,
&[0],
&[-2_147_483_648, -1, 1, 2_147_483_647, i64::MAX, i64::MIN]
);
check_ranges!(
std::num::NonZeroIsize,
&[0],
&[-2_147_483_648, -1, 1, 2_147_483_647]
);
check_ranges!(std::num::NonZeroU8, &[0, -2, -1, 256], &[1, 255]);
check_ranges!(std::num::NonZeroU16, &[0, -2, -1, 65536], &[1, 65535]);
check_ranges!(
std::num::NonZeroU32,
&[0, -2, -1, 4_294_967_296],
&[1, 4_294_967_295]
);
check_ranges!(
std::num::NonZeroU64,
&[0, -2, -1, -4_294_967_296],
&[1, 4_294_967_295, i64::MAX as u64]
);
check_ranges!(
std::num::NonZeroUsize,
&[0, -2, -1, -4_294_967_296],
&[1, 4_294_967_295]
);
Ok(())
}
}

View File

@@ -26,7 +26,8 @@
//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
//! types, so if you store an `i64` in a column with type `REAL` it will be
//! stored as an `INTEGER`, not a `REAL`.
//! stored as an `INTEGER`, not a `REAL` (unless the column is part of a
//! [STRICT table](https://www.sqlite.org/stricttables.html)).
//!
//! If the `time` feature is enabled, implementations are
//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
@@ -102,7 +103,7 @@ mod value_ref;
/// # use rusqlite::types::{Null};
///
/// fn insert_null(conn: &Connection) -> Result<usize> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
/// conn.execute("INSERT INTO people (name) VALUES (?1)", [Null])
/// }
/// ```
#[derive(Copy, Clone)]
@@ -153,9 +154,9 @@ mod test {
let db = checked_memory_handle()?;
let v1234 = vec![1u8, 2, 3, 4];
db.execute("INSERT INTO foo(b) VALUES (?)", [&v1234])?;
db.execute("INSERT INTO foo(b) VALUES (?1)", [&v1234])?;
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
assert_eq!(v, v1234);
Ok(())
}
@@ -165,9 +166,9 @@ mod test {
let db = checked_memory_handle()?;
let empty = vec![];
db.execute("INSERT INTO foo(b) VALUES (?)", [&empty])?;
db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
assert_eq!(v, empty);
Ok(())
}
@@ -177,9 +178,9 @@ mod test {
let db = checked_memory_handle()?;
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: String = db.one_column("SELECT t FROM foo")?;
assert_eq!(from, s);
Ok(())
}
@@ -189,9 +190,9 @@ mod test {
let db = checked_memory_handle()?;
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [s.to_owned()])?;
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: String = db.one_column("SELECT t FROM foo")?;
assert_eq!(from, s);
Ok(())
}
@@ -200,12 +201,9 @@ mod test {
fn test_value() -> Result<()> {
let db = checked_memory_handle()?;
db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
assert_eq!(
10i64,
db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
);
assert_eq!(10i64, db.one_column::<i64>("SELECT i FROM foo")?);
Ok(())
}
@@ -213,11 +211,11 @@ mod test {
fn test_option() -> Result<()> {
let db = checked_memory_handle()?;
let s = Some("hello, world!");
let s = "hello, world!";
let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
db.execute("INSERT INTO foo(b) VALUES (?)", [&b])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [Some(s)])?;
db.execute("INSERT INTO foo(b) VALUES (?1)", [&b])?;
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
let mut rows = stmt.query([])?;
@@ -226,7 +224,7 @@ mod test {
let row1 = rows.next()?.unwrap();
let s1: Option<String> = row1.get_unwrap(0);
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
assert_eq!(s.unwrap(), s1.unwrap());
assert_eq!(s, s1.unwrap());
assert!(b1.is_none());
}
@@ -355,7 +353,7 @@ mod test {
assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
match row.get::<_, Value>(3)? {
Value::Real(val) => assert!((1.5 - val).abs() < f64::EPSILON),
x => panic!("Invalid Value {:?}", x),
x => panic!("Invalid Value {x:?}"),
}
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
Ok(())

View File

@@ -1,24 +1,62 @@
//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
use serde_json::Value;
use serde_json::{Number, Value};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
use crate::{Error, Result};
/// Serialize JSON `Value` to text.
/// Serialize JSON `Value` to text:
///
///
/// | JSON | SQLite |
/// |----------|---------|
/// | Null | NULL |
/// | Bool | 'true' / 'false' |
/// | Number | INT or REAL except u64 |
/// | _ | TEXT |
impl ToSql for Value {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
match self {
Value::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
Value::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
Value::Number(n) if n.is_f64() => Ok(ToSqlOutput::from(n.as_f64().unwrap())),
_ => serde_json::to_string(self)
.map(ToSqlOutput::from)
.map_err(|err| Error::ToSqlConversionFailure(err.into())),
}
}
}
/// Deserialize text/blob to JSON `Value`.
/// Deserialize SQLite value to JSON `Value`:
///
/// | SQLite | JSON |
/// |----------|---------|
/// | NULL | Null |
/// | 'null' | Null |
/// | 'true' | Bool |
/// | 1 | Number |
/// | 0.1 | Number |
/// | '"text"' | String |
/// | 'text' | _Error_ |
/// | '[0, 1]' | Array |
/// | '{"x": 1}' | Object |
impl FromSql for Value {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let bytes = value.as_bytes()?;
serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
match value {
ValueRef::Text(s) => serde_json::from_slice(s), // KO for b"text"
ValueRef::Blob(b) => serde_json::from_slice(b),
ValueRef::Integer(i) => Ok(Value::Number(Number::from(i))),
ValueRef::Real(f) => {
match Number::from_f64(f) {
Some(n) => Ok(Value::Number(n)),
_ => return Err(FromSqlError::InvalidType), // FIXME
}
}
ValueRef::Null => Ok(Value::Null),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
@@ -26,6 +64,7 @@ impl FromSql for Value {
mod test {
use crate::types::ToSql;
use crate::{Connection, Result};
use serde_json::{Number, Value};
fn checked_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
@@ -38,16 +77,59 @@ mod test {
let db = checked_memory_handle()?;
let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap();
let data: Value = serde_json::from_str(json).unwrap();
db.execute(
"INSERT INTO foo (t, b) VALUES (?, ?)",
"INSERT INTO foo (t, b) VALUES (?1, ?2)",
[&data as &dyn ToSql, &json.as_bytes()],
)?;
let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let t: Value = db.one_column("SELECT t FROM foo")?;
assert_eq!(data, t);
let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
let b: Value = db.one_column("SELECT b FROM foo")?;
assert_eq!(data, b);
Ok(())
}
#[test]
fn test_to_sql() -> Result<()> {
let db = Connection::open_in_memory()?;
let v: Option<String> = db.query_row("SELECT ?", [Value::Null], |r| r.get(0))?;
assert_eq!(None, v);
let v: String = db.query_row("SELECT ?", [Value::Bool(true)], |r| r.get(0))?;
assert_eq!("true", v);
let v: i64 = db.query_row("SELECT ?", [Value::Number(Number::from(1))], |r| r.get(0))?;
assert_eq!(1, v);
let v: f64 = db.query_row(
"SELECT ?",
[Value::Number(Number::from_f64(0.1).unwrap())],
|r| r.get(0),
)?;
assert_eq!(0.1, v);
let v: String =
db.query_row("SELECT ?", [Value::String("text".to_owned())], |r| r.get(0))?;
assert_eq!("\"text\"", v);
Ok(())
}
#[test]
fn test_from_sql() -> Result<()> {
let db = Connection::open_in_memory()?;
let v: Value = db.one_column("SELECT NULL")?;
assert_eq!(Value::Null, v);
let v: Value = db.one_column("SELECT 'null'")?;
assert_eq!(Value::Null, v);
let v: Value = db.one_column("SELECT 'true'")?;
assert_eq!(Value::Bool(true), v);
let v: Value = db.one_column("SELECT 1")?;
assert_eq!(Value::Number(Number::from(1)), v);
let v: Value = db.one_column("SELECT 0.1")?;
assert_eq!(Value::Number(Number::from_f64(0.1).unwrap()), v);
let v: Value = db.one_column("SELECT '\"text\"'")?;
assert_eq!(Value::String("text".to_owned()), v);
let v: Result<Value> = db.one_column("SELECT 'text'");
assert!(v.is_err());
Ok(())
}
}

View File

@@ -1,67 +1,155 @@
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::PrimitiveDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::Date`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::Time`].
//! Time Strings in:
//! - Format 2: "YYYY-MM-DD HH:MM"
//! - Format 5: "YYYY-MM-DDTHH:MM"
//! - Format 8: "HH:MM"
//! without an explicit second value will assume 0 seconds.
//! Time String that contain an optional timezone without an explicit date are unsupported.
//! All other assumptions described in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) section are unsupported.
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::{Error, Result};
use time::format_description::well_known::Rfc3339;
use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
);
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
);
const PRIMITIVE_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
);
const TIME_ENCODING: &[FormatItem<'_>] =
format_description!(version = 2, "[hour]:[minute]:[second].[subsecond]");
const DATE_FORMAT: &[FormatItem<'_>] = format_description!(version = 2, "[year]-[month]-[day]");
const TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
);
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
);
const UTC_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][optional [Z]]"
);
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][offset_hour sign:mandatory]:[offset_minute]"
);
const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
);
/// OffsetDatetime => RFC3339 format ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM")
impl ToSql for OffsetDateTime {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
// FIXME keep original offset
let time_string = self
.to_offset(UtcOffset::UTC)
.format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
.format(&OFFSET_DATE_TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_string))
}
}
// Supports parsing formats 2-7 from https://www.sqlite.org/lang_datefunc.html
// Formats 2-7 without a timezone assumes UTC
impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
if s.len() > 10 && s.as_bytes()[10] == b'T' {
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
return OffsetDateTime::parse(s, &Rfc3339)
if let Some(b' ') = s.as_bytes().get(23) {
// legacy
return OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(Box::new(err)));
}
let s = s.strip_suffix('Z').unwrap_or(s);
match s.len() {
len if len <= 19 => {
// TODO YYYY-MM-DDTHH:MM:SS
PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
}
_ if s.as_bytes()[19] == b':' => {
// legacy
OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
}
_ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.or_else(|err| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
.map_err(|_| err)
}),
_ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
if s[8..].contains('+') || s[8..].contains('-') {
// Formats 2-7 with timezone
return OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(Box::new(err)));
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
// Formats 2-7 without timezone
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
.map(|p| p.assume_utc())
.map_err(|err| FromSqlError::Other(Box::new(err)))
})
}
}
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for Date {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self
.format(&DATE_FORMAT)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(date_str))
}
}
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for Date {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
Date::parse(s, &DATE_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
})
}
}
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for Time {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_str = self
.format(&TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_str))
}
}
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for Time {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
Time::parse(s, &TIME_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
})
}
}
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for PrimitiveDateTime {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_time_str = self
.format(&PRIMITIVE_DATE_TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(date_time_str))
}
}
/// YYYY-MM-DD HH:MM
/// YYYY-MM-DDTHH:MM
/// YYYY-MM-DD HH:MM:SS
/// YYYY-MM-DDTHH:MM:SS
/// YYYY-MM-DD HH:MM:SS.SSS
/// YYYY-MM-DDTHH:MM:SS.SSS
/// => ISO 8601 combined date and time with timezone
impl FromSql for PrimitiveDateTime {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(err.into()))
})
}
}
@@ -69,13 +157,18 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
use crate::{Connection, Result};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
use time::macros::{date, datetime, time};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
fn checked_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
Ok(db)
}
#[test]
fn test_offset_date_time() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
let db = checked_memory_handle()?;
let mut ts_vec = vec![];
@@ -91,9 +184,9 @@ mod test {
ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", [ts])?;
db.execute("INSERT INTO foo(t) VALUES (?1)", [ts])?;
let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
let from: OffsetDateTime = db.one_column("SELECT t FROM foo")?;
db.execute("DELETE FROM foo", [])?;
@@ -103,47 +196,163 @@ mod test {
}
#[test]
fn test_string_values() -> Result<()> {
let db = Connection::open_in_memory()?;
for (s, t) in vec![
fn test_offset_date_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
// Rfc3339
(
"2013-10-07 08:23:19",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123456789Z",
datetime!(2013-10-07 8:23:19.123456789 UTC),
),
(
"2013-10-07 08:23:19Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"2013-10-07 08:23:19.123456789Z",
datetime!(2013-10-07 8:23:19.123456789 UTC),
),
// Format 2
("2013-10-07 08:23", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07 08:23Z", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07 08:23+04:00", datetime!(2013-10-07 8:23 +4)),
// Format 3
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
("2013-10-07 08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
(
"2013-10-07 08:23:19+04:00",
datetime!(2013-10-07 8:23:19 +4),
),
// Format 4
(
"2013-10-07 08:23:19.123",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07T08:23:19Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
"2013-10-07 08:23:19.123Z",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07 08:23:19.120",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"2013-10-07 08:23:19.123+04:00",
datetime!(2013-10-07 8:23:19.123 +4),
),
// Format 5
("2013-10-07T08:23", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07T08:23Z", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07T08:23+04:00", datetime!(2013-10-07 8:23 +4)),
// Format 6
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
("2013-10-07T08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
(
"2013-10-07T08:23:19+04:00",
datetime!(2013-10-07 8:23:19 +4),
),
// Format 7
(
"2013-10-07T08:23:19.123",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07 08:23:19.120Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123Z",
datetime!(2013-10-07 8:23:19.123 UTC),
),
(
"2013-10-07T08:23:19.120Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
"2013-10-07T08:23:19.123+04:00",
datetime!(2013-10-07 8:23:19.123 +4),
),
// Legacy
(
"2013-10-07 04:23:19-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
"2013-10-07 08:23:12:987 -07:00",
datetime!(2013-10-07 8:23:12.987 -7),
),
(
"2013-10-07 04:23:19.120-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
),
(
"2013-10-07T04:23:19.120-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
),
] {
let result: Result<OffsetDateTime> = db.query_row("SELECT ?", [s], |r| r.get(0));
];
for (s, t) in tests {
let result: OffsetDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t);
}
Ok(())
}
#[test]
fn test_date() -> Result<()> {
let db = checked_memory_handle()?;
let date = date!(2016 - 02 - 23);
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23", s);
let t: Date = db.one_column("SELECT t FROM foo")?;
assert_eq!(date, t);
Ok(())
}
#[test]
fn test_time() -> Result<()> {
let db = checked_memory_handle()?;
let time = time!(23:56:04.00001);
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("23:56:04.00001", s);
let v: Time = db.one_column("SELECT t FROM foo")?;
assert_eq!(time, v);
Ok(())
}
#[test]
fn test_primitive_date_time() -> Result<()> {
let db = checked_memory_handle()?;
let dt = date!(2016 - 02 - 23).with_time(time!(23:56:04));
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23 23:56:04.0", s);
let v: PrimitiveDateTime = db.one_column("SELECT t FROM foo")?;
assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
let hms: PrimitiveDateTime = db.one_column("SELECT b FROM foo")?;
assert_eq!(dt, hms);
Ok(())
}
#[test]
fn test_date_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let result: Date = db.query_row("SELECT ?1", ["2013-10-07"], |r| r.get(0))?;
assert_eq!(result, date!(2013 - 10 - 07));
Ok(())
}
#[test]
fn test_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
("08:23", time!(08:23)),
("08:23:19", time!(08:23:19)),
("08:23:19.111", time!(08:23:19.111)),
];
for (s, t) in tests {
let result: Time = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t);
}
Ok(())
}
#[test]
fn test_primitive_date_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
("2013-10-07T08:23", datetime!(2013-10-07 8:23)),
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19)),
("2013-10-07T08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
("2013-10-07 08:23", datetime!(2013-10-07 8:23)),
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19)),
("2013-10-07 08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
];
for (s, t) in tests {
let result: PrimitiveDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t);
}
Ok(())
@@ -151,17 +360,66 @@ mod test {
#[test]
fn test_sqlite_functions() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<OffsetDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
let db = checked_memory_handle()?;
db.one_column::<Time>("SELECT CURRENT_TIME").unwrap();
db.one_column::<Date>("SELECT CURRENT_DATE").unwrap();
db.one_column::<PrimitiveDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
db.one_column::<OffsetDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
Ok(())
}
#[test]
fn test_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = OffsetDateTime::now_utc().time();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_param() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
fn test_date_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = OffsetDateTime::now_utc().date();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_primitive_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = PrimitiveDateTime::new(
OffsetDateTime::now_utc().date(),
OffsetDateTime::now_utc().time(),
);
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_offset_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[OffsetDateTime::now_utc()],
|r| r.get(0),
);
result.unwrap();
Ok(())
}

View File

@@ -51,6 +51,12 @@ macro_rules! from_value(
#[inline]
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
}
);
(non_zero $t:ty) => (
impl From<$t> for ToSqlOutput<'_> {
#[inline]
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.get().into())}
}
)
);
from_value!(String);
@@ -68,6 +74,15 @@ from_value!(f32);
from_value!(f64);
from_value!(Vec<u8>);
from_value!(non_zero std::num::NonZeroI8);
from_value!(non_zero std::num::NonZeroI16);
from_value!(non_zero std::num::NonZeroI32);
from_value!(non_zero std::num::NonZeroI64);
from_value!(non_zero std::num::NonZeroIsize);
from_value!(non_zero std::num::NonZeroU8);
from_value!(non_zero std::num::NonZeroU16);
from_value!(non_zero std::num::NonZeroU32);
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
// worth adding another case to Value.
@@ -75,6 +90,10 @@ from_value!(Vec<u8>);
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(i128);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(non_zero std::num::NonZeroI128);
#[cfg(feature = "uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
from_value!(uuid::Uuid);
@@ -165,10 +184,23 @@ to_sql_self!(u32);
to_sql_self!(f32);
to_sql_self!(f64);
to_sql_self!(std::num::NonZeroI8);
to_sql_self!(std::num::NonZeroI16);
to_sql_self!(std::num::NonZeroI32);
to_sql_self!(std::num::NonZeroI64);
to_sql_self!(std::num::NonZeroIsize);
to_sql_self!(std::num::NonZeroU8);
to_sql_self!(std::num::NonZeroU16);
to_sql_self!(std::num::NonZeroU32);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(i128);
#[cfg(feature = "i128_blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(std::num::NonZeroI128);
#[cfg(feature = "uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
to_sql_self!(uuid::Uuid);
@@ -186,12 +218,27 @@ macro_rules! to_sql_self_fallible(
)))
}
}
);
(non_zero $t:ty) => (
impl ToSql for $t {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Owned(Value::Integer(
i64::try_from(self.get()).map_err(
// TODO: Include the values in the error message.
|err| Error::ToSqlConversionFailure(err.into())
)?
)))
}
}
)
);
// Special implementations for usize and u64 because these conversions can fail.
to_sql_self_fallible!(u64);
to_sql_self_fallible!(usize);
to_sql_self_fallible!(non_zero std::num::NonZeroU64);
to_sql_self_fallible!(non_zero std::num::NonZeroUsize);
impl<T: ?Sized> ToSql for &'_ T
where
@@ -267,9 +314,26 @@ mod test {
is_to_sql::<i16>();
is_to_sql::<i32>();
is_to_sql::<i64>();
is_to_sql::<isize>();
is_to_sql::<u8>();
is_to_sql::<u16>();
is_to_sql::<u32>();
is_to_sql::<u64>();
is_to_sql::<usize>();
}
#[test]
fn test_nonzero_types() {
is_to_sql::<std::num::NonZeroI8>();
is_to_sql::<std::num::NonZeroI16>();
is_to_sql::<std::num::NonZeroI32>();
is_to_sql::<std::num::NonZeroI64>();
is_to_sql::<std::num::NonZeroIsize>();
is_to_sql::<std::num::NonZeroU8>();
is_to_sql::<std::num::NonZeroU16>();
is_to_sql::<std::num::NonZeroU32>();
is_to_sql::<std::num::NonZeroU64>();
is_to_sql::<std::num::NonZeroUsize>();
}
#[test]
@@ -368,10 +432,10 @@ mod test {
db.execute(
"
INSERT INTO foo(i128, desc) VALUES
(?, 'zero'),
(?, 'neg one'), (?, 'neg two'),
(?, 'pos one'), (?, 'pos two'),
(?, 'min'), (?, 'max')",
(?1, 'zero'),
(?2, 'neg one'), (?3, 'neg two'),
(?4, 'pos one'), (?5, 'pos two'),
(?6, 'min'), (?7, 'max')",
[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
)?;
@@ -398,6 +462,54 @@ mod test {
Ok(())
}
#[cfg(feature = "i128_blob")]
#[test]
fn test_non_zero_i128() -> crate::Result<()> {
use std::num::NonZeroI128;
macro_rules! nz {
($x:expr) => {
NonZeroI128::new($x).unwrap()
};
}
let db = crate::Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
db.execute(
"INSERT INTO foo(i128, desc) VALUES
(?1, 'neg one'), (?2, 'neg two'),
(?3, 'pos one'), (?4, 'pos two'),
(?5, 'min'), (?6, 'max')",
[
nz!(-1),
nz!(-2),
nz!(1),
nz!(2),
nz!(i128::MIN),
nz!(i128::MAX),
],
)?;
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
let res = stmt
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
.collect::<Result<Vec<(NonZeroI128, String)>, _>>()?;
assert_eq!(
res,
&[
(nz!(i128::MIN), "min".to_owned()),
(nz!(-2), "neg two".to_owned()),
(nz!(-1), "neg one".to_owned()),
(nz!(1), "pos one".to_owned()),
(nz!(2), "pos two".to_owned()),
(nz!(i128::MAX), "max".to_owned()),
]
);
let err = db.query_row("SELECT ?1", [0i128], |row| row.get::<_, NonZeroI128>(0));
assert_eq!(err, Err(crate::Error::IntegralValueOutOfRange(0, 0)));
Ok(())
}
#[cfg(feature = "uuid")]
#[test]
fn test_uuid() -> crate::Result<()> {
@@ -410,11 +522,11 @@ mod test {
let id = Uuid::new_v4();
db.execute(
"INSERT INTO foo (id, label) VALUES (?, ?)",
"INSERT INTO foo (id, label) VALUES (?1, ?2)",
params![id, "target"],
)?;
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?;
let mut rows = stmt.query(params![id])?;
let row = rows.next()?.unwrap();

View File

@@ -49,7 +49,7 @@ mod test {
let url2 = "http://www.example2.com/👌";
db.execute(
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
"INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
// also insert a non-hex encoded url (which might be present if it was
// inserted separately)
params![url0, url1, url2, "illegal"],
@@ -74,7 +74,7 @@ mod test {
);
}
e => {
panic!("Expected conversion failure, got {}", e);
panic!("Expected conversion failure, got {e}");
}
}
Ok(())

View File

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

View File

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

View File

@@ -6,4 +6,4 @@ pub(crate) use small_cstr::SmallCString;
// Doesn't use any modern features or vtab stuff, but is only used by them.
mod sqlite_string;
pub(crate) use sqlite_string::SqliteMallocString;
pub(crate) use sqlite_string::{alloc, SqliteMallocString};

View File

@@ -7,6 +7,12 @@ use std::marker::PhantomData;
use std::os::raw::{c_char, c_int};
use std::ptr::NonNull;
// Space to hold this string must be obtained
// from an SQLite memory allocation function
pub(crate) fn alloc(s: &str) -> *mut c_char {
SqliteMallocString::from_str(s).into_raw()
}
/// A string we own that's allocated on the SQLite heap. Automatically calls
/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
/// on it. If constructed from a rust string, `sqlite3_malloc` is used.
@@ -100,7 +106,6 @@ impl SqliteMallocString {
/// This means it's safe to use in extern "C" functions even outside of
/// `catch_unwind`.
pub(crate) fn from_str(s: &str) -> Self {
use std::convert::TryFrom;
let s = if s.as_bytes().contains(&0) {
std::borrow::Cow::Owned(make_nonnull(s))
} else {

View File

@@ -17,7 +17,7 @@
//! let v = [1i64, 2, 3, 4];
//! // Note: A `Rc<Vec<Value>>` must be used as the parameter.
//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
//! let mut stmt = db.prepare("SELECT value from rarray(?);")?;
//! let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
//! for value in rows {
//! println!("{}", value?);
@@ -206,7 +206,7 @@ mod test {
let values: Vec<Value> = v.into_iter().map(Value::from).collect();
let ptr = Rc::new(values);
{
let mut stmt = db.prepare("SELECT value from rarray(?);")?;
let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
assert_eq!(2, Rc::strong_count(&ptr));

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3;
bitflags::bitflags! {
#[derive(Clone, Copy)]
#[repr(C)]
struct QueryPlanFlags: ::std::os::raw::c_int {
// start = $value -- constraint exists
@@ -41,7 +42,7 @@ bitflags::bitflags! {
// output in ascending order
const ASC = 16;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
}
}
@@ -115,7 +116,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
}
if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available.
//#[allow(clippy::bool_to_int_with_if)]
#[allow(clippy::bool_to_int_with_if)]
info.set_estimated_cost(f64::from(
2 - if idx_num.contains(QueryPlanFlags::STEP) {
1
@@ -199,19 +200,19 @@ unsafe impl VTabCursor for SeriesTabCursor<'_> {
let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
let mut i = 0;
if idx_num.contains(QueryPlanFlags::START) {
self.min_value = args.get(i)?;
self.min_value = args.get::<Option<_>>(i)?.unwrap_or_default();
i += 1;
} else {
self.min_value = 0;
}
if idx_num.contains(QueryPlanFlags::STOP) {
self.max_value = args.get(i)?;
self.max_value = args.get::<Option<_>>(i)?.unwrap_or_default();
i += 1;
} else {
self.max_value = 0xffff_ffff;
}
if idx_num.contains(QueryPlanFlags::STEP) {
self.step = args.get(i)?;
self.step = args.get::<Option<_>>(i)?.unwrap_or_default();
if self.step == 0 {
self.step = 1;
} else if self.step < 0 {
@@ -315,6 +316,26 @@ mod test {
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
let mut s = db.prepare("SELECT * FROM generate_series(NULL)")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
let empty = Vec::<i32>::new();
assert_eq!(empty, series);
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(empty, series);
let mut s = db.prepare("SELECT * FROM generate_series(5,10,NULL)")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(empty, series);
let mut s = db.prepare("SELECT * FROM generate_series(NULL,10,2)")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(empty, series);
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL,2)")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(empty, series);
let mut s = db.prepare("SELECT * FROM generate_series(NULL) ORDER BY value DESC")?;
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
assert_eq!(empty, series);
Ok(())
}
}

View File

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