主题
跨端 vocabulary 表 Schema 对照
版本: Schema v51 最后更新: 2026-05-13 关联: docs/plans/cefr-llm-evaluation-and-ui-passthrough.md 原则 4
设计原则
原则 4:三端 vocabulary schema 一致性优先(接受有意冗余)
RVH (SQLite) / Supabase (PostgreSQL) / RB (SQLite via Rust) 三端共享同一份 vocabulary 表结构。字段集合保持一致,即使某字段在某端无业务意义。 加字段:三端同步加;删字段:三端同步删;类型差异由各端 DB 类型系统决定。
三端列集合对照(v51,17 列)
| # | 列名 | RVH SQLite | Supabase PostgreSQL | RB SQLite | 备注 |
|---|---|---|---|---|---|
| 1 | word | TEXT PK COLLATE NOCASE | TEXT PK | TEXT PK | RVH COLLATE 是大小写护栏;应用层 lemmatizer 已归一 |
| 2 | ipa_pronunciation | TEXT | TEXT | TEXT | IPA 音标 |
| 3 | pronunciation_url | TEXT | TEXT | TEXT | 音频 URL |
| 4 | primary_cefr_level | TEXT | TEXT | TEXT | 'A1'..'C2' / NULL |
| 5 | cefr_inferred 🆕v48 | INTEGER NOT NULL DEFAULT 0 | BOOLEAN NOT NULL DEFAULT false | INTEGER NOT NULL DEFAULT 0 | 推断 vs 权威 |
| 6 | cefr_source 🆕v49 | TEXT | TEXT | TEXT | 'oxford'/'cefr_j'/'llm'/'freq'/'fallback'/NULL |
| 7 | word_tags 🆕v50 | TEXT | TEXT | TEXT | JSON array 字符串 |
| 8 | pos_definitions | TEXT (JSON 字符串) | JSONB | TEXT (JSON 字符串) | 多词性结构化数据 |
| 9 | frequency_rank | INTEGER | INTEGER | INTEGER | 词频排名 |
| 10 | source | TEXT NOT NULL DEFAULT 'preinstalled' | TEXT NOT NULL DEFAULT 'preinstalled' | TEXT NOT NULL DEFAULT 'preinstalled' | 'preinstalled'/'backfilled'/'fallback' |
| 11 | last_accessed_at | TEXT (ISO8601) | TIMESTAMPTZ | TEXT (ISO8601) | LRU 时间戳 |
| 12 | etymology | TEXT | TEXT | TEXT | 词源描述 |
| 13 | word_forms | TEXT (JSON 字符串) | TEXT | TEXT (JSON 字符串) | 词形变化 |
| 14 | audio_local_path | TEXT | TEXT | TEXT | 本地音频路径(跨端无业务意义,但保持 schema 一致) |
| 15 | word_family | TEXT (JSON 字符串) | TEXT | TEXT (JSON 字符串) | 词族数组(Phase 5 填充) |
| 16 | created_at | TEXT NOT NULL (ISO8601) | TIMESTAMPTZ NOT NULL DEFAULT NOW() | TEXT NOT NULL (ISO8601) | 创建时间 |
| 17 | updated_at | TEXT NOT NULL (ISO8601) | TIMESTAMPTZ NOT NULL DEFAULT NOW() | TEXT NOT NULL (ISO8601) | 更新时间 |
预装库专属列(红线 #10)— 仅 RVH/RB SQLite,不进 Supabase
以下列由 RVH pipeline 单点产出进预装库 reading_vocab.db,RB byte-equal 只读消费, 不在上方 Supabase 同步矩阵(vocabulary 表跨端策略:Supabase 仅当共享缓存,这些是预装内禀数据):
| 列名 | RVH SQLite | RB SQLite | Supabase | 备注 |
|---|---|---|---|---|
emoji 🆕v53 | TEXT | TEXT | ❌ 无 | 具象名词 OpenMoji hexcode / NULL |
canonical_surface 🆕v54 | TEXT | TEXT | ❌ 无 | 习语可读引用形(归一键 word 的展示形)/ NULL=普通词。匹配只走 word,本列纯展示 |
类型差异(DB 系统能力差异,非 schema 不一致)
PostgreSQL 比 SQLite 类型更丰富,差异由 SDK / 应用层自动处理:
| Supabase 类型 | RVH/RB SQLite 等价 | 转换方 |
|---|---|---|
BOOLEAN | INTEGER (0/1) | supabase-flutter 自动转 bool;写时接受 1/0 或 true/false |
JSONB | TEXT (JSON 字符串) | SDK 自动转 Map/List;写时接受 JSON 字符串或 Object |
TIMESTAMPTZ | TEXT (ISO8601) | 应用层处理 .toIso8601String() ↔ DateTime.parse() |
RVH 端代码已防御类型差异(supabase_vocabulary_service.dart):
dart
// 读 cefr_inferred — 同时处理 PostgreSQL BOOLEAN (转 num) 和 SQLite INT
cefrInferred: ((data['cefr_inferred'] as num?)?.toInt() ?? 0) == 1FK 关系跨端对照(v51 起 RESTRICT 化)
| FK 关系 | RVH 端 | Supabase 端 | RB 端 | ON DELETE |
|---|---|---|---|---|
notebook_entries.word → vocabulary.word | ✓ | ✓ (user_notebook_entries) | ✓ | RESTRICT 🔧v51 (原 CASCADE) |
excluded_words.word → vocabulary.word | ❌ v43 已删 | ✓ (user_excluded_words) | TBD | RESTRICT 🔧v51 (Supabase 端) |
保留 CASCADE 的关系(业务合理):
reading_sources.note_id → reading_notes.idword_sources.notebook_entry_id → notebook_entries.idword_sources.reading_source_id → reading_sources.id
跨端 sync 不同步的字段
虽然 schema 含这些字段,但 sync 流程显式 NULL 化(避免一端的本地状态污染另一端):
| 字段 | 说明 |
|---|---|
audio_local_path | RVH 本地音频路径,跨端无意义 |
last_accessed_at | LRU 仅本地行为,跨端无意义 |
改动 vocabulary schema 的强制流程
加 / 删 / 改任何 vocabulary 列时必须三端同步改动:
RVH 端(本仓库):
- 改
assets/sql/01_create_tables.sql - 升
lib/shared/data/database/app_database.dart::_schemaVersion - 升
assets/sql/0[123]_*.sql文件头版本号 - 改
docs/database/schema.md顶部版本 + 加变更摘要 - 改本文件「v51」处对照表
- 改
lib/features/vocabulary_filtering/data/models/vocabulary_item_model.dart(Freezed)+vocabulary_entity.dart(Equatable + copyWith + props) - 改
lib/features/vocabulary/data/services/supabase_vocabulary_service.dart(PostgREST 读取) - 改
tools/vocabulary_builder_v3/lib/models/vocabulary_entry.dart+ pipeline 各 step 的 JSON 读写 - 改
tools/vocabulary_builder_v3/lib/db_generator.dartCREATE TABLE + INSERT - 跑
dart run build_runner build --delete-conflicting-outputs
- 改
Supabase 端(后端真相源 = RB,2026-06-15 起):
- 共享表 DDL 改 RB
~/reading-browser/supabase/sql/sync-tables.sql+ Supabase Dashboard 执行(RVH 不再写共享表 migration;原supabase/migrations/死历史目录 2026-08-28 已删,见 git 历史) - Edge Function(含
lookup-or-fetch-word)源码在 RB~/reading-browser/supabase/functions/,如需用新字段在 RB 改 + 从 RB 部署
- 共享表 DDL 改 RB
RB 端(
~/reading-browser/,外部仓库):- 改 RB 的 SQLite schema(Rust
src-tauri/src/db/migrations/) - 改 RB 的 vocabulary 数据模型
- 改 RB 的 sync push/pull
- 三端同周发版(RVH + RB + Supabase migration)
- 改 RB 的 SQLite schema(Rust
历史变更追溯
| 版本 | 变更 | RVH commit | Supabase migration |
|---|---|---|---|
| v34 (2026-03) | vocabulary 瘦身(删 9 字段)+ pos_definitions 引入 | - | 20260321_v34_slim_vocabulary_items.sql |
| v43 (2026-04) | word 升 PK + 跨端 UUID 漂移消除 | 4a8f... | 20260426_v43_word_pk_rebuild.sql |
| v47 (2026-05) | reading_notes 封面字节直传(红线 #6c) | dfff432 | - |
| v48 (2026-05) | 加 cefr_inferred 列 | bbedeab | (合并入 v51) |
| v49 (2026-05) | 加 cefr_source 列 | bbedeab | (合并入 v51) |
| v50 (2026-05) | 加 word_tags 列 | 3722fab | (合并入 v51) |
| v51 (2026-05-13) | Supabase 重建 + FK CASCADE→RESTRICT + Edge Function 升级 | 8a471a6 | 20260513_v51_vocabulary_rebuild_fk_restrict.sql |
| 2026-05-21 修订 | DROP user_notebook_entries_word_fkey + user_excluded_words_word_fkey(缓冲池模型下 Supabase 两条 word FK 无业务价值;RB 端 push 撞 23503 触发) | (本批 commit) | 20260521_drop_user_word_fkeys.sql |