1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Moderately AI Inc.
//! DuckDB `PIVOT` / `UNPIVOT` — the row-to-column (and inverse) relational operators.
//!
//! DuckDB exposes each operator through *two* surfaces that build the same operator:
//! the leading-keyword **statement** (`PIVOT t ON year USING sum(x) GROUP BY city`,
//! `UNPIVOT t ON a, b INTO NAME n VALUE v`) and the SQL-standard **table factor**
//! written after a relation in `FROM` (`t PIVOT (sum(x) FOR year IN (2000, 2010))`,
//! `t UNPIVOT (v FOR n IN (a, b))`). The two spellings canonicalize onto
//! one shape per operator — [`Pivot`] and [`Unpivot`] — carrying a [`PivotSpelling`] /
//! [`UnpivotSpelling`] tag so rendering reproduces the written surface without a second
//! node. Each core is hosted in both positions:
//! [`Statement::Pivot`](crate::ast::Statement)/`Unpivot` (dispatched as a top-level
//! statement, DuckDB's `PivotStatement`) and
//! [`TableFactor::Pivot`](crate::ast::TableFactor)/`Unpivot` (the `FROM`-suffix form),
//! sharing the operator fields while the table-factor position owns the trailing alias.
//!
//! Both operators are gated on
//! [`TableFactorSyntax::pivot`](crate::dialect::TableExpressionSyntax) /
//! [`unpivot`](crate::dialect::TableExpressionSyntax) (DuckDb/Lenient) and rest on the
//! reservation of `PIVOT`/`UNPIVOT` (DuckDB `duckdb_keywords()` class `reserved`, like
//! `QUALIFY`) — without it a trailing `PIVOT` would be swallowed as the source's alias.
use ;
use crateMeta;
use ThinVec;
/// DuckDB's `PIVOT` operator: rotate the distinct values of the pivot column(s) into
/// columns, aggregating each cell.
///
/// One canonical shape for both DuckDB surfaces; [`spelling`](Self::spelling)
/// records which was written so it round-trips. The fields cover both:
///
/// - the statement `PIVOT <source> [ON <pivot_on>] [USING <aggregates>] [GROUP BY
/// <group_by>]` — any of `ON`/`USING`/`GROUP BY` may be absent, and an `ON` entry may
/// carry an inline `IN (...)` value list (`ON year IN (2000, 2010)`) or none
/// (auto-detected at bind time);
/// - the table factor `<source> PIVOT (<aggregates> FOR <col> IN (…) [<col> IN (…)]…
/// [GROUP BY <group_by>])` — one `FOR` keyword heading one or more column heads
/// (the extra heads are written bare; a second `FOR` is an engine syntax error),
/// each with a required `IN` source, and at least one aggregate (DuckDB
/// syntax-rejects an empty aggregate list here; both probed on 1.5.4).
///
/// `source` is boxed to break the `TableFactor` → `Pivot` → `TableFactor` type cycle
/// (`FROM (SELECT …) PIVOT (…)` nests a derived table; a bare `PIVOT (SELECT …) ON …`
/// nests one too). The source keeps its own correlation alias; the *pivot's* alias
/// (`… PIVOT (…) AS p`) belongs to the enclosing
/// [`TableFactor::Pivot`](crate::ast::TableFactor), never to a statement.
/// The `UNPIVOT` operator: collapse a set of columns into `NAME`/`VALUE` row pairs
/// (the inverse of [`Pivot`]).
///
/// One canonical shape for both surfaces; [`spelling`](Self::spelling)
/// records which was written. The fields cover both:
///
/// - the DuckDB statement `UNPIVOT <source> ON <columns> [INTO NAME <name> VALUE
/// <value>]` — the `INTO` clause renames the output name/value columns (absent leaves
/// DuckDB's default `name`/`value`);
/// - the table factor `<source> UNPIVOT [INCLUDE|EXCLUDE NULLS] (<value> FOR <name> IN
/// (<columns>))` — the shared DuckDB/BigQuery/Snowflake surface (the standard is
/// reachable off the DuckDB [`unpivot`](crate::dialect::TableFactorSyntax::unpivot)
/// flag under
/// [`pivot_value_sources`](crate::dialect::TableFactorSyntax::pivot_value_sources)).
/// The `NULLS` marker is table-factor-only (the statement form rejects it, so
/// [`null_inclusion`](Self::null_inclusion) is always `None` there).
///
/// [`value`](Self::value) and [`name`](Self::name) are lists because DuckDB admits a
/// multi-column unpivot (`(v1, v2) FOR n IN ((a, b), (c, d))`), whose value name list
/// has more than one entry; the common form fills each with a single [`Ident`].
/// A table-factor `UNPIVOT`'s explicit null-row treatment — the `INCLUDE NULLS` /
/// `EXCLUDE NULLS` marker shared by DuckDB, BigQuery, and Snowflake.
///
/// `EXCLUDE NULLS` is every engine's default, so [`Unpivot::null_inclusion`] wraps this
/// in an `Option`: `None` is the unwritten default and each variant records the marker as
/// written so it round-trips (an explicit `EXCLUDE NULLS` is preserved rather than elided
/// to the bare default). A fieldless presence tag — the [`UnpivotSpelling`] precedent, and
/// the direct sqlparser-rs `NullInclusion` parity.
/// An aliased expression inside a [`Pivot`]: a `USING` aggregate (`sum(x) AS total`) or
/// an `IN`-list value (`2000 AS y2000`).
///
/// One shape for both because they are identical surface — an expression with an
/// optional output-name alias. The alias may be written as an identifier or a string
/// literal (`2000 AS 'y2k'`), recorded via the [`Ident`]'s
/// [`QuoteStyle::Single`](super::QuoteStyle) exactly like MySQL's string projection
/// aliases, so the spelling round-trips.
/// One pivot column and its optional `IN` source — a statement `ON` entry (`year`,
/// `year IN (2000, 2010)`, `year IN (SELECT …)`) or a table-factor `FOR` head
/// (`FOR <col> IN (<values>)`, `FOR <col> IN <enum>`,
/// `FOR <col> IN (ANY [ORDER BY …])`, `FOR <col> IN (<subquery>)`).
///
/// The value source is spread across three mutually-exclusive fields rather than one
/// enum: [`values`](Self::values) (the explicit list) and
/// [`enum_source`](Self::enum_source) are DuckDB's native shapes and stay untouched,
/// while [`value_source`](Self::value_source) carries the standard `ANY`/subquery forms
/// added for the Snowflake/BigQuery/Oracle table factor. This deliberately deviates from
/// sqlparser-rs's single `PivotValueSource { List, Any, Subquery }` enum: that shape
/// lives at the whole-pivot level and cannot express DuckDB's *per-column* `FOR y IN (…)
/// m IN (…)` heads or its `IN <enum>` form, so this node keeps the source per column and
/// folds the two new forms into an `Option` beside the existing DuckDB fields (see
/// [`PivotValueSource`]). Exactly one of the three is populated for any one column.
/// A standard PIVOT table factor's non-list `IN` value source — the Snowflake/BigQuery/
/// Oracle forms beyond an explicit value list.
///
/// The explicit list (`IN (v1 [AS a], …)`) stays on [`PivotColumn::values`] and DuckDB's
/// `IN <enum>` on [`PivotColumn::enum_source`]; this enum carries only the two forms
/// those fields cannot: the wildcard `ANY` and a value-supplying subquery. It mirrors the
/// `Any`/`Subquery` arms of sqlparser-rs's `PivotValueSource` (the `List` arm is our
/// `values` field — see [`PivotColumn`]).
/// One `UNPIVOT` column entry: a statement `ON` item or an `IN`-list entry, holding one
/// column (`a`) or a group (`(a, b)`), with an optional alias (`(a, b) AS ab`).
/// Which surface produced a [`Pivot`] — the leading-keyword statement or the `FROM`
/// table factor. One operator, two spellings kept as data (the
/// [`SelectSpelling`](crate::ast::SelectSpelling) precedent); the renderer re-emits the
/// written form (`PIVOT t ON …` vs `t PIVOT (… FOR … IN …)`).
/// Which surface produced an [`Unpivot`] — the mirror of [`PivotSpelling`].