Files
DevFlow/crates/df-storage/src/migrations.rs
绝尘 a0987bad7d 重构: 拆migrations SQL迁移(strategy核心库)
- 新建 df-storage/migrations_helpers.rs(103行): column_exists纯查询fn + 4测试
- migrations.rs 659→541: use column_exists + 删原fn+测试, run/migrate_v1-v19主入口保留
- lib.rs: pub mod migrations_helpers
主代grep核验(不cargo避executor-split df-workflow中间态): migrations.rs use+column_exists 16处调用 + lib.rs pub mod印证; agent df-storage独立自验cargo 0+test 35+11
strategy: 核心库自底向上, 纯查询函数抽离(PRAGMA无副作用, SQL行为不变), pub(crate)零外部暴露扩大
git add指定(df-storage/*)
2026-06-19 04:46:25 +08:00

587 lines
23 KiB
Rust

//! 数据库迁移 — 建表 SQL 与版本管理
use crate::migrations_helpers::column_exists;
use anyhow::Result;
use rusqlite::Connection;
/// 执行所有迁移
pub fn run(conn: &Connection) -> Result<()> {
// 创建迁移版本表
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER PRIMARY KEY
);"
)?;
let current_version: i32 = conn
.query_row(
"SELECT COALESCE(MAX(version), 0) FROM schema_version",
[],
|row| row.get(0),
)
.unwrap_or(0);
// 迁移步骤链: 顺序执行,跳过已应用的版本(current_version < N 才跑)。
// 新增版本时,在此数组追加一项 (N, migrate_vN) 即可,无需改逻辑。
let steps: [(i32, fn(&Connection) -> Result<()>); 19] = [
(1, migrate_v1),
(2, migrate_v2),
(3, migrate_v3),
(4, migrate_v4),
(5, migrate_v5),
(6, migrate_v6),
(7, migrate_v7),
(8, migrate_v8),
(9, migrate_v9),
(10, migrate_v10),
(11, migrate_v11),
(12, migrate_v12),
(13, migrate_v13),
(14, migrate_v14),
(15, migrate_v15),
(16, migrate_v16),
(17, migrate_v17),
(18, migrate_v18),
(19, migrate_v19),
];
for (version, migrate_fn) in steps {
if current_version < version {
migrate_fn(conn)?;
}
}
Ok(())
}
/// V1: 初始表结构
fn migrate_v1(conn: &Connection) -> Result<()> {
conn.execute_batch(V1_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [1])?;
tracing::info!("迁移 v1 完成");
Ok(())
}
/// V2: 补齐关联字段 + branches 表
fn migrate_v2(conn: &Connection) -> Result<()> {
conn.execute_batch(V2_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [2])?;
tracing::info!("迁移 v2 完成");
Ok(())
}
/// V3: AI 对话表补建(新库) + 归档标记列(新老库统一)
fn migrate_v3(conn: &Connection) -> Result<()> {
conn.execute_batch(V3_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [3])?;
tracing::info!("迁移 v3 完成");
Ok(())
}
/// V4: 幂等补 ai_conversations.archived 列
///
/// 修复历史缺陷:早期 v3 迁移仅写入版本号 3,ALTER ADD COLUMN archived 未实际生效,
/// 导致 schema_version=3 但 ai_conversations 缺列,from_row 读 archived 报错,
/// list_all 失败 → 前端历史会话不显示 + 新对话 insert 失败。
/// 因 run() 按 `current_version < 3` 跳过 v3,该列无法靠 v3 自补。
/// 此处用 PRAGMA 探测列存在性,缺失才 ALTER,对新库/老库/坏库均安全。
fn migrate_v4(conn: &Connection) -> Result<()> {
let has_archived = column_exists(conn, "ai_conversations", "archived");
if !has_archived {
conn.execute(
"ALTER TABLE ai_conversations ADD COLUMN archived INTEGER NOT NULL DEFAULT 0",
[],
)?;
tracing::info!("v4: 补建 ai_conversations.archived 列");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [4])?;
tracing::info!("迁移 v4 完成");
Ok(())
}
/// V5: 幂等补 ai_conversations.prompt_tokens / completion_tokens 列
///
/// 流式 token 用量记录:对话级累计 input/output token(由 save_conversation 写入)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER,对新库/老库/坏库均安全(同 v4 模式)。
fn migrate_v5(conn: &Connection) -> Result<()> {
if !column_exists(conn, "ai_conversations", "prompt_tokens") {
conn.execute("ALTER TABLE ai_conversations ADD COLUMN prompt_tokens INTEGER", [])?;
tracing::info!("v5: 补建 ai_conversations.prompt_tokens 列");
}
if !column_exists(conn, "ai_conversations", "completion_tokens") {
conn.execute("ALTER TABLE ai_conversations ADD COLUMN completion_tokens INTEGER", [])?;
tracing::info!("v5: 补建 ai_conversations.completion_tokens 列");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [5])?;
tracing::info!("迁移 v5 完成");
Ok(())
}
/// V6: 幂等补 ai_conversations.models 列
///
/// 对话级多 model 记录:JSON 数组字符串(去重存对话用过的所有 model)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5 模式)。
fn migrate_v6(conn: &Connection) -> Result<()> {
let has_models = column_exists(conn, "ai_conversations", "models");
if !has_models {
conn.execute("ALTER TABLE ai_conversations ADD COLUMN models TEXT", [])?;
tracing::info!("v6: 补建 ai_conversations.models 列");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [6])?;
tracing::info!("迁移 v6 完成");
Ok(())
}
/// V7: 知识库表 — 经验沉淀的基本单元(共享记忆层)
///
/// 状态机: candidate → pending_review → published → archived
/// AI 只产 candidate,人工门控发布;reuse_count 是唯一客观排序信号。
/// effectiveness 列不建(决策撤销人工评分)。时间字段用毫秒字符串(同既有 model 约定)。
fn migrate_v7(conn: &Connection) -> Result<()> {
conn.execute_batch(V7_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [7])?;
tracing::info!("迁移 v7 完成");
Ok(())
}
/// V8: 幂等补 knowledges.embedding 列(向量检索)
///
/// 存 Vec<f32> 的小端字节序列化 BLOB。NULL = 未嵌入(走 LIKE 降级)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6 模式)。
fn migrate_v8(conn: &Connection) -> Result<()> {
let has_embedding = column_exists(conn, "knowledges", "embedding");
if !has_embedding {
conn.execute("ALTER TABLE knowledges ADD COLUMN embedding BLOB", [])?;
tracing::info!("v8: 补建 knowledges.embedding 列");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [8])?;
tracing::info!("迁移 v8 完成");
Ok(())
}
/// V9: 幂等补建 ai_providers + ai_tool_executions 表
///
/// 历史遗漏:这两张表从未写入迁移文件(V1-V8 均未包含),
/// 旧库可能通过其他方式已建,新库缺失导致 save_provider 等操作报 SQL 错误。
/// 用 CREATE TABLE IF NOT EXISTS 幂等,已有表不受影响。
fn migrate_v9(conn: &Connection) -> Result<()> {
conn.execute_batch(V9_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [9])?;
tracing::info!("迁移 v9 完成");
Ok(())
}
/// V10: 知识生命线 — 补 knowledges.reasoning 列 + 新建 knowledge_events 事件表
///
/// - reasoning: AI 提炼时给出的"为何值得沉淀"判断依据(此前 prompt 要求但写库丢弃,
/// 此处补列修复;老库行默认 NULL,前端降级展示"手动录入/无依据")。幂等(PRAGMA 探测)。
/// - knowledge_events: 追加型审计表,记录产生/审核/引用/归档四类事件,支撑生命线视图。
/// 独立表(非 JSON 嵌主表): 一条知识可被引用数百次,JSON 嵌入致行膨胀+更新竞争。
fn migrate_v10(conn: &Connection) -> Result<()> {
if !column_exists(conn, "knowledges", "reasoning") {
conn.execute("ALTER TABLE knowledges ADD COLUMN reasoning TEXT", [])?;
tracing::info!("v10: 补建 knowledges.reasoning 列");
}
conn.execute_batch(V10_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [10])?;
tracing::info!("迁移 v10 完成");
Ok(())
}
/// V11: 幂等补 projects.deleted_at 列(软删回收站)
///
/// 删除项目改为软删:deleted_at NULL=正常,非空=已进回收站(可恢复)。
/// ProjectRecord 不带该字段,纯靠 SQL WHERE deleted_at IS NULL 过滤;
/// 子表(tasks/releases/branches)不动,FK 仍满足,项目数据完整保留待恢复。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10 模式)。
fn migrate_v11(conn: &Connection) -> Result<()> {
if !column_exists(conn, "projects", "deleted_at") {
conn.execute("ALTER TABLE projects ADD COLUMN deleted_at TEXT", [])?;
tracing::info!("v11: 补建 projects.deleted_at 列(软删回收站)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [11])?;
tracing::info!("迁移 v11 完成");
Ok(())
}
/// V12: 幂等补 projects.path / projects.stack 列(项目绑定真实代码目录)
///
/// 项目与磁盘代码库脱钩是项目管理核心缺失:此版补 path(绑定目录绝对路径) +
/// stack(技术栈 JSON 数组字符串),为「绑定目录 + 探测技术栈」打地基,
/// 第二步「导入历史项目」直接复用。两列均 nullable,老项目 path/stack=NULL 天然兼容。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11 模式)。
fn migrate_v12(conn: &Connection) -> Result<()> {
if !column_exists(conn, "projects", "path") {
conn.execute("ALTER TABLE projects ADD COLUMN path TEXT", [])?;
tracing::info!("v12: 补建 projects.path 列(绑定代码目录)");
}
if !column_exists(conn, "projects", "stack") {
conn.execute("ALTER TABLE projects ADD COLUMN stack TEXT", [])?;
tracing::info!("v12: 补建 projects.stack 列(技术栈)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [12])?;
tracing::info!("迁移 v12 完成");
Ok(())
}
/// V13: 通用应用设置 KV 表(前端 localStorage 迁移目标)
///
/// 存主题/语言/AI 偏好/连接配置等,`value` 为 JSON 字符串。CREATE TABLE IF NOT EXISTS 幂等。
fn migrate_v13(conn: &Connection) -> Result<()> {
conn.execute_batch(V13_SQL)?;
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [13])?;
tracing::info!("迁移 v13 完成");
Ok(())
}
/// V14: 幂等补 tasks.deleted_at 列(软删回收站,对标 projects.deleted_at V11)
///
/// 删除任务改为软删:deleted_at NULL=正常,非空=已进回收站(可恢复)。
/// 与 projects.soft_delete 同模板:TaskRecord 不带该字段,纯靠 SQL WHERE deleted_at IS NULL
/// 过滤;子表(branches)不动,FK 仍满足,任务数据完整保留待恢复。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11 模式)。
fn migrate_v14(conn: &Connection) -> Result<()> {
if !column_exists(conn, "tasks", "deleted_at") {
conn.execute("ALTER TABLE tasks ADD COLUMN deleted_at TEXT", [])?;
tracing::info!("v14: 补建 tasks.deleted_at 列(软删回收站)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [14])?;
tracing::info!("迁移 v14 完成");
Ok(())
}
/// V15: 幂等补 tasks.review_rounds 列(review 退回累计轮数,F-260616-04)
///
/// 任务推进链状态机退回时累加:in_review→in_progress / testing→in_review 各 +1,
/// 由 advance_task(df-nodes::task_advance_node)原子写入。默认 0(从未退回过的任务)。
/// NOT NULL DEFAULT 0 保证老库行迁移后取值确定(非 NULL),TaskRecord 字段为 i32(非 Option)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14 模式)。
fn migrate_v15(conn: &Connection) -> Result<()> {
if !column_exists(conn, "tasks", "review_rounds") {
conn.execute(
"ALTER TABLE tasks ADD COLUMN review_rounds INTEGER NOT NULL DEFAULT 0",
[],
)?;
tracing::info!("v15: 补建 tasks.review_rounds 列(review 退回累计轮数)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [15])?;
tracing::info!("迁移 v15 完成");
Ok(())
}
/// V16: 幂等补 ai_conversations.pinned 列(对话置顶,UX-17)
///
/// 侧栏置顶分组排序信号:前端按 pinned DESC, updated_at DESC 排,置顶在前。
/// 纯元数据标记(同 archived),NOT NULL DEFAULT 0 保证老库行非 NULL,AiConversationRecord.pinned 为 bool。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15 模式),对新库/老库均安全。
fn migrate_v16(conn: &Connection) -> Result<()> {
if !column_exists(conn, "ai_conversations", "pinned") {
conn.execute(
"ALTER TABLE ai_conversations ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0",
[],
)?;
tracing::info!("v16: 补建 ai_conversations.pinned 列(对话置顶)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [16])?;
tracing::info!("迁移 v16 完成");
Ok(())
}
/// V17: 幂等补 tasks.output_json 列(AiNode 自审闭环产出,决策 a:task 中心)
///
/// 任务产出 JSON 字符串:ai_execute 写产出 / ai_self_review 读产出做自审 / human_review 展示对象。
/// TEXT NULL 向后兼容(老库行默认 NULL,TaskRecord 字段为 Option<String>),
/// 经通用 update_field 白名单写入(非 status 状态机收口字段,status 收口 F-03 不变)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15/v16 模式),对新库/老库均安全。
fn migrate_v17(conn: &Connection) -> Result<()> {
if !column_exists(conn, "tasks", "output_json") {
conn.execute("ALTER TABLE tasks ADD COLUMN output_json TEXT", [])?;
tracing::info!("v17: 补建 tasks.output_json 列(AiNode 自审闭环产出)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [17])?;
tracing::info!("迁移 v17 完成");
Ok(())
}
/// V18: 幂等补 ai_providers.model_configs 列(模型能力配置,F-01 阶段1)
///
/// 模型 4 维度(模态/能力/价格/智力)+ 路由控制配置 JSON 字符串。TEXT NULL 向后兼容:
/// 老库行默认 NULL,from_row 经 deserialize_model_configs 解析为空 Vec(配合 default_model 过渡)。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15/v16/v17 模式),对新库/老库均安全。
fn migrate_v18(conn: &Connection) -> Result<()> {
if !column_exists(conn, "ai_providers", "model_configs") {
conn.execute("ALTER TABLE ai_providers ADD COLUMN model_configs TEXT", [])?;
tracing::info!("v18: 补建 ai_providers.model_configs 列(模型能力配置)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [18])?;
tracing::info!("迁移 v18 完成");
Ok(())
}
/// V19: 幂等补 ai_providers.enabled + ai_providers.weight 列(F-260614-04 多 Provider 负载均衡池)
///
/// - `enabled INTEGER NOT NULL DEFAULT 1`:provider 是否进入负载均衡池。
/// 老库行迁移后默认 1(所有现存 provider 默认启用,单 provider 路径零变化)。
/// is_default 仍保留作启动兜底(get_active_provider 无 active_provider_id 时取 is_default)。
/// - `weight INTEGER NOT NULL DEFAULT 50`:provider 在池中的选择权重(0-100)。
/// 高权重 provider 优先被选为主;同权重时退化近似轮询。
///
/// 向后兼容:老库行 ALTER 后取 DEFAULT,from_row 经 i32→bool / i32→u32 解析。
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v17/v18 模式),对新库/老库均安全。
fn migrate_v19(conn: &Connection) -> Result<()> {
if !column_exists(conn, "ai_providers", "enabled") {
conn.execute(
"ALTER TABLE ai_providers ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1",
[],
)?;
tracing::info!("v19: 补建 ai_providers.enabled 列(多 Provider 负载均衡池,F-260614-04)");
}
if !column_exists(conn, "ai_providers", "weight") {
conn.execute(
"ALTER TABLE ai_providers ADD COLUMN weight INTEGER NOT NULL DEFAULT 50",
[],
)?;
tracing::info!("v19: 补建 ai_providers.weight 列(多 Provider 负载均衡池,F-260614-04)");
}
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [19])?;
tracing::info!("迁移 v19 完成");
Ok(())
}
/// V1 建表 SQL
const V1_SQL: &str = "
-- 想法表
CREATE TABLE IF NOT EXISTS ideas (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'draft',
priority INTEGER NOT NULL DEFAULT 1,
score REAL,
tags TEXT,
source TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- 项目表
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'planning',
idea_id TEXT REFERENCES ideas(id),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- 任务表
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'todo',
-- priority 默认 2 对齐 task.rs default_priority()=2(medium)
priority INTEGER NOT NULL DEFAULT 2,
branch_name TEXT,
assignee TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- 发布表
CREATE TABLE IF NOT EXISTS releases (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
version TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'planned',
task_ids TEXT NOT NULL DEFAULT '[]',
changelog TEXT,
created_at TEXT NOT NULL,
released_at TEXT
);
-- 工作流执行表
CREATE TABLE IF NOT EXISTS workflow_executions (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
dag_json TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
triggered_by TEXT,
created_at TEXT NOT NULL,
completed_at TEXT
);
-- 节点执行表
CREATE TABLE IF NOT EXISTS node_executions (
id TEXT PRIMARY KEY,
workflow_id TEXT NOT NULL REFERENCES workflow_executions(id),
node_id TEXT NOT NULL,
node_type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
input_json TEXT,
output_json TEXT,
error_message TEXT,
started_at TEXT,
completed_at TEXT
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_releases_project_id ON releases(project_id);
CREATE INDEX IF NOT EXISTS idx_node_executions_workflow_id ON node_executions(workflow_id);
";
/// V2 迁移 SQL — 补齐数据层断裂字段
///
/// 注意: SQLite 的 ALTER TABLE ADD COLUMN 一条语句只能加一列。
const V2_SQL: &str = "
-- 想法表: 晋升关联 + AI 分析 + 多维评分
ALTER TABLE ideas ADD COLUMN promoted_to TEXT;
ALTER TABLE ideas ADD COLUMN ai_analysis TEXT;
ALTER TABLE ideas ADD COLUMN scores TEXT;
-- 任务表: 工作流定义关联 + 基础分支
ALTER TABLE tasks ADD COLUMN workflow_def_id TEXT;
ALTER TABLE tasks ADD COLUMN base_branch TEXT;
-- 工作流执行表: 项目 / 任务关联
ALTER TABLE workflow_executions ADD COLUMN project_id TEXT;
ALTER TABLE workflow_executions ADD COLUMN task_id TEXT;
-- 分支表 — 任务与 Git 分支绑定(核心功能)
CREATE TABLE IF NOT EXISTS branches (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
task_id TEXT REFERENCES tasks(id),
name TEXT NOT NULL,
base TEXT NOT NULL DEFAULT 'main',
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
merged_at TEXT
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_branches_project_id ON branches(project_id);
CREATE INDEX IF NOT EXISTS idx_branches_task_id ON branches(task_id);
";
/// V3 迁移 SQL — AI 对话表补建(新库首次创建;老库 IF NOT EXISTS 跳过)
///
/// 注:archived 列不在此处 ALTER —— 由 v4 迁移幂等补建。
/// (历史 v3 曾写入版本号但 ALTER 未生效,统一交 v4 用 PRAGMA 探测修复)
const V3_SQL: &str = "
CREATE TABLE IF NOT EXISTS ai_conversations (
id TEXT PRIMARY KEY,
title TEXT,
messages TEXT NOT NULL DEFAULT '[]',
provider_id TEXT,
model TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
";
/// V7 建表 SQL — 知识库表
///
/// kind: 7 种 KnowledgeKind snake_case(review_rule/prompt_template/pitfall/
/// architecture_pattern/diagnosis/deployment_note/workflow_optimization)
/// status: candidate|pending_review|published|archived
/// confidence: high|medium|low(AI 提炼自评,可空)
/// verified: 发布审核时一次性人工标(INTEGER 0/1)
/// reuse_count: 检索命中自动 +1(唯一客观排序信号)
/// source_project/source_ref: 来源溯源(不过滤,仅展示)
const V7_SQL: &str = "
CREATE TABLE IF NOT EXISTS knowledges (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL DEFAULT 'pitfall',
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
tags TEXT,
status TEXT NOT NULL DEFAULT 'candidate',
confidence TEXT,
reuse_count INTEGER NOT NULL DEFAULT 0,
verified INTEGER NOT NULL DEFAULT 0,
source_project TEXT,
source_ref TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_knowledges_status ON knowledges(status);
CREATE INDEX IF NOT EXISTS idx_knowledges_kind ON knowledges(kind);
CREATE INDEX IF NOT EXISTS idx_knowledges_reuse_count ON knowledges(reuse_count DESC);
";
/// V9 建表 SQL — AI Provider 配置 + 工具执行审计
///
/// 历史遗漏补建:ai_providers(AI 提供商配置) + ai_tool_executions(工具调用审计记录)。
/// CREATE TABLE IF NOT EXISTS 保证老库(已有表)和新库(缺表)均安全。
const V9_SQL: &str = "
CREATE TABLE IF NOT EXISTS ai_providers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
provider_type TEXT NOT NULL DEFAULT 'openai_compat',
api_key TEXT NOT NULL,
base_url TEXT NOT NULL,
default_model TEXT NOT NULL,
models TEXT,
is_default INTEGER NOT NULL DEFAULT 0,
config TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
model_configs TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
weight INTEGER NOT NULL DEFAULT 50
);
CREATE TABLE IF NOT EXISTS ai_tool_executions (
id TEXT PRIMARY KEY,
conversation_id TEXT,
tool_call_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
arguments TEXT NOT NULL,
result TEXT,
status TEXT NOT NULL DEFAULT 'pending',
risk_level TEXT NOT NULL DEFAULT 'medium',
requested_at TEXT NOT NULL,
executed_at TEXT,
decided_by TEXT
);
";
/// V10 建表 SQL — 知识生命线事件表
///
/// 追加型审计表(只增不改),记录知识产生/审核/引用/归档四类事件,支撑生命线视图。
/// event_type: created | extracted | status_changed | referenced | archived
/// context_json: 因 event_type 而异的上下文(如引用事件的 conv_id+query)。
const V10_SQL: &str = "
CREATE TABLE IF NOT EXISTS knowledge_events (
id TEXT PRIMARY KEY,
knowledge_id TEXT NOT NULL,
event_type TEXT NOT NULL,
source_ref TEXT,
context_json TEXT,
timestamp TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_knowledge_events_kid ON knowledge_events(knowledge_id);
CREATE INDEX IF NOT EXISTS idx_knowledge_events_type ON knowledge_events(event_type);
CREATE INDEX IF NOT EXISTS idx_knowledge_events_kid_type ON knowledge_events(knowledge_id, event_type);
";
/// V13 建表 SQL — 通用应用设置 KV 表
///
/// 前端 localStorage 迁移目标:key/value(JSON 字符串)+ updated_at。
/// CREATE TABLE IF NOT EXISTS 幂等(新库建、老库已有则跳过)。
const V13_SQL: &str = "
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
";