From 5166d97545544e95e0a67a07bedfb31840ff48ad Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Mon, 21 Sep 2026 17:01:38 +0800 Subject: [PATCH] refactor(db): use rusqlite query_row/optional instead of hand-rolled reads pending_backlog, earliest_run_after, ChatStore::get and LinkCache::get each hand-rolled prepare + query + rows.next() for what is a single-row read. query_row + OptionalExtension::optional is the same statement and the same error mapping with less scaffolding; the backlog's NULL-on-empty MIN still goes through the count check, so a pending row with a NULL run_after is not misread. Also fixes the rustfmt drift from the previous commit. --- crates/xmedia-bot/src/link_cache.rs | 15 +++++++----- crates/xmedia-bot/src/queue.rs | 35 ++++++++++++---------------- crates/xmedia-bot/src/send/upload.rs | 2 +- crates/xmedia-bot/src/state.rs | 13 ++++++----- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/crates/xmedia-bot/src/link_cache.rs b/crates/xmedia-bot/src/link_cache.rs index ce1def5..d868271 100644 --- a/crates/xmedia-bot/src/link_cache.rs +++ b/crates/xmedia-bot/src/link_cache.rs @@ -9,6 +9,7 @@ //! by the periodic prune in `main`. use crate::db::now_f64; +use rusqlite::OptionalExtension; use rusqlite::params; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -76,14 +77,16 @@ impl LinkCache { let result = self .pool .with_conn(move |conn| { - let mut stmt = - conn.prepare("SELECT payload, created_at FROM link_cache WHERE url = ?1")?; - let mut rows = stmt.query(params![key])?; - let Some(row) = rows.next()? else { + let Some((payload, created_at)) = conn + .query_row( + "SELECT payload, created_at FROM link_cache WHERE url = ?1", + params![key], + |row| Ok((row.get::<_, String>(0)?, row.get::<_, f64>(1)?)), + ) + .optional()? + else { return Ok(None); }; - let payload: String = row.get(0)?; - let created_at: f64 = row.get(1)?; if now_f64() - created_at > ttl { conn.execute("DELETE FROM link_cache WHERE url = ?1", params![key])?; return Ok(None); diff --git a/crates/xmedia-bot/src/queue.rs b/crates/xmedia-bot/src/queue.rs index c46ddab..52ee8f7 100644 --- a/crates/xmedia-bot/src/queue.rs +++ b/crates/xmedia-bot/src/queue.rs @@ -294,19 +294,16 @@ impl PersistentTaskQueue { let result = self .pool .with_conn(|conn| { - let mut stmt = conn - .prepare("SELECT COUNT(*), MIN(run_after) FROM tasks WHERE status='pending'")?; - let mut rows = stmt.query([])?; - match rows.next()? { - Some(row) => { - let count = row.get::<_, i64>(0)?; - match row.get::<_, Option>(1)? { - Some(oldest) if count > 0 => Ok(Some((count, oldest))), - _ => Ok(None), - } - } - None => Ok(None), - } + let (count, oldest) = conn.query_row( + "SELECT COUNT(*), MIN(run_after) FROM tasks WHERE status='pending'", + [], + |row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option>(1)?)), + )?; + // `MIN` over zero rows is NULL, so the count is what decides. + Ok(match oldest { + Some(oldest) if count > 0 => Some((count, oldest)), + _ => None, + }) }) .await; match result { @@ -461,13 +458,11 @@ impl QueueWorker { let result = self .pool .with_conn(|conn| { - let mut stmt = - conn.prepare("SELECT MIN(run_after) FROM tasks WHERE status='pending'")?; - let mut rows = stmt.query([])?; - match rows.next()? { - Some(row) => Ok(row.get::<_, Option>(0)?), - None => Ok(None), - } + conn.query_row( + "SELECT MIN(run_after) FROM tasks WHERE status='pending'", + [], + |row| row.get::<_, Option>(0), + ) }) .await; match result { diff --git a/crates/xmedia-bot/src/send/upload.rs b/crates/xmedia-bot/src/send/upload.rs index 1f2e039..a86b088 100644 --- a/crates/xmedia-bot/src/send/upload.rs +++ b/crates/xmedia-bot/src/send/upload.rs @@ -170,7 +170,7 @@ pub(super) async fn prepare_upload_item( let media_url = item_url(&item); if !media_url.starts_with("http://") && !media_url.starts_with("https://") { let media = media_from_file(&item, std::path::PathBuf::from(media_url), caption) - .map_err(|message| FallbackError::Permanent { message })?; + .map_err(|message| FallbackError::Permanent { message })?; return Ok(PreparedItem { index, media, diff --git a/crates/xmedia-bot/src/state.rs b/crates/xmedia-bot/src/state.rs index fa44efd..2bec876 100644 --- a/crates/xmedia-bot/src/state.rs +++ b/crates/xmedia-bot/src/state.rs @@ -3,6 +3,7 @@ use crate::db::unix_now; use parking_lot::Mutex; +use rusqlite::OptionalExtension; use rusqlite::params; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -64,12 +65,12 @@ impl ChatStore { // Concurrent handler tasks (batch-forwards) may write chat_state // while this read runs; the shared busy timeout handles the // write-lock collision instead of failing the query. - let mut stmt = conn.prepare("SELECT payload FROM chat_state WHERE chat_id = ?1")?; - let mut rows = stmt.query(params![chat_key])?; - match rows.next()? { - Some(row) => Ok(Some(row.get::<_, String>(0)?)), - None => Ok(None), - } + conn.query_row( + "SELECT payload FROM chat_state WHERE chat_id = ?1", + params![chat_key], + |row| row.get::<_, String>(0), + ) + .optional() }) .await .unwrap_or_else(|e| {