Skip to Content
核心概念軟刪除與歷史版本

軟刪除、回收桶與版本歷史

自訂資料表把「刪除後可復原」與「回到過去某一版」分成兩套功能。回收桶改變物件是否為 live;版本還原則把舊快照寫成一個新版本。兩者都保留稽核軌跡,也都必須重新通過目前的權限與規則。第三條流程——永久 purge——是這兩者的反面:它不可逆地銷毀一張表或整個 tag 系統(連同歷史),由下文的 preview 加 ticket 同意閘把關。

表的回收桶

管理者刪除表時,DELETE .../tables/{table_id} 會把整張表移入 trash,而不是立即清掉 schema、資料列、設定與 history。管理 UI 可用:

GET .../tables/trash?skip=0&limit=20 POST .../tables/{table_id}/restore

列出 trash 是範圍層級操作;restore 則用一張已刪除表的 ID。表在 active migration lock 中不能刪除。不可逆的資料清除是一條刻意更重的獨立流程——下面的兩步驟 purge——因此一般產品 UI 應把「移到回收桶」與「永久清除」用完全不同的文案與確認層級呈現。

永久 purge:preview、ticket、execute

自 2026-07-28 版起,永久刪除是租戶操作,不再只屬於 root 操作員。同意閘不是保留天數,而是兩步驟流程

POST .../tables/{table_id}/purge/preview → 爆炸半徑 + 一次性 ticket POST .../tables/{table_id}/purge?ticket=… → 燒掉 ticket、銷毀資料表

preview 會算出 purge 將碰到的一切——現存資料列數、外部活表會被剝除的連結欄位(以顯示名稱列出)與被剝除的 rules 數量、saved views、IaC state rows、將被刪除或變成殭屍的 commands——blockers 為空時發出 ticket:綁定呼叫者、效期 15 分鐘、只夠一次執行嘗試。把這份回應原樣渲染成確認畫面;preview 與實際 cascade 共用同一套實作,畫面不可能與執行結果漂移。

execute 無論成功或被拒都會燒掉 ticket。preview 之後只要結構有變——表被還原或改名、command 增減、tag 掛載變動——回應就是 409error: "purge_preview_stale":重新 preview、重新核准你現在看到的內容。指紋刻意只鎖結構;preview 與 execute 之間寫入的資料列會跟著表一起銷毀,因為活表若連資料一起鎖,每次 execute 都必然 stale。

UI 上要緊的細節:

  • **活表直接 purge。**不需要先進垃圾桶;ticket 流程本身就是同意。垃圾桶裡的表也能 purge——這會釋放它占用的名字,光待在垃圾桶則會一直占著。
  • 誰能做:單表 lane 要求資料表 moderator(活表與垃圾桶中的表都解析)。tag lane——POST .../table-tags/{tag_id}/purge/preview.../purge——一次銷毀所有成員表、該 tag 的全部 commands(名字立即釋放)、其 IaC state rows 與 tag 本身,因此要求 scope-manager 權威,不是一般 tag 刪除那道門檻。
  • **掛多個 tag 的成員會擋下。**成員表同時掛著別的 tag 就是 table_in_other_tags blocker:不發 ticket,execute 時還會再驗一次——A 系統的 purge 永遠不能連坐 B 系統的成員。另一種 blocker 是 command_referenced_by_trigger:tag 的 command 仍被系統之外某張表的觸發器呼叫時擋下;將被 purge 的成員表自己的引用不會擋。
  • **部分失敗是持久事實。**tag lane 裡每張成員表的 cascade 獨立 commit。中途失敗時,purge_partial_failure 的內容會精確列出哪些表已經消失——把那份清單當事實,對剩餘系統重新 preview。
  • root 操作員 purge(POST /root/custom-tables/{table_id}/purge,含 force)原樣保留,仍是操作員內部工具。

資料列的 trash/restore

DELETE .../records/{record_id} 會把資料列標為 deleted;一般 list/search/get 不再回傳它。回應中的 incoming_links_removed 告訴你刪除時移除了多少反向連結。

