Skip to content

fix: whitespace- and order-tolerant verbatim aggregate detection - #1207

Draft
domoritz wants to merge 3 commits into
mainfrom
sql-fix/F-RX-07
Draft

fix: whitespace- and order-tolerant verbatim aggregate detection#1207
domoritz wants to merge 3 commits into
mainfrom
sql-fix/F-RX-07

Conversation

@domoritz

@domoritz domoritz commented Sep 3, 2026

Copy link
Copy Markdown
Member

Fixes #1204, #1205, #1206. Part of the audit tracked in #1170.

Note: This fix was created by Claude (an agent team ran a binder-error audit of the SQL layer; each fix went through automated implementation and adversarial review passes).

Three fixes to the verbatim-SQL classifier in visit/visitors.ts, the scan that decides whether a sql template contains an aggregate (and so whether a mark emits GROUP BY). One commit per issue; they ship together because the second alone would regress a multi-line subquery written before an aggregate, which only the third repairs.

  1. Verbatim aggregate detection misses a space between the function name and its paren #1204 fix: tolerate whitespace between a function name and its paren — the call token pattern becomes \w+\s*\( and the name is trimmed before the aggregate-name test, so max (num1) and MAX\n(num1) classify as DuckDB parses them. No non-aggregate input changes class (checked against count_total * (x + 1), x in (1, 2), 'count (' || x, a bare column named sum, quoted "count (x)", avg (x) over (…)).
  2. Verbatim subquery detection requires the exact text (select #1205 fix: match scalar subquery starts regardless of whitespaceindexOf('(select ') becomes a subqueryRegExp (/\(\s*select\b/) at module level next to windowRegExp, so (\n SELECT … is stripped; (selection_id) is not.
  3. Verbatim aggregates written after a scalar subquery are not detected #1206 fix: strip every scalar subquery, not the expression tail — a stripSubqueries helper excises each (select …) group by matching parentheses and rescans the remainder, so (select max(y) from t) + sum(x) classifies as an aggregate like its swapped spelling does.

The classifier stays quote-unaware by decision (#1170 defers that). Consequence for 3: a ) inside a string literal inside a subquery can end the excision early; that needs an unbalanced ) in a literal followed by an aggregate call in the same subquery. Where it bites, the result is a binder error if the expression also reads a row-level column, and otherwise a regrouping of a constant subquery with identical plotted values; preaggregation is unaffected. Pinned in a test and noted in the one source comment.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DEmcoxyFx2BRM5BbmJGFsa

domoritz and others added 3 commits September 2, 2026 23:39
Fixes #1204.

SQL allows whitespace between a function name and its argument list, so
`sum (amount)` in a verbatim fragment was classified as non-aggregate and
vgplot emitted no GROUP BY for the mark's other channels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEmcoxyFx2BRM5BbmJGFsa
Fixes #1205.

The strip searched for the literal text `(select `, so a subquery broken
across lines kept its aggregate calls in view and the whole expression was
misclassified as an aggregate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEmcoxyFx2BRM5BbmJGFsa
Fixes #1206.

The strip truncated at the first `(select`, so an aggregate written after a
subquery was never seen and classification depended on operand order:
`(select max(a) from t) + sum(b)` was treated as row-level while the swapped
spelling was not. Each subquery's balanced-paren extent is now excised and
the remaining text scanned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEmcoxyFx2BRM5BbmJGFsa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verbatim aggregate detection misses a space between the function name and its paren

1 participant