Skip to content

Commit 5dc4c8b

Browse files
authored
Merge pull request #10676 from SkyFan2002/pivot
2 parents 448db8a + 6a927a2 commit 5dc4c8b

File tree

16 files changed

+1029
-10
lines changed

16 files changed

+1029
-10
lines changed

src/query/ast/src/ast/format/ast_format.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,6 +2193,8 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor {
21932193
table,
21942194
alias,
21952195
travel_point,
2196+
pivot,
2197+
unpivot,
21962198
} => {
21972199
let mut name = String::new();
21982200
name.push_str("TableIdentifier ");
@@ -2206,6 +2208,16 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor {
22062208
}
22072209
name.push_str(&table.to_string());
22082210

2211+
if let Some(pivot) = pivot {
2212+
name.push(' ');
2213+
name.push_str(&pivot.to_string());
2214+
}
2215+
2216+
if let Some(unpivot) = unpivot {
2217+
name.push(' ');
2218+
name.push_str(&unpivot.to_string());
2219+
}
2220+
22092221
let mut children = Vec::new();
22102222
if let Some(travel_point) = travel_point {
22112223
self.visit_time_travel_point(travel_point);

src/query/ast/src/ast/format/syntax/query.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ pub(crate) fn pretty_table(table: TableReference) -> RcDoc<'static> {
275275
table,
276276
alias,
277277
travel_point,
278+
pivot,
279+
unpivot,
278280
} => if let Some(catalog) = catalog {
279281
RcDoc::text(catalog.to_string()).append(RcDoc::text("."))
280282
} else {
@@ -286,6 +288,16 @@ pub(crate) fn pretty_table(table: TableReference) -> RcDoc<'static> {
286288
RcDoc::nil()
287289
})
288290
.append(RcDoc::text(table.to_string()))
291+
.append(if let Some(pivot) = pivot {
292+
RcDoc::text(format!(" {pivot}"))
293+
} else {
294+
RcDoc::nil()
295+
})
296+
.append(if let Some(unpivot) = unpivot {
297+
RcDoc::text(format!(" {unpivot}"))
298+
} else {
299+
RcDoc::nil()
300+
})
289301
.append(if let Some(TimeTravelPoint::Snapshot(sid)) = travel_point {
290302
RcDoc::text(format!(" AT (SNAPSHOT => {sid})"))
291303
} else if let Some(TimeTravelPoint::Timestamp(ts)) = travel_point {

src/query/ast/src/ast/query.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,26 @@ pub enum SelectTarget {
145145
},
146146
}
147147

148+
impl SelectTarget {
149+
pub fn is_star(&self) -> bool {
150+
match self {
151+
SelectTarget::AliasedExpr { .. } => false,
152+
SelectTarget::QualifiedName { qualified, .. } => {
153+
matches!(qualified.last(), Some(Indirection::Star(_)))
154+
}
155+
}
156+
}
157+
158+
pub fn exclude(&mut self, exclude: Vec<Identifier>) {
159+
match self {
160+
SelectTarget::AliasedExpr { .. } => unreachable!(),
161+
SelectTarget::QualifiedName { exclude: e, .. } => {
162+
*e = Some(exclude);
163+
}
164+
}
165+
}
166+
}
167+
148168
pub type QualifiedName = Vec<Indirection>;
149169

150170
/// Indirection of a select result, like a part of `db.table.column`.
@@ -164,6 +184,20 @@ pub enum TimeTravelPoint {
164184
Timestamp(Box<Expr>),
165185
}
166186

187+
#[derive(Debug, Clone, PartialEq)]
188+
pub struct Pivot {
189+
pub aggregate: Expr,
190+
pub value_column: Identifier,
191+
pub values: Vec<Expr>,
192+
}
193+
194+
#[derive(Debug, Clone, PartialEq)]
195+
pub struct Unpivot {
196+
pub value_column: Identifier,
197+
pub column_name: Identifier,
198+
pub names: Vec<Identifier>,
199+
}
200+
167201
/// A table name or a parenthesized subquery with an optional alias
168202
#[derive(Debug, Clone, PartialEq)]
169203
pub enum TableReference {
@@ -175,6 +209,8 @@ pub enum TableReference {
175209
table: Identifier,
176210
alias: Option<TableAlias>,
177211
travel_point: Option<TimeTravelPoint>,
212+
pivot: Option<Box<Pivot>>,
213+
unpivot: Option<Box<Unpivot>>,
178214
},
179215
// `TABLE(expr)[ AS alias ]`
180216
TableFunction {
@@ -202,6 +238,22 @@ pub enum TableReference {
202238
},
203239
}
204240

241+
impl TableReference {
242+
pub fn pivot(&self) -> Option<&Pivot> {
243+
match self {
244+
TableReference::Table { pivot, .. } => pivot.as_ref().map(|b| b.as_ref()),
245+
_ => None,
246+
}
247+
}
248+
249+
pub fn unpivot(&self) -> Option<&Unpivot> {
250+
match self {
251+
TableReference::Table { unpivot, .. } => unpivot.as_ref().map(|b| b.as_ref()),
252+
_ => None,
253+
}
254+
}
255+
}
256+
205257
#[derive(Debug, Clone, PartialEq, Eq)]
206258
pub struct TableAlias {
207259
pub name: Identifier,
@@ -282,6 +334,28 @@ impl Display for TableAlias {
282334
}
283335
}
284336

337+
impl Display for Pivot {
338+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
339+
write!(f, "PIVOT({} FOR {} IN (", self.aggregate, self.value_column)?;
340+
write_comma_separated_list(f, &self.values)?;
341+
write!(f, "))")?;
342+
Ok(())
343+
}
344+
}
345+
346+
impl Display for Unpivot {
347+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
348+
write!(
349+
f,
350+
"UNPIVOT({} FOR {} IN (",
351+
self.value_column, self.column_name
352+
)?;
353+
write_comma_separated_list(f, &self.names)?;
354+
write!(f, "))")?;
355+
Ok(())
356+
}
357+
}
358+
285359
impl Display for TableReference {
286360
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287361
match self {
@@ -292,6 +366,8 @@ impl Display for TableReference {
292366
table,
293367
alias,
294368
travel_point,
369+
pivot,
370+
unpivot,
295371
} => {
296372
write_period_separated_list(
297373
f,
@@ -309,6 +385,13 @@ impl Display for TableReference {
309385
if let Some(alias) = alias {
310386
write!(f, " AS {alias}")?;
311387
}
388+
if let Some(pivot) = pivot {
389+
write!(f, " {pivot}")?;
390+
}
391+
392+
if let Some(unpivot) = unpivot {
393+
write!(f, " {unpivot}")?;
394+
}
312395
}
313396
TableReference::TableFunction {
314397
span: _,
@@ -545,6 +628,7 @@ impl Display for CTE {
545628
Ok(())
546629
}
547630
}
631+
548632
impl Display for With {
549633
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
550634
if self.recursive {

src/query/ast/src/parser/query.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ pub enum TableReferenceElement {
264264
table: Identifier,
265265
alias: Option<TableAlias>,
266266
travel_point: Option<TimeTravelPoint>,
267+
pivot: Option<Box<Pivot>>,
268+
unpivot: Option<Box<Unpivot>>,
267269
},
268270
// `TABLE(expr)[ AS alias ]`
269271
TableFunction {
@@ -292,16 +294,42 @@ pub enum TableReferenceElement {
292294
}
293295

294296
pub fn table_reference_element(i: Input) -> IResult<WithSpan<TableReferenceElement>> {
297+
// PIVOT(expr FOR col IN (ident, ...))
298+
let pivot = map(
299+
rule! {
300+
PIVOT ~ "(" ~ #expr ~ FOR ~ #ident ~ IN ~ "(" ~ #comma_separated_list1(expr) ~ ")" ~ ")"
301+
},
302+
|(_pivot, _, aggregate, _for, value_column, _in, _, values, _, _)| Pivot {
303+
aggregate,
304+
value_column,
305+
values,
306+
},
307+
);
308+
// UNPIVOT(ident for ident IN (ident, ...))
309+
let unpivot = map(
310+
rule! {
311+
UNPIVOT ~ "(" ~ #ident ~ FOR ~ #ident ~ IN ~ "(" ~ #comma_separated_list1(ident) ~ ")" ~ ")"
312+
},
313+
|(_unpivot, _, value_column, _for, column_name, _in, _, names, _, _)| Unpivot {
314+
value_column,
315+
column_name,
316+
names,
317+
},
318+
);
295319
let aliased_table = map(
296320
rule! {
297-
#period_separated_idents_1_to_3 ~ (AT ~ #travel_point)? ~ #table_alias?
321+
#period_separated_idents_1_to_3 ~ (AT ~ #travel_point)? ~ #table_alias? ~ #pivot? ~ #unpivot?
298322
},
299-
|((catalog, database, table), travel_point_opt, alias)| TableReferenceElement::Table {
300-
catalog,
301-
database,
302-
table,
303-
alias,
304-
travel_point: travel_point_opt.map(|p| p.1),
323+
|((catalog, database, table), travel_point_opt, alias, pivot, unpivot)| {
324+
TableReferenceElement::Table {
325+
catalog,
326+
database,
327+
table,
328+
alias,
329+
travel_point: travel_point_opt.map(|p| p.1),
330+
pivot: pivot.map(Box::new),
331+
unpivot: unpivot.map(Box::new),
332+
}
305333
},
306334
);
307335
let table_function = map(
@@ -426,13 +454,17 @@ impl<'a, I: Iterator<Item = WithSpan<'a, TableReferenceElement>>> PrattParser<I>
426454
table,
427455
alias,
428456
travel_point,
457+
pivot,
458+
unpivot,
429459
} => TableReference::Table {
430460
span: transform_span(input.span.0),
431461
catalog,
432462
database,
433463
table,
434464
alias,
435465
travel_point,
466+
pivot,
467+
unpivot,
436468
},
437469
TableReferenceElement::TableFunction {
438470
name,

src/query/ast/src/parser/statement.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,7 @@ pub fn statement(i: Input) -> IResult<StatementMsg> {
11401140
| #show_file_formats: "`SHOW FILE FORMATS`"
11411141
| #drop_file_format: "`DROP FILE FORMAT [ IF EXISTS ] <format_name>`"
11421142
),
1143-
rule! (
1143+
rule!(
11441144
#copy_into: "`COPY
11451145
INTO { internalStage | externalStage | externalLocation | [<database_name>.]<table_name> }
11461146
FROM { internalStage | externalStage | externalLocation | [<database_name>.]<table_name> | ( <query> ) }
@@ -1150,7 +1150,7 @@ pub fn statement(i: Input) -> IResult<StatementMsg> {
11501150
[ VALIDATION_MODE = RETURN_ROWS ]
11511151
[ copyOptions ]`"
11521152
),
1153-
rule! (
1153+
rule!(
11541154
#call: "`CALL <procedure_name>(<parameter>, ...)`"
11551155
),
11561156
rule!(
@@ -1889,6 +1889,8 @@ pub fn table_reference_only(i: Input) -> IResult<TableReference> {
18891889
table,
18901890
alias: None,
18911891
travel_point: None,
1892+
pivot: None,
1893+
unpivot: None,
18921894
},
18931895
)(i)
18941896
}

src/query/ast/src/parser/token.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,10 @@ pub enum TokenKind {
681681
SECOND,
682682
#[token("SELECT", ignore(ascii_case))]
683683
SELECT,
684+
#[token("PIVOT", ignore(ascii_case))]
685+
PIVOT,
686+
#[token("UNPIVOT", ignore(ascii_case))]
687+
UNPIVOT,
684688
#[token("SEGMENT", ignore(ascii_case))]
685689
SEGMENT,
686690
#[token("SET", ignore(ascii_case))]
@@ -1136,6 +1140,8 @@ impl TokenKind {
11361140
// | TokenKind::REFERENCES
11371141
| TokenKind::RIGHT
11381142
| TokenKind::SELECT
1143+
| TokenKind::PIVOT
1144+
| TokenKind::UNPIVOT
11391145
// | TokenKind::SESSION_USER
11401146
// | TokenKind::SIMILAR
11411147
| TokenKind::SOME

src/query/ast/tests/it/parser.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,8 @@ fn test_query() {
485485
r#"select * from t1 union select * from t2 intersect select * from t3"#,
486486
r#"(select * from t1 union select * from t2) union select * from t3"#,
487487
r#"select * from t1 union (select * from t2 union select * from t3)"#,
488+
r#"select * from monthly_sales pivot(sum(amount) for month in ('JAN', 'FEB', 'MAR', 'APR')) order by empid"#,
489+
r#"select * from monthly_sales_1 unpivot(sales for month in (jan, feb, mar, april)) order by empid"#,
488490
];
489491

490492
for case in cases {

0 commit comments

Comments
 (0)