From 7176a544757eacc2823f1c0f014861ad2525e6da Mon Sep 17 00:00:00 2001 From: Alexander Batischev Date: Mon, 26 Apr 2021 11:41:02 +0300 Subject: Generalize the machinery to arbitrary result types --- src/generation.rs | 73 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 10143ab..54c55a7 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -196,7 +196,7 @@ impl LocalGeneration { sql::file_count(&self.conn) } - pub fn files(&self) -> LocalGenerationResult { + pub fn files(&self) -> LocalGenerationResult> { sql::files(&self.conn) } @@ -289,37 +289,74 @@ mod sql { Ok(count) } - // A pointer to an iterator over values of type `LocalGenerationResult`. The iterator is - // only valid for the lifetime 'stmt. + // A pointer to a "fallible iterator" over values of type `T`, which is to say it's an iterator + // over values of type `LocalGenerationResult`. The iterator is only valid for the lifetime + // 'stmt. // // The fact that it's a pointer (`Box`) means we don't care what the actual type of // the iterator is, and who produces it. type SqlResultsIterator<'stmt, T> = Box> + 'stmt>; - pub struct SqlResults<'conn> { + // A pointer to a function which, when called on a prepared SQLite statement, would create + // a "fallible iterator" over values of type `ItemT`. (See above for an explanation of what + // a "fallible iterator" is.) + // + // The iterator is only valid for the lifetime of the associated SQLite statement; we + // call this lifetime 'stmt, and use it both both on the reference and the returned iterator. + // + // Now we're in a pickle: all named lifetimes have to be declared _somewhere_, but we can't add + // 'stmt to the signature of `CreateIterFn` because then we'll have to specify it when we + // define the function. Obviously, at that point we won't yet have a `Statement`, and thus we + // would have no idea what its lifetime is going to be. So we can't put the 'stmt lifetime into + // the signature of `CreateIterFn`. + // + // That's what `for<'stmt>` is for. This is a so-called ["higher-rank trait bound"][hrtb], and + // it enables us to say that a function is valid for *some* lifetime 'stmt that we pass into it + // at the call site. It lets Rust continue to track lifetimes even though `CreateIterFn` + // interferes by "hiding" the 'stmt lifetime from its signature. + // + // [hrtb]: https://doc.rust-lang.org/nomicon/hrtb.html + type CreateIterFn<'conn, ItemT> = Box< + dyn for<'stmt> Fn( + &'stmt mut Statement<'conn>, + ) -> LocalGenerationResult>, + >; + + pub struct SqlResults<'conn, ItemT> { stmt: Statement<'conn>, + create_iter: CreateIterFn<'conn, ItemT>, } - impl<'conn> SqlResults<'conn> { - fn new(conn: &'conn Connection, statement: &str) -> LocalGenerationResult { + impl<'conn, ItemT> SqlResults<'conn, ItemT> { + fn new( + conn: &'conn Connection, + statement: &str, + create_iter: CreateIterFn<'conn, ItemT>, + ) -> LocalGenerationResult { let stmt = conn.prepare(statement)?; - Ok(Self { stmt }) + Ok(Self { stmt, create_iter }) } - pub fn iter(&'_ mut self) -> LocalGenerationResult> { - let iter = self.stmt.query_map(params![], |row| row_to_entry(row))?; - let iter = iter.map(|x| match x { - Ok((fileno, json, reason)) => serde_json::from_str(&json) - .map(|entry| BackedUpFile::new(fileno, entry, &reason)) - .map_err(|e| e.into()), - Err(e) => Err(e.into()), - }); - Ok(iter) + pub fn iter(&'_ mut self) -> LocalGenerationResult> { + (self.create_iter)(&mut self.stmt) } } - pub fn files(conn: &Connection) -> LocalGenerationResult> { - SqlResults::new(conn, "SELECT * FROM files") + pub fn files(conn: &Connection) -> LocalGenerationResult> { + SqlResults::new( + conn, + "SELECT * FROM files", + Box::new(|stmt| { + let iter = stmt.query_map(params![], |row| row_to_entry(row))?; + let iter = iter.map(|x| match x { + Ok((fileno, json, reason)) => serde_json::from_str(&json) + .map(|entry| BackedUpFile::new(fileno, entry, &reason)) + .map_err(|e| e.into()), + Err(e) => Err(e.into()), + }); + Ok(Box::new(iter)) + }), + ) } pub fn chunkids(conn: &Connection, fileno: FileId) -> LocalGenerationResult> { -- cgit v1.2.1