Skip to content

Refuse CUME_DIST, PERCENT_RANK and NTH_VALUE without OVER (SQ0205, E232) - #111

Closed
kvlonge wants to merge 11 commits into
fix/d215-lag-leadfrom
fix/e232-window-without-over
Closed

kvlonge wants to merge 11 commits into
fix/d215-lag-leadfrom
fix/e232-window-without-over

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #98 (fix/d215-lag-lead); review this diff against that branch.

What changes

Snowflake rejects CUME_DIST(), PERCENT_RANK() and NTH_VALUE(x, n) without an OVER clause when compiling: error 2064, SQLSTATE 42601, "Missing window specification for function [...]". The receipts are on evidence/d215-receipts 19c3fd9, cases translation/functions/window-without-over.toml. Before this change nothing caught them: sqlquack check reported no problems, and convert only refused with SQ0002 (no verified rule).

  • SQ0205 window_without_over (sqlquack-core refusals): a call to one of the three that no OVER clause applies to is now rejected, nested or not, in both check and convert. The calls an OVER clause wraps are collected once per statement, so every other call is judged against that list.
  • E232 (sqlquack-semantics lineage scope rule): the same three now report "Window function requires an OVER clause". This covers the typed nodes DuckDB's parse builds (NthValue, and CumeDist/PercentRank without hypothetical arguments) and the named calls Snowflake's faithful parse keeps.
  • The diagnostic reference is regenerated, and the codes.lock entry added.
  • Evidence: A's three window receipts, plus the three cast-then-path receipts (d888870) that get-path.cast-then-path.toml on this chain needs.

Evidence

  • check and convert before: no problems / SQ0002. After: error[SQ0205] for each of the three. With OVER, nothing is reported.
  • Zero-fire: across every case query that calls one of the three functions (14), SQ0205 fires only on the 3 recorded compile errors.
  • Replay of window-without-over.toml and get-path.cast-then-path.toml: 6 refused, 0 pending, 0 failed. The cast-then-path cases refuse on a known parser gap, which is queued separately.
  • Tests: sqlquack-core 17 passed, sqlquack-semantics 27 passed, and sqlquack lib 85 passed. The sqlquack CLI tests passed except 3 Python-runtime tests, which need the interpreter that make check-merge configures. The D94 gate runs on the landing head.
  • fensu: 0 faults. The hygiene scan is clean.

Scope

Only the three receipt-backed functions are covered. ROW_NUMBER, RANK, LAG and the others without OVER also pass check today under Snowflake's faithful parse; that needs receipts before it's widened.

Follow-ups

  • NTH_VALUE(x, n) FROM FIRST|LAST [IGNORE|RESPECT NULLS] written without OVER still passes check. That's a false negative only (convert refuses with SQ0002), and no receipt shows it yet. The same spellings under OVER are pinned as reporting nothing (96ff683).

CUME_DIST(), PERCENT_RANK() and NTH_VALUE(x, 1) without OVER: A's
screen showed Snowflake refusing each when compiling (2064, 42601,
missing window specification). compile_error and tags are not hashed,
so the case hashes stay as screened.
The translation category needs a case covering type-combination inputs;
dropping the tag made sqlquack pending refuse the file.
Snowflake results for the three cases in cases/translation/functions/window-without-over.toml at 2d94c3e:
each is refused with 2064 (SQLSTATE 42601).
Snowflake rejects these window functions without an OVER clause when
compiling (2064, SQLSTATE 42601, missing window specification; receipts
on evidence/d215-receipts 19c3fd9). Nothing caught them: check passed
the SQL, and convert only refused it for lacking a rule (SQ0002).

The refusal layer now rejects a call to one of the three that no OVER
clause applies to as SQ0205 window_without_over, in check and convert,
nested or not. The lineage scope rule reports E232 for them as well, for
the typed nodes DuckDB parses and the named calls Snowflake's faithful
parse keeps.
Snowflake results for the three cases in cases/translation/functions/get-path.cast-then-path.toml at 7b15243.
Review asked whether a modifier between the call and OVER hides the call
from the windowed set. It does not: these spellings report nothing in the
refusal layer or the E232 rule, and now stay pinned that way.

kvlonge commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing: SQLQuack lands through local-gate trains, not PR merges (D94), so open PRs only add clutter (D628/D630). The branch is kept. Any work here that isn't on main yet is tracked by the train owner and lands through a train.

@kvlonge kvlonge closed this Nov 8, 2026
@kvlonge
kvlonge deleted the fix/e232-window-without-over branch November 9, 2026 21:31
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.

1 participant