Page the version name sweep with a resume cursor (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9191257fbb
commit
008dd0e7f4
3 files changed
+139
-25
No files matched your search
@@ -230,12 +230,15 @@ pub trait VersionDao: Sync + Send {
|
||||
|
||||
/// Rows in a library whose file name follows the version convention
|
||||
/// (`<name>.<kind>[.<n>].<ext>`), have a hash and aren't linked yet.
|
||||
/// Returns `(rel_path, content_hash)`, oldest first.
|
||||
/// Returns `(id, rel_path, content_hash)` ordered by row id, starting
|
||||
/// after `after_id` (the caller's resume cursor, so rows that never
|
||||
/// resolve can't starve the ones behind them).
|
||||
fn unlinked_name_candidates(
|
||||
&mut self,
|
||||
library_id: i32,
|
||||
after_id: i32,
|
||||
limit: i64,
|
||||
) -> Result<Vec<(String, String)>, DbError>;
|
||||
) -> Result<Vec<(i32, String, String)>, DbError>;
|
||||
|
||||
/// Copy the original's date, GPS and camera fields onto every version
|
||||
/// row whose copy is stale. Returns the number of rows updated.
|
||||
@@ -723,11 +726,14 @@ impl VersionDao for SqliteVersionDao {
|
||||
fn unlinked_name_candidates(
|
||||
&mut self,
|
||||
library_id: i32,
|
||||
after_id: i32,
|
||||
limit: i64,
|
||||
) -> Result<Vec<(String, String)>, DbError> {
|
||||
) -> Result<Vec<(i32, String, String)>, DbError> {
|
||||
trace_db_call("query", "unlinked_name_candidates", |_span| {
|
||||
#[derive(QueryableByName)]
|
||||
struct Row {
|
||||
#[diesel(sql_type = Integer)]
|
||||
id: i32,
|
||||
#[diesel(sql_type = Text)]
|
||||
rel_path: String,
|
||||
#[diesel(sql_type = Text)]
|
||||
@@ -739,8 +745,8 @@ impl VersionDao for SqliteVersionDao {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
let sql = format!(
|
||||
"SELECT rel_path, content_hash FROM image_exif \
|
||||
WHERE library_id = ? AND content_hash IS NOT NULL AND ({kinds}) \
|
||||
"SELECT id, rel_path, content_hash FROM image_exif \
|
||||
WHERE library_id = ? AND id > ? AND content_hash IS NOT NULL AND ({kinds}) \
|
||||
AND NOT EXISTS (SELECT 1 FROM photo_versions pv \
|
||||
WHERE pv.version_hash = image_exif.content_hash) \
|
||||
ORDER BY id LIMIT ?"
|
||||
@@ -748,11 +754,12 @@ impl VersionDao for SqliteVersionDao {
|
||||
let mut conn = self.connection.lock().expect("Unable to get VersionDao");
|
||||
sql_query(sql)
|
||||
.bind::<Integer, _>(library_id)
|
||||
.bind::<Integer, _>(after_id)
|
||||
.bind::<BigInt, _>(limit)
|
||||
.load::<Row>(&mut *conn)
|
||||
.map(|rows| {
|
||||
rows.into_iter()
|
||||
.map(|r| (r.rel_path, r.content_hash))
|
||||
.map(|r| (r.id, r.rel_path, r.content_hash))
|
||||
.collect()
|
||||
})
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
@@ -1089,8 +1096,18 @@ mod tests {
|
||||
insert_exif(&c, 2, "a/y.jpg.restored.png", "h4");
|
||||
dao.link_version(&link("h2", "ox", "a/x.jpg.edit.2.png"))
|
||||
.unwrap_or(LinkOutcome::Linked);
|
||||
let got = dao.unlinked_name_candidates(1, 100).unwrap();
|
||||
assert_eq!(got, vec![("a/x.jpg.restored.png".to_string(), "h1".into())]);
|
||||
let got = dao.unlinked_name_candidates(1, 0, 100).unwrap();
|
||||
assert_eq!(got.len(), 1);
|
||||
assert_eq!(
|
||||
(got[0].1.as_str(), got[0].2.as_str()),
|
||||
("a/x.jpg.restored.png", "h1")
|
||||
);
|
||||
// The cursor skips rows at or before it.
|
||||
assert!(
|
||||
dao.unlinked_name_candidates(1, got[0].0, 100)
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user