Skip to content
Astera Technology

Why did the first row remain after the next insert failed?

Roll back the intended batch rather than treating statement ABORT as a whole-transaction rollback.

Download complete case notes and answer keys ↓

Read the source and its declared rule

The original import requires this two-row batch to commit together, with each quantity present and positive. A2 inserts before B−1 fails CHECK. SQLite ABORT reverses the failing statement, not A’s earlier successful statement in the transaction. Catching that error and committing preserves a partial batch.

Filled synthetic input
RecordInput JSON
Base input{"records":[{"id":"A","quantity":2},{"id":"B","quantity":-1}]}
Download editable CSV ↓

Compare the actual paths

Correct error handling issues ROLLBACK, so target is empty. The code retains the actual code 275 beside committed-state rows.

Misleading and corrected outputs
PathOutput JSON
Misleading path{"error":{"code":275},"target":[{"id":"A","quantity":2}]}
Corrected path{"error":{"code":275},"target":[]}
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 target(id TEXT PRIMARY KEY,quantity INTEGER NOT NULL CHECK(quantity>0));BEGIN;');
      const error=failed(()=>{for(const r of i.records)db.prepare('INSERT INTO target VALUES(?,?)').run(r.id,r.quantity);});
      db.exec(corrected&&error?'ROLLBACK;':'COMMIT;');return {error,target:rows(db,'SELECT * FROM target ORDER BY id')};
    }finally{db.close();}
  };
const cases=[{"label":"misleading base","input":{"records":[{"id":"A","quantity":2},{"id":"B","quantity":-1}]},"expected":{"error":{"code":275},"target":[{"id":"A","quantity":2}]},"corrected":false},{"label":"corrected base","input":{"records":[{"id":"A","quantity":2},{"id":"B","quantity":-1}]},"expected":{"error":{"code":275},"target":[]},"corrected":true},{"label":"all-good batch commits both","input":{"records":[{"id":"A","quantity":2},{"id":"B","quantity":3}]},"expected":{"error":null,"target":[{"id":"A","quantity":2},{"id":"B","quantity":3}]},"corrected":true},{"label":"first statement fails","input":{"records":[{"id":"A","quantity":-2},{"id":"B","quantity":3}]},"expected":{"error":{"code":275},"target":[]},"corrected":true},{"label":"missing second quantity rolls back the batch","input":{"records":[{"id":"A","quantity":2},{"id":"B","quantity":null}]},"expected":{"error":{"code":1299},"target":[]},"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

All-good A/B commit together. If the first insert fails, no later row is attempted and the rollback leaves empty state. Missing B quantity fails NOT NULL1299 and rolls back the earlier A too.

Editable boundary records and expected outputs
CaseInput JSONExpected output JSON
Complete valid batch{"records":[{"id":"A","quantity":2},{"id":"B","quantity":3}]}{"error":null,"target":[{"id":"A","quantity":2},{"id":"B","quantity":3}]}
First statement fails{"records":[{"id":"A","quantity":-2},{"id":"B","quantity":3}]}{"error":{"code":275},"target":[]}
Missing second quantity rolls back all{"records":[{"id":"A","quantity":2},{"id":"B","quantity":null}]}{"error":{"code":1299},"target":[]}
Download editable CSV ↓

Does catching code 275 automatically remove earlier successful statements?

Show the worked answer

No. A remains unless the batch transaction is rolled back. Corrected failure leaves no committed rows.

Related resources