Skip to content

feat having(): post-aggregation group filtering #291

Description

@0x054

What to build

The post-aggregation filter deferred from #282 (grill decision, 2026-07-12): a .having(lambda t: t.amount.sum() > 100) chainer that filters which groups an aggregate projection returns. The predicate grammar is where()'s plus aggregate leaves (count/sum/avg/min/max over scalar columns, traversal included, INNER-narrowing per ADR-0006); renders HAVING on both backends. Grouping stays derived from the projection (CONTEXT.md: Aggregate projection) — having() contributes no grouping opinion and no join-type opinion. where() keeps rejecting aggregate predicates at build time, with its pointer flipped from the deferral message to naming having(). having() on a query without an aggregate projection raises at build time (there are no groups to filter). Output aliases are out of scope in having predicates — HAVING sees input columns and aggregates (SQL scoping); order_by remains the alias-scoped clause.

Acceptance criteria

  • having() filters groups on both backends and composes with where (traversal included), order_by, limit/offset.
  • Build-time rejections: aggregate in where() points at having(); having() without an aggregate projection; alias references inside a having predicate.
  • having section in the QueryIR payload; golden vectors + Rust round-trip pins; static fixtures extended.

Blocked by

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions