diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 895959a3d..6bea28cb7 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)` + /// + AlterSortKey { + /// Column references in the sort key. + columns: Vec, + }, /// 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, /// Redshift `DISTKEY` option /// - pub distkey: Option, + pub distkey: Option, + /// Redshift `SORTKEY` option + /// + pub sortkey: Option>, } 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 258f9c835..5963e9404 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, /// Redshift `DISTKEY` option. - pub distkey: Option, + pub distkey: Option, + /// Redshift `SORTKEY` option. + pub sortkey: Option>, } 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) -> Self { + pub fn distkey(mut self, distkey: Option) -> Self { self.distkey = distkey; self } + /// Set Redshift `SORTKEY` option. + pub fn sortkey(mut self, sortkey: Option>) -> 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 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 3f73af408..57d57b249 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 37a822270..9ea85fd3a 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 75b5bfa76..eaaa95ec8 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 + fn parse_alter_sort_key(&mut self) -> Result { + 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 { 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 0512053a4..33a6cb456 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 aa31b6327..da6ecace6 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 c83860ff5..60aca14b3 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 243b0646c..184aa5b69 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)"); +}