Skip to content

Add DuckDB PIVOT query support#1025

Open
SeanCassiere wants to merge 6 commits into
uwdata:mainfrom
SeanCassiere:pivot-operator
Open

Add DuckDB PIVOT query support#1025
SeanCassiere wants to merge 6 commits into
uwdata:mainfrom
SeanCassiere:pivot-operator

Conversation

@SeanCassiere
Copy link
Copy Markdown

Building on the discussion in #892, this PR is basically my first stab at adding support for DuckDB’s simplified PIVOT syntax to the sql package.

This add Query.pivot(source) as a new query-level AST node, with chainable support for ON, IN, USING, and GROUP BY:

Query
  .pivot('sales')
  .on('year')
  .in(2020, 2021)
  .using({ total: sum('amount') })
  .groupby('region')

The new node is exported publicly, works with the existing SQL visitor/codegen path, can be used as a FROM source in SelectQuery, and is covered by focused tests for construction, rendering, cloning, and clause behavior.


This PR intentionally sticks to DuckDB’s simplified PIVOT form, but of-course should be extendable to support the SQL specific implementation. It does not add SQL-standard PIVOT, runtime validation that USING expressions are aggregates, or rendered ORDER BY/LIMIT/OFFSET behavior for pivot queries. So any feedback here would be great!

Introduce a first-class PivotQuery node for DuckDB simplified PIVOT
queries. The new query type can be constructed with Query.pivot(source),
normalizes string sources to table refs, participates in generic query
type checks, and exposes an isPivotQuery guard.

Wire the pivot node through public AST exports, visitor dispatch, DuckDB
shell rendering, and traversal metadata so it can be used anywhere a
Query source is accepted, including nested SELECT FROM sources.

Add focused tests covering construction, type guards, source
normalization, exported API usage, and basic composability as a SELECT
source.
Add a chainable PivotQuery.on(...) API for DuckDB simplified PIVOT
queries.

ON expressions now use the existing expression-list coercion path, so
string inputs are recorded as column references while existing AST
expressions are preserved.

Render recorded ON expressions in DuckDB SQL generation as a single
ordered ON clause. Repeated on(...) calls append to the existing
expression list.

Add pivot tests covering string columns, variadic and array inputs,
repeated calls, AST expression inputs, and use of pivot clauses when
a PivotQuery is nested as a SELECT source.
Add PivotQuery.in(...) for DuckDB simplified PIVOT queries so callers
can constrain and order generated pivot columns with ON ... IN (...).

IN values are stored independently from ON expressions, appended across
repeated calls, and coerced as SQL literals by default while preserving
existing SQL expression nodes. Empty dynamic calls now throw a clear
error instead of producing invalid IN () SQL.

Render IN clauses immediately after pivot ON expressions, preserve pivot
IN values during cloning and deep cloning, and add coverage for literal
coercion, expression passthrough, ordering, repeated calls, nested
pivot-as-source rendering, and clone isolation.
Add a chainable PivotQuery.using(...) API for DuckDB simplified PIVOT
queries. USING expressions now accept unaliased expression inputs as
well as object-style aliases, reuse SelectClauseNode rendering for AS
aliases, and preserve caller-provided expression order across chained
calls.

Render USING clauses in the DuckDB code generator and include pivot
USING nodes in clone/deep-clone traversal.

Add focused pivot tests for unaliased, aliased, multiple, cloned, deep
cloned, and nested SELECT source USING expressions without changing the
existing pivot test coverage.
Add grouping support to PivotQuery with groupby and setGroupby methods
matching the SelectQuery API. Pivot grouping expressions now use the
existing expression coercion path, append across repeated groupby calls,
and participate in clone and visitor recursion behavior.

Update DuckDB SQL generation to emit GROUP BY after simplified PIVOT
USING clauses when grouping expressions are present. Add tests covering
string and AST grouping inputs, multiple and repeated groups, reset
behavior, cloning, deep cloning, and use as a SELECT source.
@SeanCassiere SeanCassiere changed the title Pivot operator Add DuckDB PIVOT query support May 6, 2026
@derekperkins
Copy link
Copy Markdown
Collaborator

Looking forward to this!

bunch of our optimizations would have to be carefully reviewed to make sure they still work

@domoritz brought this up in the issue, I'm not sure the best way to go about confirming this works with those materializations

@derekperkins
Copy link
Copy Markdown
Collaborator

Comparing the DuckDB simplified syntax vs the SQL standard syntax for convenience here.

image

https://duckdb.org/docs/current/sql/statements/pivot#simplified-pivot-syntax

image

https://duckdb.org/docs/current/sql/statements/pivot#sql-standard-pivot-syntax

My gut feeling is that there's no need to support the less convenient syntax in the AST, even if it is the standard. Since we have the pivot AST node, translations to other SQL syntax should still be possible, and the convenience of the DuckDB setup is 100x better for the user.

@SeanCassiere SeanCassiere marked this pull request as ready for review May 6, 2026 21:29
@derekperkins derekperkins requested a review from Copilot May 11, 2026 05:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class DuckDB PIVOT support to the mosaic/sql AST and DuckDB SQL codegen, enabling Query.pivot(source) with chainable on, in, using, and groupby clauses and use as a FROM source.

Changes:

  • Introduces PivotQuery + Query.pivot(...) / WithClause.pivot(...) builder APIs and type guards/exports.
  • Extends visitor recursion + SQL codegen dispatch to support a new PIVOT_QUERY node type.
  • Adds focused Vitest coverage for PivotQuery construction, rendering, cloning, and clause composition.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/mosaic/sql/test/pivot.test.ts New tests covering PivotQuery builder behavior, rendering, and cloning.
packages/mosaic/sql/src/visit/recurse.ts Adds PivotQuery node recursion configuration for visitors (walk/clone/rewrite).
packages/mosaic/sql/src/visit/codegen/sql.ts Adds PivotQuery to the codegen dispatch + abstract visitor surface.
packages/mosaic/sql/src/visit/codegen/duckdb.ts Implements DuckDB-specific SQL rendering for PivotQuery.
packages/mosaic/sql/src/types.ts Adds Pivot-specific expression input types.
packages/mosaic/sql/src/constants.ts Introduces PIVOT_QUERY node type constant.
packages/mosaic/sql/src/ast/query.ts Adds PivotQuery AST node, builders, type guard, and WithClause integration.
packages/mosaic/sql/src/ast/index.ts Re-exports PivotQuery APIs from the AST index barrel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/mosaic/sql/src/visit/codegen/duckdb.ts Outdated
Comment thread packages/mosaic/sql/src/ast/query.ts
Comment thread packages/mosaic/sql/test/pivot.test.ts
Render PivotQuery WITH clauses in DuckDB SQL generation so Query.with(...).pivot(...) and Query.pivot(...).with(...) preserve CTEs.

Clone inherited mutable query clause arrays for PivotQuery to avoid sharing WITH and ORDER BY state between originals and clones.

Add focused regression tests for pivot CTE rendering and clone isolation.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@derekperkins
Copy link
Copy Markdown
Collaborator

@jheer @domoritz how does this look as an approach? We can track down any performance interactions if you think this is viable and can point us in the right direction.

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.

3 participants