From 3c37eed3ef2ff46a1b72c0a993f0731cbb04ab6d Mon Sep 17 00:00:00 2001 From: Heng-Yi Wu <2316687+henry40408@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:36:32 +0800 Subject: [PATCH 1/2] bisect: schema and migration half only --- src/db.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/db.rs b/src/db.rs index 94dabce..6adabe2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -418,7 +418,8 @@ impl Database { upstream TEXT, doh_token TEXT, result TEXT, - authenticated_data INTEGER NOT NULL DEFAULT 0 + authenticated_data INTEGER NOT NULL DEFAULT 0, + has_result INTEGER GENERATED ALWAYS AS (result IS NOT NULL AND result != '') VIRTUAL ); CREATE INDEX IF NOT EXISTS idx_query_logs_timestamp ON query_logs(timestamp); CREATE INDEX IF NOT EXISTS idx_query_logs_domain_ts ON query_logs(domain, timestamp); @@ -642,7 +643,22 @@ impl Database { )?; } - const LATEST_VERSION: i64 = 11; + if version < 12 { + add_column_if_missing( + conn, + "query_logs", + "has_result", + "INTEGER GENERATED ALWAYS AS (result IS NOT NULL AND result != '') VIRTUAL", + )?; + conn.execute_batch( + "DROP INDEX IF EXISTS idx_query_logs_ts_metrics; + CREATE INDEX idx_query_logs_ts_metrics \ + ON query_logs(timestamp, blocked, cached, response_ms, query_type, has_result); + ANALYZE;", + )?; + } + + const LATEST_VERSION: i64 = 12; if version < LATEST_VERSION { conn.pragma_update(None, "user_version", LATEST_VERSION)?; } @@ -2297,7 +2313,7 @@ fn add_column_if_missing( ) -> Result<(), rusqlite::Error> { let exists: bool = conn .query_row( - &format!("SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name = ?1"), + &format!("SELECT COUNT(*) FROM pragma_table_xinfo('{table}') WHERE name = ?1"), params![column], |row| row.get::<_, i64>(0), ) From 67bf65044c7ea20e07a6922ce6bafd1f10748b1a Mon Sep 17 00:00:00 2001 From: Heng-Yi Wu <2316687+henry40408@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:43:22 +0800 Subject: [PATCH 2/2] bisect: domain_stats_since only --- src/db.rs | 54 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/db.rs b/src/db.rs index 6adabe2..8ee6a39 100644 --- a/src/db.rs +++ b/src/db.rs @@ -230,6 +230,12 @@ pub struct TimelineMultiPoint { pub cached: i64, } +#[derive(Debug, Clone)] +pub struct DomainStats { + pub unique: i64, + pub top: Vec, +} + #[derive(Debug, Clone, Serialize)] pub struct HeatmapCell { pub weekday: i64, // 0 = Sunday, 6 = Saturday (matches strftime('%w')) @@ -1907,25 +1913,43 @@ impl Database { since: i64, limit: i64, ) -> Result, DbError> { + Ok(self.domain_stats_since(since, limit).await?.top) + } + + pub async fn domain_stats_since(&self, since: i64, limit: i64) -> Result { let since_ms = since * 1000; - let rows = self + let stats = self .reader() .call(move |conn| { let mut stmt = conn.prepare_cached( - "SELECT domain, COUNT(*) as cnt FROM query_logs WHERE timestamp >= ?1 GROUP BY domain ORDER BY cnt DESC LIMIT ?2", + "WITH d AS ( \ + SELECT domain, COUNT(*) AS cnt \ + FROM query_logs \ + WHERE timestamp >= ?1 \ + GROUP BY domain \ + ) \ + SELECT (SELECT COUNT(*) FROM d), domain, cnt \ + FROM d ORDER BY cnt DESC LIMIT ?2", )?; let rows = stmt .query_map(params![since_ms, limit], |row| { - Ok(TopDomain { - domain: row.get(0)?, - count: row.get(1)?, - }) + Ok(( + row.get::<_, i64>(0)?, + TopDomain { + domain: row.get(1)?, + count: row.get(2)?, + }, + )) })? .collect::, _>>()?; - Ok(rows) + let unique = rows.first().map_or(0, |(n, _)| *n); + Ok(DomainStats { + unique, + top: rows.into_iter().map(|(_, d)| d).collect(), + }) }) .await?; - Ok(rows) + Ok(stats) } pub async fn top_clients_since( @@ -2135,19 +2159,7 @@ impl Database { } pub async fn unique_domains_since(&self, since: i64) -> Result { - let since_ms = since * 1000; - let count = self - .reader() - .call(move |conn| { - let n: i64 = conn.query_row( - "SELECT COUNT(DISTINCT domain) FROM query_logs WHERE timestamp >= ?1", - params![since_ms], - |row| row.get(0), - )?; - Ok(n) - }) - .await?; - Ok(count) + Ok(self.domain_stats_since(since, 1).await?.unique) } pub async fn latency_summary_since(&self, since: i64) -> Result {