Skip to content
Astera Technology

Why did one rejected line leave half an imported document?

Use a savepoint for the complete document family while preserving independent successful families.

Download complete case notes and answer keys ↓

Read the source and its declared rule

The original batch allows independent document families to complete separately. D contains a header, L1=2, L2=−1; E has its own valid line 3. Each existing line requires a non-NULL positive quantity. D’s header and lines must succeed together. A savepoint per family removes all D allocations on its line error; header-only drafts are explicitly allowed.

Filled synthetic input
RecordInput JSON
Base input{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":-1}]},{"id":"E","lines":[{"item":"L1","quantity":3}]}]}
Download editable CSV ↓

Compare the actual paths

Corrected target retains E and its line, with D’s code 275 recorded and no D fragments. Row-by-row catching instead leaves D header and L1.

Misleading and corrected outputs
PathOutput JSON
Misleading path{"errors":[{"document":"D","code":275}],"documents":[{"id":"D"},{"id":"E"}],"lines":[{"document_id":"D","item":"L1","quantity":2},{"document_id":"E","item":"L1","quantity":3}]}
Corrected path{"errors":[{"document":"D","code":275}],"documents":[{"id":"E"}],"lines":[{"document_id":"E","item":"L1","quantity":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('PRAGMA foreign_keys=ON;CREATE TABLE document(id TEXT PRIMARY KEY);CREATE TABLE line(document_id TEXT REFERENCES document(id),item TEXT,quantity INTEGER NOT NULL CHECK(quantity>0));');const errors=[];
      for(const d of i.documents){if(corrected)db.exec('SAVEPOINT family;');db.prepare('INSERT INTO document VALUES(?)').run(d.id);
        let error=null;for(const l of d.lines){error=failed(()=>db.prepare('INSERT INTO line VALUES(?,?,?)').run(d.id,l.item,l.quantity));if(error){errors.push({document:d.id,code:error.code});if(corrected)break;}}
        if(corrected){if(error)db.exec('ROLLBACK TO family;');db.exec('RELEASE family;');}}
      return {errors,documents:rows(db,'SELECT * FROM document ORDER BY id'),lines:rows(db,'SELECT * FROM line ORDER BY document_id,item')};
    }finally{db.close();}
  };
const cases=[{"label":"misleading base","input":{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":-1}]},{"id":"E","lines":[{"item":"L1","quantity":3}]}]},"expected":{"errors":[{"document":"D","code":275}],"documents":[{"id":"D"},{"id":"E"}],"lines":[{"document_id":"D","item":"L1","quantity":2},{"document_id":"E","item":"L1","quantity":3}]},"corrected":false},{"label":"corrected base","input":{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":-1}]},{"id":"E","lines":[{"item":"L1","quantity":3}]}]},"expected":{"errors":[{"document":"D","code":275}],"documents":[{"id":"E"}],"lines":[{"document_id":"E","item":"L1","quantity":3}]},"corrected":true},{"label":"complete entity commits","input":{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":1}]}]},"expected":{"errors":[],"documents":[{"id":"D"}],"lines":[{"document_id":"D","item":"L1","quantity":2},{"document_id":"D","item":"L2","quantity":1}]},"corrected":true},{"label":"permitted header-only draft","input":{"documents":[{"id":"D","lines":[]}]},"expected":{"errors":[],"documents":[{"id":"D"}],"lines":[]},"corrected":true},{"label":"missing line quantity holds its complete family","input":{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":null}]},{"id":"E","lines":[{"item":"L1","quantity":3}]}]},"expected":{"errors":[{"document":"D","code":1299}],"documents":[{"id":"E"}],"lines":[{"document_id":"E","item":"L1","quantity":3}]},"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

A fully valid D commits. A permitted header-only D keeps its header and no lines. If an existing D line lacks quantity, NOT NULL1299 rolls back D while independent valid E remains.

Editable boundary records and expected outputs
CaseInput JSONExpected output JSON
All lines in one family valid{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":1}]}]}{"errors":[],"documents":[{"id":"D"}],"lines":[{"document_id":"D","item":"L1","quantity":2},{"document_id":"D","item":"L2","quantity":1}]}
Allowed header-only draft{"documents":[{"id":"D","lines":[]}]}{"errors":[],"documents":[{"id":"D"}],"lines":[]}
Missing line quantity holds its family{"documents":[{"id":"D","lines":[{"item":"L1","quantity":2},{"item":"L2","quantity":null}]},{"id":"E","lines":[{"item":"L1","quantity":3}]}]}{"errors":[{"document":"D","code":1299}],"documents":[{"id":"E"}],"lines":[{"document_id":"E","item":"L1","quantity":3}]}
Download editable CSV ↓

Must E roll back because independent D failed?

Show the worked answer

No under this supplied family policy. Roll back all of D, retain complete E, and record D’s unresolved error.

Related resources