先核來源與已訂規則
本例向前步驟把 1/A、2/B 合成 1/Group,再刪原 SQLite 表。目標已沒有兩原行。已准精確資料回復要用保留原資料;未留就暫停,不虛構標籤。
| 紀錄 | 輸入 JSON |
|---|---|
| 原例輸入 | {"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true} |
比較兩條實際路徑
更正從真正原資料表還原 1/A、2/B。只把目標拷回,仍只有 Group,沒有補回已丟資料。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"Group"}]} |
| 更正路徑 | {"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]} |
import {DatabaseSync} from 'node:sqlite';
import {TextDecoder} from 'node:util';
import {inflateSync} from 'node:zlib';
import {posix} from 'node:path';
import {createHash} from 'node:crypto';
const rows=(db,sql)=>db.prepare(sql).all().map(r=>({...r}));
const failed=(action)=>{try{action();return null;}catch(e){return {code:e.errcode??e.code??e.name};}};
import assert from 'node:assert/strict';
const run=function run(i,corrected=true){
const db=new DatabaseSync(':memory:');try{db.exec('CREATE TABLE original(id INTEGER PRIMARY KEY,label TEXT);CREATE TABLE target(id INTEGER PRIMARY KEY,label TEXT);CREATE TABLE restored(id INTEGER PRIMARY KEY,label TEXT);');
for(const r of i.original)db.prepare('INSERT INTO original VALUES(?,?)').run(r.id,r.label);
if(i.keepPreimage)db.exec('CREATE TABLE preimage AS SELECT * FROM original;');
if(i.collapse&&i.original.length)db.exec("INSERT INTO target VALUES(1,'Group');");else db.exec('INSERT INTO target SELECT * FROM original;');
db.exec('DROP TABLE original;');let error=null;
if(corrected&&i.collapse){if(!i.keepPreimage)error='missing-preimage';else db.exec('INSERT INTO restored SELECT * FROM preimage;');}
else db.exec('INSERT INTO restored SELECT * FROM target;');
return {error,target:rows(db,'SELECT * FROM target ORDER BY id'),restored:rows(db,'SELECT * FROM restored ORDER BY id')};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true},"expected":{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"Group"}]},"corrected":false},{"label":"corrected base","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true},"expected":{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]},"corrected":true},{"label":"lossy target without preimage cannot restore","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":false},"expected":{"error":"missing-preimage","target":[{"id":1,"label":"Group"}],"restored":[]},"corrected":true},{"label":"identity transition has direct inverse","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":false,"keepPreimage":false},"expected":{"error":null,"target":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]},"corrected":true}];
for(const test of cases){
const actual=run(test.input,test.corrected);
assert.deepEqual(actual,test.expected,test.label);
console.log(test.label,actual);
}
下次載入前核邊界
有損目標未留原資料便停;沒有合併的恆等轉換,則可直接精確逆回。
| 情況 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 有損目標未留原資料 | {"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":false} | {"error":"missing-preimage","target":[{"id":1,"label":"Group"}],"restored":[]} |
| 可直接逆回的恆等轉換 | {"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":false,"keepPreimage":false} | {"error":null,"target":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]} |
只見 Group,可確定舊標籤就是 A、B 嗎?
查看參考答案
不可,要靠保留原資料才有這兩個精確行;否則不能聲稱已精確回復。