优化: 存储层清债与子轨迹来源迁移
- V44 加 ai_conversations.origin 列(子代理轨迹来源标记) - V21 断点续迁改造(逐对话进度判定,崩溃重跑补漏) - 手写事务改 guard.transaction,panic 不残留打开事务 - 软删 CAS 补 deleted_at IS NULL(任务/项目/灵感三处) - 父任务校验拒回收站挂子,删模块先清依赖边 - 列缺失与未知 status 降级补 warn 留痕,limit=0 返空集
This commit is contained in:
@@ -147,6 +147,10 @@ impl AiMessageRepo {
|
||||
/// list_by_conversation 一致的 seq 升序)。`before_seq` 可选:指定后只取 seq < before_seq
|
||||
/// 的消息(滚顶加载更多时的游标,取下一页更早的历史)。
|
||||
///
|
||||
/// `limit = 0` 契约:返空集(SQLite LIMIT 0 合法且恒 0 行),不做旧版的 `.max(1)`
|
||||
/// 静默钳制——请求 0 条却回 1 条会让调用方误判页内有数据。当前唯一调用方传
|
||||
/// 常量 PAGE_SIZE=50 恒 >0,0 只可能来自未来调用方失误,空集是最不易误导的语义。
|
||||
///
|
||||
/// 典型用法:
|
||||
/// - 首次切入对话:list_recent(conv_id, 50, None) → 最近 50 条
|
||||
/// - 滚顶加载更多:list_recent(conv_id, 50, Some(最早已加载消息的 seq)) → 再加载 50 条更早的
|
||||
@@ -160,8 +164,9 @@ impl AiMessageRepo {
|
||||
let conv_id = conversation_id.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
// before_seq 有无分两个 SQL(参数化 LIMIT 必须用固定占位,Rust 侧 clamp 防 0)
|
||||
let limit = limit.max(1) as i64;
|
||||
// before_seq 有无分两个 SQL(参数化 LIMIT 必须用固定占位);limit=0 直传
|
||||
// LIMIT 0 返空集(契约见方法文档),不再钳成 1
|
||||
let limit = limit as i64;
|
||||
let sql = if before_seq.is_some() {
|
||||
"SELECT id, conversation_id, seq, role, content, parts, tool_call_id,
|
||||
tool_calls, model, status, reasoning_content, timestamp, created_at,
|
||||
@@ -681,4 +686,29 @@ mod tests {
|
||||
let got = repo.list_by_conversation("c").await.expect("list");
|
||||
assert_eq!(got.len(), 1, "重复 replace 不应叠加");
|
||||
}
|
||||
|
||||
/// limit=0 契约:返空集(直传 SQLite LIMIT 0),不再旧版 max(1) 钳成 1 条。
|
||||
/// 锁死语义防回归:请求 0 条却回 1 条会让调用方误判页内有数据。
|
||||
#[tokio::test]
|
||||
async fn list_recent_limit_zero_returns_empty() {
|
||||
let db = Database::open_in_memory().await.expect("open_in_memory");
|
||||
let repo = AiMessageRepo::new(&db);
|
||||
repo.insert_batch(vec![
|
||||
mk_msg("m0", "c", 0, "user", "0"),
|
||||
mk_msg("m1", "c", 1, "user", "1"),
|
||||
])
|
||||
.await
|
||||
.expect("insert");
|
||||
|
||||
let got = repo.list_recent("c", 0, None).await.expect("list limit=0");
|
||||
assert!(got.is_empty(), "limit=0 无游标应返空集");
|
||||
|
||||
let got2 = repo.list_recent("c", 0, Some(5)).await.expect("list limit=0 带游标");
|
||||
assert!(got2.is_empty(), "limit=0 带游标也应返空集");
|
||||
|
||||
// 邻位 sanity:limit=1 行为不受影响,仍返最近 1 条
|
||||
let got3 = repo.list_recent("c", 1, None).await.expect("list limit=1");
|
||||
assert_eq!(got3.len(), 1);
|
||||
assert_eq!(got3[0].seq, 1, "limit=1 应取 seq 最大(最新)一条");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user