The fault model
Faults arrive in three lanes, and the distinction matters:
Infrastructure failures are deliberately uncatchable: a
WHEN OTHERS
handler can never mask an outage as a handled condition. An infra
failure always exits 1, so your scheduler’s alert lane stays honest.
The typed faults:
ZERO_DIVIDE— division by zero in compute.OVERFLOW— integer width overflow on+ - *or a cast, decimal precision overflow, a float operation that would produce NaN or Infinity, or an out-of-range date result.NO_DATA_FOUND— a keyed-collection probe missed (or used a NULL key), or aSELECT ... INTOfound no row.
The EXCEPTION block
A program may end with one handler block:- Handler actions are exactly two:
STOPorRAISE. There is no handler that patches a value and continues — a handler never resumes the loop it interrupted. WHEN OTHERSmust be the last handler, and no exception may be handled twice.- One
EXCEPTIONblock per program, covering the whole body.
STOP versus RAISE
STOP is the graceful ending for a fault you anticipated: the
in-flight batch’s open transaction rolls back, every previously
committed batch stays durable, and the process exits 2 with
Stopped — STOP handled; committed prefix retained. A stop is not a
success — exit 2 exists precisely so schedulers can tell “finished”
from “stopped early with valid partial progress”.
RAISE rethrows: the run dies with exit 1, for faults that
should page a human. An unhandled fault behaves the same way.
Raising faults yourself
Business invariants can reuse the typed faults, with an optional condition:ZERO_DIVIDE and OVERFLOW can be raised explicitly;
NO_DATA_FOUND and OTHERS cannot.
The durable prefix
The commit grain you chose in the program (see COMMIT: placement is meaning) decides what a fault leaves behind:- Per-batch commits — a fault on batch 40 leaves batches 1–39 committed. That committed prefix is always valid data, because a batch only commits after its writes applied.
- At-end commit — a fault anywhere leaves nothing committed; all-or-nothing.
weight stops this run, every earlier batch is already
committed and flagged. Fix the data, run the same program again, and
the read picks up exactly the unprocessed remainder.
Re-running is always your action — Sumatra has no automatic retry
or resume. And note that carried variables restart from their
DECLARE
initializers on a re-run: design carried state so it is either
derivable from the data (as in a per-customer reset) or irrelevant
across runs.Guard, don’t catch
A handler stops the run; it cannot skip the faulting row and carry on. When bad rows are expected and should be tolerated, guard before the fault instead:EXCEPTION block for
stopping the run cleanly when something genuinely unexpected happens.
