Repository navigation
Expression errors still fold silently inside aggregates, GROUP BY keys, window functions, and join probes (follow-up to #216/#226) #228
Description
Activity
- addedtype:bugA defect — broken, incorrect, or lost dataA defect — broken, incorrect, or lost datasev:2-highMajor functionality broken; no acceptable workaroundMajor functionality broken; no acceptable workaroundpriority:P2Scheduled, not urgentScheduled, not urgentarea:sqlParser, planner, SQL semanticsParser, planner, SQL semantics
on Jul 26, 2026 Fixed in 6d89563.
The four paths named here — aggregate arguments, GROUP BY keys, window functions, and the hash-join probe — were already converted by the #226 follow-ups, so the reported repro now raises 22012 as expected. Widening the fix along the same invariant turned up the other half of the class, where the expression was not folding on error but was never evaluated at all, producing wrong answers even with no zero divisor present:
- Window function arguments were reduced to a column name, so any non-column argument silently returned 0/NULL —
SUM(n * 2) OVER (...),LAG(n * 10) OVER (...), and framed aggregates all did. Arguments are now evaluated per row, and the planner rejects a non-constantLAG/LEAD/NTILE/NTH_VALUEoffset instead of quietly substituting its default. - ORDER BY over an expression was dropped at plan conversion, returning rows in storage order under a sort the client asked for. Sort keys now carry the expression through every engine, including the external spill and k-way merge.
- GROUP BY referencing a SELECT alias collapsed every row into one global group. It now resolves to the aliased expression, with PostgreSQL's precedence (an input column of the same name wins).
- HAVING dropped every group whenever the aggregate was not also projected, and never resolved output aliases. The predicate is now bound to the computed aggregate columns, and an aggregate that appears only in HAVING is computed rather than ignored.
- A JOIN whose ON clause holds only an inequality matched nothing at all, since the probe had no key to hash. Keyless probes now consider every candidate pair and let the residual predicate decide.
Coverage went in ahead of the fix: 20 tests across seven files, each asserting the correct behavior rather than the current one, plus value-level guards against the specific silent modes (all-zero window columns, unchanged row order, a single collapsed group, an empty join result).
Two related gaps were flagged rather than silently folded into the fix above, and have since been completed in b9191df08109c2128b6fc89cd132283c04c7dc3a:
ORDER BY ... NULLS FIRST/LASTwas ignored. Sort keys now carry NULL placement, with PostgreSQL's defaults (ASC→ NULLS LAST,DESC→ NULLS FIRST) and the placement never flipped by the sort direction. This also settled two related inconsistencies: the previous default was inverted, and the engines disagreed on whether a NULL sorted high or low.- A post-aggregate
ORDER BY <expression>returned a typed planner error.GROUP BY cat ORDER BY 1000 / SUM(amount)andORDER BY SUM(amount) DESCover an unprojected aggregate now work; an aggregate introduced only by the sort is computed without becoming an output column.
- Window function arguments were reduced to a column name, so any non-column argument silently returned 0/NULL —
Version / build tested against
PR #226's branch (
fix/216-suppressed-expression-errorson top oforigin/main); the behavior also exists on currentmainin its pre-#226 form (everything folds). Filed as the tracked follow-up to #226's documented deferral.Deployment mode
Origin — single node (local), via pgwire.
Engine(s) involved
SQL execution — aggregate accumulators, GROUP BY key building, window functions, hash-join probe path.
Summary
#226 makes scalar expression contexts raise 22012/42883 instead of folding to NULL, but expressions evaluated inside certain pervasive hot paths still fold errors silently (each site carries an in-code deferral comment): aggregate arguments (an error row is skipped, so
sum(10/d)over a table containingd = 0returns a number silently missing rows), GROUP BY key expressions (zero-divisor rows group under NULL), window-function arguments / PARTITION BY / ORDER BY, and the hash-join residual-predicate probe path (matching row-pairs silently dropped). The result is an internal inconsistency with real corruption potential:SELECT 10/d FROM terrors, whileSELECT sum(10/d) FROM ton the same table succeeds with a silently wrong total — the exact "silent wrong result" class #216 exists to eliminate, one context over.Steps to reproduce
On a #226 build:
Expected behavior
Error contexts behave uniformly: an expression that raises 22012 in a scalar context also raises it when evaluated as an aggregate argument, GROUP BY key, window argument, or join predicate — matching Postgres.
Actual behavior
The four listed paths swallow the error per-row: aggregates skip the row, GROUP BY buckets it under NULL, joins drop the pair — all with green results.
What actually happened? (check all that are true)
Proposed severity
SEV-2 — High: silently-wrong query results; stored data intact.
Reproducibility
Always — every attempt.
Last known-good version / commit (if a regression)
Not a regression — these paths have folded since introduction; #226 makes the inconsistency visible by fixing the scalar half.
Environment & logs
Linux x86_64, source build. Why deferred out of #226: these are infallible-by-signature hot paths spanning ~10 files each (accumulator feed, group-key builder, both window evaluators, grace-hash probe); converting them is a materially larger, perf-sensitive change than the ~19 mechanical call-site conversions #226 shipped. Each site carries an in-code comment naming this issue class. Suggested shape: same
EvalErrorpropagation, threaded through the accumulator/group-key/window/join result types, with attention to the spill/shuffle variants used by cluster mode.Before submitting
mainbuild. (Scalar-vs-aggregate divergence reproduced on the fix(sql): raise 42883/22012 for undefined functions and division by zero instead of folding to NULL #226 branch build; the folding itself exists on main.)