Files
DevFlow/docs/01-技术文档/SQLite-CRUD模式-2026-06-12.md
绝尘 ff3f153d45 修复: 安全加固+DRY 收敛+文档同步+测试补齐
安全:
- ScriptNode 默认黑名单兜底(rm/del/format/shutdown/mkfs/dd)
- bind_directory 分段 .. 检测替代 contains 子串(对齐 tool_registry)
- ai_providers 白名单移除 api_key(防 update_field 旁路写明文)

DRY:
- useAiEvents 抽 cleanupTerminatedConversation 统一三分支收尾
- 新增 useStoreAction 工具,4 个 store 替换 38 处 try/catch 样板

文档:
- df-core → df-types 批量替换(ARCHITECTURE/PROGRESS/SQLite-CRUD)
- INDEX 补齐 9 漏列文档(单对话并行多轮/跑题试验/工程系统设计等)
- Agent架构说明 死链修复(../构想审查/)
- AI对话引擎工具清单改为数量+按风险分组(不再用固定数字)
- ARCH 状态标签 设计阶段 → Phase 2 验证

测试:
- df-relay 新增 registry_test: ConnRegistry 路由 + RelayState + 16 项单测
2026-06-29 21:57:07 +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_types::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-types/src/error.rs` — 统一错误类型