diff --git a/src/lib.rs b/src/lib.rs index 622fb09..cd381b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ extern crate libsqlite3_sys as ffi; #[macro_use] extern crate bitflags; use std::default::Default; +use std::convert; use std::mem; use std::ptr; use std::fmt; @@ -90,7 +91,7 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String { } /// Encompasses an error result from a call to the SQLite C API. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct SqliteError { /// The error code returned by a SQLite C API call. See [SQLite Result /// Codes](http://www.sqlite.org/rescode.html) for details. @@ -302,6 +303,37 @@ impl SqliteConnection { } } + /// 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`. + /// + /// ## Example + /// + /// ```rust,no_run + /// # use rusqlite::{SqliteResult,SqliteConnection}; + /// fn preferred_locale(conn: &SqliteConnection) -> SqliteResult { + /// conn.query_row_and_then("SELECT value FROM preferences WHERE name='locale'", &[], |row| { + /// row.get_checked(0) + /// }) + /// } + /// ``` + /// + /// If the query returns more than one row, all rows except the first are ignored. + pub fn query_row_and_then(&self, sql: &str, params: &[&ToSql], f: F) -> Result + where F: FnOnce(SqliteRow) -> Result, + E: convert::From { + let mut stmt = try!(self.prepare(sql)); + let mut rows = try!(stmt.query(params)); + + match rows.next() { + Some(row) => row.map_err(E::from).and_then(f), + None => Err(E::from(SqliteError{ + code: ffi::SQLITE_NOTICE, + message: "Query did not return a row".to_string(), + })) + } + } + /// Convenience method to execute a query that is expected to return a single row. /// /// ## Example @@ -690,6 +722,25 @@ impl<'conn> SqliteStatement<'conn> { }) } + /// 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). + /// + /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility + /// for accessing stale rows. + pub fn query_and_then<'a, T, E, F>(&'a mut self, params: &[&ToSql], f: F) + -> SqliteResult> + where T: 'static, + E: convert::From, + F: FnMut(SqliteRow) -> Result { + let row_iter = try!(self.query(params)); + + Ok(AndThenRows{ + rows: row_iter, + map: f, + }) + } + /// Consumes the statement. /// /// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors @@ -760,6 +811,26 @@ impl<'stmt, T, F> Iterator for MappedRows<'stmt, F> } } +/// An iterator over the mapped resulting rows of a query, with an Error type +/// unifying with SqliteError. +pub struct AndThenRows<'stmt, F> { + rows: SqliteRows<'stmt>, + map: F, +} + +impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> + where T: 'static, + E: convert::From, + F: FnMut(SqliteRow) -> Result { + type Item = Result; + + fn next(&mut self) -> Option { + self.rows.next().map(|row_result| row_result + .map_err(E::from) + .and_then(|row| (self.map)(row))) + } +} + /// An iterator over the resulting rows of a query. /// /// ## Warning @@ -880,9 +951,19 @@ impl<'stmt> SqliteRow<'stmt> { /// Returns a `SQLITE_MISMATCH`-coded `SqliteError` if the underlying SQLite column /// type is not a valid type as a source for `T`. /// - /// Panics if `idx` is outside the range of columns in the returned query or if this row - /// is stale. + /// Returns a `SQLITE_MISUSE`-coded `SqliteError` if `idx` is outside the valid column range + /// for this row or if this row is stale. pub fn get_checked(&self, idx: c_int) -> SqliteResult { + if self.row_idx != self.current_row.get() { + return Err(SqliteError{ code: ffi::SQLITE_MISUSE, + message: "Cannot get values from a row after advancing to next row".to_string() }); + } + unsafe { + if idx < 0 || idx >= ffi::sqlite3_column_count(self.stmt.stmt) { + return Err(SqliteError{ code: ffi::SQLITE_MISUSE, + message: "Invalid column index".to_string() }); + } + } let valid_column_type = unsafe { T::column_has_valid_sqlite_type(self.stmt.stmt, idx) }; @@ -924,6 +1005,8 @@ mod test { extern crate tempdir; use super::*; use self::tempdir::TempDir; + use std::error::Error as StdError; + use std::fmt; // this function is never called, but is still type checked; in // particular, calls with specific instantiations will require @@ -1093,6 +1176,184 @@ mod test { assert_eq!(results.unwrap().concat(), "hello, world!"); } + #[test] + fn test_query_and_then() { + let db = checked_memory_handle(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql).unwrap(); + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); + let results: SqliteResult> = query + .query_and_then(&[], |row| row.get_checked(1)) + .unwrap() + .collect(); + + assert_eq!(results.unwrap().concat(), "hello, world!"); + } + + #[test] + fn test_query_and_then_fails() { + let db = checked_memory_handle(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql).unwrap(); + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); + let bad_type: SqliteResult> = query + .query_and_then(&[], |row| row.get_checked(1)) + .unwrap() + .collect(); + + assert_eq!(bad_type, Err(SqliteError{ + code: ffi::SQLITE_MISMATCH, + message: "Invalid column type".to_owned(), + })); + + let bad_idx: SqliteResult> = query + .query_and_then(&[], |row| row.get_checked(3)) + .unwrap() + .collect(); + + assert_eq!(bad_idx, Err(SqliteError{ + code: ffi::SQLITE_MISUSE, + message: "Invalid column index".to_owned(), + })); + } + + #[test] + fn test_query_and_then_custom_error() { + #[derive(Debug)] + enum CustomError { + Sqlite(SqliteError), + }; + + impl fmt::Display for CustomError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se), + } + } + } + + impl StdError for CustomError { + fn description(&self) -> &str { "my custom error" } + fn cause(&self) -> Option<&StdError> { + match *self { + CustomError::Sqlite(ref se) => Some(se), + } + } + } + + impl From for CustomError { + fn from(se: SqliteError) -> CustomError { + CustomError::Sqlite(se) + } + } + type CustomResult = Result; + + let db = checked_memory_handle(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql).unwrap(); + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); + let results: CustomResult> = query + .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite)) + .unwrap() + .collect(); + + assert_eq!(results.unwrap().concat(), "hello, world!"); + } + + #[test] + fn test_query_and_then_custom_error_fails() { + #[derive(Debug, PartialEq)] + enum CustomError { + SomeError, + Sqlite(SqliteError), + }; + + impl fmt::Display for CustomError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + CustomError::SomeError => write!(f, "{}", self.description()), + CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se), + } + } + } + + impl StdError for CustomError { + fn description(&self) -> &str { "my custom error" } + fn cause(&self) -> Option<&StdError> { + match *self { + CustomError::SomeError => None, + CustomError::Sqlite(ref se) => Some(se), + } + } + } + + impl From for CustomError { + fn from(se: SqliteError) -> CustomError { + CustomError::Sqlite(se) + } + } + type CustomResult = Result; + + let db = checked_memory_handle(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + INSERT INTO foo VALUES(3, \", \"); + INSERT INTO foo VALUES(2, \"world\"); + INSERT INTO foo VALUES(1, \"!\"); + END;"; + db.execute_batch(sql).unwrap(); + + let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); + let bad_type: CustomResult> = query + .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite)) + .unwrap() + .collect(); + + assert_eq!(bad_type, Err(CustomError::Sqlite(SqliteError{ + code: ffi::SQLITE_MISMATCH, + message: "Invalid column type".to_owned(), + }))); + + let bad_idx: CustomResult> = query + .query_and_then(&[], |row| row.get_checked(3).map_err(CustomError::Sqlite)) + .unwrap() + .collect(); + + assert_eq!(bad_idx, Err(CustomError::Sqlite(SqliteError{ + code: ffi::SQLITE_MISUSE, + message: "Invalid column index".to_owned(), + }))); + + let non_sqlite_err: CustomResult> = query + .query_and_then(&[], |_| Err(CustomError::SomeError)) + .unwrap() + .collect(); + + assert_eq!(non_sqlite_err, Err(CustomError::SomeError)); + } + #[test] fn test_query_row() { let db = checked_memory_handle();