先核來源與已訂規則
本例來源父 7 搬入已有 7、100 的目標,新分配編號是 101。子行來源 7 指這個搬入父,不是目標舊 7。例子先建搬入父的暫存行,子參考未有對應便暫停。
| 紀錄 | 輸入 JSON |
|---|---|
| 原例輸入 | {"existing":[7,100],"sourceId":7,"childSource":7} |
比較兩條實際路徑
錯子連 7,因舊目標 7 存在,普通外鍵仍可過,卻連錯對象。更正要沿來源 7→目標 101,子連 101。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"mapping":{"source":7,"target":101},"error":null,"parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[{"parent_id":7}]} |
| 更正路徑 | {"mapping":{"source":7,"target":101},"error":null,"parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[{"parent_id":101}]} |
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('PRAGMA foreign_keys=ON;CREATE TABLE parent(id INTEGER PRIMARY KEY AUTOINCREMENT,label TEXT);CREATE TABLE child(parent_id INTEGER REFERENCES parent(id));');
for(const id of i.existing)db.prepare('INSERT INTO parent VALUES(?,?)').run(id,'Existing');
const newId=Number(db.prepare("INSERT INTO parent(label) VALUES('Imported')").run().lastInsertRowid);
const mapping={source:i.sourceId,target:newId};
const error=corrected&&i.childSource!==i.sourceId?'unmapped-source-parent':failed(()=>db.prepare('INSERT INTO child VALUES(?)').run(corrected?newId:i.childSource));
return {mapping,error,parents:rows(db,'SELECT * FROM parent ORDER BY id'),children:rows(db,'SELECT * FROM child')};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"existing":[7,100],"sourceId":7,"childSource":7},"expected":{"mapping":{"source":7,"target":101},"error":null,"parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[{"parent_id":7}]},"corrected":false},{"label":"corrected base","input":{"existing":[7,100],"sourceId":7,"childSource":7},"expected":{"mapping":{"source":7,"target":101},"error":null,"parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[{"parent_id":101}]},"corrected":true},{"label":"coincidental source-target number equality","input":{"existing":[],"sourceId":1,"childSource":1},"expected":{"mapping":{"source":1,"target":1},"error":null,"parents":[{"id":1,"label":"Imported"}],"children":[{"parent_id":1}]},"corrected":true},{"label":"unmapped child source","input":{"existing":[7,100],"sourceId":7,"childSource":8},"expected":{"mapping":{"source":7,"target":101},"error":"unmapped-source-parent","parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[]},"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);
}
下次載入前核邊界
空目標剛好同號,不能證明直接拷鍵可靠。未知子來源 8 要暫停,不寫子行。
| 情況 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 來源與目標剛好同號 | {"existing":[],"sourceId":1,"childSource":1} | {"mapping":{"source":1,"target":1},"error":null,"parents":[{"id":1,"label":"Imported"}],"children":[{"parent_id":1}]} |
| 子來源沒有對應 | {"existing":[7,100],"sourceId":7,"childSource":8} | {"mapping":{"source":7,"target":101},"error":"unmapped-source-parent","parents":[{"id":7,"label":"Existing"},{"id":100,"label":"Existing"},{"id":101,"label":"Imported"}],"children":[]} |
原例子行必須指哪個目標父編號?
查看參考答案
101,要依分配對應。目標 7 是不相關舊紀錄,即使存在外鍵檢查會過。