-- 0007 — record 有身分，關係是唯一機制（樹狀 record 模型第一刀）
-- 規格：system-dev/docs/3-specs/pending-changes.md「record 要有身分」v7 定稿（leo 2026-08-15 confirm）
--
-- 模型一句話（leo 定案）：「真身在 pool 的 entry 裡，所有的虛擬表虛擬欄位都是指向這個 entry 的指標。」
--   · record ＝ 池中一顆有身分的 entry（沿用原 record_id 字串當 id，既有引用不失效）
--   · 欄位是關係、歸屬也是關係——同一種機制：一列關係 ＝ src／rel／dst 三個型別化指標欄
--     （「池上型別化指標欄」，parent_id 是這族欄位的既有先例。紅線：不准用關係實作關係、
--       不准把指標塞回 content／JSON——那是 D91 的位置換個門進來）
--   · 舊 entry_values 四欄的下場：record_id → record entry 自己的 id／entry_id → 指標終點（dst）
--     ／slot_name → 指標謂詞（rel ＝ field entry）／template_id → 一條屬於關係（rel ＝ sys_belongs）
--   · 那張表該死的理由：它是「關係」的第二套實作——同一件事兩個實作必然漂移（D92）
--
-- 資料遷移形狀（v7 §3）：既有 value entries 一列都不動（id 穩定、向量索引不失效）、
--   全部純 INSERT、零 UPDATE。以舊儲存格列 id 衍生關係列 id ⇒ INSERT OR IGNORE 天然冪等。
--
-- 冪等設計（三條套用路徑：官方 wrangler migrations／cli deploy.ts 每次重跑／安裝器逐句重放）：
--   · 除了三句 ADD COLUMN 之外，每一句都是語句級冪等（IF NOT EXISTS／INSERT OR IGNORE）
--   · SQLite 沒有「ADD COLUMN IF NOT EXISTS」——重跑時那三句會報 duplicate column，
--     套用端必須把「duplicate column」視為已套用（cli deploy.ts 的 applyD1MigrationTolerant、
--     安裝器逐句 try/catch 本就容錯）。官方路徑走 wrangler migrations 追蹤表，只跑一次。
--   · 不走「重建整張 entries」的做法：那雖然能純語句冪等，但 deploy.ts 每次部署都重跑
--     migration ⇒ 每次部署全表複製一輪，直接吃掉 D1 每日列寫入額度。
--   · entry_values 拆表用 0006 的「墊表、搬、拆」手法：重跑時先墊一份空殼，搬 0 筆，再拆，無害。
--
-- 施工窗口注意：本檔跑完 entry_values 就不存在了，必須與讀寫端改版（kbdb/src 同一批）一起部署。
--   舊 worker 碰新庫會炸 entry_values 不存在——這是刻意的（leo：「不做長期雙讀相容層，一次翻」）。

-- ============================================================
-- 1. 關係的物理載體：池上型別化指標欄 + 一對方向索引
-- ============================================================

ALTER TABLE entries ADD COLUMN src_id TEXT; -- kbdb-sql-ok: 牆內 migration 本體（v7 定案載體「池上型別化指標欄」），重跑由套用端容錯 duplicate column
ALTER TABLE entries ADD COLUMN rel_id TEXT; -- kbdb-sql-ok: 同上
ALTER TABLE entries ADD COLUMN dst_id TEXT; -- kbdb-sql-ok: 同上

-- 一對方向索引（v7 §2-1）：src 端服務「r1 的所有欄位」，(dst,rel) 端服務
-- 「某張 sheet 的所有 record」（rel=屬於, dst=sheet）與「誰指到 e1」（dst=e1）。
-- partial index：內容 entry（指標欄全 NULL）不進索引，索引大小 ≈ 關係列數。
CREATE INDEX IF NOT EXISTS idx_entries_rel_src ON entries(src_id) WHERE src_id IS NOT NULL; -- kbdb-sql-ok: 牆內 migration 本體，普通欄位索引（0001 十條索引的同族）
CREATE INDEX IF NOT EXISTS idx_entries_rel_dst ON entries(dst_id, rel_id) WHERE dst_id IS NOT NULL; -- kbdb-sql-ok: 同上

-- ============================================================
-- 2. 啟動常數（v7 §2-3：一組、極小、只讀）
-- ============================================================

-- 保留根：屬於鏈的終點。某顆 entry ─屬於→ sys_root ＝ 它是一張 sheet。
INSERT OR IGNORE INTO entries (id, content, entry_type, owner_id) VALUES ('sys_root', 'root', 'system', NULL);
-- 「屬於」謂詞：record ─屬於→ sheet（歸屬關係的 rel）。
INSERT OR IGNORE INTO entries (id, content, entry_type, owner_id) VALUES ('sys_belongs', 'belongs', 'system', NULL);
-- 「欄位屬於表」謂詞：field ─field_of→ sheet（sheet 的欄位名冊，與 record 歸屬分開，
-- 免得「某張 sheet 的所有 record」把欄位也撈進來）。
INSERT OR IGNORE INTO entries (id, content, entry_type, owner_id) VALUES ('sys_field_of', 'field_of', 'system', NULL);

-- ============================================================
-- 3. 墊表（0006 手法）：本檔尾端會拆掉 entry_values，重跑時先墊空殼
--    讓下面的搬遷語句永遠合法（搬 0 筆），最後再拆一次。
-- ============================================================

