From 032aea73b890fa6cc42104b86a0e9f83205886de Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Mon, 2 Nov 2020 23:34:08 -0800 Subject: [PATCH] Overhaul query API, removing the need for the `_named` variants of all functions, and `rusqlite::NO_PARAMS` --- src/blob/mod.rs | 4 +- src/functions.rs | 6 +- src/lib.rs | 216 ++++++++++--------- src/params.rs | 198 +++++++++++++++++ src/row.rs | 65 ++---- src/statement.rs | 503 +++++++++++++++++++++++++++++--------------- src/trace.rs | 6 +- src/transaction.rs | 2 +- src/types/mod.rs | 6 +- src/types/to_sql.rs | 2 +- 10 files changed, 680 insertions(+), 328 deletions(-) create mode 100644 src/params.rs diff --git a/src/blob/mod.rs b/src/blob/mod.rs index e09156b..e1f65f5 100644 --- a/src/blob/mod.rs +++ b/src/blob/mod.rs @@ -138,7 +138,7 @@ //! // rust (potentially with a dynamic size). //! db.execute( //! "INSERT INTO test_table (content) VALUES (?)", -//! &[ZeroBlob(64)], +//! [ZeroBlob(64)], //! )?; //! //! // given a new row ID, we can reopen the blob on that row @@ -182,7 +182,7 @@ //! // rust (potentially with a dynamic size). //! db.execute( //! "INSERT INTO test_table (content) VALUES (?)", -//! &[ZeroBlob(64)], +//! [ZeroBlob(64)], //! )?; //! //! // given a new row ID, we can reopen the blob on that row diff --git a/src/functions.rs b/src/functions.rs index b767bb3..e283f39 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -45,7 +45,7 @@ //! //! let is_match: bool = db.query_row( //! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')", -//! NO_PARAMS, +//! [], //! |row| row.get(0), //! )?; //! @@ -311,7 +311,7 @@ impl Connection { /// # Example /// /// ```rust - /// # use rusqlite::{Connection, Result, NO_PARAMS}; + /// # use rusqlite::{Connection, Result}; /// # use rusqlite::functions::FunctionFlags; /// fn scalar_function_example(db: Connection) -> Result<()> { /// db.create_scalar_function( @@ -324,7 +324,7 @@ impl Connection { /// }, /// )?; /// - /// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?; + /// let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?; /// assert_eq!(six_halved, 3f64); /// Ok(()) /// } diff --git a/src/lib.rs b/src/lib.rs index e3161a8..a45c9a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ pub use crate::ffi::ErrorCode; pub use crate::hooks::Action; #[cfg(feature = "load_extension")] pub use crate::load_extension_guard::LoadExtensionGuard; +pub use crate::params::{params_from_iter, Params, ParamsFromIter}; pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows}; pub use crate::statement::{Statement, StatementStatus}; pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; @@ -107,6 +108,7 @@ mod inner_connection; pub mod limits; #[cfg(feature = "load_extension")] mod load_extension_guard; +mod params; mod pragma; mod raw_statement; mod row; @@ -127,11 +129,20 @@ 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](https://sqlite.org/lang_expr.html#varparam). +/// To be used when your statement has no [parameter][sqlite-varparam]. +/// +/// [sqlite-varparam]: https://sqlite.org/lang_expr.html#varparam +/// +/// Note that for most uses this is no longer necessary, and an empty array +/// should be used instead. +/// +/// That is, in previous rusqlite releases you would need to do +/// `conn.execute(rusqlite::NO_PARAMS)`, however you can now do +/// `conn.execute([])` which is equivalent. pub const NO_PARAMS: &[&dyn ToSql] = &[]; -/// A macro making it more convenient to pass heterogeneous lists -/// of parameters as a `&[&dyn ToSql]`. +/// A macro making it more convenient to pass heterogeneous or long lists of +/// parameters as a `&[&dyn ToSql]`. /// /// # Example /// @@ -191,12 +202,12 @@ macro_rules! params { #[macro_export] macro_rules! named_params { () => { - &[] + (&[] as &[(&str, &dyn $crate::ToSql)]) }; // Note: It's a lot more work to support this as part of the same macro as // `params!`, unfortunately. ($($param_name:literal: $param_val:expr),+ $(,)?) => { - &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] + (&[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)]) }; } @@ -471,25 +482,47 @@ impl Connection { /// /// ## Example /// + /// ### With positional params + /// /// ```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 = ?", [1i32]) { /// Ok(updated) => println!("{} rows were updated", updated), /// Err(err) => println!("update failed: {}", err), /// } /// } /// ``` /// + /// ### With positional params of varying types + /// + /// ```rust,no_run + /// # use rusqlite::{Connection}; + /// fn update_rows(conn: &Connection) { + /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) { + /// Ok(updated) => println!("{} rows were updated", updated), + /// Err(err) => println!("update failed: {}", err), + /// } + /// } + /// ``` + /// + /// ### With named params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn insert(conn: &Connection) -> Result { + /// conn.execute_named( + /// "INSERT INTO test (name) VALUES (:name)", + /// rusqlite::named_params!{ ":name": "one" }, + /// ) + /// } + /// ``` + /// /// # Failure /// /// Will return `Err` if `sql` cannot be converted to a C-compatible string /// or if the underlying SQLite call fails. - pub fn execute

