Merge pull request #249 from jgallagher/reorganize-extract-row

Reorganize: Extract Row/Rows and helper types into their own mod file.
This commit is contained in:
John Gallagher 2017-03-09 09:30:28 -05:00 committed by GitHub
commit ccf1f61127
4 changed files with 242 additions and 206 deletions

View File

@ -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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(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::<i32, i64>(0));
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
stmt.discard();
}
assert_eq!(0, cache.len());

View File

@ -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<T>;
fn next(&mut self) -> Option<Result<T>> {
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<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
type Item = result::Result<T, E>;
fn next(&mut self) -> Option<Self::Item> {
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<Row<'a, 'stmt>> {
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<Result<Row<'a, 'stmt>>> {
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<I: RowIndex, T: FromSql>(&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<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
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<i32>;
}
impl RowIndex for i32 {
#[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> {
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<i32> {
stmt.column_index(*self)
}
}
#[cfg(test)]
mod test {

222
src/row.rs Normal file
View File

@ -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<Result<Row<'a, 'stmt>>> {
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<Row<'a, 'stmt>>;
}
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<Row<'a, 'stmt>> {
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<T>;
fn next(&mut self) -> Option<Result<T>> {
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<T, E>
{
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<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
type Item = result::Result<T, E>;
fn next(&mut self) -> Option<Self::Item> {
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<I: RowIndex, T: FromSql>(&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<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
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<i32>;
}
impl RowIndex for i32 {
#[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> {
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<i32> {
stmt.column_index(*self)
}
}

View File

@ -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<MappedRows<'a, F>>
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<MappedRows<'a, F>>
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<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
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<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
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