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

github-bot pushed a commit to branch 
gh-readonly-queue/main/pr-2261-6691f31b753708bd5952cc453b65440d7177a9bb
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 64f4b1fa2b47f957b1c8d39c3c54b0380daa32b8
Author: Andriy Romanov <[email protected]>
AuthorDate: Thu Mar 5 05:45:41 2026 -0800

    Added SORTKEY keyword parsing for redshift queries (#2261)
---
 src/ast/ddl.rs                       | 18 +++++++++++++++++-
 src/ast/helpers/stmt_create_table.rs | 14 ++++++++++++--
 src/ast/spans.rs                     |  6 ++++--
 src/keywords.rs                      |  1 +
 src/parser/mod.rs                    | 32 +++++++++++++++++++++++++++++---
 tests/sqlparser_duckdb.rs            |  1 +
 tests/sqlparser_mssql.rs             |  2 ++
 tests/sqlparser_postgres.rs          |  1 +
 tests/sqlparser_redshift.rs          | 26 ++++++++++++++++++++++++++
 9 files changed, 93 insertions(+), 8 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 895959a3..6bea28cb 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -457,6 +457,12 @@ pub enum AlterTableOperation {
     },
     /// Remove the clustering key from the table.
     DropClusteringKey,
+    /// Redshift `ALTER SORTKEY (column_list)`
+    /// <https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html>
+    AlterSortKey {
+        /// Column references in the sort key.
+        columns: Vec<Expr>,
+    },
     /// Suspend background reclustering operations.
     SuspendRecluster,
     /// Resume background reclustering operations.
@@ -993,6 +999,10 @@ impl fmt::Display for AlterTableOperation {
                 write!(f, "DROP CLUSTERING KEY")?;
                 Ok(())
             }
+            AlterTableOperation::AlterSortKey { columns } => {
+                write!(f, "ALTER SORTKEY({})", 
display_comma_separated(columns))?;
+                Ok(())
+            }
             AlterTableOperation::SuspendRecluster => {
                 write!(f, "SUSPEND RECLUSTER")?;
                 Ok(())
@@ -3037,7 +3047,10 @@ pub struct CreateTable {
     pub diststyle: Option<DistStyle>,
     /// Redshift `DISTKEY` option
     /// 
<https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
-    pub distkey: Option<Ident>,
+    pub distkey: Option<Expr>,
+    /// Redshift `SORTKEY` option
+    /// 
<https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
+    pub sortkey: Option<Vec<Expr>>,
 }
 
 impl fmt::Display for CreateTable {
@@ -3342,6 +3355,9 @@ impl fmt::Display for CreateTable {
         if let Some(distkey) = &self.distkey {
             write!(f, " DISTKEY({distkey})")?;
         }
+        if let Some(sortkey) = &self.sortkey {
+            write!(f, " SORTKEY({})", display_comma_separated(sortkey))?;
+        }
         if let Some(query) = &self.query {
             write!(f, " AS {query}")?;
         }
diff --git a/src/ast/helpers/stmt_create_table.rs 
b/src/ast/helpers/stmt_create_table.rs
index 258f9c83..5963e940 100644
--- a/src/ast/helpers/stmt_create_table.rs
+++ b/src/ast/helpers/stmt_create_table.rs
@@ -174,7 +174,9 @@ pub struct CreateTableBuilder {
     /// Redshift `DISTSTYLE` option.
     pub diststyle: Option<DistStyle>,
     /// Redshift `DISTKEY` option.
-    pub distkey: Option<Ident>,
+    pub distkey: Option<Expr>,
+    /// Redshift `SORTKEY` option.
+    pub sortkey: Option<Vec<Expr>>,
 }
 
 impl CreateTableBuilder {
@@ -236,6 +238,7 @@ impl CreateTableBuilder {
             require_user: false,
             diststyle: None,
             distkey: None,
+            sortkey: None,
         }
     }
     /// Set `OR REPLACE` for the CREATE TABLE statement.
@@ -517,10 +520,15 @@ impl CreateTableBuilder {
         self
     }
     /// Set Redshift `DISTKEY` option.
-    pub fn distkey(mut self, distkey: Option<Ident>) -> Self {
+    pub fn distkey(mut self, distkey: Option<Expr>) -> Self {
         self.distkey = distkey;
         self
     }
+    /// Set Redshift `SORTKEY` option.
+    pub fn sortkey(mut self, sortkey: Option<Vec<Expr>>) -> Self {
+        self.sortkey = sortkey;
+        self
+    }
     /// Consume the builder and produce a `CreateTable`.
     pub fn build(self) -> CreateTable {
         CreateTable {
@@ -579,6 +587,7 @@ impl CreateTableBuilder {
             require_user: self.require_user,
             diststyle: self.diststyle,
             distkey: self.distkey,
+            sortkey: self.sortkey,
         }
     }
 }
@@ -656,6 +665,7 @@ impl From<CreateTable> for CreateTableBuilder {
             require_user: table.require_user,
             diststyle: table.diststyle,
             distkey: table.distkey,
+            sortkey: table.sortkey,
         }
     }
 }
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 3f73af40..57d57b24 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -582,8 +582,9 @@ impl Spanned for CreateTable {
             refresh_mode: _,
             initialize: _,
             require_user: _,
-            diststyle: _, // enum, no span
-            distkey: _,   // Ident, todo
+            diststyle: _,
+            distkey: _,
+            sortkey: _,
         } = self;
 
         union_spans(
@@ -1193,6 +1194,7 @@ impl Spanned for AlterTableOperation {
             AlterTableOperation::OwnerTo { .. } => Span::empty(),
             AlterTableOperation::ClusterBy { exprs } => 
union_spans(exprs.iter().map(|e| e.span())),
             AlterTableOperation::DropClusteringKey => Span::empty(),
+            AlterTableOperation::AlterSortKey { .. } => Span::empty(),
             AlterTableOperation::SuspendRecluster => Span::empty(),
             AlterTableOperation::ResumeRecluster => Span::empty(),
             AlterTableOperation::Refresh { .. } => Span::empty(),
diff --git a/src/keywords.rs b/src/keywords.rs
index 37a82227..9ea85fd3 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -953,6 +953,7 @@ define_keywords!(
     SOME,
     SORT,
     SORTED,
+    SORTKEY,
     SOURCE,
     SPATIAL,
     SPECIFIC,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 75b5bfa7..eaaa95ec 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8384,7 +8384,7 @@ impl<'a> Parser<'a> {
 
         let strict = self.parse_keyword(Keyword::STRICT);
 
-        // Redshift: DISTSTYLE, DISTKEY
+        // Redshift: DISTSTYLE, DISTKEY, SORTKEY
         let diststyle = if self.parse_keyword(Keyword::DISTSTYLE) {
             Some(self.parse_dist_style()?)
         } else {
@@ -8392,9 +8392,17 @@ impl<'a> Parser<'a> {
         };
         let distkey = if self.parse_keyword(Keyword::DISTKEY) {
             self.expect_token(&Token::LParen)?;
-            let column = self.parse_identifier()?;
+            let expr = self.parse_expr()?;
             self.expect_token(&Token::RParen)?;
-            Some(column)
+            Some(expr)
+        } else {
+            None
+        };
+        let sortkey = if self.parse_keyword(Keyword::SORTKEY) {
+            self.expect_token(&Token::LParen)?;
+            let columns = self.parse_comma_separated(|p| p.parse_expr())?;
+            self.expect_token(&Token::RParen)?;
+            Some(columns)
         } else {
             None
         };
@@ -8440,6 +8448,7 @@ impl<'a> Parser<'a> {
             .strict(strict)
             .diststyle(diststyle)
             .distkey(distkey)
+            .sortkey(sortkey)
             .build())
     }
 
@@ -9979,6 +9988,18 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Parse Redshift `ALTER SORTKEY (column_list)`.
+    ///
+    /// See <https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html>
+    fn parse_alter_sort_key(&mut self) -> Result<AlterTableOperation, 
ParserError> {
+        self.expect_keyword_is(Keyword::ALTER)?;
+        self.expect_keyword_is(Keyword::SORTKEY)?;
+        self.expect_token(&Token::LParen)?;
+        let columns = self.parse_comma_separated(|p| p.parse_expr())?;
+        self.expect_token(&Token::RParen)?;
+        Ok(AlterTableOperation::AlterSortKey { columns })
+    }
+
     /// Parse a single `ALTER TABLE` operation and return an 
`AlterTableOperation`.
     pub fn parse_alter_table_operation(&mut self) -> 
Result<AlterTableOperation, ParserError> {
         let operation = if self.parse_keyword(Keyword::ADD) {
@@ -10257,6 +10278,11 @@ impl<'a> Parser<'a> {
                 column_position,
             }
         } else if self.parse_keyword(Keyword::ALTER) {
+            if self.peek_keyword(Keyword::SORTKEY) {
+                self.prev_token();
+                return self.parse_alter_sort_key();
+            }
+
             let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
             let column_name = self.parse_identifier()?;
             let is_postgresql = dialect_of!(self is PostgreSqlDialect);
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index 0512053a..33a6cb45 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -790,6 +790,7 @@ fn test_duckdb_union_datatype() {
             require_user: Default::default(),
             diststyle: Default::default(),
             distkey: Default::default(),
+            sortkey: Default::default(),
         }),
         stmt
     );
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index aa31b632..da6ecace 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -2008,6 +2008,7 @@ fn parse_create_table_with_valid_options() {
                 require_user: false,
                 diststyle: None,
                 distkey: None,
+                sortkey: None,
             })
         );
     }
@@ -2178,6 +2179,7 @@ fn parse_create_table_with_identity_column() {
                 require_user: false,
                 diststyle: None,
                 distkey: None,
+                sortkey: None,
             }),
         );
     }
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index c83860ff..60aca14b 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6476,6 +6476,7 @@ fn parse_trigger_related_functions() {
             require_user: false,
             diststyle: None,
             distkey: None,
+            sortkey: None,
         }
     );
 
diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs
index 243b0646..184aa5b6 100644
--- a/tests/sqlparser_redshift.rs
+++ b/tests/sqlparser_redshift.rs
@@ -474,3 +474,29 @@ fn test_copy_credentials() {
         "COPY t1 FROM 's3://bucket/file.csv' CREDENTIALS 
'aws_access_key_id=AK;aws_secret_access_key=SK' CSV",
     );
 }
+
+#[test]
+fn test_create_table_sortkey() {
+    redshift().verified_stmt("CREATE TABLE t1 (c1 INT, c2 INT, c3 TIMESTAMP) 
SORTKEY(c3)");
+    redshift().verified_stmt("CREATE TABLE t1 (c1 INT, c2 INT) SORTKEY(c1, 
c2)");
+}
+
+#[test]
+fn test_create_table_distkey_sortkey_with_ctas() {
+    redshift().verified_stmt(
+        "CREATE TABLE t1 DISTKEY(1) SORTKEY(1, 3) AS SELECT eventid, venueid, 
dateid, eventname FROM event",
+    );
+}
+
+#[test]
+fn test_create_table_diststyle_distkey_sortkey() {
+    redshift().verified_stmt(
+        "CREATE TABLE t1 (c1 INT, c2 INT) DISTSTYLE KEY DISTKEY(c1) 
SORTKEY(c1, c2)",
+    );
+}
+
+#[test]
+fn test_alter_table_alter_sortkey() {
+    redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(created_at)");
+    redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(c1, c2)");
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to