From 5bd7cb37c754265bb2d5218634bcd5c23646df0d Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 8 Mar 2017 11:20:43 -0500 Subject: [PATCH] Move named_params.rs into statement.rs, greatly reducing the StatementCrateImpl trait size. --- src/lib.rs | 41 ++++- src/named_params.rs | 357 ------------------------------------ src/statement.rs | 435 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 407 insertions(+), 426 deletions(-) delete mode 100644 src/named_params.rs diff --git a/src/lib.rs b/src/lib.rs index 6f2ace8..536b7da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,7 +103,6 @@ pub mod types; mod version; mod transaction; mod cache; -mod named_params; mod error; mod convenient; mod raw_statement; @@ -297,6 +296,28 @@ impl Connection { self.prepare(sql).and_then(|mut stmt| stmt.execute(params)) } + /// 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`). + /// + /// ## 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. + pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result { + self.prepare(sql).and_then(|mut stmt| 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 @@ -331,6 +352,24 @@ impl Connection { stmt.query_row(params, f) } + /// Convenience method to execute a query with named parameter(s) that is expected to return + /// a single row. + /// + /// If the query returns more than one row, all rows except the first are ignored. + /// + /// # Failure + /// + /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the + /// underlying SQLite call fails. + pub fn query_row_named(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result + where F: FnOnce(&Row) -> T + { + let mut stmt = try!(self.prepare(sql)); + let mut rows = try!(stmt.query_named(params)); + + rows.get_expected_row().map(|r| f(&r)) + } + /// 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 `std::convert::From`. diff --git a/src/named_params.rs b/src/named_params.rs deleted file mode 100644 index 424fac5..0000000 --- a/src/named_params.rs +++ /dev/null @@ -1,357 +0,0 @@ -use std::convert; -use std::result; -use std::os::raw::c_int; -use statement::StatementCrateImpl; - -use {Result, Error, Connection, Statement, MappedRows, AndThenRows, Rows, Row, str_to_cstring}; -use types::ToSql; - -impl Connection { - /// 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`). - /// - /// ## 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. - pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result { - self.prepare(sql).and_then(|mut stmt| stmt.execute_named(params)) - } - - /// 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. - /// - /// # Failure - /// - /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the - /// underlying SQLite call fails. - pub fn query_row_named(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result - where F: FnOnce(&Row) -> T - { - let mut stmt = try!(self.prepare(sql)); - let mut rows = try!(stmt.query_named(params)); - - rows.get_expected_row().map(|r| f(&r)) - } -} - -impl<'conn> Statement<'conn> { - /// Return the index of an SQL parameter given its name. - /// - /// # Failure - /// - /// Will return Err if `name` is invalid. Will return Ok(None) if the name - /// is valid but not a bound parameter of this statement. - pub fn parameter_index(&self, name: &str) -> Result> { - let c_name = try!(str_to_cstring(name)); - Ok(self.bind_parameter_index(&c_name)) - } - - /// 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 - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// fn insert(conn: &Connection) -> Result { - /// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)")); - /// stmt.execute_named(&[(":name", &"one")]) - /// } - /// ``` - /// - /// # Failure - /// - /// Will return `Err` if binding parameters fails, the executed statement returns rows (in - /// which case `query` should be used instead), or the underling SQLite call fails. - pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result { - try!(self.bind_parameters_named(params)); - self.execute_with_bound_parameters() - } - - /// 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. - /// - /// ## Example - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// fn query(conn: &Connection) -> Result<()> { - /// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name")); - /// let mut rows = try!(stmt.query_named(&[(":name", &"one")])); - /// while let Some(row) = rows.next() { - /// // ... - /// } - /// Ok(()) - /// } - /// ``` - /// - /// # Failure - /// - /// Will return `Err` if binding parameters fails. - pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result> { - try!(self.bind_parameters_named(params)); - Ok(Rows::new(self)) - } - - /// 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 - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// fn get_names(conn: &Connection) -> Result> { - /// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); - /// let rows = try!(stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))); - /// - /// let mut names = Vec::new(); - /// for name_result in rows { - /// names.push(try!(name_result)); - /// } - /// - /// Ok(names) - /// } - /// ``` - /// - /// ## Failure - /// - /// Will return `Err` if binding parameters fails. - pub fn query_map_named<'a, T, F>(&'a mut self, - params: &[(&str, &ToSql)], - f: F) - -> Result> - where F: FnMut(&Row) -> T - { - let rows = try!(self.query_named(params)); - Ok(MappedRows { - rows: rows, - map: 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 - /// - /// ```rust,no_run - /// # use rusqlite::{Connection, Result}; - /// struct Person { name: String }; - /// - /// fn name_to_person(name: String) -> Result { - /// // ... check for valid name - /// Ok(Person{ name: name }) - /// } - /// - /// fn get_names(conn: &Connection) -> Result> { - /// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); - /// let rows = try!(stmt.query_and_then_named(&[(":id", &"one")], |row| { - /// name_to_person(row.get(0)) - /// })); - /// - /// let mut persons = Vec::new(); - /// for person_result in rows { - /// persons.push(try!(person_result)); - /// } - /// - /// Ok(persons) - /// } - /// ``` - /// - /// ## Failure - /// - /// Will return `Err` if binding parameters fails. - pub fn query_and_then_named<'a, T, E, F>(&'a mut self, - params: &[(&str, &ToSql)], - f: F) - -> Result> - where E: convert::From, - F: FnMut(&Row) -> result::Result - { - let rows = try!(self.query_named(params)); - Ok(AndThenRows { - rows: rows, - map: f, - }) - } - - fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> { - for &(name, value) in params { - if let Some(i) = try!(self.parameter_index(name)) { - try!(self.bind_parameter(value, i)); - } else { - return Err(Error::InvalidParameterName(name.into())); - } - } - Ok(()) - } -} - -#[cfg(test)] -mod test { - use Connection; - use error::Error; - - #[test] - fn test_execute_named() { - let db = Connection::open_in_memory().unwrap(); - db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); - - assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(), - 1); - assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(), - 1); - - assert_eq!(3i32, - db.query_row_named::("SELECT SUM(x) FROM foo WHERE x > :x", - &[(":x", &0i32)], - |r| r.get(0)) - .unwrap()); - } - - #[test] - 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 \ - INTEGER)"; - db.execute_batch(sql).unwrap(); - - let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)").unwrap(); - stmt.execute_named(&[(":name", &"one")]).unwrap(); - - assert_eq!(1i32, - db.query_row_named::("SELECT COUNT(*) FROM test WHERE name = :name", - &[(":name", &"one")], - |r| r.get(0)) - .unwrap()); - } - - #[test] - fn test_query_named() { - 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"); - "#; - db.execute_batch(sql).unwrap(); - - let mut stmt = db.prepare("SELECT id FROM test where name = :name").unwrap(); - let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); - - let id: i32 = rows.next().unwrap().unwrap().get(0); - assert_eq!(1, id); - } - - #[test] - fn test_query_map_named() { - 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"); - "#; - db.execute_batch(sql).unwrap(); - - 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: i32 = row.get(0); - 2 * id - }) - .unwrap(); - - let doubled_id: i32 = rows.next().unwrap().unwrap(); - assert_eq!(2, doubled_id); - } - - #[test] - fn test_query_and_then_named() { - - 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_named(&[(":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 - match rows.next().unwrap() { - Ok(_) => panic!("invalid Ok"), - Err(Error::SqliteSingleThreadedMode) => (), - Err(_) => panic!("invalid Err"), - } - } - - #[test] - fn test_unbound_parameters_are_null() { - let db = Connection::open_in_memory().unwrap(); - let sql = "CREATE TABLE test (x TEXT, y TEXT)"; - db.execute_batch(sql).unwrap(); - - let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)").unwrap(); - stmt.execute_named(&[(":x", &"one")]).unwrap(); - - let result: Option = - db.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0)) - .unwrap(); - assert!(result.is_none()); - } - - #[test] - fn test_unbound_parameters_are_reused() { - let db = Connection::open_in_memory().unwrap(); - let sql = "CREATE TABLE test (x TEXT, y TEXT)"; - db.execute_batch(sql).unwrap(); - - 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(); - - let result: String = - db.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0)) - .unwrap(); - assert_eq!(result, "one"); - } -} diff --git a/src/statement.rs b/src/statement.rs index ba93870..dc70bda 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -79,6 +79,33 @@ impl<'conn> Statement<'conn> { 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 + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn insert(conn: &Connection) -> Result { + /// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)")); + /// stmt.execute_named(&[(":name", &"one")]) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if binding parameters fails, the executed statement returns rows (in + /// which case `query` should be used instead), or the underling SQLite call fails. + pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result { + try!(self.bind_parameters_named(params)); + self.execute_with_bound_parameters() + } + /// Execute the prepared statement, returning a handle to the resulting rows. /// /// Due to lifetime restricts, the rows handle returned by `query` does not @@ -111,6 +138,33 @@ impl<'conn> Statement<'conn> { 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. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn query(conn: &Connection) -> Result<()> { + /// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name")); + /// let mut rows = try!(stmt.query_named(&[(":name", &"one")])); + /// while let Some(row) = rows.next() { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// + /// # Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result> { + try!(self.bind_parameters_named(params)); + Ok(Rows::new(self)) + } + /// Executes the prepared statement and maps a function over the resulting rows, returning /// an iterator over the mapped function results. /// @@ -145,6 +199,45 @@ impl<'conn> Statement<'conn> { }) } + /// 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 + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); + /// let rows = try!(stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))); + /// + /// let mut names = Vec::new(); + /// for name_result in rows { + /// names.push(try!(name_result)); + /// } + /// + /// Ok(names) + /// } + /// ``` + /// + /// ## Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query_map_named<'a, T, F>(&'a mut self, + params: &[(&str, &ToSql)], + f: F) + -> Result> + where F: FnMut(&Row) -> T + { + let rows = try!(self.query_named(params)); + Ok(MappedRows { + rows: rows, + map: 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). @@ -167,6 +260,55 @@ impl<'conn> Statement<'conn> { }) } + /// 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 + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// struct Person { name: String }; + /// + /// fn name_to_person(name: String) -> Result { + /// // ... check for valid name + /// Ok(Person{ name: name }) + /// } + /// + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); + /// let rows = try!(stmt.query_and_then_named(&[(":id", &"one")], |row| { + /// name_to_person(row.get(0)) + /// })); + /// + /// let mut persons = Vec::new(); + /// for person_result in rows { + /// persons.push(try!(person_result)); + /// } + /// + /// Ok(persons) + /// } + /// ``` + /// + /// ## Failure + /// + /// Will return `Err` if binding parameters fails. + pub fn query_and_then_named<'a, T, E, F>(&'a mut self, + params: &[(&str, &ToSql)], + f: F) + -> Result> + where E: convert::From, + F: FnMut(&Row) -> result::Result + { + let rows = try!(self.query_named(params)); + Ok(AndThenRows { + rows: rows, + map: f, + }) + } + /// Consumes the statement. /// /// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors @@ -179,6 +321,17 @@ impl<'conn> Statement<'conn> { self.finalize_() } + /// Return the index of an SQL parameter given its name. + /// + /// # Failure + /// + /// Will return Err if `name` is invalid. Will return Ok(None) if the name + /// is valid but not a bound parameter of this statement. + pub fn parameter_index(&self, name: &str) -> Result> { + let c_name = try!(str_to_cstring(name)); + Ok(self.stmt.bind_parameter_index(&c_name)) + } + fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> { assert!(params.len() as c_int == self.stmt.bind_parameter_count(), "incorrect number of parameters to query(): expected {}, got {}", @@ -192,74 +345,15 @@ impl<'conn> Statement<'conn> { Ok(()) } - fn finalize_(&mut self) -> Result<()> { - let mut stmt = RawStatement::new(ptr::null_mut()); - mem::swap(&mut stmt, &mut self.stmt); - self.conn.decode_result(stmt.finalize()) - } -} - -impl<'conn> Into for Statement<'conn> { - fn into(mut self) -> RawStatement { - let mut stmt = RawStatement::new(ptr::null_mut()); - mem::swap(&mut stmt, &mut self.stmt); - stmt - } -} - -impl<'conn> fmt::Debug for Statement<'conn> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let sql = str::from_utf8(self.stmt.sql().to_bytes()); - f.debug_struct("Statement") - .field("conn", self.conn) - .field("stmt", &self.stmt) - .field("sql", &sql) - .finish() - } -} - -impl<'conn> Drop for Statement<'conn> { - #[allow(unused_must_use)] - fn drop(&mut self) { - self.finalize_(); - } -} - -// TODO: This trait lets us have "pub(crate)" visibility on some Statement methods. Remove this -// once pub(crate) is stable. -pub trait StatementCrateImpl<'conn> { - fn new(conn: &'conn Connection, stmt: RawStatement) -> Self; - fn execute_with_bound_parameters(&mut self) -> Result; - fn bind_parameter(&self, param: &ToSql, col: c_int) -> Result<()>; - fn bind_parameter_index(&self, name: &CStr) -> Option; - fn last_insert_rowid(&self) -> i64; - fn value_ref(&self, col: c_int) -> ValueRef; - fn step(&self) -> Result; - fn reset(&self) -> c_int; -} - -impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { - fn new(conn: &Connection, stmt: RawStatement) -> Statement { - Statement { - conn: conn, - stmt: stmt, - } - } - - fn execute_with_bound_parameters(&mut self) -> Result { - let r = self.stmt.step(); - self.stmt.reset(); - match r { - ffi::SQLITE_DONE => { - if self.column_count() == 0 { - Ok(self.conn.changes()) - } else { - Err(Error::ExecuteReturnedResults) - } + fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> { + for &(name, value) in params { + if let Some(i) = try!(self.parameter_index(name)) { + try!(self.bind_parameter(value, i)); + } else { + return Err(Error::InvalidParameterName(name.into())); } - ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults), - _ => Err(self.conn.decode_result(r).unwrap_err()), } + Ok(()) } fn bind_parameter(&self, param: &ToSql, col: c_int) -> Result<()> { @@ -311,8 +405,71 @@ impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { }) } - fn bind_parameter_index(&self, name: &CStr) -> Option { - self.stmt.bind_parameter_index(name) + fn execute_with_bound_parameters(&mut self) -> Result { + let r = self.stmt.step(); + self.stmt.reset(); + match r { + ffi::SQLITE_DONE => { + if self.column_count() == 0 { + Ok(self.conn.changes()) + } else { + Err(Error::ExecuteReturnedResults) + } + } + ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults), + _ => Err(self.conn.decode_result(r).unwrap_err()), + } + } + + fn finalize_(&mut self) -> Result<()> { + let mut stmt = RawStatement::new(ptr::null_mut()); + mem::swap(&mut stmt, &mut self.stmt); + self.conn.decode_result(stmt.finalize()) + } +} + +impl<'conn> Into for Statement<'conn> { + fn into(mut self) -> RawStatement { + let mut stmt = RawStatement::new(ptr::null_mut()); + mem::swap(&mut stmt, &mut self.stmt); + stmt + } +} + +impl<'conn> fmt::Debug for Statement<'conn> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let sql = str::from_utf8(self.stmt.sql().to_bytes()); + f.debug_struct("Statement") + .field("conn", self.conn) + .field("stmt", &self.stmt) + .field("sql", &sql) + .finish() + } +} + +impl<'conn> Drop for Statement<'conn> { + #[allow(unused_must_use)] + fn drop(&mut self) { + self.finalize_(); + } +} + +// TODO: This trait lets us have "pub(crate)" visibility on some Statement methods. Remove this +// once pub(crate) is stable. +pub trait StatementCrateImpl<'conn> { + fn new(conn: &'conn Connection, stmt: RawStatement) -> Self; + fn last_insert_rowid(&self) -> i64; + fn value_ref(&self, col: c_int) -> ValueRef; + fn step(&self) -> Result; + fn reset(&self) -> c_int; +} + +impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { + fn new(conn: &Connection, stmt: RawStatement) -> Statement { + Statement { + conn: conn, + stmt: stmt, + } } fn last_insert_rowid(&self) -> i64 { @@ -373,3 +530,145 @@ impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { self.stmt.reset() } } + +#[cfg(test)] +mod test { + use Connection; + use error::Error; + + #[test] + fn test_execute_named() { + let db = Connection::open_in_memory().unwrap(); + db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); + + assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(), + 1); + assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(), + 1); + + assert_eq!(3i32, + db.query_row_named::("SELECT SUM(x) FROM foo WHERE x > :x", + &[(":x", &0i32)], + |r| r.get(0)) + .unwrap()); + } + + #[test] + 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 \ + INTEGER)"; + db.execute_batch(sql).unwrap(); + + let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)").unwrap(); + stmt.execute_named(&[(":name", &"one")]).unwrap(); + + assert_eq!(1i32, + db.query_row_named::("SELECT COUNT(*) FROM test WHERE name = :name", + &[(":name", &"one")], + |r| r.get(0)) + .unwrap()); + } + + #[test] + fn test_query_named() { + 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"); + "#; + db.execute_batch(sql).unwrap(); + + let mut stmt = db.prepare("SELECT id FROM test where name = :name").unwrap(); + let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); + + let id: i32 = rows.next().unwrap().unwrap().get(0); + assert_eq!(1, id); + } + + #[test] + fn test_query_map_named() { + 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"); + "#; + db.execute_batch(sql).unwrap(); + + 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: i32 = row.get(0); + 2 * id + }) + .unwrap(); + + let doubled_id: i32 = rows.next().unwrap().unwrap(); + assert_eq!(2, doubled_id); + } + + #[test] + fn test_query_and_then_named() { + + 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_named(&[(":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 + match rows.next().unwrap() { + Ok(_) => panic!("invalid Ok"), + Err(Error::SqliteSingleThreadedMode) => (), + Err(_) => panic!("invalid Err"), + } + } + + #[test] + fn test_unbound_parameters_are_null() { + let db = Connection::open_in_memory().unwrap(); + let sql = "CREATE TABLE test (x TEXT, y TEXT)"; + db.execute_batch(sql).unwrap(); + + let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)").unwrap(); + stmt.execute_named(&[(":x", &"one")]).unwrap(); + + let result: Option = + db.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0)) + .unwrap(); + assert!(result.is_none()); + } + + #[test] + fn test_unbound_parameters_are_reused() { + let db = Connection::open_in_memory().unwrap(); + let sql = "CREATE TABLE test (x TEXT, y TEXT)"; + db.execute_batch(sql).unwrap(); + + 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(); + + let result: String = + db.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0)) + .unwrap(); + assert_eq!(result, "one"); + } +}