diff options
Diffstat (limited to 'src/row.rs')
-rw-r--r-- | src/row.rs | 230 |
1 files changed, 230 insertions, 0 deletions
@@ -35,6 +35,17 @@ impl<'stmt> Rows<'stmt> { Ok((*self).get()) } + /// Map over this `Rows`, converting it to a [`Map`], which + /// implements `FallibleIterator`. + /// ```rust,no_run + /// use fallible_iterator::FallibleIterator; + /// # use rusqlite::{Result, Statement, NO_PARAMS}; + /// fn query(stmt: &mut Statement) -> Result<Vec<i64>> { + /// let rows = stmt.query(NO_PARAMS)?; + /// rows.map(|r| r.get(0)).collect() + /// } + /// ``` + // FIXME Hide FallibleStreamingIterator::map pub fn map<F, B>(self, f: F) -> Map<'stmt, F> where F: FnMut(&Row<'_>) -> Result<B>, @@ -84,6 +95,7 @@ impl Drop for Rows<'_> { } } +/// `F` is used to tranform the _streaming_ iterator into a _fallible_ iterator. pub struct Map<'stmt, F> { rows: Rows<'stmt>, f: F, @@ -105,6 +117,8 @@ where } /// An iterator over the mapped resulting rows of a query. +/// +/// `F` is used to tranform the _streaming_ iterator into a _standard_ iterator. pub struct MappedRows<'stmt, F> { rows: Rows<'stmt>, map: F, @@ -166,6 +180,24 @@ where } } +/// `FallibleStreamingIterator` differs from the standard library's `Iterator` +/// in two ways: +/// * each call to `next` (sqlite3_step) can fail. +/// * returned `Row` is valid until `next` is called again or `Statement` is +/// reset or finalized. +/// +/// While these iterators cannot be used with Rust `for` loops, `while let` +/// loops offer a similar level of ergonomics: +/// ```rust,no_run +/// # use rusqlite::{Result, Statement, NO_PARAMS}; +/// fn query(stmt: &mut Statement) -> Result<()> { +/// let mut rows = stmt.query(NO_PARAMS)?; +/// while let Some(row) = rows.next()? { +/// // scan columns value +/// } +/// Ok(()) +/// } +/// ``` impl<'stmt> FallibleStreamingIterator for Rows<'stmt> { type Error = Error; type Item = Row<'stmt>; @@ -334,3 +366,201 @@ impl RowIndex for &'_ str { stmt.column_index(*self) } } + +macro_rules! tuple_try_from_row { + ($($field:ident),*) => { + impl<'a, $($field,)*> convert::TryFrom<&'a Row<'a>> for ($($field,)*) where $($field: FromSql,)* { + type Error = crate::Error; + + // we end with index += 1, which rustc warns about + // unused_variables and unused_mut are allowed for () + #[allow(unused_assignments, unused_variables, unused_mut)] + fn try_from(row: &'a Row<'a>) -> Result<Self> { + let mut index = 0; + $( + #[allow(non_snake_case)] + let $field = row.get::<_, $field>(index)?; + index += 1; + )* + Ok(($($field,)*)) + } + } + } +} + +macro_rules! tuples_try_from_row { + () => { + // not very useful, but maybe some other macro users will find this helpful + tuple_try_from_row!(); + }; + ($first:ident $(, $remaining:ident)*) => { + tuple_try_from_row!($first $(, $remaining)*); + tuples_try_from_row!($($remaining),*); + }; +} + +tuples_try_from_row!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +#[cfg(test)] +mod tests { + #![allow(clippy::redundant_closure)] // false positives due to lifetime issues; clippy issue #5594 + + #[test] + fn test_try_from_row_for_tuple_1() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute( + "CREATE TABLE test (a INTEGER)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to create table"); + conn.execute( + "INSERT INTO test VALUES (42)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT a FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32,)>::try_from(row), + ) + .expect("failed to query row"); + assert_eq!(val, (42,)); + let fail = conn.query_row( + "SELECT a FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32)>::try_from(row), + ); + assert!(fail.is_err()); + } + + #[test] + fn test_try_from_row_for_tuple_2() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute( + "CREATE TABLE test (a INTEGER, b INTEGER)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to create table"); + conn.execute( + "INSERT INTO test VALUES (42, 47)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT a, b FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32)>::try_from(row), + ) + .expect("failed to query row"); + assert_eq!(val, (42, 47)); + let fail = conn.query_row( + "SELECT a, b FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32, u32)>::try_from(row), + ); + assert!(fail.is_err()); + } + + #[test] + fn test_try_from_row_for_tuple_16() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let create_table = "CREATE TABLE test ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER, + e INTEGER, + f INTEGER, + g INTEGER, + h INTEGER, + i INTEGER, + j INTEGER, + k INTEGER, + l INTEGER, + m INTEGER, + n INTEGER, + o INTEGER, + p INTEGER + )"; + + let insert_values = "INSERT INTO test VALUES ( + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + )"; + + type BigTuple = ( + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + ); + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute(create_table, std::iter::empty::<&dyn ToSql>()) + .expect("failed to create table"); + conn.execute(insert_values, std::iter::empty::<&dyn ToSql>()) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT * FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| BigTuple::try_from(row), + ) + .expect("failed to query row"); + // Debug is not implemented for tuples of 16 + assert_eq!(val.0, 0); + assert_eq!(val.1, 1); + assert_eq!(val.2, 2); + assert_eq!(val.3, 3); + assert_eq!(val.4, 4); + assert_eq!(val.5, 5); + assert_eq!(val.6, 6); + assert_eq!(val.7, 7); + assert_eq!(val.8, 8); + assert_eq!(val.9, 9); + assert_eq!(val.10, 10); + assert_eq!(val.11, 11); + assert_eq!(val.12, 12); + assert_eq!(val.13, 13); + assert_eq!(val.14, 14); + assert_eq!(val.15, 15); + + // We don't test one bigger because it's unimplemented + } +} |