CREATE TABLE IF NOT EXISTS entry_values ( -- kbdb-sql-ok: 表退場施工步驟①保底存在（0006 同款），非資料存取違規
  id TEXT PRIMARY KEY,
  record_id TEXT NOT NULL,
  template_id TEXT NOT NULL,
  slot_name TEXT NOT NULL,
  entry_id TEXT NOT NULL,
  created_at INTEGER DEFAULT (unixepoch()),
  UNIQUE(record_id, slot_name)
);

-- ============================================================
-- 4. sheet／field 落池（templates 表遷移期雙軌：欄位定義的真相源暫仍在 templates 表，
--    sheet/field entry 是它在池中的身分——第二刀把 templates 表整個退役）
-- ============================================================

-- 每個 template 一顆 sheet entry（id 沿用 template id，既有引用不失效）
INSERT OR IGNORE INTO entries (id, content, entry_type, owner_id, created_at, updated_at)
SELECT t.id, t.name, 'sheet', NULL, t.created_at, t.updated_at FROM templates t;

-- sheet ─屬於→ 保留根（「什麼讓一顆 entry 成為 sheet」的答案：這條關係）
INSERT OR IGNORE INTO entries (id, entry_type, src_id, rel_id, dst_id)
SELECT 'relb_' || t.id, 'relation', t.id, 'sys_belongs', 'sys_root' FROM templates t;

-- 每個 slot 一顆 field entry（id 決定性衍生：fld_<template>_<slot>，欄名＝關係的謂詞）
INSERT OR IGNORE INTO entries (id, content, entry_type)
SELECT 'fld_' || t.id || '_' || j.value, j.value, 'field'
FROM templates t, json_each(t.slots_json) j;

-- field ─field_of→ sheet（欄位名冊）
INSERT OR IGNORE INTO entries (id, entry_type, src_id, rel_id, dst_id)
SELECT 'relf_' || t.id || '_' || j.value, 'relation', 'fld_' || t.id || '_' || j.value, 'sys_field_of', t.id
FROM templates t, json_each(t.slots_json) j;

-- 保險網：entry_values 裡實際用過、但 slots_json 沒宣告的 slot（宣告與實作漂移的實證），
-- 一樣補出 field entry 與名冊，資料一筆都不能掉。
INSERT OR IGNORE INTO entries (id, content, entry_type)
SELECT DISTINCT 'fld_' || ev.template_id || '_' || ev.slot_name, ev.slot_name, 'field' FROM entry_values ev;

INSERT OR IGNORE INTO entries (id, entry_type, src_id, rel_id, dst_id)
SELECT DISTINCT 'relf_' || ev.template_id || '_' || ev.slot_name, 'relation',
       'fld_' || ev.template_id || '_' || ev.slot_name, 'sys_field_of', ev.template_id
FROM entry_values ev;

-- ============================================================
-- 5. record 有身分：每筆 record 一顆池中 entry（id ＝ 原 record_id 字串）
--    歸屬 owner 沿用「第一個非 NULL 的 slot entry owner」——與舊讀端的推導逐字同義。
--    INSERT OR IGNORE 的另一層意義：record_id 已經是池中既有 entry（library_map 的
--    block 即 record 慣例）時，那顆 entry 本人就是身分，不另建。
-- ============================================================

INSERT OR IGNORE INTO entries (id, entry_type, owner_id, created_at, updated_at)
SELECT ev.record_id, 'record',
       (SELECT e2.owner_id FROM entry_values ev2 JOIN entries e2 ON ev2.entry_id = e2.id
         WHERE ev2.record_id = ev.record_id AND e2.owner_id IS NOT NULL LIMIT 1),
       MIN(ev.created_at), MIN(ev.created_at)
FROM entry_values ev GROUP BY ev.record_id;

-- record ─屬於→ sheet（舊 template_id 欄的下場：變成一條關係）
INSERT OR IGNORE INTO entries (id, entry_type, owner_id, src_id, rel_id, dst_id, created_at)
SELECT 'relb_' || ev.record_id || '_' || ev.template_id, 'relation',
       (SELECT e2.owner_id FROM entry_values ev2 JOIN entries e2 ON ev2.entry_id = e2.id
         WHERE ev2.record_id = ev.record_id AND e2.owner_id IS NOT NULL LIMIT 1),
       ev.record_id, 'sys_belongs', ev.template_id, MIN(ev.created_at)
FROM entry_values ev GROUP BY ev.record_id, ev.template_id;

-- ============================================================
-- 6. 每個儲存格 → 一條關係列（src=record、rel=欄位謂詞、dst=原 value entry）
--    關係列 id 以舊儲存格列 id 衍生（relv_<ev.id>）⇒ 重跑天然冪等。
-- ============================================================

INSERT OR IGNORE INTO entries (id, entry_type, owner_id, src_id, rel_id, dst_id, created_at)
SELECT 'relv_' || ev.id, 'relation', e.owner_id,
       ev.record_id, 'fld_' || ev.template_id || '_' || ev.slot_name, ev.entry_id, ev.created_at
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id;

-- ============================================================
-- 7. 拆表（本提案的完成證明：關係的第二套實作不再有位置）
-- ============================================================

DROP TABLE IF EXISTS entry_values; -- kbdb-sql-ok: 表退場施工（0006 同款），v7 定案「兩套機制合成一套」的落地
