//! 数据库迁移 — 通用辅助(幂等迁移探测) use rusqlite::Connection; /// 检测表是否存在指定列(幂等迁移通用辅助) pub(crate) fn column_exists(conn: &Connection, table: &str, col: &str) -> bool { let Ok(mut stmt) = conn.prepare(&format!("PRAGMA table_info({table})")) else { return false }; let Ok(rows) = stmt.query_map([], |r| r.get::<_, String>(1)) else { return false }; for r in rows { if let Ok(name) = r { if name == col { return true; } } } false } #[cfg(test)] mod tests { use super::*; // column_exists 是幂等迁移的通用探测辅助(V4+ 全部迁移靠它判断列存在性), // 本组用 in-memory SQLite(:memory:) 建临时表覆盖四种语义: // 列存在→true / 列不存在→false / 表不存在→false / 多列中正确匹配目标列名。 /// 列存在返回 true:建表后探测真实列名。 #[test] fn column_exists_returns_true_for_existing_column() { let conn = Connection::open_in_memory().expect("open in-memory db"); conn.execute_batch( "CREATE TABLE probe_t (id TEXT PRIMARY KEY, name TEXT NOT NULL, score INTEGER);", ) .expect("create probe table"); assert!(column_exists(&conn, "probe_t", "id")); assert!(column_exists(&conn, "probe_t", "name")); assert!(column_exists(&conn, "probe_t", "score")); } /// 列不存在返回 false:表存在但目标列名未建。 #[test] fn column_exists_returns_false_for_missing_column() { let conn = Connection::open_in_memory().expect("open in-memory db"); conn.execute_batch("CREATE TABLE probe_t (id TEXT PRIMARY KEY, name TEXT);") .expect("create probe table"); assert!(!column_exists(&conn, "probe_t", "archived")); assert!(!column_exists(&conn, "probe_t", "deleted_at")); } /// 表不存在返回 false:PRAGMA table_info 对未知表返回空结果集,函数应安全降级为 false。 #[test] fn column_exists_returns_false_when_table_absent() { let conn = Connection::open_in_memory().expect("open in-memory db"); // 不建任何表,直接探测 assert!(!column_exists(&conn, "ghost_table", "any_col")); } /// 多列表中正确匹配目标列名(不误命中前缀/子串):验证精确比较非 contains。 #[test] fn column_exists_matches_exact_name_among_many() { let conn = Connection::open_in_memory().expect("open in-memory db"); conn.execute_batch( "CREATE TABLE probe_t ( id TEXT PRIMARY KEY, prompt_tokens INTEGER, completion_tokens INTEGER, model TEXT );", ) .expect("create probe table"); // 命中真实列 assert!(column_exists(&conn, "probe_t", "prompt_tokens")); assert!(column_exists(&conn, "probe_t", "completion_tokens")); // 不误命中子串(prefix/suffix 近似名) assert!(!column_exists(&conn, "probe_t", "tokens")); assert!(!column_exists(&conn, "probe_t", "prompt")); assert!(!column_exists(&conn, "probe_t", "model_configs")); } }