aboutsummaryrefslogtreecommitdiff
path: root/src/row.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/row.rs')
-rw-r--r--src/row.rs230
1 files changed, 230 insertions, 0 deletions
diff --git a/src/row.rs b/src/row.rs
index c45f38e..3e536d9 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -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
+ }
+}