summaryrefslogtreecommitdiff
path: root/src/generation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/generation.rs')
-rw-r--r--src/generation.rs107
1 files changed, 86 insertions, 21 deletions
diff --git a/src/generation.rs b/src/generation.rs
index 1d0ee52..0055bfe 100644
--- a/src/generation.rs
+++ b/src/generation.rs
@@ -196,11 +196,11 @@ impl LocalGeneration {
sql::file_count(&self.conn)
}
- pub fn files(&self) -> LocalGenerationResult<Vec<BackedUpFile>> {
+ pub fn files(&self) -> LocalGenerationResult<sql::SqlResults<BackedUpFile>> {
sql::files(&self.conn)
}
- pub fn chunkids(&self, fileno: FileId) -> LocalGenerationResult<Vec<ChunkId>> {
+ pub fn chunkids(&self, fileno: FileId) -> LocalGenerationResult<sql::SqlResults<ChunkId>> {
sql::chunkids(&self.conn, fileno)
}
@@ -221,7 +221,7 @@ mod sql {
use crate::backup_reason::Reason;
use crate::chunkid::ChunkId;
use crate::fsentry::FilesystemEntry;
- use rusqlite::{params, Connection, OpenFlags, Row, Transaction};
+ use rusqlite::{params, Connection, OpenFlags, Row, Statement, Transaction};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
@@ -289,27 +289,92 @@ mod sql {
Ok(count)
}
- pub fn files(conn: &Connection) -> LocalGenerationResult<Vec<BackedUpFile>> {
- let mut stmt = conn.prepare("SELECT * FROM files")?;
- let iter = stmt.query_map(params![], |row| row_to_entry(row))?;
- let mut files = vec![];
- for x in iter {
- let (fileno, json, reason) = x?;
- let entry = serde_json::from_str(&json)?;
- files.push(BackedUpFile::new(fileno, entry, &reason));
- }
- Ok(files)
+ // A pointer to a "fallible iterator" over values of type `T`, which is to say it's an iterator
+ // over values of type `LocalGenerationResult<T>`. The iterator is only valid for the lifetime
+ // 'stmt.
+ //
+ // The fact that it's a pointer (`Box<dyn ...>`) means we don't care what the actual type of
+ // the iterator is, and who produces it.
+ type SqlResultsIterator<'stmt, T> = Box<dyn Iterator<Item = LocalGenerationResult<T>> + 'stmt>;
+
+ // 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<SqlResultsIterator<'stmt, ItemT>>,
+ >;
+
+ pub struct SqlResults<'conn, ItemT> {
+ stmt: Statement<'conn>,
+ create_iter: CreateIterFn<'conn, ItemT>,
}
- pub fn chunkids(conn: &Connection, fileno: FileId) -> LocalGenerationResult<Vec<ChunkId>> {
- let mut stmt = conn.prepare("SELECT chunkid FROM chunks WHERE fileno = ?1")?;
- let iter = stmt.query_map(params![fileno], |row| row.get(0))?;
- let mut ids: Vec<ChunkId> = vec![];
- for x in iter {
- let fileno: String = x?;
- ids.push(ChunkId::from(&fileno));
+ impl<'conn, ItemT> SqlResults<'conn, ItemT> {
+ fn new(
+ conn: &'conn Connection,
+ statement: &str,
+ create_iter: CreateIterFn<'conn, ItemT>,
+ ) -> LocalGenerationResult<Self> {
+ let stmt = conn.prepare(statement)?;
+ Ok(Self { stmt, create_iter })
+ }
+
+ pub fn iter(&'_ mut self) -> LocalGenerationResult<SqlResultsIterator<'_, ItemT>> {
+ (self.create_iter)(&mut self.stmt)
}
- Ok(ids)
+ }
+
+ pub fn files(conn: &Connection) -> LocalGenerationResult<SqlResults<BackedUpFile>> {
+ 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<SqlResults<ChunkId>> {
+ SqlResults::new(
+ conn,
+ "SELECT chunkid FROM chunks WHERE fileno = ?1",
+ Box::new(move |stmt| {
+ let iter = stmt.query_map(params![fileno], |row| row.get(0))?;
+ let iter = iter.map(|x| {
+ let fileno: String = x?;
+ Ok(ChunkId::from(&fileno))
+ });
+ Ok(Box::new(iter))
+ }),
+ )
}
pub fn get_file(