feat(sql): HAVING — post-aggregation filter (SQLR-52)#161
Merged
Conversation
WHERE filters rows before grouping; HAVING filters groups after aggregation. Closes the Phase 9e aggregates story. Parser (src/sql/parser/select.rs): - SelectQuery grows `having: Option<Expr>`, passed through raw from sqlparser like WHERE. parse_aggregate_call / AggregateFn::from_name exposed pub(crate) for the executor's HAVING lowering. - HAVING without GROUP BY rejected with a typed NotImplemented (the degenerate single-group form SQLite allows isn't worth the executor branch in v0). HAVING + JOIN stays covered by the existing GROUP-BY-over-JOIN rejection (SQLR-6 is the follow-up). Executor (src/sql/executor.rs): - lower_having_expr rewrites aggregate calls in the HAVING tree to identifiers naming their output slot (SUM(salary) → "SUM(salary)"), registering hidden trailing projection slots for aggregates and GROUP BY keys referenced only in HAVING so aggregate_rows computes them alongside the visible ones. - New GroupRowScope resolves those identifiers against the group's output row through the shared expression evaluator — comparisons, AND/OR/NOT, arithmetic, IS NULL, LIKE, IN all work, with the same NULL-as-false collapse WHERE applies (design-decisions §13). - filter_groups_by_having runs after aggregation, before DISTINCT / ORDER BY / LIMIT; hidden slots are stripped after filtering. Tests: +13 executor tests (612 → 625 in the engine suite): COUNT/SUM thresholds, aggregate alias, aggregate-only-in-HAVING, group-key-only- in-HAVING, compound AND, ORDER BY/LIMIT composition, all-groups- excluded, NULL-aggregate collapse, lowercase call form, no-GROUP-BY rejection, out-of-scope column rejection, all four JOIN flavors rejected cleanly. Docs: supported-sql.md (HAVING semantics section + syntax block), sql-engine.md aggregation pipeline, roadmap.md shipped entry, README, design-decisions §13 wording, web docs page / sql-ref / roadmap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
joaoh82
added a commit
that referenced
this pull request
Jun 10, 2026
…ets) (#163) * docs(release): bump displayed version to 0.13.0 (site + install snippets) Same shape as #160 for v0.12.0: the Cargo install snippets in README, docs/ask.md, and docs/embedding.md, plus the site-wide version constant in web/src/lib/site.ts. v0.13.0 shipped HAVING (SQLR-52, #161). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(playground): refresh vendored WASM bundle to the 0.13.0 engine The /playground pkg is a pinned copy Vercel never rebuilds, so it was still serving the pre-0.12 engine (last refreshed in SQLR-42). Rebuilt via the documented flow in examples/wasm-playground/README.md; only the .wasm binary changed (no SDK API change, glue files identical). Also picks up sdk/wasm/Cargo.lock's 0.13.0 bump — sdk/wasm sits outside the workspace, so the Release PR's lock refresh couldn't reach it. Smoke-verified in Node: GROUP BY ... HAVING total > 100 returns the filtered group through the new bundle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WHEREis the pre-aggregate filter;HAVINGis the post-aggregate one. This lifts theNotImplementedand closes the Phase 9e aggregates story (SQLR-52).Design
Parser (src/sql/parser/select.rs):
SelectQuerygrowshaving: Option<Expr>, carried raw from sqlparser exactly likeWHERE'sselection.HAVINGwithoutGROUP BYis rejected with a typedNotImplemented("use WHERE for row-level filters or restructure with a subquery"). SQLite's degenerate single-group form isn't worth the executor branch in v0.HAVING+ JOIN stays covered by the existing GROUP-BY-over-JOIN rejection (SQLR-6 is the follow-up increment).Executor (src/sql/executor.rs):
lower_having_exprrewrites aggregate calls in the HAVING tree into identifiers naming their output slot (SUM(salary)→"SUM(salary)"), registering hidden trailing projection slots for aggregates and GROUP BY keys referenced only in HAVING — soSELECT dept FROM emp GROUP BY dept HAVING COUNT(*) > 1computes the count without projecting it.GroupRowScope(thirdRowScopeimpl) resolves those identifiers against each group's output row through the shared expression evaluator — comparisons,AND/OR/NOT, arithmetic,IS NULL,LIKE,IN (list)all work in HAVING for free, with the same NULL-as-false collapseWHEREapplies (design-decisions §13).filter_groups_by_havingruns after aggregation and beforeDISTINCT/ORDER BY/LIMIT; hidden slots are stripped after filtering so they never leak into output width.Tests
Engine suite 612 → 625 (+13), all green:
COUNT(*)/SUMthresholds; aggregate alias (HAVING total > 100); lowercase call formANDpredicate; composition withORDER BY+LIMIT; all-groups-excludedSUMgroup dropped)HAVINGwithoutGROUP BY→ typed error; out-of-scope column → typed errorGROUP BY+HAVING→ cleanNotImplemented, no wrong resultsVerification
cargo build/test/fmt --check/clippy(no new warnings) /doc— all passweb/npm run build— pass (three web files updated where they claimed HAVING was unsupported)Docs
supported-sql.md(new "HAVING semantics" section + syntax block),sql-engine.mdaggregation pipeline,roadmap.mdshipped entry, README extras list,design-decisions.md§13 wording, web docs page / sql-ref / roadmap component.Ref: SQLR-52
🤖 Generated with Claude Code