From 29072e585b289426bd248fb6ddc523adad8dc833 Mon Sep 17 00:00:00 2001 From: Patrick Fernie Date: Thu, 27 Aug 2015 13:43:43 -0400 Subject: [PATCH] Implement SqliteStatement::query_and_then() Allows for more ergonomic unification of error types --- src/lib.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 290c4a2..229cc8e 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. @@ -664,7 +665,7 @@ impl<'conn> SqliteStatement<'conn> { } /// Executes the prepared statement and maps a function over the resulting - /// rows. + /// rows. /// /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// for accessing stale rows. @@ -680,6 +681,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 @@ -746,6 +766,28 @@ 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; + + // Through the magic of FromIterator, if F returns a Result, + // you can collect that to a Result, E> + 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 @@ -920,6 +962,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 @@ -1081,6 +1125,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();