修复: AR-3 审批 reason 查项目名拼对象名(用户再反馈 P0)
build_approval_reason 改 async + 接收 db,对 delete/restore/purge/update/bind/create_task 的 id/project_id 查 ProjectRepo.get_by_id 拼「项目名」(id=x)(原只拼裸 id,用户反馈'只返回 ID 不知道是什么数据') 新增 resolve_project_label helper;process_tool_calls 调用改 await 来源 aichat审查报告 第二章 + 用户 2026-06-14 再反馈;cargo 0 err
This commit is contained in:
79
docs/01-技术文档/SQLite-CRUD模式-2026-06-12.md
Normal file
79
docs/01-技术文档/SQLite-CRUD模式-2026-06-12.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# SQLite CRUD 模式
|
||||
|
||||
> 创建: 2026-06-10 | 状态: 初稿
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
DevFlow 使用 SQLite (rusqlite) 作为本地存储引擎。df-storage 负责 SQLite 连接管理、Schema 迁移和 CRUD 操作。
|
||||
|
||||
## 当前状态
|
||||
|
||||
- SQLite 连接管理: 已实现(`Arc<Mutex<Connection>>` 单连接,非连接池)
|
||||
- Schema 迁移 (V1-V9 累计 11 张表 + 9 索引): 已实现
|
||||
- CRUD 层: 已实现(`impl_repo!` 宏 + 11 个 Repo + 向量检索)
|
||||
|
||||
## 设计要点
|
||||
|
||||
### 已实施内容
|
||||
|
||||
1. **`impl_repo!` 宏 CRUD** — 一次性为各表生成 insert / get_by_id / list_all / query / update_field / update_full / delete(当前 11 个 Repo,非 trait 抽象)
|
||||
2. **参数化查询** — `?1`/`?2` 占位符 + `params![]` 绑定,防止 SQL 注入
|
||||
3. **列名白名单校验** — `ALLOWED_COLUMNS` 校验 query / update_field 的动态列名
|
||||
4. **`update_full(record)`** — 整体更新全可变字段(保留 id 与 created_at)的原子写
|
||||
5. **向量检索** — KnowledgeRepo 余弦相似度 top-N(纯 Rust,数据量 <50k 暴力遍历)
|
||||
|
||||
### 仍待实施
|
||||
|
||||
1. **泛型 `Repository<T>` trait** — 当前用宏非 trait,无统一接口抽象
|
||||
2. **事务支持** — 跨表操作的原子性保证(当前单连接 + Mutex,无显式事务)
|
||||
3. **批量操作** — `insert_batch` / `update_batch`
|
||||
|
||||
### 约定
|
||||
|
||||
- ID 字段统一使用 `TEXT` (UUID v4)
|
||||
- 时间字段使用 `TEXT` (毫秒时间戳字符串,`now_millis_str()` 返回 String)
|
||||
- JSON 字段使用 `TEXT` 存储 JSON 字符串
|
||||
- 所有写操作统一返回 `df_core::error::Error` 错误类型:insert 返 `Result<String>`(id),update/delete 返 `Result<bool>`(是否影响行)
|
||||
|
||||
## update_field 的强制时间戳约束
|
||||
|
||||
`impl_repo!` 宏为各 Repo 统一生成的 `update_field(id, field, value)` 生成 SQL:
|
||||
```sql
|
||||
UPDATE {table} SET {field} = ?1, updated_at = ?2 WHERE id = ?3
|
||||
```
|
||||
**总是连带刷新 updated_at = now**,不可关闭。
|
||||
|
||||
### 适用与不适用
|
||||
|
||||
- 适用:内容更新(标题、状态、描述等)——这些变更本就该反映为新近修改时间。
|
||||
- 不适用:纯元数据标记切换(如归档 archived)。归档是元数据,不应改对话的"最近活跃时间",否则侧栏相对时间会跳变为"刚刚"。
|
||||
|
||||
### 例外模式:专用方法走原生 SQL
|
||||
|
||||
对元数据类切换,新增专用方法绕过 update_field,直接写原生 SQL 只动目标列。例如 AiConversationRepo::set_archived:
|
||||
```sql
|
||||
UPDATE ai_conversations SET archived = ?1 WHERE id = ?2
|
||||
```
|
||||
不带 updated_at。
|
||||
|
||||
同类专用原生 SQL 方法(均不调 update_field),按「是否刷 updated_at」分两类:
|
||||
|
||||
- **不刷**(与 set_archived 同策略,只改目标列):
|
||||
- `KnowledgeRepo::set_embedding` — `UPDATE knowledges SET embedding = ?1 WHERE id = ?2`(向量写库,非业务修改)
|
||||
- **刷**(策略相反,刷新时间反映复用行为):
|
||||
- `KnowledgeRepo::increment_reuse_count` — `UPDATE knowledges SET reuse_count = reuse_count + 1, updated_at = ?1 WHERE id = ?2`(原子自增 + 刷时间,因复用确属"近期活跃")
|
||||
|
||||
> 取舍:是否刷 updated_at 取决于语义——元数据/内部字段切换不刷(避免污染相对时间),反映业务行为变更的刷。
|
||||
|
||||
### 相关机制
|
||||
|
||||
- `update_full(record)`:整体更新全可变字段,保留 id 与 created_at,用于 save_conversation 落库对话内容(此时确实要刷 updated_at)。
|
||||
- `ALLOWED_COLUMNS` 白名单:update_field / query 的列名经白名单校验防注入。set_archived 因列名硬编码无需白名单。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `crates/df-storage/src/lib.rs` — 存储层入口
|
||||
- `crates/df-storage/src/migrations.rs` — Schema 定义与迁移
|
||||
- `crates/df-core/src/error.rs` — 统一错误类型
|
||||
Reference in New Issue
Block a user