From d4e8a7ae1c655a3beb2d63ec6002177839fd8470 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 20 Apr 2025 16:17:30 -0600 Subject: [PATCH] Fix report timestamp storage & comparison --- migrations/16_reports_timestamp_fix.sql | 1 + src/db/mod.rs | 37 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 migrations/16_reports_timestamp_fix.sql diff --git a/migrations/16_reports_timestamp_fix.sql b/migrations/16_reports_timestamp_fix.sql new file mode 100644 index 0000000..4ad8085 --- /dev/null +++ b/migrations/16_reports_timestamp_fix.sql @@ -0,0 +1 @@ +UPDATE reports SET timestamp = datetime(timestamp) WHERE TRUE; diff --git a/src/db/mod.rs b/src/db/mod.rs index b457d12..fee20b8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -9,7 +9,8 @@ use prost::Message; use sqlx::{ migrate::MigrateDatabase, Connection, Executor, Pool, Row, Sqlite, SqliteConnection, SqlitePool, }; -use time::{OffsetDateTime, UtcOffset}; +use time::{macros::format_description, OffsetDateTime, UtcDateTime}; + use crate::{ config::DbConfig, models::{ @@ -108,7 +109,7 @@ impl Database { report.migrate()?; let units = mem::take(&mut report.units); let data = compress(&report.encode_to_vec()); - let timestamp = commit.timestamp.to_offset(UtcOffset::UTC); + let timestamp = to_primitive_date_time(commit.timestamp); let report_id = sqlx::query!( r#" INSERT INTO reports (project_id, version, git_commit, git_commit_message, timestamp, data, data_version) @@ -266,21 +267,21 @@ impl Database { pub async fn upgrade_report(&self, file: &CachedReportFile) -> Result { let mut units = Vec::with_capacity(file.report.units.len()); let mut missing_unit_keys = Vec::with_capacity(file.report.units.len()); - let mut missing_unit_idx = HashMap::new(); + let mut missing_unit_idx = HashMap::>::new(); for (idx, &key) in file.report.units.iter().enumerate() { if let Some(unit) = self.report_unit_cache.get(&key).await { units.push(Some(unit)); } else { units.push(None); missing_unit_keys.push(key); - missing_unit_idx.insert(key, idx); + missing_unit_idx.entry(key).or_default().push(idx); } } if !missing_unit_keys.is_empty() { let mut conn = self.pool.acquire().await?; for chunk in missing_unit_keys.chunks(BIND_LIMIT) { let mut builder = sqlx::QueryBuilder::::new( - "SELECT ru.id AS id, ru.data FROM report_units ru WHERE ru.id IN (", + "SELECT ru.id, ru.data FROM report_units ru WHERE ru.id IN (", ); let mut separated = builder.separated(", "); for key in chunk { @@ -292,7 +293,6 @@ impl Database { let row_id: Box<[u8]> = row.get(0); let row_data: Box<[u8]> = row.get(1); let key = row_id.as_ref().try_into()?; - let unit_idx = missing_unit_idx.get(&key).copied().unwrap(); let data = decompress(&row_data).context("Failed to decompress report unit data")?; // Skip hash check since we're rehydrating a cached report @@ -302,17 +302,31 @@ impl Database { .context("Failed to decode report unit")?, ); self.report_unit_cache.insert(key, unit.clone()).await; - units[unit_idx] = Some(unit); + if let Some(v) = missing_unit_idx.get(&key) { + for &idx in v { + units[idx] = Some(unit.clone()); + } + } else { + tracing::error!("Unexpected unit index: {:?}", hex::encode(key)); + } } } } + let mut out_units = Vec::with_capacity(file.report.units.len()); + for (unit, &unit_key) in units.into_iter().zip(&file.report.units) { + if let Some(unit) = unit { + out_units.push(unit); + } else { + tracing::error!("Failed to load report unit: {}", hex::encode(unit_key)); + } + } Ok(FullReportFile { commit: file.commit.clone(), version: file.version.clone(), report: FullReport { version: file.report.version, measures: file.report.measures, - units: units.into_iter().map(Option::unwrap).collect(), + units: out_units, categories: file.report.categories.clone(), }, }) @@ -479,7 +493,7 @@ impl Database { }; if let Some(first_report) = reports.first() { // Fetch previous and next commits - let timestamp = first_report.timestamp; + let timestamp = to_primitive_date_time(first_report.timestamp.to_utc()); let prev_commit = sqlx::query!( r#" SELECT git_commit @@ -949,3 +963,8 @@ fn decompress(data: &[u8]) -> Result> { Err(_) => Ok(Cow::Borrowed(data)), // Assume uncompressed } } + +#[inline] +fn to_primitive_date_time(date: UtcDateTime) -> String { + date.format(format_description!("[year]-[month]-[day] [hour]:[minute]:[second]")).unwrap() +}