This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-2375-2f3b5b824c24304720f1e8e504f54b9910e63bad
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit fd0421a184cb1dc8501a505e51496543d4e060fb
Author: kfirSatori <[email protected]>
AuthorDate: Mon Aug 3 14:17:31 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 2d51d471..ab539036 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -16584,6 +16584,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
@@ -16665,6 +16671,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)?;
@@ -17663,6 +17671,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]

Reply via email to