Skip to content
Astera Technology

Why did a one-row change counter miss two triggered writes?

Inspect each declared affected table beside the direct write count.

Download complete case notes and answer keys ↓

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.

Filled synthetic input
RecordInput JSON
Base input{"id":1,"fanout":2}
Download editable CSV ↓

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.

Misleading and corrected outputs
PathOutput JSON
Misleading path{"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":1}
Corrected path{"directChanges":1,"children":[{"n":1},{"n":2}],"reportedEffects":3}
Download editable CSV ↓
Local Node.js program: 22.23.3 or 24.14.1
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);
}
Download runnable example ↓

Check boundaries before the next load

No trigger writes gives total 1. An unmatched parent produces no direct or child writes and total 0.

Editable boundary records and expected outputs
CaseInput JSONExpected 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}
Download editable CSV ↓

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.

Related resources