Skip to content

Trigger-body assignments silently write NULL on division by zero — procedural evaluator folds expression errors (22012 suppressed on the write path) #227

Description

@emanzx

Version / build tested against

origin/main @ f7fcc7718 (release build; predates #226, but the affected evaluator is untouched by that PR — see Summary).

Deployment mode

Origin — single node (local), via psql over pgwire.

Engine(s) involved

Procedural executor (trigger bodies / DO blocks) — a separate evaluator from the scalar-expression path fixed in #226.

Summary

The procedural executor has its own arithmetic evaluator, and it silently folds division by zero to NULL on the write path: a BEFORE INSERT trigger body assignment NEW.ratio := NEW.numerator / NEW.denominator; with a zero denominator writes NULL into the row and lets the INSERT commit — no error, no SQLSTATE 22012. This is the same suppressed-error class as #216, in a different evaluator: try_eval_constant's division arm only matches a non-zero divisor, the zero case falls through to None, and evaluate_to_value coerces None to Ok(Value::Null) (control/planner/procedural/executor/eval.rs ~181/195, ~88-92), reaching execute_assign / execute_return. #226 deliberately scoped this out (separate evaluator); filing so it is tracked rather than implicit.

Steps to reproduce

CREATE COLLECTION trig_probe (id TEXT PRIMARY KEY, numerator INT, denominator INT, ratio INT);
CREATE TRIGGER trig_ratio BEFORE INSERT ON trig_probe FOR EACH ROW
  BEGIN NEW.ratio := NEW.numerator / NEW.denominator; END;
INSERT INTO trig_probe (id, numerator, denominator) VALUES ('r1', 10, 0);  -- succeeds
INSERT INTO trig_probe (id, numerator, denominator) VALUES ('r2', 10, 2);  -- succeeds
SELECT id, numerator, denominator, ratio FROM trig_probe ORDER BY id;
--  r1 | 10 | 0 | NULL   <- silently wrong: 10/0 folded to NULL, write committed
--  r2 | 10 | 2 | 5      <- control: trigger works

Expected behavior

The zero-divisor assignment raises division_by_zero (SQLSTATE 22012), aborting the triggering statement — consistent with the post-#226 behavior of the main scalar evaluator, and with the procedural engine's own EXCEPTION WHEN DIVISION_BY_ZERO handler machinery (which string-matches a phrase that is currently never raised anywhere).

Actual behavior

The assignment silently produces NULL; the INSERT commits with the wrong value; no error is observable anywhere.

What actually happened? (check all that are true)

  • Acknowledged/committed data was lost, corrupted, or silently wrong
  • The process crashed, hung, or failed to start
  • A security or isolation boundary was crossed
  • Core functionality is broken with no acceptable workaround
  • A workaround exists (guard the divisor in the trigger body with an IF, or validate before insert)

Proposed severity

SEV-2 — High: silently-wrong stored results on the write path; pre-existing data intact.

Reproducibility

Always — every attempt.

Last known-good version / commit (if a regression)

Not a regression — the procedural evaluator has folded eval failures to NULL since introduction.

Environment & logs

Linux x86_64, source build of origin/main @ f7fcc7718, single local node, fresh collections. Found during the adversarial review pass for #226. Note: the same None → Ok(Value::Null) coercion in evaluate_to_value also swallows other expression failures in trigger bodies (unknown identifiers, unsupported operators), so a fix probably wants a typed error out of try_eval_constant rather than a div-zero special case.

Before submitting

  • I searched existing issues and this is not a duplicate.
  • I reproduced this on a released tag or a current main build.
  • This is not a security vulnerability (those go to a private advisory).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sqlParser, planner, SQL semanticspriority:P2Scheduled, not urgentsev:2-highMajor functionality broken; no acceptable workaroundtype:bugA defect — broken, incorrect, or lost data

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions