Merge remote-tracking branch 'origin/master' into clippy

This commit is contained in:
gwenn
2023-03-12 15:49:58 +01:00
22 changed files with 3576 additions and 1860 deletions

View File

@@ -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();
}
}

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;
@@ -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::*;

View File

@@ -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

View File

@@ -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'
}

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]

View File

@@ -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> {

View File

@@ -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());

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,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(())
}
}