(&self, sql: &str, params: P) -> Result - where - P: IntoIterator, - P::Item: ToSql, - { + pub fn execute(&self, sql: &str, params: P) -> Result { self.prepare(sql) .and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params))) } @@ -500,23 +533,14 @@ impl Connection { /// On success, returns the number of rows that were changed or inserted or /// deleted (via `sqlite3_changes`). /// - /// ## Example - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// fn insert(conn: &Connection) -> Result { - /// conn.execute_named( - /// "INSERT INTO test (name) VALUES (:name)", - /// &[(":name", &"one")], - /// ) - /// } - /// ``` - /// /// # 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 { + // 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)) @@ -537,11 +561,11 @@ impl Connection { /// ## Example /// /// ```rust,no_run - /// # use rusqlite::{Result,Connection, NO_PARAMS}; + /// # use rusqlite::{Result, Connection}; /// fn preferred_locale(conn: &Connection) -> Result { /// conn.query_row( /// "SELECT value FROM preferences WHERE name='locale'", - /// NO_PARAMS, + /// [], /// |row| row.get(0), /// ) /// } @@ -560,8 +584,7 @@ impl Connection { /// or if the underlying SQLite call fails. pub fn query_row(&self, sql: &str, params: P, f: F) -> Result where - P: IntoIterator, - P::Item: ToSql, + P: Params, F: FnOnce(&Row<'_>) -> Result, { let mut stmt = self.prepare(sql)?; @@ -583,13 +606,12 @@ impl Connection { /// /// 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(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result where F: FnOnce(&Row<'_>) -> Result, { - let mut stmt = self.prepare(sql)?; - stmt.check_no_tail()?; - stmt.query_row_named(params, f) + self.query_row(sql, params, f) } /// Convenience method to execute a query that is expected to return a @@ -600,11 +622,11 @@ impl Connection { /// ## Example /// /// ```rust,no_run - /// # use rusqlite::{Result,Connection, NO_PARAMS}; + /// # use rusqlite::{Result, Connection}; /// fn preferred_locale(conn: &Connection) -> Result { /// conn.query_row_and_then( /// "SELECT value FROM preferences WHERE name='locale'", - /// NO_PARAMS, + /// [], /// |row| row.get(0), /// ) /// } @@ -619,8 +641,7 @@ impl Connection { /// or if the underlying SQLite call fails. pub fn query_row_and_then(&self, sql: &str, params: P, f: F) -> Result where - P: IntoIterator, - P::Item: ToSql, + P: Params, F: FnOnce(&Row<'_>) -> Result, E: convert::From, { @@ -1039,13 +1060,13 @@ mod test { let tx2 = db2.transaction().unwrap(); // SELECT first makes sqlite lock with a shared lock - tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(())) + tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(())) .unwrap(); - tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(())) + tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(())) .unwrap(); - tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap(); - let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[2]); + tx1.execute("INSERT INTO foo VALUES(?1)", &[&1]).unwrap(); + let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]); let _ = tx1.commit(); let _ = tx2.commit(); @@ -1075,7 +1096,7 @@ mod test { let path_string = path.to_str().unwrap(); let db = Connection::open(&path_string).unwrap(); - let the_answer: Result = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0)); + let the_answer: Result = db.query_row("SELECT x FROM foo", [], |r| r.get(0)); assert_eq!(42i64, the_answer.unwrap()); } @@ -1132,7 +1153,7 @@ mod test { } let db = Connection::open(&db_path).unwrap(); - let the_answer: Result = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0)); + let the_answer: Result = db.query_row("SELECT x FROM foo", [], |r| r.get(0)); assert_eq!(42i64, the_answer.unwrap()); } @@ -1214,18 +1235,16 @@ mod test { assert_eq!( 1, - db.execute("INSERT INTO foo(x) VALUES (?)", &[1i32]) - .unwrap() + db.execute("INSERT INTO foo(x) VALUES (?)", [1i32]).unwrap() ); assert_eq!( 1, - db.execute("INSERT INTO foo(x) VALUES (?)", &[2i32]) - .unwrap() + db.execute("INSERT INTO foo(x) VALUES (?)", [2i32]).unwrap() ); assert_eq!( 3i32, - db.query_row::("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) + db.query_row::("SELECT SUM(x) FROM foo", [], |r| r.get(0)) .unwrap() ); } @@ -1234,7 +1253,7 @@ 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 < ?", [1i32]).unwrap_err(); if err != Error::ExecuteReturnedResults { panic!("Unexpected error: {}", err); } @@ -1247,7 +1266,7 @@ mod test { let err = db .execute( "CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)", - NO_PARAMS, + [], ) .unwrap_err(); match err { @@ -1276,18 +1295,18 @@ mod test { db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap(); - assert_eq!(insert_stmt.execute(&[1i32]).unwrap(), 1); - assert_eq!(insert_stmt.execute(&[2i32]).unwrap(), 1); - assert_eq!(insert_stmt.execute(&[3i32]).unwrap(), 1); + assert_eq!(insert_stmt.execute([1i32]).unwrap(), 1); + assert_eq!(insert_stmt.execute([2i32]).unwrap(), 1); + assert_eq!(insert_stmt.execute([3i32]).unwrap(), 1); - assert_eq!(insert_stmt.execute(&["hello".to_string()]).unwrap(), 1); - assert_eq!(insert_stmt.execute(&["goodbye".to_string()]).unwrap(), 1); - assert_eq!(insert_stmt.execute(&[types::Null]).unwrap(), 1); + assert_eq!(insert_stmt.execute(["hello".to_string()]).unwrap(), 1); + assert_eq!(insert_stmt.execute(["goodbye".to_string()]).unwrap(), 1); + assert_eq!(insert_stmt.execute([types::Null]).unwrap(), 1); let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x::new(); while let Some(row) = rows.next().unwrap() { @@ -1315,7 +1334,7 @@ mod test { } { - let mut rows = query.query(&[3i32]).unwrap(); + let mut rows = query.query([3i32]).unwrap(); let mut v = Vec::::new(); while let Some(row) = rows.next().unwrap() { @@ -1339,11 +1358,7 @@ mod test { db.execute_batch(sql).unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); - let results: Result> = query - .query(NO_PARAMS) - .unwrap() - .map(|row| row.get(1)) - .collect(); + let results: Result> = query.query([]).unwrap().map(|row| row.get(1)).collect(); assert_eq!(results.unwrap().concat(), "hello, world!"); } @@ -1362,18 +1377,17 @@ mod test { assert_eq!( 10i64, - db.query_row::("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) + db.query_row::("SELECT SUM(x) FROM foo", [], |r| r.get(0)) .unwrap() ); - let result: Result = - db.query_row("SELECT x FROM foo WHERE x > 5", NO_PARAMS, |r| r.get(0)); + let result: Result = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0)); match result.unwrap_err() { Error::QueryReturnedNoRows => (), err => panic!("Unexpected error {}", err), } - let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| Ok(())); + let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(())); assert!(bad_query_result.is_err()); } @@ -1382,22 +1396,21 @@ mod test { fn test_optional() { let db = checked_memory_handle(); - let result: Result = db.query_row("SELECT 1 WHERE 0 <> 0", NO_PARAMS, |r| r.get(0)); + let result: Result = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0)); let result = result.optional(); match result.unwrap() { None => (), _ => panic!("Unexpected result"), } - let result: Result = db.query_row("SELECT 1 WHERE 0 == 0", NO_PARAMS, |r| r.get(0)); + let result: Result = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0)); let result = result.optional(); match result.unwrap() { Some(1) => (), _ => panic!("Unexpected result"), } - let bad_query_result: Result = - db.query_row("NOT A PROPER QUERY", NO_PARAMS, |r| r.get(0)); + let bad_query_result: Result = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0)); let bad_query_result = bad_query_result.optional(); assert!(bad_query_result.is_err()); } @@ -1408,12 +1421,12 @@ mod test { assert_eq!( "memory", - db.query_row::("PRAGMA journal_mode", NO_PARAMS, |r| r.get(0)) + db.query_row::("PRAGMA journal_mode", [], |r| r.get(0)) .unwrap() ); assert_eq!( "off", - db.query_row::("PRAGMA journal_mode=off", NO_PARAMS, |r| r.get(0)) + db.query_row::("PRAGMA journal_mode=off", [], |r| r.get(0)) .unwrap() ); } @@ -1438,7 +1451,7 @@ mod test { let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES").unwrap(); for _ in 0i32..9 { - stmt.execute(NO_PARAMS).unwrap(); + stmt.execute([]).unwrap(); } assert_eq!(db.last_insert_rowid(), 10); } @@ -1460,7 +1473,7 @@ mod test { let mut stmt = db.prepare("PRAGMA schema_version").unwrap(); assert!(!db.is_busy()); { - let mut rows = stmt.query(NO_PARAMS).unwrap(); + let mut rows = stmt.query([]).unwrap(); assert!(!db.is_busy()); let row = rows.next().unwrap(); assert!(db.is_busy()); @@ -1492,7 +1505,7 @@ mod test { let db = checked_memory_handle(); db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap(); - let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", NO_PARAMS); + let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []); assert!(result.is_err()); match result.unwrap_err() { @@ -1536,7 +1549,7 @@ mod test { .prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)") .unwrap(); - let result: Result> = stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).collect(); + let result: Result> = stmt.query([]).unwrap().map(|r| r.get(0)).collect(); match result.unwrap_err() { Error::SqliteFailure(err, _) => { @@ -1576,7 +1589,7 @@ mod test { } let mut query = db.prepare("SELECT i, x FROM foo").unwrap(); - let mut rows = query.query(NO_PARAMS).unwrap(); + let mut rows = query.query([]).unwrap(); while let Some(row) = rows.next().unwrap() { let i = row.get_raw(0).as_i64().unwrap(); @@ -1651,7 +1664,7 @@ mod test { let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let results: Result> = query - .query_and_then(NO_PARAMS, |row| row.get(1)) + .query_and_then([], |row| row.get(1)) .unwrap() .collect(); @@ -1672,7 +1685,7 @@ mod test { let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let bad_type: Result> = query - .query_and_then(NO_PARAMS, |row| row.get(1)) + .query_and_then([], |row| row.get(1)) .unwrap() .collect(); @@ -1682,7 +1695,7 @@ mod test { } let bad_idx: Result> = query - .query_and_then(NO_PARAMS, |row| row.get(3)) + .query_and_then([], |row| row.get(3)) .unwrap() .collect(); @@ -1706,7 +1719,7 @@ mod test { let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let results: CustomResult> = query - .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite)) + .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite)) .unwrap() .collect(); @@ -1727,7 +1740,7 @@ mod test { let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let bad_type: CustomResult> = query - .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite)) + .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite)) .unwrap() .collect(); @@ -1737,7 +1750,7 @@ mod test { } let bad_idx: CustomResult> = query - .query_and_then(NO_PARAMS, |row| row.get(3).map_err(CustomError::Sqlite)) + .query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite)) .unwrap() .collect(); @@ -1747,7 +1760,7 @@ mod test { } let non_sqlite_err: CustomResult> = query - .query_and_then(NO_PARAMS, |_| Err(CustomError::SomeError)) + .query_and_then([], |_| Err(CustomError::SomeError)) .unwrap() .collect(); @@ -1767,9 +1780,8 @@ mod test { db.execute_batch(sql).unwrap(); let query = "SELECT x, y FROM foo ORDER BY x DESC"; - let results: CustomResult = db.query_row_and_then(query, NO_PARAMS, |row| { - row.get(1).map_err(CustomError::Sqlite) - }); + let results: CustomResult = + db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite)); assert_eq!(results.unwrap(), "hello"); } @@ -1784,18 +1796,16 @@ mod test { db.execute_batch(sql).unwrap(); let query = "SELECT x, y FROM foo ORDER BY x DESC"; - let bad_type: CustomResult = db.query_row_and_then(query, NO_PARAMS, |row| { - row.get(1).map_err(CustomError::Sqlite) - }); + let bad_type: CustomResult = + db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite)); match bad_type.unwrap_err() { CustomError::Sqlite(Error::InvalidColumnType(..)) => (), err => panic!("Unexpected error {}", err), } - let bad_idx: CustomResult = db.query_row_and_then(query, NO_PARAMS, |row| { - row.get(3).map_err(CustomError::Sqlite) - }); + let bad_idx: CustomResult = + db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite)); match bad_idx.unwrap_err() { CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (), @@ -1803,7 +1813,7 @@ mod test { } let non_sqlite_err: CustomResult = - db.query_row_and_then(query, NO_PARAMS, |_| Err(CustomError::SomeError)); + db.query_row_and_then(query, [], |_| Err(CustomError::SomeError)); match non_sqlite_err.unwrap_err() { CustomError::SomeError => (), @@ -1821,7 +1831,7 @@ mod test { END;"; db.execute_batch(sql).unwrap(); - db.query_row("SELECT * FROM foo", NO_PARAMS, |r| { + db.query_row("SELECT * FROM foo", [], |r| { assert_eq!(2, r.column_count()); Ok(()) }) @@ -1832,8 +1842,8 @@ mod test { let db = checked_memory_handle(); db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); let b: Box = Box::new(5); - db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap(); - db.query_row("SELECT x FROM foo", NO_PARAMS, |r| { + db.execute("INSERT INTO foo VALUES(?)", [b]).unwrap(); + db.query_row("SELECT x FROM foo", [], |r| { assert_eq!(5, r.get_unwrap::<_, i32>(0)); Ok(()) }) @@ -1867,7 +1877,7 @@ mod test { let db = checked_memory_handle(); db.execute_batch("CREATE TABLE x(t);").unwrap(); // `execute_batch` should be used but `execute` should also work - db.execute("ALTER TABLE x RENAME TO y;", NO_PARAMS).unwrap(); + db.execute("ALTER TABLE x RENAME TO y;", []).unwrap(); } #[test] @@ -1880,7 +1890,7 @@ mod test { let batch = Batch::new(&db, sql); for stmt in batch { let mut stmt = stmt.unwrap(); - stmt.execute(NO_PARAMS).unwrap(); + stmt.execute([]).unwrap(); } } } diff --git a/src/params.rs b/src/params.rs new file mode 100644 index 0000000..9ac500f --- /dev/null +++ b/src/params.rs @@ -0,0 +1,198 @@ +use crate::{Result, Statement, ToSql}; + +mod sealed { + /// This trait exists just to ensure that the only impls of `trait Params` + /// that are allowed are ones in this crate. + pub trait Sealed {} +} +// must not be `pub use`. +use sealed::Sealed; + +/// Trait used for parameter sets passed into SQL statements/queries. +/// +/// Currently, this trait can only be implemented inside this crate. +pub trait Params: Sealed { + /// Binds the parameters to the statement. It is unlikely calling this + /// explicitly will do what you want. Please use `Statement::query` or + /// similar directly. + // For now, just hide the function in the docs... + #[doc(hidden)] + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()>; +} + +// 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. +impl Sealed for [&dyn ToSql; 0] {} +impl Params for [&dyn ToSql; 0] { + #[inline] + fn bind_in(self, _: &mut Statement<'_>) -> Result<()> { + Ok(()) + } +} + +impl Sealed for &[&dyn ToSql] {} +impl Params for &[&dyn ToSql] { + #[inline] + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self) + } +} + +impl Sealed for &[(&str, &dyn ToSql)] {} +impl Params for &[(&str, &dyn ToSql)] { + #[inline] + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters_named(self) + } +} + +macro_rules! impl_for_array_ref { + ($($N:literal)+) => {$( + impl Sealed for &[&T; $N] {} + impl Params for &[&T; $N] { + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self) + } + } + impl Sealed for &[(&str, &T); $N] {} + impl Params for &[(&str, &T); $N] { + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters_named(self) + } + } + impl Sealed for [T; $N] {} + impl Params for [T; $N] { + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(&self) + } + } + )+}; +} +impl_for_array_ref!( + 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 29 30 31 32 +); + +/// Adapter type which allows any iterator over [`ToSql`] values to implement +/// [`Params`]. +/// +/// This struct is created by the [`params_from_iter`] function. +/// +/// This can be useful if you have something like an `&[String]` (of unknown +/// length), and you want to use them with an API that wants something +/// implementing `Params`. This way, you can avoid having to allocate storage +/// for something like a `&[&dyn ToSql]`. +/// +/// This essentially is only ever actually needed when dynamically generating +/// SQL — static SQL (by definition) has the number of parameters known +/// statically. As dynamically generating SQL is itself pretty advanced, this +/// API is itself for advanced use cases (See "Realistic use case" in the +/// examples). +/// +/// # Example +/// +/// ## Basic usage +/// +/// ```rust,no_run +/// use rusqlite::{Connection, Result, params_from_iter}; +/// use std::collections::BTreeSet; +/// +/// fn query(conn: &Connection, ids: &BTreeSet) -> Result<()> { +/// assert_eq!(ids.len(), 3, "Unrealistic sample code"); +/// +/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?; +/// let _rows = stmt.query(params_from_iter(ids.iter()))?; +/// +/// // use _rows... +/// Ok(()) +/// } +/// ``` +/// +/// ## Realistic use case +/// +/// Here's how you'd use `ParamsFromIter` to call a function with no `_iter` +/// equivalent, e.g. [`Statement::exists`]. +/// +/// ```rust,no_run +/// use rusqlite::{Connection, Result}; +/// +/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result { +/// if usernames.is_empty() { +/// return Ok(false); +/// } +/// +/// // Note: `repeat_vars` never returns anything attacker-controlled, so +/// // it's fine to use it in a dynamically-built SQL string. +/// let vars = repeat_vars(usernames.len()); +/// +/// let sql = format!( +/// // In practice this would probably be better as an `EXISTS` query. +/// "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1", +/// vars, +/// ); +/// let mut stmt = conn.prepare(&sql)?; +/// stmt.exists(rusqlite::params_from_iter(usernames)) +/// } +/// +/// // Helper function to return a comma-separated sequence of `?`. +/// // - `repeat_vars(0) => panic!(...)` +/// // - `repeat_vars(1) => "?"` +/// // - `repeat_vars(2) => "?,?"` +/// // - `repeat_vars(3) => "?,?,?"` +/// // - ... +/// fn repeat_vars(count: usize) -> String { +/// assert_ne!(count, 0); +/// let mut s = "?,".repeat(count); +/// // Remove trailing comma +/// s.pop(); +/// s +/// } +/// ``` +/// +/// That is fairly complex, and even so would need even more work to be fully +/// production-ready: +/// +/// - production code should ensure `usernames` isn't so large that it will +/// surpass [`conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)`][limits]) +/// (chunking if too large). +/// +/// - `repeat_vars` can be implemented in a way that avoids needing to allocate +/// a String. +/// +/// [limits]: crate::Connection::limit +/// +/// This complexity reflects the fact that `ParamsFromIter` is mainly intended +/// for advanced use cases — most of the time you should know how many +/// parameters you have statically. +#[derive(Clone, Debug)] +pub struct ParamsFromIter(I); + +/// Constructor function for a [`ParamsFromIter`]. See its documentation for +/// more. +#[inline] +pub fn params_from_iter(iter: I) -> ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ + ParamsFromIter(iter) +} + +impl Sealed for ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ +} + +impl Params for ParamsFromIter +where + I: IntoIterator, + I::Item: ToSql, +{ + #[inline] + fn bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { + stmt.bind_parameters(self.0) + } +} diff --git a/src/row.rs b/src/row.rs index 7a8d4ae..b46c572 100644 --- a/src/row.rs +++ b/src/row.rs @@ -427,65 +427,44 @@ mod tests { let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); conn.execute( "CREATE TABLE test (a INTEGER)", - std::iter::empty::<&dyn ToSql>(), + crate::params_from_iter(std::iter::empty::<&dyn ToSql>()), ) .expect("failed to create table"); - conn.execute( - "INSERT INTO test VALUES (42)", - std::iter::empty::<&dyn ToSql>(), - ) - .expect("failed to insert value"); + conn.execute("INSERT INTO test VALUES (42)", []) + .expect("failed to insert value"); let val = conn - .query_row( - "SELECT a FROM test", - std::iter::empty::<&dyn ToSql>(), - |row| <(u32,)>::try_from(row), - ) + .query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row)) .expect("failed to query row"); assert_eq!(val, (42,)); - let fail = conn.query_row( - "SELECT a FROM test", - std::iter::empty::<&dyn ToSql>(), - |row| <(u32, u32)>::try_from(row), - ); + let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row)); assert!(fail.is_err()); } #[test] fn test_try_from_row_for_tuple_2() { - use crate::{Connection, ToSql}; + use crate::Connection; use std::convert::TryFrom; let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); - conn.execute( - "CREATE TABLE test (a INTEGER, b INTEGER)", - std::iter::empty::<&dyn ToSql>(), - ) - .expect("failed to create table"); - conn.execute( - "INSERT INTO test VALUES (42, 47)", - std::iter::empty::<&dyn ToSql>(), - ) - .expect("failed to insert value"); + conn.execute("CREATE TABLE test (a INTEGER, b INTEGER)", []) + .expect("failed to create table"); + conn.execute("INSERT INTO test VALUES (42, 47)", []) + .expect("failed to insert value"); let val = conn - .query_row( - "SELECT a, b FROM test", - std::iter::empty::<&dyn ToSql>(), - |row| <(u32, u32)>::try_from(row), - ) + .query_row("SELECT a, b FROM test", [], |row| { + <(u32, u32)>::try_from(row) + }) .expect("failed to query row"); assert_eq!(val, (42, 47)); - let fail = conn.query_row( - "SELECT a, b FROM test", - std::iter::empty::<&dyn ToSql>(), - |row| <(u32, u32, u32)>::try_from(row), - ); + let fail = conn.query_row("SELECT a, b FROM test", [], |row| { + <(u32, u32, u32)>::try_from(row) + }); assert!(fail.is_err()); } #[test] fn test_try_from_row_for_tuple_16() { - use crate::{Connection, ToSql}; + use crate::Connection; use std::convert::TryFrom; let create_table = "CREATE TABLE test ( @@ -546,16 +525,12 @@ mod tests { ); let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); - conn.execute(create_table, std::iter::empty::<&dyn ToSql>()) + conn.execute(create_table, []) .expect("failed to create table"); - conn.execute(insert_values, std::iter::empty::<&dyn ToSql>()) + conn.execute(insert_values, []) .expect("failed to insert value"); let val = conn - .query_row( - "SELECT * FROM test", - std::iter::empty::<&dyn ToSql>(), - |row| BigTuple::try_from(row), - ) + .query_row("SELECT * FROM test", [], |row| BigTuple::try_from(row)) .expect("failed to query row"); // Debug is not implemented for tuples of 16 assert_eq!(val.0, 0); diff --git a/src/statement.rs b/src/statement.rs index 648a9b7..7c9123c 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -8,7 +8,7 @@ use std::{convert, fmt, mem, ptr, str}; use super::ffi; use super::{len_as_c_int, str_for_sqlite}; use super::{ - AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef, + AndThenRows, Connection, Error, MappedRows, Params, RawStatement, Result, Row, Rows, ValueRef, }; use crate::types::{ToSql, ToSqlOutput}; #[cfg(feature = "array")] @@ -28,48 +28,28 @@ impl Statement<'_> { /// /// ## Example /// + /// ### Use with positional parameters + /// /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; + /// # use rusqlite::{Connection, Result, params}; /// fn update_rows(conn: &Connection) -> Result<()> { /// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?; /// - /// stmt.execute(&[1i32])?; - /// stmt.execute(&[2i32])?; + /// stmt.execute(params![1i32])?; + /// // Similarly... + /// stmt.execute(&[&2i32])?; /// /// Ok(()) /// } /// ``` /// - /// # 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. - pub fn execute

(&mut self, params: P) -> Result - where - P: IntoIterator, - P::Item: ToSql, - { - self.bind_parameters(params)?; - self.execute_with_bound_parameters() - } - - /// Execute the prepared statement with named parameter(s). 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`). - /// - /// ## Example + /// ### Use with named parameters /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// fn insert(conn: &Connection) -> Result { /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?; - /// stmt.execute_named(&[(":name", &"one")]) + /// stmt.execute(&[(":name", &"one")]) /// } /// ``` /// @@ -80,7 +60,18 @@ impl Statement<'_> { /// # use rusqlite::{Connection, Result, named_params}; /// fn insert(conn: &Connection) -> Result { /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?; - /// stmt.execute_named(named_params!{":name": "one"}) + /// stmt.execute(named_params!{":name": "one"}) + /// } + /// ``` + /// + /// ### Use without parameters + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, params}; + /// fn delete_all(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("DELETE FROM users")?; + /// stmt.execute([])?; + /// Ok(()) /// } /// ``` /// @@ -89,11 +80,34 @@ impl Statement<'_> { /// 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. - pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result { - self.bind_parameters_named(params)?; + pub fn execute(&mut self, params: P) -> Result { + params.bind_in(self)?; 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. + #[deprecated = "You can use `execute` with named params now."] + pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result { + self.execute(params) + } + /// Execute an INSERT and return the ROWID. /// /// # Note @@ -107,11 +121,7 @@ impl Statement<'_> { /// # Failure /// /// Will return `Err` if no row is inserted or many rows are inserted. - pub fn insert

(&mut self, params: P) -> Result - where - P: IntoIterator, - P::Item: ToSql, - { + pub fn insert(&mut self, params: P) -> Result { let changes = self.execute(params)?; match changes { 1 => Ok(self.conn.last_insert_rowid()), @@ -128,11 +138,13 @@ impl Statement<'_> { /// /// ## Example /// + /// ### Use without parameters + /// /// ```rust,no_run - /// # use rusqlite::{Connection, Result, NO_PARAMS}; + /// # use rusqlite::{Connection, Result}; /// fn get_names(conn: &Connection) -> Result> { /// let mut stmt = conn.prepare("SELECT name FROM people")?; - /// let mut rows = stmt.query(NO_PARAMS)?; + /// let mut rows = stmt.query([])?; /// /// let mut names = Vec::new(); /// while let Some(row) = rows.next()? { @@ -143,32 +155,41 @@ impl Statement<'_> { /// } /// ``` /// - /// ## Failure + /// ### Use with positional parameters /// - /// Will return `Err` if binding parameters fails. - pub fn query

(&mut self, params: P) -> Result> - where - P: IntoIterator, - P::Item: ToSql, - { - self.check_readonly()?; - self.bind_parameters(params)?; - Ok(Rows::new(self)) - } - - /// Execute the prepared statement with named parameter(s), returning a - /// handle for the resulting rows. 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. + /// ```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 rows = stmt.query(rusqlite::params![name])?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` /// - /// ## Example + /// Or, equivalently (but without the [`params!`] macro). + /// + /// ```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 rows = stmt.query(&[name])?; + /// while let Some(row) = rows.next()? { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// ### Use with named parameters /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// fn query(conn: &Connection) -> Result<()> { /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?; - /// let mut rows = stmt.query_named(&[(":name", &"one")])?; + /// let mut rows = stmt.query(&[(":name", &"one")])?; /// while let Some(row) = rows.next()? { /// // ... /// } @@ -183,7 +204,7 @@ impl Statement<'_> { /// # use rusqlite::{Connection, Result, named_params}; /// fn query(conn: &Connection) -> Result<()> { /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?; - /// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?; + /// let mut rows = stmt.query(named_params!{ ":name": "one" })?; /// while let Some(row) = rows.next()? { /// // ... /// } @@ -191,25 +212,49 @@ impl Statement<'_> { /// } /// ``` /// + /// ## Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query(&mut self, params: P) -> Result> { + self.check_readonly()?; + params.bind_in(self)?; + 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. + #[deprecated = "You can use `query` with named params now."] pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result> { - self.check_readonly()?; - self.bind_parameters_named(params)?; - Ok(Rows::new(self)) + self.query(params) } /// Executes the prepared statement and maps a function over the resulting /// rows, returning an iterator over the mapped function results. /// + /// `f` is used to tranform the _streaming_ iterator into a _standard_ + /// iterator. + /// /// ## Example /// + /// ### Use with positional params + /// /// ```rust,no_run - /// # use rusqlite::{Connection, Result, NO_PARAMS}; + /// # use rusqlite::{Connection, Result}; /// fn get_names(conn: &Connection) -> Result> { /// let mut stmt = conn.prepare("SELECT name FROM people")?; - /// let rows = stmt.query_map(NO_PARAMS, |row| row.get(0))?; + /// let rows = stmt.query_map([], |row| row.get(0))?; /// /// let mut names = Vec::new(); /// for name_result in rows { @@ -219,16 +264,29 @@ impl Statement<'_> { /// Ok(names) /// } /// ``` - /// `f` is used to tranform the _streaming_ iterator into a _standard_ - /// iterator. /// + /// ### Use with named params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?; + /// let rows = stmt.query_map(&[(":id", &"one")], |row| row.get(0))?; + /// + /// let mut names = Vec::new(); + /// for name_result in rows { + /// names.push(name_result?); + /// } + /// + /// Ok(names) + /// } + /// ``` /// ## Failure /// /// Will return `Err` if binding parameters fails. pub fn query_map(&mut self, params: P, f: F) -> Result> where - P: IntoIterator, - P::Item: ToSql, + P: Params, F: FnMut(&Row<'_>) -> Result, { let rows = self.query(params)?; @@ -237,33 +295,23 @@ impl Statement<'_> { /// Execute the prepared statement with named parameter(s), returning an /// iterator over the result of calling the mapping function over the - /// query's rows. If any parameters that were in the prepared statement + /// 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. /// - /// ## Example - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// fn get_names(conn: &Connection) -> Result> { - /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?; - /// let rows = stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))?; - /// - /// let mut names = Vec::new(); - /// for name_result in rows { - /// names.push(name_result?); - /// } - /// - /// Ok(names) - /// } - /// ``` /// `f` is used to tranform the _streaming_ iterator into a _standard_ /// iterator. /// /// ## Failure /// /// Will return `Err` if binding parameters fails. + #[deprecated = "You can use `query_map` with named params now."] pub fn query_map_named( &mut self, params: &[(&str, &dyn ToSql)], @@ -272,38 +320,17 @@ impl Statement<'_> { where F: FnMut(&Row<'_>) -> Result, { - let rows = self.query_named(params)?; - Ok(MappedRows::new(rows, f)) + 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` (so errors can be unified). /// - /// # Failure - /// - /// Will return `Err` if binding parameters fails. - pub fn query_and_then(&mut self, params: P, f: F) -> Result> - where - P: IntoIterator, - P::Item: ToSql, - E: convert::From, - F: FnMut(&Row<'_>) -> Result, - { - let rows = self.query(params)?; - Ok(AndThenRows::new(rows, 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. 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. - /// /// ## Example /// + /// ### Use with named params + /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// struct Person { @@ -318,7 +345,7 @@ impl Statement<'_> { /// fn get_names(conn: &Connection) -> Result> { /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?; /// let rows = - /// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?; + /// stmt.query_and_then(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?; /// /// let mut persons = Vec::new(); /// for person_result in rows { @@ -329,9 +356,52 @@ impl Statement<'_> { /// } /// ``` /// + /// ### Use with positional params + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?")?; + /// let rows = stmt.query_and_then(&["one"], |row| row.get::<_, String>(0))?; + /// + /// let mut persons = Vec::new(); + /// for person_result in rows { + /// persons.push(person_result?); + /// } + /// + /// Ok(persons) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query_and_then(&mut self, params: P, f: F) -> Result> + where + P: Params, + E: convert::From, + F: FnMut(&Row<'_>) -> Result, + { + let rows = self.query(params)?; + Ok(AndThenRows::new(rows, 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. + #[deprecated = "You can use `query_and_then` with named params now."] pub fn query_and_then_named( &mut self, params: &[(&str, &dyn ToSql)], @@ -341,17 +411,12 @@ impl Statement<'_> { E: convert::From, F: FnMut(&Row<'_>) -> Result, { - let rows = self.query_named(params)?; - Ok(AndThenRows::new(rows, f)) + 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. - pub fn exists

(&mut self, params: P) -> Result - where - P: IntoIterator, - P::Item: ToSql, - { + pub fn exists(&mut self, params: P) -> Result { let mut rows = self.query(params)?; let exists = rows.next()?.is_some(); Ok(exists) @@ -372,8 +437,7 @@ impl Statement<'_> { /// Will return `Err` if the underlying SQLite call fails. pub fn query_row(&mut self, params: P, f: F) -> Result where - P: IntoIterator, - P::Item: ToSql, + P: Params, F: FnOnce(&Row<'_>) -> Result, { let mut rows = self.query(params)?; @@ -384,6 +448,9 @@ impl Statement<'_> { /// 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. /// @@ -395,13 +462,12 @@ impl Statement<'_> { /// /// 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(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result where F: FnOnce(&Row<'_>) -> Result, { - let mut rows = self.query_named(params)?; - - rows.get_expected_row().and_then(|r| f(&r)) + self.query_row(params, f) } /// Consumes the statement. @@ -439,7 +505,7 @@ impl Statement<'_> { Ok(self.stmt.bind_parameter_index(name)) } - fn bind_parameters

(&mut self, params: P) -> Result<()> + pub(crate) fn bind_parameters

(&mut self, params: P) -> Result<()> where P: IntoIterator, P::Item: ToSql, @@ -460,10 +526,14 @@ impl Statement<'_> { } } - fn bind_parameters_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> { + pub(crate) fn bind_parameters_named( + &mut self, + params: &[(&str, &T)], + ) -> Result<()> { for &(name, value) in params { if let Some(i) = self.parameter_index(name)? { - self.bind_parameter(value, i)?; + let ts: &dyn ToSql = &value; + self.bind_parameter(ts, i)?; } else { return Err(Error::InvalidParameterName(name.into())); } @@ -839,9 +909,10 @@ pub enum StatementStatus { #[cfg(test)] mod test { use crate::types::ToSql; - use crate::{Connection, Error, Result, NO_PARAMS}; + use crate::{params_from_iter, Connection, Error, Result, NO_PARAMS}; #[test] + #[allow(deprecated)] fn test_execute_named() { let db = Connection::open_in_memory().unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); @@ -852,13 +923,21 @@ mod test { 1 ); assert_eq!( - db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]) + db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]) .unwrap(), 1 ); + assert_eq!( + db.execute( + "INSERT INTO foo(x) VALUES (:x)", + crate::named_params! {":x": 3i32} + ) + .unwrap(), + 1 + ); assert_eq!( - 3i32, + 6i32, db.query_row_named::( "SELECT SUM(x) FROM foo WHERE x > :x", &[(":x", &0i32)], @@ -866,9 +945,19 @@ mod test { ) .unwrap() ); + assert_eq!( + 5i32, + db.query_row::( + "SELECT SUM(x) FROM foo WHERE x > :x", + &[(":x", &1i32)], + |r| r.get(0) + ) + .unwrap() + ); } #[test] + #[allow(deprecated)] fn test_stmt_execute_named() { let db = Connection::open_in_memory().unwrap(); let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \ @@ -888,9 +977,15 @@ mod test { stmt.query_row_named::(&[(":name", &"one")], |r| r.get(0)) .unwrap() ); + assert_eq!( + 1i32, + stmt.query_row::(&[(":name", &"one")], |r| r.get(0)) + .unwrap() + ); } #[test] + #[allow(deprecated)] fn test_query_named() { let db = Connection::open_in_memory().unwrap(); let sql = r#" @@ -902,13 +997,23 @@ mod test { let mut stmt = db .prepare("SELECT id FROM test where name = :name") .unwrap(); - let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); + // legacy `_named` api + { + let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); + let id: Result = rows.next().unwrap().unwrap().get(0); + assert_eq!(Ok(1), id); + } - let id: Result = rows.next().unwrap().unwrap().get(0); - assert_eq!(Ok(1), id); + // plain api + { + let mut rows = stmt.query(&[(":name", &"one")]).unwrap(); + let id: Result = rows.next().unwrap().unwrap().get(0); + assert_eq!(Ok(1), id); + } } #[test] + #[allow(deprecated)] fn test_query_map_named() { let db = Connection::open_in_memory().unwrap(); let sql = r#" @@ -920,18 +1025,34 @@ mod test { let mut stmt = db .prepare("SELECT id FROM test where name = :name") .unwrap(); - let mut rows = stmt - .query_map_named(&[(":name", &"one")], |row| { - let id: Result = row.get(0); - id.map(|i| 2 * i) - }) - .unwrap(); + // legacy `_named` api + { + let mut rows = stmt + .query_map_named(&[(":name", &"one")], |row| { + let id: Result = row.get(0); + id.map(|i| 2 * i) + }) + .unwrap(); - let doubled_id: i32 = rows.next().unwrap().unwrap(); - assert_eq!(2, doubled_id); + let doubled_id: i32 = rows.next().unwrap().unwrap(); + assert_eq!(2, doubled_id); + } + // plain api + { + let mut rows = stmt + .query_map(&[(":name", &"one")], |row| { + let id: Result = row.get(0); + id.map(|i| 2 * i) + }) + .unwrap(); + + let doubled_id: i32 = rows.next().unwrap().unwrap(); + assert_eq!(2, doubled_id); + } } #[test] + #[allow(deprecated)] fn test_query_and_then_named() { let db = Connection::open_in_memory().unwrap(); let sql = r#" @@ -969,6 +1090,44 @@ mod test { } #[test] + fn test_query_and_then_by_name() { + let db = Connection::open_in_memory().unwrap(); + 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).unwrap(); + + let mut stmt = db + .prepare("SELECT id FROM test where name = :name ORDER BY id ASC") + .unwrap(); + let mut rows = stmt + .query_and_then(&[(":name", &"one")], |row| { + let id: i32 = row.get(0)?; + if id == 1 { + Ok(id) + } else { + Err(Error::SqliteSingleThreadedMode) + } + }) + .unwrap(); + + // first row should be Ok + let doubled_id: i32 = rows.next().unwrap().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"), + } + } + + #[test] + #[allow(deprecated)] fn test_unbound_parameters_are_null() { let db = Connection::open_in_memory().unwrap(); let sql = "CREATE TABLE test (x TEXT, y TEXT)"; @@ -980,9 +1139,7 @@ mod test { stmt.execute_named(&[(":x", &"one")]).unwrap(); let result: Option = db - .query_row("SELECT y FROM test WHERE x = 'one'", NO_PARAMS, |row| { - row.get(0) - }) + .query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0)) .unwrap(); assert!(result.is_none()); } @@ -1027,8 +1184,8 @@ mod test { let mut stmt = db .prepare("INSERT INTO test (x, y) VALUES (:x, :y)") .unwrap(); - stmt.execute_named(&[(":x", &"one")]).unwrap(); - stmt.execute_named(&[(":y", &"two")]).unwrap(); + stmt.execute(&[(":x", &"one")]).unwrap(); + stmt.execute(&[(":y", &"two")]).unwrap(); let result: String = db .query_row("SELECT x FROM test WHERE y = 'two'", NO_PARAMS, |row| { @@ -1046,16 +1203,16 @@ mod test { let mut stmt = db .prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)") .unwrap(); - assert_eq!(stmt.insert(&[1i32]).unwrap(), 1); - assert_eq!(stmt.insert(&[2i32]).unwrap(), 2); - match stmt.insert(&[1i32]).unwrap_err() { + assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1); + assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2); + match stmt.insert(&[&1i32]).unwrap_err() { Error::StatementChangedRows(0) => (), err => panic!("Unexpected error {}", err), } let mut multi = db .prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4") .unwrap(); - match multi.insert(NO_PARAMS).unwrap_err() { + match multi.insert([]).unwrap_err() { Error::StatementChangedRows(2) => (), err => panic!("Unexpected error {}", err), } @@ -1076,14 +1233,14 @@ mod test { assert_eq!( db.prepare("INSERT INTO foo VALUES (10)") .unwrap() - .insert(NO_PARAMS) + .insert([]) .unwrap(), 1 ); assert_eq!( db.prepare("INSERT INTO bar VALUES (10)") .unwrap() - .insert(NO_PARAMS) + .insert([]) .unwrap(), 1 ); @@ -1099,9 +1256,9 @@ mod test { END;"; db.execute_batch(sql).unwrap(); let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?").unwrap(); - assert!(stmt.exists(&[1i32]).unwrap()); - assert!(stmt.exists(&[2i32]).unwrap()); - assert!(!stmt.exists(&[0i32]).unwrap()); + assert!(stmt.exists([1i32]).unwrap()); + assert!(stmt.exists(&[&2i32]).unwrap()); + assert!(!stmt.exists([&0i32]).unwrap()); } #[test] @@ -1114,7 +1271,7 @@ mod test { END;"; db.execute_batch(sql).unwrap(); let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?").unwrap(); - let y: Result = stmt.query_row(&[1i32], |r| r.get(0)); + let y: Result = stmt.query_row([1i32], |r| r.get(0)); assert_eq!(3i64, y.unwrap()); } @@ -1127,7 +1284,7 @@ mod test { END;"; db.execute_batch(sql).unwrap(); let mut stmt = db.prepare("SELECT y FROM foo").unwrap(); - let y: Result = stmt.query_row(NO_PARAMS, |r| r.get("y")); + let y: Result = stmt.query_row([], |r| r.get("y")); assert_eq!(3i64, y.unwrap()); } @@ -1165,28 +1322,40 @@ mod test { .unwrap(); // existing collection: let data = vec![1, 2, 3]; - db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0)) - .unwrap(); - db.query_row("SELECT ?1, ?2, ?3", data.as_slice(), |row| { + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, u8>(0) + }) + .unwrap(); + db.query_row( + "SELECT ?1, ?2, ?3", + params_from_iter(data.as_slice()), + |row| row.get::<_, u8>(0), + ) + .unwrap(); + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data), |row| { row.get::<_, u8>(0) }) .unwrap(); - db.query_row("SELECT ?1, ?2, ?3", data, |row| row.get::<_, u8>(0)) - .unwrap(); use std::collections::BTreeSet; let data: BTreeSet = ["one", "two", "three"] .iter() .map(|s| (*s).to_string()) .collect(); - db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0)) - .unwrap(); + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, String>(0) + }) + .unwrap(); let data = [0; 3]; - db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0)) - .unwrap(); - db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0)) - .unwrap(); + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| { + row.get::<_, u8>(0) + }) + .unwrap(); + db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data.iter()), |row| { + row.get::<_, u8>(0) + }) + .unwrap(); } #[test] @@ -1243,7 +1412,7 @@ mod test { let db = Connection::open_in_memory().unwrap(); let expected = "a\x00b"; let actual: String = db - .query_row("SELECT ?", &[&expected], |row| row.get(0)) + .query_row("SELECT ?", [expected], |row| row.get(0)) .unwrap(); assert_eq!(expected, actual); } diff --git a/src/trace.rs b/src/trace.rs index 76e0969..2957919 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -142,13 +142,13 @@ mod test { let mut db = Connection::open_in_memory().unwrap(); db.trace(Some(tracer)); { - let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(())); + let _ = db.query_row("SELECT ?", &[&1i32], |_| Ok(())); let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(())); } db.trace(None); { - let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(())); - let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(())); + let _ = db.query_row("SELECT ?", [2i32], |_| Ok(())); + let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(())); } let traced_stmts = TRACED_STMTS.lock().unwrap(); diff --git a/src/transaction.rs b/src/transaction.rs index 5e649b7..371560b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -661,7 +661,7 @@ mod test { } fn insert(x: i32, conn: &Connection) { - conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap(); + conn.execute("INSERT INTO foo VALUES(?)", [x]).unwrap(); } fn assert_current_sum(x: i32, conn: &Connection) { diff --git a/src/types/mod.rs b/src/types/mod.rs index d4bc440..8f62108 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -94,7 +94,7 @@ mod value_ref; /// # use rusqlite::types::{Null}; /// /// fn insert_null(conn: &Connection) -> Result { -/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null]) +/// conn.execute("INSERT INTO people (name) VALUES (?)", [Null]) /// } /// ``` #[derive(Copy, Clone)] @@ -188,7 +188,7 @@ 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 (?)", [s.to_owned()]) .unwrap(); let from: String = db @@ -201,7 +201,7 @@ mod test { fn test_value() { let db = checked_memory_handle(); - db.execute("INSERT INTO foo(i) VALUES (?)", &[Value::Integer(10)]) + db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)]) .unwrap(); assert_eq!( diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs index 8f90422..61ec6b7 100644 --- a/src/types/to_sql.rs +++ b/src/types/to_sql.rs @@ -336,7 +336,7 @@ mod test { (?, 'neg one'), (?, 'neg two'), (?, 'pos one'), (?, 'pos two'), (?, 'min'), (?, 'max')", - &[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX], + [0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX], ) .unwrap();