Skip to content

Commit 0930429

Browse files
Merge branch 'main' into copy-stdin-verbatim-payload
2 parents edd1396 + 30d0836 commit 0930429

6 files changed

Lines changed: 89 additions & 0 deletions

File tree

src/ast/query.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,21 @@ pub enum TableFactor {
16531653
/// Optional alias for the resulting table.
16541654
alias: Option<TableAlias>,
16551655
},
1656+
/// Object unpivoting on a SUPER expression in the FROM clause.
1657+
///
1658+
/// Syntax:
1659+
/// ```sql
1660+
/// UNPIVOT expression AS value_alias [AT attribute_alias]
1661+
/// ```
1662+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/query-super.html#unpivoting)
1663+
UnpivotExpr {
1664+
/// SUPER expression to unpivot.
1665+
expression: Expr,
1666+
/// Alias for the generated unpivoted value.
1667+
value_alias: Ident,
1668+
/// Optional alias for the generated attribute key/index.
1669+
attribute_alias: Option<Ident>,
1670+
},
16561671
/// A `MATCH_RECOGNIZE` operation on a table.
16571672
///
16581673
/// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize>.
@@ -2422,6 +2437,17 @@ impl fmt::Display for TableFactor {
24222437
}
24232438
Ok(())
24242439
}
2440+
TableFactor::UnpivotExpr {
2441+
expression,
2442+
value_alias,
2443+
attribute_alias,
2444+
} => {
2445+
write!(f, "UNPIVOT {expression} AS {value_alias}")?;
2446+
if let Some(attribute_alias) = attribute_alias {
2447+
write!(f, " AT {attribute_alias}")?;
2448+
}
2449+
Ok(())
2450+
}
24252451
TableFactor::MatchRecognize {
24262452
table,
24272453
partition_by,

src/ast/spans.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,6 +2063,15 @@ impl Spanned for TableFactor {
20632063
.chain(columns.iter().map(|ilist| ilist.span()))
20642064
.chain(alias.as_ref().map(|alias| alias.span())),
20652065
),
2066+
TableFactor::UnpivotExpr {
2067+
expression,
2068+
value_alias,
2069+
attribute_alias,
2070+
} => union_spans(
2071+
core::iter::once(expression.span())
2072+
.chain(core::iter::once(value_alias.span))
2073+
.chain(attribute_alias.as_ref().map(|alias| alias.span)),
2074+
),
20662075
TableFactor::MatchRecognize {
20672076
table,
20682077
partition_by,

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,16 @@ pub trait Dialect: Debug + Any {
12671267
false
12681268
}
12691269

1270+
/// Returns true if the dialect supports object-unpivot table factors in the FROM clause.
1271+
///
1272+
/// Syntax:
1273+
/// ```sql
1274+
/// SELECT * FROM T UNPIVOT expression AS value_alias [AT attribute_alias]
1275+
/// ```
1276+
fn supports_unpivot_expr(&self) -> bool {
1277+
false
1278+
}
1279+
12701280
/// Returns true if the dialect supports the `CONSTRAINT` keyword without a name
12711281
/// in table constraint definitions.
12721282
///

src/dialect/redshift.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ impl Dialect for RedshiftSqlDialect {
118118
true
119119
}
120120

121+
fn supports_unpivot_expr(&self) -> bool {
122+
true
123+
}
124+
121125
fn supports_string_escape_constant(&self) -> bool {
122126
true
123127
}

src/parser/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16597,6 +16597,12 @@ impl<'a> Parser<'a> {
1659716597
// `(mytable AS alias)`
1659816598
alias.replace(outer_alias);
1659916599
}
16600+
TableFactor::UnpivotExpr { .. } => {
16601+
return Err(ParserError::ParserError(
16602+
"alias after parenthesized UNPIVOT expression is not supported"
16603+
.to_string(),
16604+
))
16605+
}
1660016606
};
1660116607
}
1660216608
// Do not store the extra set of parens in the AST
@@ -16678,6 +16684,8 @@ impl<'a> Parser<'a> {
1667816684
with_offset_alias,
1667916685
with_ordinality,
1668016686
})
16687+
} else if self.dialect.supports_unpivot_expr() && self.peek_keyword(Keyword::UNPIVOT) {
16688+
self.parse_unpivot_expr_table_factor()
1668116689
} else if self.parse_keyword_with_tokens(Keyword::JSON_TABLE, &[Token::LParen]) {
1668216690
let json_expr = self.parse_expr()?;
1668316691
self.expect_token(&Token::Comma)?;
@@ -17676,6 +17684,28 @@ impl<'a> Parser<'a> {
1767617684
})
1767717685
}
1767817686

17687+
/// Parse an object UNPIVOT table factor in FROM clause.
17688+
///
17689+
/// Syntax:
17690+
/// `UNPIVOT expression AS value_alias [AT attribute_alias]`
17691+
pub fn parse_unpivot_expr_table_factor(&mut self) -> Result<TableFactor, ParserError> {
17692+
self.expect_keyword_is(Keyword::UNPIVOT)?;
17693+
let expression = self.parse_expr()?;
17694+
self.expect_keyword_is(Keyword::AS)?;
17695+
let value_alias = self.parse_identifier()?;
17696+
let attribute_alias = if self.parse_keyword(Keyword::AT) {
17697+
Some(self.parse_identifier()?)
17698+
} else {
17699+
None
17700+
};
17701+
17702+
Ok(TableFactor::UnpivotExpr {
17703+
expression,
17704+
value_alias,
17705+
attribute_alias,
17706+
})
17707+
}
17708+
1767917709
/// Parse a JOIN constraint (`NATURAL`, `ON <expr>`, `USING (...)`, or no constraint).
1768017710
pub fn parse_join_constraint(&mut self, natural: bool) -> Result<JoinConstraint, ParserError> {
1768117711
if natural {

tests/sqlparser_redshift.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,13 @@ fn test_partiql_from_alias_with_at_index() {
542542
_ => panic!("expected table factor"),
543543
}
544544
}
545+
546+
#[test]
547+
fn parse_unpivot_expression() {
548+
let dialects = all_dialects_where(|d| d.supports_unpivot_expr());
549+
550+
dialects.verified_stmt(
551+
"SELECT t.id, k, v FROM test_colors AS t, UNPIVOT t.count_by_color AS v AT k",
552+
);
553+
dialects.verified_stmt("SELECT t.id, k, v FROM test_colors AS t, UNPIVOT t AS v AT k");
554+
}

0 commit comments

Comments
 (0)