ADR 0003:内容定位 —— 一行 locator_json 与 ord
日期:2026-09-17 · 状态:已采纳
背景
M3 要把漫画(mokuro)、EPUB 轻小说与流媒体接进来。现有模型是流式的:Source → CaptureSession → Line, 一条台词的定位信息散在三处:
start_ms/end_ms:字幕与音频用;position_json:OCR 截屏在屏幕上的框;- 顺序:默认靠
captured_at与自增id。
漫画需要「第几页 + 页面上的框」,EPUB 需要「第几章 + 章内偏移」,两者在模型里都没有位置。照 「一种格式加两列」的路子,Line 会长出 page_no、chapter_id、char_offset、cfi……每种内容 只用得上其中一小撮,而每次导入都要迁移一次表。
决策
Line 增加两个可空列:
locator_json(Text):{"kind": "time" | "page" | "offset" | …, …},kind必填,其余字段随kind而定;ord(Integer,索引):作品内的稳定顺序,流式内容用采集顺序,文档式内容用页/章顺序。
老的 start_ms / end_ms / position_json 保留不动,新代码写 locator_json;schemas/lines.line_locator() 提供兼容读取:没有 locator_json 时从旧字段合成,保证老数据照样能排序与渲染。
理由
- 加列不可持续。 每接一种内容就要迁移一次表,且列在不同内容间互斥,读的代码到处
if。 - 多态表过度。 单独一张
locators表要 join、要区分来源,而定位信息永远与行一一对应、 读写都跟着行走;SQLite 下没有把行拆开的理由。 - JSON 合适。 定位结构随内容格式演化,查询需求弱(只按
kind判读、按ord排序), SQLite 的 JSON1 也足以在需要时按内部字段过滤。格式继续增加时不需要再动 schema。 ord是排序问题的正解。 导入一本书时所有行的时间戳几乎相同,captured_at不再是顺序;ord让「作品内顺序」显式化,流式与文档式共用一种表达。
代价与边界
- 无法对 locator 内部字段建索引。 需要按页/章查询时,先用
ord范围或额外列再过滤; 若真的出现高频的「按页查」,那时再加具体列或生成列,而不是现在预判。 - 弱类型。
kind与字段的合法性只有约定的校验函数保证,数据库不管。 - 兼容期同时存在两套读法;等所有写入方迁移完(字幕、OCR、漫画、EPUB)后,旧字段可另开 issue 讨论弃用。