有 edit 權限的呼叫者可檢查與還原自己在該權限切片中看得到的已刪除列:

GET .../records/deleted?skip=0&limit=20 GET .../records/deleted/numOfData POST .../records/{record_id}/restore

Restore 不是把舊 JSON 不經檢查塞回去。伺服器會以目前 schema重新驗證;欄位已移除、型別改變或 required 條件不再成立時可能回 409 conflicts。成功後 is_deleted 變回 false、排序位置重新建立,且 version 再加一,而不是回退版本號。刪除時解除的 link 不應假設會自動重建。

重新建立的排序位置常常不是這一列原本的位置。刪除資料列時會把 sort_order 設成 NULL,並把舊值收進 deleted_sort_order;restore 只有在沒有任何資料列正佔著那個槽位時才會拿回原位,否則就取 (全表每一列的 MAX(sort_order)) + 1。因為刪除已經把那個整數釋放出去,之後的 insert 完全可能合法地拿走它——所以除非你重新讀一次,否則就把還原後的位置當成「被接在最後面」。這個 fallback 的 MAX 刻意也涵蓋已刪除的資料列:在槽位還沒被設成 NULL 的年代被 soft delete 的舊資料列仍然佔著一個,還原時落在它上面,才是 restore 不會撞上 uq_table_sort_order unique key 的原因。Constraint 與 insert 端的槽位運算見 sort_order 槽位

權限與審批仍然生效

Trash 列表需要 edit ACL;restore 也重新檢查 edit row policy。若 require_approval 規則涵蓋 restore,呼叫會先建立 staged change,live row 仍維持 deleted,直到核准。

版本是只增不減的時間線

每次 create、update、delete、restore 或 revert 都會留下歷史。可從兩個角度讀取:

  • GET .../tables/{table_id}/history:整表 audit log,包含資料列與 schema 變更,可依 change_typerecord_id 篩選。
  • GET .../records/{record_id}/history:單列版本,由新到舊分頁。
  • GET .../history/{version}:一版完整 stored-data snapshot。
  • GET .../history/{version}/diff?compare_to={other_version}:兩版欄位級 diff。

History 的 datadiff 使用內部 col_<hex> 鍵,讓改名後的稽核仍能對到同一欄。UI 要拿目前表的 column_mapping 顯示欄名;若欄位已刪除,保留內部鍵作為誠實的歷史識別,不要猜一個新名稱。

{ "from_version": 3, "to_version": 1, "diff": { "col_a1111111_1111_4111_8111_111111111111": { "old": "已出貨", "new": "草稿" } } }

Version restore 會建立新版

POST .../records/{record_id}/history/{version}/restore 不會刪掉目標版本之後的歷史,也不會把版本計數器倒轉。它把指定版的 stored scalar data 套用到目前列,新增一筆 change_type: "revert" 的 history entry:

{ "restored_from_version": 1, "new_version": 4, "links_not_restored": true, "record": { "version": 4, "data": { "狀態": "草稿" } } }

Link 狀態不屬於 scalar snapshot,因此不會跟著歷史版本回復;請檢查 links_not_restored 並讓使用者重新確認連結。伺服器也會對照目前 schema,若舊版包含已移除/不相容欄位,以 409 conflicts 拒絕。

Version restore 本身是一種寫入,會套用 edit ACL、write rules、migration lock 與 require_approval。命中審批時回 approval_required,舊版尚未進入 live row;核准後才產生新的 revert 版本。流程見審批總覽

UI 建議

把四個動作分開命名:

  • 從回收桶復原:讓 deleted 物件重新變 live。
  • 檢視版本/比較差異:唯讀稽核,不改資料。
  • 還原為此版本:新增一版,可能需要審批,link 不還原。
  • 永久 purge:兩步驟 preview → ticket → execute;把 preview 的爆炸半徑原樣渲染成確認畫面,用你最重的確認文案——之後沒有任何東西可以復原。

完整回應與錯誤見回收桶參考歷史參考資料表參考;逐步 UI 流程見歷史還原指南

Last updated on