From ec78a2b5faa717437d4a21712eb61edb098ea15c Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 8 Mar 2017 16:57:43 -0500 Subject: [PATCH] Reorganize: Extract Row/Rows and helper types into their own mod files. --- src/cache.rs | 18 ++-- src/lib.rs | 177 ++----------------------------------- src/row.rs | 222 +++++++++++++++++++++++++++++++++++++++++++++++ src/statement.rs | 31 ++----- 4 files changed, 242 insertions(+), 206 deletions(-) create mode 100644 src/row.rs diff --git a/src/cache.rs b/src/cache.rs index 37105dc..d0cf289 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -176,16 +176,14 @@ mod test { { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); } assert_eq!(1, cache.len()); { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); } assert_eq!(1, cache.len()); @@ -203,8 +201,7 @@ mod test { { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); } assert_eq!(1, cache.len()); @@ -214,8 +211,7 @@ mod test { { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); } assert_eq!(0, cache.len()); @@ -223,8 +219,7 @@ mod test { { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); } assert_eq!(1, cache.len()); } @@ -238,8 +233,7 @@ mod test { { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!(0, cache.len()); - assert_eq!(0, - stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0)); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); stmt.discard(); } assert_eq!(0, cache.len()); diff --git a/src/lib.rs b/src/lib.rs index ad919e0..a25777a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,6 @@ extern crate lazy_static; use std::default::Default; use std::convert; -use std::marker::PhantomData; use std::mem; use std::ptr; use std::fmt; @@ -75,7 +74,7 @@ use std::sync::{Once, ONCE_INIT}; use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; use std::os::raw::{c_int, c_char}; -use types::{ToSql, FromSql, FromSqlError, ValueRef}; +use types::{ToSql, ValueRef}; use error::{error_from_sqlite_code, error_from_handle}; use raw_statement::RawStatement; use cache::StatementCache; @@ -83,6 +82,9 @@ use cache::StatementCache; pub use statement::Statement; use statement::StatementCrateImpl; +pub use row::{Row, Rows, MappedRows, AndThenRows}; +use row::RowsCrateImpl; + #[allow(deprecated)] pub use transaction::{SqliteTransaction, SqliteTransactionBehavior}; pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; @@ -105,6 +107,7 @@ mod transaction; mod cache; mod error; mod raw_statement; +mod row; mod statement; #[cfg(feature = "load_extension")] mod load_extension_guard; @@ -865,185 +868,15 @@ impl Drop for InnerConnection { #[deprecated(since = "0.6.0", note = "Use Statement instead")] pub type SqliteStatement<'conn> = Statement<'conn>; -/// An iterator over the mapped resulting rows of a query. -pub struct MappedRows<'stmt, F> { - rows: Rows<'stmt>, - map: F, -} - -impl<'stmt, T, F> Iterator for MappedRows<'stmt, F> - where F: FnMut(&Row) -> T -{ - type Item = Result; - - fn next(&mut self) -> Option> { - let map = &mut self.map; - self.rows.next().map(|row_result| row_result.map(|row| (map)(&row))) - } -} - -/// An iterator over the mapped resulting rows of a query, with an Error type -/// unifying with Error. -pub struct AndThenRows<'stmt, F> { - rows: Rows<'stmt>, - map: F, -} - -impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> - where E: convert::From, - F: FnMut(&Row) -> result::Result -{ - type Item = result::Result; - - fn next(&mut self) -> Option { - let map = &mut self.map; - self.rows.next().map(|row_result| { - row_result.map_err(E::from) - .and_then(|row| (map)(&row)) - }) - } -} - /// Old name for `Rows`. `SqliteRows` is deprecated. #[deprecated(since = "0.6.0", note = "Use Rows instead")] pub type SqliteRows<'stmt> = Rows<'stmt>; -/// An handle for the resulting rows of a query. -pub struct Rows<'stmt> { - stmt: Option<&'stmt Statement<'stmt>>, -} - -#[allow(should_implement_trait)] -impl<'stmt> Rows<'stmt> { - fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> { - Rows { stmt: Some(stmt) } - } - - fn get_expected_row<'a>(&'a mut self) -> Result> { - match self.next() { - Some(row) => row, - None => Err(Error::QueryReturnedNoRows), - } - } - - fn reset(&mut self) { - if let Some(stmt) = self.stmt.take() { - stmt.reset(); - } - } - - /// Attempt to get the next row from the query. Returns `Some(Ok(Row))` if there - /// is another row, `Some(Err(...))` if there was an error getting the next - /// row, and `None` if all rows have been retrieved. - /// - /// ## Note - /// - /// This interface is not compatible with Rust's `Iterator` trait, because the - /// lifetime of the returned row is tied to the lifetime of `self`. This is a - /// "streaming iterator". For a more natural interface, consider using `query_map` - /// or `query_and_then` instead, which return types that implement `Iterator`. - pub fn next<'a>(&'a mut self) -> Option>> { - self.stmt.and_then(|stmt| match stmt.step() { - Ok(true) => Some(Ok(Row { - stmt: stmt, - phantom: PhantomData, - })), - Ok(false) => { - self.reset(); - None - } - Err(err) => { - self.reset(); - Some(Err(err)) - } - }) - } -} - -impl<'stmt> Drop for Rows<'stmt> { - fn drop(&mut self) { - self.reset(); - } -} /// Old name for `Row`. `SqliteRow` is deprecated. #[deprecated(since = "0.6.0", note = "Use Row instead")] pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>; -/// A single result row of a query. -pub struct Row<'a, 'stmt> { - stmt: &'stmt Statement<'stmt>, - phantom: PhantomData<&'a ()>, -} - -impl<'a, 'stmt> Row<'a, 'stmt> { - /// Get the value of a particular column of the result row. - /// - /// ## Failure - /// - /// Panics if calling `row.get_checked(idx)` would return an error, including: - /// - /// * If the underlying SQLite column type is not a valid type as a source for `T` - /// * If the underlying SQLite integral value is outside the range representable by `T` - /// * If `idx` is outside the range of columns in the returned query - pub fn get(&self, idx: I) -> T { - self.get_checked(idx).unwrap() - } - - /// Get the value of a particular column of the result row. - /// - /// ## Failure - /// - /// Returns an `Error::InvalidColumnType` if the underlying SQLite column - /// type is not a valid type as a source for `T`. - /// - /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid column range - /// for this row. - /// - /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column name - /// for this row. - pub fn get_checked(&self, idx: I) -> Result { - let idx = try!(idx.idx(self.stmt)); - let value = self.stmt.value_ref(idx); - FromSql::column_result(value).map_err(|err| match err { - FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()), - FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), - FromSqlError::Other(err) => { - Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) - } - }) - } - - /// Return the number of columns in the current row. - pub fn column_count(&self) -> i32 { - self.stmt.column_count() - } -} - -/// A trait implemented by types that can index into columns of a row. -pub trait RowIndex { - /// Returns the index of the appropriate column, or `None` if no such - /// column exists. - fn idx(&self, stmt: &Statement) -> Result; -} - -impl RowIndex for i32 { - #[inline] - fn idx(&self, stmt: &Statement) -> Result { - if *self < 0 || *self >= stmt.column_count() { - Err(Error::InvalidColumnIndex(*self)) - } else { - Ok(*self) - } - } -} - -impl<'a> RowIndex for &'a str { - #[inline] - fn idx(&self, stmt: &Statement) -> Result { - stmt.column_index(*self) - } -} #[cfg(test)] mod test { diff --git a/src/row.rs b/src/row.rs new file mode 100644 index 0000000..1c98639 --- /dev/null +++ b/src/row.rs @@ -0,0 +1,222 @@ +use std::{convert, result}; +use std::marker::PhantomData; + +use super::{Statement, Error, Result}; +use types::{FromSql, FromSqlError}; +use statement::StatementCrateImpl; + +/// An handle for the resulting rows of a query. +pub struct Rows<'stmt> { + stmt: Option<&'stmt Statement<'stmt>>, +} + +impl<'stmt> Rows<'stmt> { + fn reset(&mut self) { + if let Some(stmt) = self.stmt.take() { + stmt.reset(); + } + } + + /// Attempt to get the next row from the query. Returns `Some(Ok(Row))` if there + /// is another row, `Some(Err(...))` if there was an error getting the next + /// row, and `None` if all rows have been retrieved. + /// + /// ## Note + /// + /// This interface is not compatible with Rust's `Iterator` trait, because the + /// lifetime of the returned row is tied to the lifetime of `self`. This is a + /// "streaming iterator". For a more natural interface, consider using `query_map` + /// or `query_and_then` instead, which return types that implement `Iterator`. + pub fn next<'a>(&'a mut self) -> Option>> { + self.stmt.and_then(|stmt| match stmt.step() { + Ok(true) => { + Some(Ok(Row { + stmt: stmt, + phantom: PhantomData, + })) + } + Ok(false) => { + self.reset(); + None + } + Err(err) => { + self.reset(); + Some(Err(err)) + } + }) + } +} + +// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this +// once pub(crate) is stable. +pub trait RowsCrateImpl<'stmt> { + fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt>; + fn get_expected_row<'a>(&'a mut self) -> Result>; +} + +impl<'stmt> RowsCrateImpl<'stmt> for Rows<'stmt> { + fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> { + Rows { stmt: Some(stmt) } + } + + fn get_expected_row<'a>(&'a mut self) -> Result> { + match self.next() { + Some(row) => row, + None => Err(Error::QueryReturnedNoRows), + } + } +} + +impl<'stmt> Drop for Rows<'stmt> { + fn drop(&mut self) { + self.reset(); + } +} + +/// An iterator over the mapped resulting rows of a query. +pub struct MappedRows<'stmt, F> { + rows: Rows<'stmt>, + map: F, +} + +// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this +// once pub(crate) is stable. +pub trait MappedRowsCrateImpl<'stmt, T, F> { + fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F>; +} + +impl<'stmt, T, F> MappedRowsCrateImpl<'stmt, T, F> for MappedRows<'stmt, F> + where F: FnMut(&Row) -> T +{ + fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> { + MappedRows { + rows: rows, + map: f, + } + } +} + +impl<'conn, T, F> Iterator for MappedRows<'conn, F> + where F: FnMut(&Row) -> T +{ + type Item = Result; + + fn next(&mut self) -> Option> { + let map = &mut self.map; + self.rows.next().map(|row_result| row_result.map(|row| (map)(&row))) + } +} + +/// An iterator over the mapped resulting rows of a query, with an Error type +/// unifying with Error. +pub struct AndThenRows<'stmt, F> { + rows: Rows<'stmt>, + map: F, +} + +// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this +// once pub(crate) is stable. +pub trait AndThenRowsCrateImpl<'stmt, T, E, F> { + fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F>; +} + +impl<'stmt, T, E, F> AndThenRowsCrateImpl<'stmt, T, E, F> for AndThenRows<'stmt, F> + where F: FnMut(&Row) -> result::Result +{ + fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> { + AndThenRows { + rows: rows, + map: f, + } + } +} + +impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> + where E: convert::From, + F: FnMut(&Row) -> result::Result +{ + type Item = result::Result; + + fn next(&mut self) -> Option { + let map = &mut self.map; + self.rows.next().map(|row_result| { + row_result.map_err(E::from) + .and_then(|row| (map)(&row)) + }) + } +} + +/// A single result row of a query. +pub struct Row<'a, 'stmt> { + stmt: &'stmt Statement<'stmt>, + phantom: PhantomData<&'a ()>, +} + +impl<'a, 'stmt> Row<'a, 'stmt> { + /// Get the value of a particular column of the result row. + /// + /// ## Failure + /// + /// Panics if calling `row.get_checked(idx)` would return an error, including: + /// + /// * If the underlying SQLite column type is not a valid type as a source for `T` + /// * If the underlying SQLite integral value is outside the range representable by `T` + /// * If `idx` is outside the range of columns in the returned query + pub fn get(&self, idx: I) -> T { + self.get_checked(idx).unwrap() + } + + /// Get the value of a particular column of the result row. + /// + /// ## Failure + /// + /// Returns an `Error::InvalidColumnType` if the underlying SQLite column + /// type is not a valid type as a source for `T`. + /// + /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid column range + /// for this row. + /// + /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column name + /// for this row. + pub fn get_checked(&self, idx: I) -> Result { + let idx = try!(idx.idx(self.stmt)); + let value = self.stmt.value_ref(idx); + FromSql::column_result(value).map_err(|err| match err { + FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()), + FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), + FromSqlError::Other(err) => { + Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) + } + }) + } + + /// Return the number of columns in the current row. + pub fn column_count(&self) -> i32 { + self.stmt.column_count() + } +} + +/// A trait implemented by types that can index into columns of a row. +pub trait RowIndex { + /// Returns the index of the appropriate column, or `None` if no such + /// column exists. + fn idx(&self, stmt: &Statement) -> Result; +} + +impl RowIndex for i32 { + #[inline] + fn idx(&self, stmt: &Statement) -> Result { + if *self < 0 || *self >= stmt.column_count() { + Err(Error::InvalidColumnIndex(*self)) + } else { + Ok(*self) + } + } +} + +impl<'a> RowIndex for &'a str { + #[inline] + fn idx(&self, stmt: &Statement) -> Result { + stmt.column_index(*self) + } +} diff --git a/src/statement.rs b/src/statement.rs index 5e5b94d..09db33d 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -7,6 +7,7 @@ use super::ffi; use super::{Connection, RawStatement, Result, Error, ValueRef, Row, Rows, AndThenRows, MappedRows}; use super::str_to_cstring; use types::{ToSql, ToSqlOutput}; +use row::{RowsCrateImpl, MappedRowsCrateImpl, AndThenRowsCrateImpl}; /// A prepared statement. pub struct Statement<'conn> { @@ -211,12 +212,8 @@ impl<'conn> Statement<'conn> { pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result> where F: FnMut(&Row) -> T { - let row_iter = try!(self.query(params)); - - Ok(MappedRows { - rows: row_iter, - map: f, - }) + let rows = self.query(params)?; + Ok(MappedRows::new(rows, f)) } /// Execute the prepared statement with named parameter(s), returning an iterator over the @@ -251,11 +248,8 @@ impl<'conn> Statement<'conn> { -> Result> where F: FnMut(&Row) -> T { - let rows = try!(self.query_named(params)); - Ok(MappedRows { - rows: rows, - map: f, - }) + let rows = self.query_named(params)?; + Ok(MappedRows::new(rows, f)) } /// Executes the prepared statement and maps a function over the resulting @@ -272,12 +266,8 @@ impl<'conn> Statement<'conn> { where E: convert::From, F: FnMut(&Row) -> result::Result { - let row_iter = try!(self.query(params)); - - Ok(AndThenRows { - rows: row_iter, - map: f, - }) + let rows = self.query(params)?; + Ok(AndThenRows::new(rows, f)) } /// Execute the prepared statement with named parameter(s), returning an iterator over the @@ -322,11 +312,8 @@ impl<'conn> Statement<'conn> { where E: convert::From, F: FnMut(&Row) -> result::Result { - let rows = try!(self.query_named(params)); - Ok(AndThenRows { - rows: rows, - map: f, - }) + let rows = self.query_named(params)?; + Ok(AndThenRows::new(rows, f)) } /// Return `true` if a query in the SQL statement it executes returns one or more rows