修复: gen_stream判重+重试分类+状态机最短路径+既有测试修复

This commit is contained in:
lxy
2026-08-12 00:29:06 +08:00
parent 9407f821b9
commit d31a27512c
36 changed files with 2614 additions and 264 deletions
+50 -4
View File
@@ -391,12 +391,17 @@ impl TaskRepo {
params_vec.push(Box::new(mid.clone()));
}
// keyword: title/description LIKE %kw%(P2,对齐知识库 search 的 LIKE 模式)
// ESCAPE 字符用 |(管道符,任务标题/描述几乎不含),不用反斜杠——反斜杠在
// Rust format! → rusqlite 绑定 → SQLite 多层转义里极易出错(SQLite 报
// "ESCAPE expression must be a single character"),改 | 一劳永逸。
// 注意:ESCAPE 只能跟单个 LIKE,不能跟括号分组(实测 `(a OR b) ESCAPE 'x'`
// 报 near "ESCAPE" syntax error),故每个 LIKE 各自 ESCAPE。
if let Some(kw) = &keyword {
let escaped = kw.replace('%', "\\%").replace('_', "\\_");
let escaped = kw.replace('|', "||").replace('%', "|%").replace('_', "|_");
let pat = format!("%{escaped}%");
let p1 = params_vec.len() + 1;
let p2 = p1 + 1;
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2}) ESCAPE '\\'"));
where_clauses.push(format!("(title LIKE ?{p1} ESCAPE '|' OR description LIKE ?{p2} ESCAPE '|')"));
params_vec.push(Box::new(pat.clone()));
params_vec.push(Box::new(pat));
}
@@ -494,11 +499,11 @@ impl TaskRepo {
params_vec.push(Box::new(a.clone()));
}
if let Some(ref kw) = keyword {
let escaped = kw.replace('%', "\\%").replace('_', "\\_");
let escaped = kw.replace('|', "||").replace('%', "|%").replace('_', "|_");
let pat = format!("%{escaped}%");
let p1 = params_vec.len() + 1;
let p2 = p1 + 1;
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2}) ESCAPE '\\'"));
where_clauses.push(format!("(title LIKE ?{p1} ESCAPE '|' OR description LIKE ?{p2} ESCAPE '|')"));
params_vec.push(Box::new(pat.clone()));
params_vec.push(Box::new(pat));
}
@@ -1358,4 +1363,45 @@ mod tests {
let after = repo.get_by_id("t1").await.unwrap().unwrap();
assert_eq!(after.title, "新标题", "软删后字段不应被改动");
}
// ============================================================
// keyword LIKE 查询(ESCAPE 转义)—— 防 2026-08-11 语法回归
// ============================================================
// 背景:旧写法 `(a LIKE ?1 OR b LIKE ?2) ESCAPE '|'`(ESCAPE 跟括号分组)在 SQLite
// 报 near "ESCAPE" syntax error,keyword 查询全挂。正确写法:ESCAPE 跟每个 LIKE。
// 本测试锁两种语义:普通子串匹配 + 含 %/_ 通配符字面匹配(转义生效)。
#[tokio::test]
async fn list_by_query_keyword_matches_substring() {
let repo = setup().await;
repo.insert(trec("t1", "todo", None)).await.unwrap();
repo.insert(trec("t2", "todo", None)).await.unwrap();
// 定制 title:t1 含「支付」,t2 不含
repo.update_field_active("t1", "title", "海外支付集成").await.unwrap();
let rows = repo
.list_by_query(&TaskQuery { keyword: Some("支付".into()), ..Default::default() })
.await
.unwrap();
assert_eq!(rows.len(), 1, "keyword 子串应只命中 t1");
assert_eq!(rows[0].id, "t1");
}
#[tokio::test]
async fn list_by_query_keyword_escapes_wildcards() {
let repo = setup().await;
repo.insert(trec("t1", "todo", None)).await.unwrap();
repo.insert(trec("t2", "todo", None)).await.unwrap();
// t1 标题含字面 % 与 _(通配符需转义,按字面匹配)
repo.update_field_active("t1", "title", "比率 100%_cache").await.unwrap();
repo.update_field_active("t2", "title", "比率 100x_cache").await.unwrap();
// 查询字面 "%_"(含两个通配符,转义后应按字面匹配 t1;t2 的 x 不匹配 %)
let rows = repo
.list_by_query(&TaskQuery { keyword: Some("100%_".into()), ..Default::default() })
.await
.unwrap();
assert_eq!(rows.len(), 1, "% 与 _ 应被转义为字面,只命中 t1");
assert_eq!(rows[0].id, "t1");
}
}