先核來源與已訂規則
本例過渡結構存 old2、scaled20,須保持 scaled=old×10。兩種表示本來都要有值,來源只准改 old;更新值須是整數 Number,且本身與乘十結果都須為安全整數,NULL 則暫停。更正先核已有值、差異、權限及所提值,才在同一交易更新兩欄。
| 紀錄 | 輸入 JSON |
|---|---|
| 原例輸入 | {"old":2,"scaled":20,"edit":{"side":"old","value":3}} |
比較兩條實際路徑
輸入 old3,更正真正狀態是 3/30。原有 2/21 相反就保原暫停,不在發現矛盾後靜靜選一邊當真。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"error":null,"records":[{"id":1,"size_old":3,"size_new":20}]} |
| 更正路徑 | {"error":null,"records":[{"id":1,"size_old":3,"size_new":30}]} |
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 target(id INTEGER PRIMARY KEY,size_old INTEGER,size_new INTEGER);');db.prepare('INSERT INTO target VALUES(1,?,?)').run(i.old,i.scaled);let error=null;
if(corrected){db.exec('BEGIN;');if(db.prepare('SELECT COUNT(*) AS n FROM target WHERE size_old IS NULL OR size_new IS NULL').get().n)error='missing-representation';
else if(db.prepare('SELECT COUNT(*) AS n FROM target WHERE size_new<>size_old*10').get().n)error='divergent-existing-pair';
else if(i.edit.side!=='old')error='non-authoritative-edit';else if(i.edit.value===null)error='missing-edit-value';
else if(!Number.isSafeInteger(i.edit.value)||!Number.isSafeInteger(i.edit.value*10))error='invalid-edit-value';
else db.prepare('UPDATE target SET size_old=?,size_new=? WHERE id=1').run(i.edit.value,i.edit.value*10);
db.exec(error?'ROLLBACK;':'COMMIT;');}
else db.prepare('UPDATE target SET '+(i.edit.side==='old'?'size_old':'size_new')+'=? WHERE id=1').run(i.edit.value);
return {error,records:rows(db,'SELECT * FROM target')};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"old":2,"scaled":20,"edit":{"side":"old","value":3}},"expected":{"error":null,"records":[{"id":1,"size_old":3,"size_new":20}]},"corrected":false},{"label":"corrected base","input":{"old":2,"scaled":20,"edit":{"side":"old","value":3}},"expected":{"error":null,"records":[{"id":1,"size_old":3,"size_new":30}]},"corrected":true},{"label":"existing divergence blocks a new edit","input":{"old":2,"scaled":21,"edit":{"side":"old","value":3}},"expected":{"error":"divergent-existing-pair","records":[{"id":1,"size_old":2,"size_new":21}]},"corrected":true},{"label":"new-side direct edit lacks authority","input":{"old":2,"scaled":20,"edit":{"side":"new","value":30}},"expected":{"error":"non-authoritative-edit","records":[{"id":1,"size_old":2,"size_new":20}]},"corrected":true},{"label":"missing scaled counterpart held","input":{"old":2,"scaled":null,"edit":{"side":"old","value":3}},"expected":{"error":"missing-representation","records":[{"id":1,"size_old":2,"size_new":null}]},"corrected":true},{"label":"missing old counterpart held","input":{"old":null,"scaled":20,"edit":{"side":"old","value":3}},"expected":{"error":"missing-representation","records":[{"id":1,"size_old":null,"size_new":20}]},"corrected":true},{"label":"missing incoming old value held","input":{"old":2,"scaled":20,"edit":{"side":"old","value":null}},"expected":{"error":"missing-edit-value","records":[{"id":1,"size_old":2,"size_new":20}]},"corrected":true},{"label":"numeric zero edit is present","input":{"old":2,"scaled":20,"edit":{"side":"old","value":0}},"expected":{"error":null,"records":[{"id":1,"size_old":0,"size_new":0}]},"corrected":true},{"label":"text numeral is not a numeric edit","input":{"old":2,"scaled":20,"edit":{"side":"old","value":"3"}},"expected":{"error":"invalid-edit-value","records":[{"id":1,"size_old":2,"size_new":20}]},"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);
}
下次載入前核邊界
原差異照留 2/21;未准 new 側改 30,則仍 2/20。scaled 或 old 欠值都以 missing-representation 保原暫停;所提 old 值 NULL 則以 missing-edit-value 暫停。數字零可改成 0/0,文字 3 卻要停,不自行轉型。
| 情況 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 原有兩表示已不符 | {"old":2,"scaled":21,"edit":{"side":"old","value":3}} | {"error":"divergent-existing-pair","records":[{"id":1,"size_old":2,"size_new":21}]} |
| 輸入改未獲准一側 | {"old":2,"scaled":20,"edit":{"side":"new","value":30}} | {"error":"non-authoritative-edit","records":[{"id":1,"size_old":2,"size_new":20}]} |
| scaled 表示欠值 | {"old":2,"scaled":null,"edit":{"side":"old","value":3}} | {"error":"missing-representation","records":[{"id":1,"size_old":2,"size_new":null}]} |
| old 表示欠值 | {"old":null,"scaled":20,"edit":{"side":"old","value":3}} | {"error":"missing-representation","records":[{"id":1,"size_old":null,"size_new":20}]} |
| 所提 old 更新值欠缺 | {"old":2,"scaled":20,"edit":{"side":"old","value":null}} | {"error":"missing-edit-value","records":[{"id":1,"size_old":2,"size_new":20}]} |
| 有值的數字零更新 | {"old":2,"scaled":20,"edit":{"side":"old","value":0}} | {"error":null,"records":[{"id":1,"size_old":0,"size_new":0}]} |
| 文字數字留待查 | {"old":2,"scaled":20,"edit":{"side":"old","value":"3"}} | {"error":"invalid-edit-value","records":[{"id":1,"size_old":2,"size_new":20}]} |
old3 可否提交,scaled20 卻不改?
查看參考答案
不可,違本例兩表示約定。獲准更新須一同提交 3/30。