This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 30d0836b Added support for unpivot in Redshift with expression and
bracketsless (#2375)
30d0836b is described below
commit 30d0836b673d2198262f0662596ac8dbb7379987
Author: kfirSatori <[email protected]>
AuthorDate: Tue Aug 4 18:28:29 2026 +0300
Added support for unpivot in Redshift with expression and bracketsless
(#2375)
---
src/ast/query.rs | 26 ++++++++++++++++++++++++++
src/ast/spans.rs | 9 +++++++++
src/dialect/mod.rs | 10 ++++++++++
src/dialect/redshift.rs | 4 ++++
src/parser/mod.rs | 30 ++++++++++++++++++++++++++++++
tests/sqlparser_redshift.rs | 10 ++++++++++
6 files changed, 89 insertions(+)
diff --git a/src/ast/query.rs b/src/ast/query.rs
index c683d66c..2ada46a9 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -1653,6 +1653,21 @@ pub enum TableFactor {
/// Optional alias for the resulting table.
alias: Option<TableAlias>,
},
+ /// Object unpivoting on a SUPER expression in the FROM clause.
+ ///
+ /// Syntax:
+ /// ```sql
+ /// UNPIVOT expression AS value_alias [AT attribute_alias]
+ /// ```
+ ///
[Redshift](https://docs.aws.amazon.com/redshift/latest/dg/query-super.html#unpivoting)
+ UnpivotExpr {
+ /// SUPER expression to unpivot.
+ expression: Expr,
+ /// Alias for the generated unpivoted value.
+ value_alias: Ident,
+ /// Optional alias for the generated attribute key/index.
+ attribute_alias: Option<Ident>,
+ },
/// A `MATCH_RECOGNIZE` operation on a table.
///
/// See
<https://docs.snowflake.com/en/sql-reference/constructs/match_recognize>.
@@ -2422,6 +2437,17 @@ impl fmt::Display for TableFactor {
}
Ok(())
}
+ TableFactor::UnpivotExpr {
+ expression,
+ value_alias,
+ attribute_alias,
+ } => {
+ write!(f, "UNPIVOT {expression} AS {value_alias}")?;
+ if let Some(attribute_alias) = attribute_alias {
+ write!(f, " AT {attribute_alias}")?;
+ }
+ Ok(())
+ }
TableFactor::MatchRecognize {
table,
partition_by,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index e1fa0c75..a34fe66d 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -2063,6 +2063,15 @@ impl Spanned for TableFactor {
.chain(columns.iter().map(|ilist| ilist.span()))
.chain(alias.as_ref().map(|alias| alias.span())),
),
+ TableFactor::UnpivotExpr {
+ expression,
+ value_alias,
+ attribute_alias,
+ } => union_spans(
+ core::iter::once(expression.span())
+ .chain(core::iter::once(value_alias.span))
+ .chain(attribute_alias.as_ref().map(|alias| alias.span)),
+ ),
TableFactor::MatchRecognize {
table,
partition_by,
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index f884b054..f99cbe2e 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1267,6 +1267,16 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect supports object-unpivot table factors in
the FROM clause.
+ ///
+ /// Syntax:
+ /// ```sql
+ /// SELECT * FROM T UNPIVOT expression AS value_alias [AT attribute_alias]
+ /// ```
+ fn supports_unpivot_expr(&self) -> bool {
+ false
+ }
+
/// Returns true if the dialect supports the `CONSTRAINT` keyword without
a name
/// in table constraint definitions.
///
diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs
index 09c6d694..bb48b14e 100644
--- a/src/dialect/redshift.rs
+++ b/src/dialect/redshift.rs
@@ -118,6 +118,10 @@ impl Dialect for RedshiftSqlDialect {
true
}
+ fn supports_unpivot_expr(&self) -> bool {
+ true
+ }
+
fn supports_string_escape_constant(&self) -> bool {
true
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index d2668d3b..b2b3f42b 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -16587,6 +16587,12 @@ impl<'a> Parser<'a> {
// `(mytable AS alias)`
alias.replace(outer_alias);
}
+ TableFactor::UnpivotExpr { .. } => {
+ return Err(ParserError::ParserError(
+ "alias after parenthesized UNPIVOT expression
is not supported"
+ .to_string(),
+ ))
+ }
};
}
// Do not store the extra set of parens in the AST
@@ -16668,6 +16674,8 @@ impl<'a> Parser<'a> {
with_offset_alias,
with_ordinality,
})
+ } else if self.dialect.supports_unpivot_expr() &&
self.peek_keyword(Keyword::UNPIVOT) {
+ self.parse_unpivot_expr_table_factor()
} else if self.parse_keyword_with_tokens(Keyword::JSON_TABLE,
&[Token::LParen]) {
let json_expr = self.parse_expr()?;
self.expect_token(&Token::Comma)?;
@@ -17666,6 +17674,28 @@ impl<'a> Parser<'a> {
})
}
+ /// Parse an object UNPIVOT table factor in FROM clause.
+ ///
+ /// Syntax:
+ /// `UNPIVOT expression AS value_alias [AT attribute_alias]`
+ pub fn parse_unpivot_expr_table_factor(&mut self) -> Result<TableFactor,
ParserError> {
+ self.expect_keyword_is(Keyword::UNPIVOT)?;
+ let expression = self.parse_expr()?;
+ self.expect_keyword_is(Keyword::AS)?;
+ let value_alias = self.parse_identifier()?;
+ let attribute_alias = if self.parse_keyword(Keyword::AT) {
+ Some(self.parse_identifier()?)
+ } else {
+ None
+ };
+
+ Ok(TableFactor::UnpivotExpr {
+ expression,
+ value_alias,
+ attribute_alias,
+ })
+ }
+
/// Parse a JOIN constraint (`NATURAL`, `ON <expr>`, `USING (...)`, or no
constraint).
pub fn parse_join_constraint(&mut self, natural: bool) ->
Result<JoinConstraint, ParserError> {
if natural {
diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs
index 9609f521..197d5b8f 100644
--- a/tests/sqlparser_redshift.rs
+++ b/tests/sqlparser_redshift.rs
@@ -542,3 +542,13 @@ fn test_partiql_from_alias_with_at_index() {
_ => panic!("expected table factor"),
}
}
+
+#[test]
+fn parse_unpivot_expression() {
+ let dialects = all_dialects_where(|d| d.supports_unpivot_expr());
+
+ dialects.verified_stmt(
+ "SELECT t.id, k, v FROM test_colors AS t, UNPIVOT t.count_by_color AS
v AT k",
+ );
+ dialects.verified_stmt("SELECT t.id, k, v FROM test_colors AS t, UNPIVOT t
AS v AT k");
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]