先核來源與已訂規則
本例固定本地腳本把數量加 1 或 2。已用 V2 是加 1,且記 SHA-256。再以 V2 放加 2,是不同內容;只看名卻當已完成。更正要比真正腳本摘要。
| 紀錄 | 輸入 JSON |
|---|---|
| 原例輸入 | {"version":"V2","script":"UPDATE target SET quantity=quantity+2;"} |
比較兩條實際路徑
已變 V2 暫停,數量 2 與原記錄照留。V2/內容全符才認重跑;新 V3 才執行所附加 2 並記自己身分。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"accepted":true,"error":null,"applied":false,"quantity":2,"versions":["V2"]} |
| 更正路徑 | {"accepted":false,"error":"changed-script","applied":false,"quantity":2,"versions":["V2"]} |
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){
if(!['V2','V3'].includes(i.version)||!['UPDATE target SET quantity=quantity+1;','UPDATE target SET quantity=quantity+2;'].includes(i.script))throw new Error('Outside the fixed local migration scripts');
const db=new DatabaseSync(':memory:');try{db.exec('CREATE TABLE target(quantity INTEGER);INSERT INTO target VALUES(1);CREATE TABLE ledger(version TEXT PRIMARY KEY,hash TEXT);');
const first='UPDATE target SET quantity=quantity+1;';const hash=s=>createHash('sha256').update(s).digest('hex');db.exec(first);db.prepare('INSERT INTO ledger VALUES(?,?)').run('V2',hash(first));
const stored=db.prepare('SELECT hash FROM ledger WHERE version=?').get(i.version);let error=null,applied=false;
if(stored){if(corrected&&stored.hash!==hash(i.script))error='changed-script';}
else{db.exec('BEGIN;');db.exec(i.script);db.prepare('INSERT INTO ledger VALUES(?,?)').run(i.version,hash(i.script));db.exec('COMMIT;');applied=true;}
return {accepted:error===null,error,applied,quantity:db.prepare('SELECT quantity FROM target').get().quantity,versions:rows(db,'SELECT version FROM ledger ORDER BY version').map(r=>r.version)};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"version":"V2","script":"UPDATE target SET quantity=quantity+2;"},"expected":{"accepted":true,"error":null,"applied":false,"quantity":2,"versions":["V2"]},"corrected":false},{"label":"corrected base","input":{"version":"V2","script":"UPDATE target SET quantity=quantity+2;"},"expected":{"accepted":false,"error":"changed-script","applied":false,"quantity":2,"versions":["V2"]},"corrected":true},{"label":"same version and exact same script","input":{"version":"V2","script":"UPDATE target SET quantity=quantity+1;"},"expected":{"accepted":true,"error":null,"applied":false,"quantity":2,"versions":["V2"]},"corrected":true},{"label":"new named migration step executes","input":{"version":"V3","script":"UPDATE target SET quantity=quantity+2;"},"expected":{"accepted":true,"error":null,"applied":true,"quantity":4,"versions":["V2","V3"]},"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);
}
下次載入前核邊界
同腳本不再執行;新 V3 把已有 2 加成 4,記 V2、V3。
| 情況 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 同名且腳本完全一致 | {"version":"V2","script":"UPDATE target SET quantity=quantity+1;"} | {"accepted":true,"error":null,"applied":false,"quantity":2,"versions":["V2"]} |
| 新命名搬移內容 | {"version":"V3","script":"UPDATE target SET quantity=quantity+2;"} | {"accepted":true,"error":null,"applied":true,"quantity":4,"versions":["V2","V3"]} |
版本名稱同 V2,是否已代表工作內容相同?
查看參考答案
不是,加 2 與已做加 1 不同。用 V2 的變更腳本要停,數量仍 2。