diff options
Diffstat (limited to 'src/db.rs')
| -rw-r--r-- | src/db.rs | 187 |
1 files changed, 103 insertions, 84 deletions
@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc}; use rusqlite::Connection; use rusqlite::OptionalExtension; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone)] pub struct Image { pub id: i64, pub registry: String, @@ -10,17 +10,24 @@ pub struct Image { pub expires_at: DateTime<Utc>, } +#[derive(Debug, Clone)] +pub struct Tag { + pub id: i64, + pub image_id: i64, + pub tag: String, + pub digest: Option<String>, +} + pub trait Db { fn insert_image(&self, image: &mut Image); fn get_image(&self, registry: &str, image: &str) -> Option<Image>; fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image>; - fn get_tags_sorted(&self, image_id: i64) -> Vec<String>; - fn delete_tag(&self, image_id: i64, tag: &str); - fn insert_tag(&self, image_id: i64, tag: &str); + fn get_tags_sorted(&self, image_id: i64) -> Vec<Tag>; + fn insert_tags(&self, tags: &mut [Tag]); + fn delete_tags(&self, tag_ids: &[i64]); + fn update_tags(&self, tags: &[Tag]); fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>); - fn get_tag_digest(&self, image_id: i64, tag: &str) -> Option<String>; - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str); } pub struct SqliteDb { @@ -162,45 +169,49 @@ impl Db for SqliteDb { return images; } - fn get_tags_sorted(&self, image_id: i64) -> Vec<String> { + fn get_tags_sorted(&self, image_id: i64) -> Vec<Tag> { let _timer = crate::metrics::get().db_query_duration.start_timer(); - let mut stmt = self.conn.prepare("SELECT tag FROM tags WHERE image_id = ?1 ORDER BY tag").unwrap(); + let mut stmt = self.conn.prepare("SELECT id, image_id, tag, digest FROM tags WHERE image_id = ?1 ORDER BY tag").unwrap(); let mut rows = stmt.query((image_id,)).unwrap(); let mut tags = vec![]; while let Some(row) = rows.next().unwrap() { - tags.push(row.get(0).unwrap()); + tags.push(Tag { + id: row.get(0).unwrap(), + image_id: row.get(1).unwrap(), + tag: row.get(2).unwrap(), + digest: row.get(3).unwrap(), + }); } return tags; } - fn delete_tag(&self, image_id: i64, tag: &str) { - let _timer = crate::metrics::get().db_query_duration.start_timer(); - self.conn.execute("DELETE FROM tags WHERE image_id = ?1 AND tag = ?2", - (image_id, tag)).unwrap(); - } - - fn insert_tag(&self, image_id: i64, tag: &str) { + fn insert_tags(&self, tags: &mut [Tag]) { let _timer = crate::metrics::get().db_query_duration.start_timer(); - self.conn.execute("INSERT INTO tags(image_id, tag) VALUES (?1, ?2)", - (image_id, tag)).unwrap(); + for tag in tags { + self.conn.execute("INSERT INTO tags(image_id, tag, digest) VALUES (?1, ?2, ?3)", + (&tag.image_id, &tag.tag, &tag.digest)).unwrap(); + tag.id = self.conn.last_insert_rowid(); + } } - fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>) { + fn delete_tags(&self, tag_ids: &[i64]) { let _timer = crate::metrics::get().db_query_duration.start_timer(); - self.conn.execute("UPDATE images SET expires_at = ?1 WHERE id = ?2", (expires_at, image_id)).unwrap(); + for id in tag_ids { + self.conn.execute("DELETE FROM tags WHERE id = ?1", (id,)).unwrap(); + } } - fn get_tag_digest(&self, image_id: i64, tag: &str) -> Option<String> { + fn update_tags(&self, tags: &[Tag]) { let _timer = crate::metrics::get().db_query_duration.start_timer(); - return self.conn.query_row(" - SELECT digest FROM tags WHERE image_id = ?1 AND tag = ?2 AND digest IS NOT NULL - ", (image_id, tag), |row| row.get::<_, String>(0)).optional().unwrap(); + for tag in tags { + self.conn.execute("UPDATE tags SET tag = ?1, digest = ?2 WHERE id = ?3", + (&tag.tag, &tag.digest, &tag.id)).unwrap(); + } } - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str) { + fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>) { let _timer = crate::metrics::get().db_query_duration.start_timer(); - self.conn.execute("UPDATE tags SET digest = ?1 WHERE image_id = ?2 AND tag = ?3", - (digest, image_id, tag)).unwrap(); + self.conn.execute("UPDATE images SET expires_at = ?1 WHERE id = ?2", (expires_at, image_id)).unwrap(); } } @@ -208,7 +219,7 @@ impl Db for SqliteDb { pub struct StubDb { next_id: std::cell::RefCell<i64>, images: std::cell::RefCell<Vec<Image>>, - tags: std::cell::RefCell<Vec<(i64, String, Option<String>)>>, + tags: std::cell::RefCell<Vec<Tag>>, } #[cfg(test)] @@ -265,47 +276,46 @@ impl Db for StubDb { return result; } - fn get_tags_sorted(&self, image_id: i64) -> Vec<String> { + fn get_tags_sorted(&self, image_id: i64) -> Vec<Tag> { let mut result = vec![]; - for (id, tag, _) in self.tags.borrow().iter() { - if *id == image_id { + for tag in self.tags.borrow().iter() { + if tag.image_id == image_id { result.push(tag.clone()); } } - result.sort(); + result.sort_by(|a, b| a.tag.cmp(&b.tag)); return result; } - fn delete_tag(&self, image_id: i64, tag: &str) { - self.tags.borrow_mut().retain(|(id, t, _)| !(*id == image_id && t == tag)); + fn insert_tags(&self, tags: &mut [Tag]) { + for tag in tags { + let id = *self.next_id.borrow(); + *self.next_id.borrow_mut() += 1; + tag.id = id; + self.tags.borrow_mut().push(tag.clone()); + } } - fn insert_tag(&self, image_id: i64, tag: &str) { - self.tags.borrow_mut().push((image_id, tag.to_string(), None)); + fn delete_tags(&self, tag_ids: &[i64]) { + self.tags.borrow_mut().retain(|t| !tag_ids.contains(&t.id)); } - fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>) { - for img in self.images.borrow_mut().iter_mut() { - if img.id == image_id { - img.expires_at = *expires_at; - return; + fn update_tags(&self, tags: &[Tag]) { + for tag in tags { + for t in self.tags.borrow_mut().iter_mut() { + if t.id == tag.id { + t.tag = tag.tag.clone(); + t.digest = tag.digest.clone(); + break; + } } } } - fn get_tag_digest(&self, image_id: i64, tag: &str) -> Option<String> { - for (id, t, digest) in self.tags.borrow().iter() { - if *id == image_id && t == tag { - return digest.clone(); - } - } - return None; - } - - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str) { - for (id, t, d) in self.tags.borrow_mut().iter_mut() { - if *id == image_id && t == tag { - *d = Some(digest.to_string()); + fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>) { + for img in self.images.borrow_mut().iter_mut() { + if img.id == image_id { + img.expires_at = *expires_at; return; } } @@ -327,7 +337,7 @@ mod tests { } fn test_get_image_returns_none_for_unknown(db: &dyn Db) { - assert_eq!(db.get_image("docker.io", "unknown"), None); + assert!(db.get_image("docker.io", "unknown").is_none()); } fn test_get_image_returns_inserted(db: &dyn Db) { @@ -343,27 +353,34 @@ mod tests { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }; db.insert_image(&mut img); - assert_eq!(db.get_tags_sorted(img.id), Vec::<String>::new()); + assert!(db.get_tags_sorted(img.id).is_empty()); } fn test_get_tags_returns_sorted(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }; db.insert_image(&mut img); - db.insert_tag(img.id, "2.0"); - db.insert_tag(img.id, "1.0"); - db.insert_tag(img.id, "latest"); - assert_eq!(db.get_tags_sorted(img.id), vec!["1.0", "2.0", "latest"]); + db.insert_tags(&mut [ + Tag { id: 0, image_id: img.id, tag: "2.0".into(), digest: None }, + Tag { id: 0, image_id: img.id, tag: "1.0".into(), digest: None }, + Tag { id: 0, image_id: img.id, tag: "latest".into(), digest: None }, + ]); + let tags: Vec<String> = db.get_tags_sorted(img.id).into_iter().map(|t| t.tag).collect(); + assert_eq!(tags, vec!["1.0", "2.0", "latest"]); } - fn test_delete_tag_removes_tag(db: &dyn Db) { + fn test_delete_tags_removes_tags(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }; db.insert_image(&mut img); - db.insert_tag(img.id, "1.0"); - db.insert_tag(img.id, "2.0"); - db.delete_tag(img.id, "1.0"); - assert_eq!(db.get_tags_sorted(img.id), vec!["2.0"]); + let mut tags = [ + Tag { id: 0, image_id: img.id, tag: "1.0".into(), digest: None }, + Tag { id: 0, image_id: img.id, tag: "2.0".into(), digest: None }, + ]; + db.insert_tags(&mut tags); + db.delete_tags(&[tags[0].id]); + let result: Vec<String> = db.get_tags_sorted(img.id).into_iter().map(|t| t.tag).collect(); + assert_eq!(result, vec!["2.0"]); } fn test_set_expires_at(db: &dyn Db) { @@ -375,21 +392,23 @@ mod tests { assert_eq!(db.get_image("docker.io", "nginx").unwrap().expires_at, t2); } - fn test_get_tag_digest_returns_none_when_unset(db: &dyn Db) { + fn test_tag_digest_none_when_unset(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }; db.insert_image(&mut img); - db.insert_tag(img.id, "1.0"); - assert_eq!(db.get_tag_digest(img.id, "1.0"), None); + db.insert_tags(&mut [Tag { id: 0, image_id: img.id, tag: "1.0".into(), digest: None }]); + assert_eq!(db.get_tags_sorted(img.id)[0].digest, None); } - fn test_update_tag_digest(db: &dyn Db) { + fn test_update_tags(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }; db.insert_image(&mut img); - db.insert_tag(img.id, "1.0"); - db.update_tag_digest(img.id, "1.0", "sha256:abc"); - assert_eq!(db.get_tag_digest(img.id, "1.0"), Some("sha256:abc".to_string())); + let mut tags = [Tag { id: 0, image_id: img.id, tag: "1.0".into(), digest: None }]; + db.insert_tags(&mut tags); + tags[0].digest = Some("sha256:abc".into()); + db.update_tags(&tags); + assert_eq!(db.get_tags_sorted(img.id)[0].digest, Some("sha256:abc".to_string())); } fn test_get_expired_images(db: &dyn Db) { @@ -467,15 +486,15 @@ mod tests { } #[test] - fn conformance_stub_delete_tag() { - test_delete_tag_removes_tag(&StubDb::default()); + fn conformance_stub_delete_tags() { + test_delete_tags_removes_tags(&StubDb::default()); } #[test] - fn conformance_sqlite_delete_tag() { + fn conformance_sqlite_delete_tags() { crate::metrics::init(); let dir = tempfile::tempdir().unwrap(); - test_delete_tag_removes_tag(&SqliteDb::new(&dir.path().join("db.sqlite"))); + test_delete_tags_removes_tags(&SqliteDb::new(&dir.path().join("db.sqlite"))); } #[test] @@ -491,27 +510,27 @@ mod tests { } #[test] - fn conformance_stub_get_tag_digest_none() { - test_get_tag_digest_returns_none_when_unset(&StubDb::default()); + fn conformance_stub_tag_digest_none() { + test_tag_digest_none_when_unset(&StubDb::default()); } #[test] - fn conformance_sqlite_get_tag_digest_none() { + fn conformance_sqlite_tag_digest_none() { crate::metrics::init(); let dir = tempfile::tempdir().unwrap(); - test_get_tag_digest_returns_none_when_unset(&SqliteDb::new(&dir.path().join("db.sqlite"))); + test_tag_digest_none_when_unset(&SqliteDb::new(&dir.path().join("db.sqlite"))); } #[test] - fn conformance_stub_update_tag_digest() { - test_update_tag_digest(&StubDb::default()); + fn conformance_stub_update_tags() { + test_update_tags(&StubDb::default()); } #[test] - fn conformance_sqlite_update_tag_digest() { + fn conformance_sqlite_update_tags() { crate::metrics::init(); let dir = tempfile::tempdir().unwrap(); - test_update_tag_digest(&SqliteDb::new(&dir.path().join("db.sqlite"))); + test_update_tags(&SqliteDb::new(&dir.path().join("db.sqlite"))); } #[test] |
