Skip to content

feat(sql-macros)!: relation slots with COLUMNS groups, replacing COLUMN OF - #1254

Merged
kvlonge merged 5 commits into
mainfrom
feat/relation-slots
Oct 26, 2026
Merged

kvlonge merged 5 commits into
mainfrom
feat/relation-slots

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Why

A SQL macro meant for several grains had to be written once per grain, and COLUMN OF split one
input across arguments that had to agree with it.

Changes

  • RELATION (...) declares slots: typed single columns and untyped COLUMNS groups.
  • Calls map slots with rel.slot := column or rel.slot := (a, b). Groups must be mapped.
  • Reads follow the slot kind, never the mapping:
    • alias.@rel.slot reads through the alias; a group's prefix applies to every column.
    • A plain group read always stands as bare column names, so later CTEs need no alias. A name
      two joined relations share fails with B003, as hand-written SQL does.
    • A plain single-slot read always reads its column through the passed relation.
    • Reads in USING (...) are always bare. Forwarded slots keep their inner kind.
  • Capture check: a plain group read in a subquery (including LATERAL) fails with P017 when a
    relation there other than the passed one has one of its columns, or its columns are unknown.
  • Group rules and call checks (columns, types, AS outside expression subqueries, P018
    clashes with defined or bare columns) have exact messages.
  • Rules lint a slot read without taking the slot's name as its column.
  • COLUMN OF is removed; use relation slots.
  • The duckshop benchmarks use slots. Docs and the bundled skill are updated.

BREAKING CHANGE: COLUMN OF SQL macro arguments are removed; use relation slots.

Verification

  • Rust tests for expansion by kind, group rules and call errors.
  • DuckDB E2E: a macro reading @weekly.keys plainly in PARTITION BY, later CTEs and GROUP BY
    compiles at two grains to SQL byte-identical to two per-grain macros, with the same rows.
    Capture, LATERAL, forwarding, USING, subquery AS and clash cases, ambiguous joins,
    lineage, Rules, typing, --since.
  • Both duckshop benchmarks compile with no diagnostics.
  • make test, make check-ci, fensu check and the hygiene scan pass.

…MN OF

A RELATION type declares everything a macro reads from it as slots: typed
single columns and untyped COLUMNS groups the caller fills. Bodies read a
slot as alias.@rel.slot, or plainly as @rel.slot: a plain group read always
stands as bare column names, and a plain single-slot read always reads the
slot's column through the passed relation. A plain group read inside a
subquery fails when a relation of that subquery has one of its columns, or
its columns are unknown, since the bare names could bind there. Calls map
slots with rel.slot := column or rel.slot := (columns). Group positions,
mappings, overlaps, types and name clashes are checked with exact messages.

BREAKING CHANGE: COLUMN OF SQL macro arguments are removed; use relation slots.
…r column

A mapped slot reads whatever column the call passes, so alias.@rel.slot AS slot is no
self-alias. Aliased reads neutralize whole and bare reads hide the slot name.
@kvlonge
kvlonge enabled auto-merge (squash) October 26, 2026 00:02
@kvlonge
kvlonge merged commit 911882f into main Oct 26, 2026
46 checks passed
@kvlonge
kvlonge deleted the feat/relation-slots branch October 26, 2026 00:28
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