Migrate scan store to shared entities
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# Entity-Based Store Migration Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将当前按 run 复制的 SQLite 存储改成按文件实体去重存储,并支持把旧库迁移到新模型,保留最有价值的检测结果。
|
||||
|
||||
**Architecture:** 保留 `scan_runs` 作为扫描批次概念,引入 `file_entities` 作为跨 run 共享的唯一文件实体,引入 `run_files` 作为“某次扫描里看到了哪个实体”的成员关系。`stage1_results` / `stage2_results` 改为按 `entity_id` 存储,旧 schema 在 `initialize()` 时自动迁移并去重。
|
||||
|
||||
**Tech Stack:** Python 3.12+, sqlite3, typer, orjson, pytest
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 写迁移与实体存储失败测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/test_resume_logic.py`
|
||||
- Modify: `tests/test_cli.py`
|
||||
|
||||
**Step 1: 写旧 schema 迁移后实体去重的失败测试**
|
||||
|
||||
- 构造旧版 `files/stage1_results/stage2_results` schema
|
||||
- 插入两个 run 中同一个 `path+size+mtime` 的重复记录
|
||||
- 调用 `ScanStore.initialize()`
|
||||
- 断言新 schema 存在,实体表只有 1 条,共享的 `stage1_results/stage2_results` 也只有 1 条
|
||||
|
||||
**Step 2: 写新 schema 下复用结果不再复制 `stage1_results` 的失败测试**
|
||||
|
||||
- 创建两个 run,插入同一个实体
|
||||
- 为第一个 run 写入 stage1 结果
|
||||
- 第二个 run 复用后断言 `run_files` 有 2 条,但 `stage1_results` 仍只有 1 条
|
||||
|
||||
**Step 3: 写 `compact-db` 会清理孤儿实体/结果的失败测试**
|
||||
|
||||
- 造两个重复 run
|
||||
- 执行压缩后断言被删除 run 对应的 `run_files` 消失
|
||||
- 如果某实体已无任何 run 引用,则其 `file_entities/stage1_results/stage2_results` 被一并清掉
|
||||
|
||||
**Step 4: 跑聚焦测试确认失败**
|
||||
|
||||
Run: `pytest tests/test_resume_logic.py tests/test_cli.py -k "entity or migrate or compact" -q`
|
||||
Expected: FAIL,失败点落在缺失的新 schema / 新行为
|
||||
|
||||
### Task 2: 引入新 schema 与兼容模型
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/tgvideo_check/models.py`
|
||||
- Modify: `src/tgvideo_check/store.py`
|
||||
|
||||
**Step 1: 定义新的实体/运行时数据模型**
|
||||
|
||||
- 在 `models.py` 中补充实体级字段映射所需结构
|
||||
- 保持 `StoredFile` 对上层调用接口尽量稳定,`file_id` 继续表示 run 级成员记录 ID
|
||||
|
||||
**Step 2: 在 `store.py.initialize()` 中创建新 schema**
|
||||
|
||||
- 新建 `file_entities`
|
||||
- 新建 `run_files`
|
||||
- 将 `stage1_results/stage2_results` 改为 `entity_id` 主键
|
||||
- 为实体身份、run 顺序、run 路径建立索引
|
||||
|
||||
**Step 3: 在 `initialize()` 中探测并迁移旧 schema**
|
||||
|
||||
- 如果仍存在旧版 `files` 表结构,则迁移到新表
|
||||
- 迁移后删除旧表,避免双份存储
|
||||
- 对重复实体只保留一份 stage 结果,优先保留“最新且信息更完整”的记录
|
||||
|
||||
### Task 3: 重写存储读写路径
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/tgvideo_check/store.py`
|
||||
|
||||
**Step 1: 重写 `upsert_files()`**
|
||||
|
||||
- 先按 `path+size+mtime+extension` 获取或创建实体
|
||||
- 再按 `run_id+path` 写入/更新 `run_files`
|
||||
- 复用历史状态时只复用 run 级状态字段,不再复制 stage 结果表
|
||||
|
||||
**Step 2: 重写 stage1/stage2 结果写入**
|
||||
|
||||
- `record_stage1_result()` 通过 `run_file_id -> entity_id` 写实体结果
|
||||
- `record_stage2_result()` 同理
|
||||
- 同一个实体跨 run 只保留一条 stage 结果
|
||||
|
||||
**Step 3: 重写读路径**
|
||||
|
||||
- `list_files()`
|
||||
- `list_stage1_debug_rows()`
|
||||
- `get_stage1_stdout_text()`
|
||||
- `compact_duplicate_runs()`
|
||||
|
||||
### Task 4: 调整 CLI 与压缩行为
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/tgvideo_check/cli.py`
|
||||
- Modify: `README.md`
|
||||
|
||||
**Step 1: 保持现有 CLI 输出兼容**
|
||||
|
||||
- `scan`
|
||||
- `verify-stage2`
|
||||
- `inspect-stage1`
|
||||
- `compact-db`
|
||||
|
||||
**Step 2: 让 `compact-db` 在删除 run 后清理孤儿实体与孤儿 stage 结果**
|
||||
|
||||
- 压缩后再执行 `VACUUM`
|
||||
- 输出删除的 run 数、run_files 数、实体数、stage 结果数
|
||||
|
||||
**Step 3: 文档更新**
|
||||
|
||||
- 说明数据库已改为实体去重存储
|
||||
- 说明旧库会自动迁移
|
||||
- 说明 `compact-db` 现在还会清孤儿实体
|
||||
|
||||
### Task 5: 验证
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/test_resume_logic.py`
|
||||
- Test: `tests/test_cli.py`
|
||||
- Test: `tests/test_integration_resume.py`
|
||||
|
||||
**Step 1: 跑新增聚焦测试**
|
||||
|
||||
Run: `pytest tests/test_resume_logic.py tests/test_cli.py -k "entity or migrate or compact" -q`
|
||||
|
||||
**Step 2: 跑 resume 相关回归**
|
||||
|
||||
Run: `pytest tests/test_resume_logic.py tests/test_integration_resume.py tests/test_cli.py -k "resume or compact" -q`
|
||||
|
||||
**Step 3: 若环境允许,再跑更大范围回归**
|
||||
|
||||
Run: `pytest -q`
|
||||
|
||||
Reference in New Issue
Block a user