From d5bbbbd763c78b5dd5eeda0ab4099c0a424a4daa Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 18 May 2016 14:15:56 -0500 Subject: [PATCH] Add query_map_named and query_and_then_named to Statement. --- Changelog.md | 1 + src/lib.rs | 21 +++++- src/named_params.rs | 170 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 178 insertions(+), 14 deletions(-) diff --git a/Changelog.md b/Changelog.md index 282c805..0b6b471 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ connection are inherently nested. While a transaction is alive, the parent connection or transaction is unusable, so `Transaction` now implements `Deref`, giving access to `Connection`'s methods via the `Transaction` itself. +* Adds `query_map_named` and `query_and_then_named` to `Statement`. * Adds `insert` convenience method to `Statement` which returns the row ID of an inserted row. * Adds `exists` convenience method returning whether a query finds one or more rows. * Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature. diff --git a/src/lib.rs b/src/lib.rs index 840a60c..2e72d20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -824,7 +824,7 @@ impl<'conn> Statement<'conn> { /// } /// ``` /// - /// # Failure + /// ## Failure /// /// Will return `Err` if binding parameters fails. pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> Result> { @@ -838,7 +838,24 @@ impl<'conn> Statement<'conn> { /// Executes the prepared statement and maps a function over the resulting rows, returning /// an iterator over the mapped function results. /// - /// # Failure + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result}; + /// fn get_names(conn: &Connection) -> Result> { + /// let mut stmt = try!(conn.prepare("SELECT name FROM people")); + /// let rows = try!(stmt.query_map(&[], |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<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result> diff --git a/src/named_params.rs b/src/named_params.rs index a6b82bd..9bbb673 100644 --- a/src/named_params.rs +++ b/src/named_params.rs @@ -1,8 +1,10 @@ +use std::convert; +use std::result; use libc::c_int; use super::ffi; -use {Result, Error, Connection, Statement, Rows, Row, str_to_cstring}; +use {Result, Error, Connection, Statement, MappedRows, AndThenRows, Rows, Row, str_to_cstring}; use types::ToSql; impl Connection { @@ -117,7 +119,93 @@ impl<'conn> Statement<'conn> { Ok(Rows::new(self)) } - // TODO: add query_map_named and query_and_then_named + /// 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 { @@ -134,6 +222,7 @@ impl<'conn> Statement<'conn> { #[cfg(test)] mod test { use Connection; + use error::Error; #[test] fn test_execute_named() { @@ -147,9 +236,9 @@ mod test { assert_eq!(3i32, db.query_row_named("SELECT SUM(x) FROM foo WHERE x > :x", - &[(":x", &0i32)], - |r| r.get(0)) - .unwrap()); + &[(":x", &0i32)], + |r| r.get(0)) + .unwrap()); } #[test] @@ -164,20 +253,77 @@ mod test { assert_eq!(1i32, db.query_row_named("SELECT COUNT(*) FROM test WHERE name = :name", - &[(":name", &"one")], - |r| r.get(0)) - .unwrap()); + &[(":name", &"one")], + |r| r.get(0)) + .unwrap()); } #[test] fn test_query_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)"; + 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 * FROM test where name = :name").unwrap(); - stmt.query_named(&[(":name", &"one")]).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]