Skip to main content

max / goingson

Performance: fix email-search date filter, guard db-watcher clock underflow Ultra-fuzz Run #28 Phase 4 (Rust portion): - Email search with a date bound queried a nonexistent `e.date` column, erroring at runtime on every dated search. Compare the stored `received_at` text directly (sortable format), bound in the same format — correct and sargable (uses idx_emails_received_at) instead of the old non-sargable datetime() wrap. - db_watcher rate-limit: now.saturating_sub(last) so a backward wall-clock step can't underflow the u64 and wedge change-event emission. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-19 20:02 UTC
Commit: 9d72370768927eb6f058636a4244b956b6dd6f6f
Parent: 7e48a4a
2 files changed, +15 insertions, -13 deletions
@@ -13,7 +13,7 @@ use goingson_core::{
13 13 SearchRepository, SearchResultItem, SearchResultType, UserId,
14 14 };
15 15
16 - use crate::utils::{escape_like, parse_uuid};
16 + use crate::utils::{escape_like, format_datetime, parse_uuid};
17 17
18 18 /// SQLite-backed implementation of [`SearchRepository`].
19 19 ///
@@ -468,21 +468,20 @@ async fn search_emails_fts(
468 468 param_idx += 1;
469 469 }
470 470
471 - // Date filters
471 + // Date filters. Compare the stored `received_at` text directly (it is written
472 + // in the sortable "%Y-%m-%d %H:%M:%S" format, so lexicographic order is
473 + // chronological) rather than wrapping it in datetime(): the old code queried a
474 + // nonexistent `e.date` column (a runtime error on any dated search) and the
475 + // datetime() wrap was also non-sargable. Bind the bound in the same format so
476 + // the string comparison is correct and can use idx_emails_received_at.
472 477 if let Some(df) = &query.date_from {
473 - sql.push_str(&format!(
474 - " AND datetime(e.date) >= datetime(${})",
475 - param_idx
476 - ));
477 - params.push(df.to_rfc3339());
478 + sql.push_str(&format!(" AND e.received_at >= ${}", param_idx));
479 + params.push(format_datetime(df));
478 480 param_idx += 1;
479 481 }
480 482 if let Some(dt) = &query.date_to {
481 - sql.push_str(&format!(
482 - " AND datetime(e.date) <= datetime(${})",
483 - param_idx
484 - ));
485 - params.push(dt.to_rfc3339());
483 + sql.push_str(&format!(" AND e.received_at <= ${}", param_idx));
484 + params.push(format_datetime(dt));
486 485 }
487 486
488 487 sql.push_str(&format!(" ORDER BY rank LIMIT {}", per_type_limit));
@@ -126,7 +126,10 @@ pub fn start_db_watcher(app: tauri::AppHandle, shutdown: Arc<AtomicBool>) {
126 126
127 127 let last = last_event_time_clone.load(Ordering::Relaxed);
128 128
129 - if now - last >= MIN_EVENT_INTERVAL_MS {
129 + // saturating_sub: a backward wall-clock step (NTP, suspend/resume)
130 + // would otherwise underflow this u64 and either panic in debug or
131 + // wedge the rate limiter (ultra-fuzz Run #28).
132 + if now.saturating_sub(last) >= MIN_EVENT_INTERVAL_MS {
130 133 last_event_time_clone.store(now, Ordering::Relaxed);
131 134
132 135 debug!("Database change detected, emitting db:external-change event");