Files
DevFlow/docs/01-技术文档/SQLite-CRUD模式-2026-06-12.md
绝尘 212a927eee 文档: todo销账(batch29 ARC-05/06+FR-D3+R-PD-10)+待审查登记+DOC整理
- todo: ARC-05越层核验0命中+四领域拆分/ARC-06破环aiShared/FR-D3三子项闭环销账+新增ARC-06b遗留

- 待审查: 登记CR-260616-02(batch29+R-PD-10代码 fc767e1)待审

- DOC-14: df-storage/SQLite-CRUD文档表数V13/V9→V15对齐migrations.rs schema_version=15
2026-06-16 03:28:20 +08:00

80 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SQLite CRUD 模式
> 创建: 2026-06-10 | 状态: 初稿
---
## 概述
DevFlow 使用 SQLite (rusqlite) 作为本地存储引擎。df-storage 负责 SQLite 连接管理、Schema 迁移和 CRUD 操作。
## 当前状态
- SQLite 连接管理: 已实现(`Arc<Mutex<Connection>>` 单连接,非连接池)
- Schema 迁移 (V1-V15 累计 14 张表 + 12 索引): 已实现
- CRUD 层: 已实现(`impl_repo!` 宏 + 17 个 Repo + 向量检索)
## 设计要点
### 已实施内容
1. **`impl_repo!` 宏 CRUD** — 一次性为各表生成 insert / get_by_id / list_all / query / update_field / update_full / delete当前 17 个 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>`idupdate/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` — 统一错误类型