Read the source and its declared rule
This original UPDATE changes one parent and its trigger inserts two child rows. The direct statement change count excludes those auxiliary writes. The fixture’s affected-row total includes the direct count plus the actual child-row increase; allowed fanout is 0 or 2 in this tiny schema.
| Record | Input JSON |
|---|---|
| Base input | {"id":1,"fanout":2} |
Compare the actual paths
Direct count 1 remains correct for its narrow meaning; reporting it as all effects is wrong. Corrected total is 1+2=3 with the two actual child rows visible.
| Path | Output JSON |
|---|---|
| Misleading path | {"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":1} |
| Corrected path | {"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":3} |
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 parent(id INTEGER PRIMARY KEY,value INTEGER);CREATE TABLE child(n INTEGER);INSERT INTO parent VALUES(1,0);');
if(i.fanout===2)db.exec('CREATE TRIGGER fanout AFTER UPDATE ON parent BEGIN INSERT INTO child VALUES(1);INSERT INTO child VALUES(2);END;');
const before=db.prepare('SELECT COUNT(*) AS n FROM child').get().n;
const directChanges=Number(db.prepare('UPDATE parent SET value=value+1 WHERE id=?').run(i.id).changes);
const children=rows(db,'SELECT * FROM child ORDER BY n');
return {directChanges,children,reportedEffects:directChanges+(corrected?children.length-before:0)};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"id":1,"fanout":2},"expected":{"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":1},"corrected":false},{"label":"corrected base","input":{"id":1,"fanout":2},"expected":{"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":3},"corrected":true},{"label":"no triggered writes","input":{"id":1,"fanout":0},"expected":{"directChanges":1,"children":[],"reportedEffects":1},"corrected":true},{"label":"no matched direct row","input":{"id":2,"fanout":2},"expected":{"directChanges":0,"children":[],"reportedEffects":0},"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);
}
Check boundaries before the next load
No trigger writes gives total 1. An unmatched parent produces no direct or child writes and total 0.
| Case | Input JSON | Expected output JSON |
|---|---|---|
| No triggered child writes | {"id":1,"fanout":0} | {"directChanges":1,"children":[],"reportedEffects":1} |
| No matched parent row | {"id":2,"fanout":2} | {"directChanges":0,"children":[],"reportedEffects":0} |
What total effect count belongs to the base fixture?
Show the worked answer
3: one direct parent update plus two inserted child rows. Preserve directChanges 1 as a separately scoped measure.