summaryrefslogtreecommitdiff
path: root/src/db.rs
diff options
context:
space:
mode:
authorJesper Jensen <jesper@jnsn.dev>2026-02-09 23:04:07 +0100
committerJesper Jensen <jesper@jnsn.dev>2026-02-09 23:08:48 +0100
commit3856d94661bd5605a839983805ec8ad962c4d307 (patch)
treee694f7c88a25cd75e5be86f5347b5811b85220ca /src/db.rs
parent343eb8bb594ca32f78b91d220ca3c303ff1aa7a2 (diff)
Remove chrono dependency
Removing that dependency didn't take a lot of code, and fewer dependencies are better
Diffstat (limited to 'src/db.rs')
-rw-r--r--src/db.rs67
1 files changed, 47 insertions, 20 deletions
diff --git a/src/db.rs b/src/db.rs
index 6a4d732..e319fd6 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -1,13 +1,41 @@
-use chrono::{DateTime, Utc};
+use std::time::{Duration, UNIX_EPOCH};
use rusqlite::Connection;
use rusqlite::OptionalExtension;
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use crate::time::{Timestamp, secs_to_ymd_hms, ymd_hms_to_secs};
+
+impl ToSql for Timestamp {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+ let d = self.0.duration_since(UNIX_EPOCH).unwrap();
+ let secs = d.as_secs();
+ let millis = d.subsec_millis();
+ let (y, mo, day, h, min, s) = secs_to_ymd_hms(secs);
+ let ts = format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}", y, mo, day, h, min, s, millis);
+ Ok(ToSqlOutput::from(ts))
+ }
+}
+
+impl FromSql for Timestamp {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let ts = value.as_str()?;
+ let y: u64 = ts[0..4].parse().unwrap();
+ let mo: u64 = ts[5..7].parse().unwrap();
+ let d: u64 = ts[8..10].parse().unwrap();
+ let h: u64 = ts[11..13].parse().unwrap();
+ let min: u64 = ts[14..16].parse().unwrap();
+ let s: u64 = ts[17..19].parse().unwrap();
+ let ms: u64 = if ts.len() >= 23 { ts[20..23].parse().unwrap_or(0) } else { 0 };
+ let secs = ymd_hms_to_secs(y, mo, d, h, min, s);
+ Ok(Timestamp(UNIX_EPOCH + Duration::from_secs(secs) + Duration::from_millis(ms)))
+ }
+}
#[derive(Debug, Clone)]
pub struct Image {
pub id: i64,
pub registry: String,
pub image: String,
- pub expires_at: DateTime<Utc>,
+ pub expires_at: Timestamp,
}
#[derive(Debug, Clone)]
@@ -20,7 +48,7 @@ pub struct Tag {
pub trait Db {
fn get_image(&self, registry: &str, image: &str) -> Option<Image>;
- fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image>;
+ fn get_expired_images(&self, now: Timestamp) -> Vec<Image>;
fn insert_images(&self, images: &mut [Image]);
fn update_images(&self, images: &[Image]);
@@ -161,7 +189,7 @@ impl Db for SqliteDb {
}
}
- fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image> {
+ fn get_expired_images(&self, now: Timestamp) -> Vec<Image> {
let _timer = crate::metrics::get().db_query_duration.start_timer();
let mut stmt = self.conn.prepare("
SELECT id, registry, image, expires_at FROM images WHERE expires_at <= ?1
@@ -275,10 +303,10 @@ impl Db for StubDb {
}
}
- fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image> {
+ fn get_expired_images(&self, now: Timestamp) -> Vec<Image> {
let mut result = vec![];
for img in self.images.borrow().iter() {
- if img.expires_at <= *now {
+ if img.expires_at <= now {
result.push(Image {
id: img.id,
registry: img.registry.clone(),
@@ -331,10 +359,9 @@ impl Db for StubDb {
#[cfg(test)]
mod tests {
use super::*;
- use chrono::TimeZone;
fn test_insert_images_returns_incrementing_ids(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let now = Timestamp::ZERO;
let mut images = [
Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now },
Image { id: 0, registry: "docker.io".into(), image: "redis".into(), expires_at: now },
@@ -348,7 +375,7 @@ mod tests {
}
fn test_get_image_returns_inserted(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
let got = db.get_image("docker.io", "nginx").unwrap();
@@ -357,14 +384,14 @@ mod tests {
}
fn test_get_tags_returns_empty_for_no_tags(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
assert!(db.get_tags_sorted(images[0].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 now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
db.insert_tags(&mut [
@@ -377,7 +404,7 @@ mod tests {
}
fn test_delete_tags_removes_tags(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
let mut tags = [
@@ -391,8 +418,8 @@ mod tests {
}
fn test_update_images(db: &dyn Db) {
- let t1 = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
- let t2 = Utc.with_ymd_and_hms(2000, 1, 1, 1, 0, 0).unwrap();
+ let t1 = Timestamp::ZERO;
+ let t2 = Timestamp::ZERO + Duration::from_secs(3600);
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: t1 }];
db.insert_images(&mut images);
images[0].expires_at = t2;
@@ -401,7 +428,7 @@ mod tests {
}
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 now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
db.insert_tags(&mut [Tag { id: 0, image_id: images[0].id, tag: "1.0".into(), digest: None }]);
@@ -409,7 +436,7 @@ mod tests {
}
fn test_update_tags(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let now = Timestamp::ZERO;
let mut images = [Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now }];
db.insert_images(&mut images);
let mut tags = [Tag { id: 0, image_id: images[0].id, tag: "1.0".into(), digest: None }];
@@ -420,15 +447,15 @@ mod tests {
}
fn test_get_expired_images(db: &dyn Db) {
- let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
- let past = now - chrono::Duration::days(1);
- let future = now + chrono::Duration::days(1);
+ let now = Timestamp::ZERO + Duration::from_secs(86400 * 30);
+ let past = now - Duration::from_secs(86400);
+ let future = now + Duration::from_secs(86400);
let mut images = [
Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: past },
Image { id: 0, registry: "docker.io".into(), image: "redis".into(), expires_at: future },
];
db.insert_images(&mut images);
- let result = db.get_expired_images(&now);
+ let result = db.get_expired_images(now);
assert_eq!(result.len(), 1);
assert_eq!(result[0].id, images[0].id);
assert_eq!(result[0].image, "nginx");