mirror of
https://github.com/isar/rusqlite.git
synced 2025-11-01 14:28:55 +08:00
Merge remote-tracking branch 'origin/master' into clippy
This commit is contained in:
@@ -371,15 +371,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
128
src/lib.rs
128
src/lib.rs
@@ -62,7 +62,7 @@ use std::ffi::{CStr, CString};
|
||||
use std::fmt;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::str;
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -140,13 +140,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 +323,6 @@ impl DatabaseName<'_> {
|
||||
pub struct Connection {
|
||||
db: RefCell<InnerConnection>,
|
||||
cache: StatementCache,
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Connection {}
|
||||
@@ -427,7 +419,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 +443,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()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -580,12 +570,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 +599,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
|
||||
@@ -671,28 +652,6 @@ impl Connection {
|
||||
self.query_row(sql, [], |r| r.get(0))
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query that is expected to return a
|
||||
/// single row, and execute a mapping via `f` on that returned row with
|
||||
/// the possibility of failure. The `Result` type of `f` must implement
|
||||
@@ -921,12 +880,30 @@ 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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -979,7 +956,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()
|
||||
}
|
||||
}
|
||||
@@ -1170,16 +1147,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");
|
||||
|
||||
@@ -1281,6 +1248,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";
|
||||
@@ -1790,6 +1772,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::*;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
14
src/row.rs
14
src/row.rs
@@ -338,20 +338,6 @@ impl<'stmt> Row<'stmt> {
|
||||
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> {
|
||||
|
||||
229
src/statement.rs
229
src/statement.rs
@@ -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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -870,6 +735,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 +879,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 +901,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 +919,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 +926,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 +937,6 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_query_named() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let sql = r#"
|
||||
@@ -1083,24 +946,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 +962,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,14 +1007,13 @@ 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.one_column("SELECT y FROM test WHERE x = 'one'")?;
|
||||
assert!(result.is_none());
|
||||
|
||||
@@ -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,7 +64,7 @@ impl FromSql for Value {
|
||||
mod test {
|
||||
use crate::types::ToSql;
|
||||
use crate::{Connection, Result};
|
||||
use serde_json::Value;
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
fn checked_memory_handle() -> Result<Connection> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
@@ -51,4 +89,47 @@ mod test {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user