From 189708eb1c18c2b8f318d01dfd38f958cdc8624a Mon Sep 17 00:00:00 2001 From: Andriy Romanov Date: Wed, 18 Feb 2026 16:16:11 -0800 Subject: [PATCH 1/2] Added DISTSTYLE and DISTKEY keywords parsing for snowflake --- src/ast/ddl.rs | 40 ++++++++++++++++++++++++++++ src/ast/helpers/stmt_create_table.rs | 29 +++++++++++++++++--- src/ast/mod.rs | 3 ++- src/ast/spans.rs | 2 ++ src/keywords.rs | 3 +++ src/parser/mod.rs | 34 +++++++++++++++++++++++ tests/sqlparser_duckdb.rs | 2 ++ tests/sqlparser_mssql.rs | 4 +++ tests/sqlparser_postgres.rs | 2 ++ tests/sqlparser_redshift.rs | 16 +++++++++++ 10 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 0c4f93e647..b19cc56b75 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3032,6 +3032,12 @@ pub struct CreateTable { /// Snowflake "REQUIRE USER" clause for dybamic tables /// pub require_user: bool, + /// Redshift `DISTSTYLE` option + /// + pub diststyle: Option, + /// Redshift `DISTKEY` option + /// + pub distkey: Option, } impl fmt::Display for CreateTable { @@ -3330,6 +3336,12 @@ impl fmt::Display for CreateTable { if self.strict { write!(f, " STRICT")?; } + if let Some(diststyle) = &self.diststyle { + write!(f, " DISTSTYLE {diststyle}")?; + } + if let Some(distkey) = &self.distkey { + write!(f, " DISTKEY({distkey})")?; + } if let Some(query) = &self.query { write!(f, " AS {query}")?; } @@ -3417,6 +3429,34 @@ impl fmt::Display for PartitionBoundValue { } } +/// Redshift distribution style for `CREATE TABLE`. +/// +/// See [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum DistStyle { + /// `DISTSTYLE AUTO` + Auto, + /// `DISTSTYLE EVEN` + Even, + /// `DISTSTYLE KEY` + Key, + /// `DISTSTYLE ALL` + All, +} + +impl fmt::Display for DistStyle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DistStyle::Auto => write!(f, "AUTO"), + DistStyle::Even => write!(f, "EVEN"), + DistStyle::Key => write!(f, "KEY"), + DistStyle::All => write!(f, "ALL"), + } + } +} + #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index e63c90dbcf..258f9c8353 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -25,10 +25,11 @@ use serde::{Deserialize, Serialize}; use sqlparser_derive::{Visit, VisitMut}; use crate::ast::{ - ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions, Expr, - FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident, InitializeKind, ObjectName, - OnCommit, OneOrManyWithParens, Query, RefreshModeKind, RowAccessPolicy, Statement, - StorageSerializationPolicy, TableConstraint, TableVersion, Tag, WrappedCollection, + ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions, + DistStyle, Expr, FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident, + InitializeKind, ObjectName, OnCommit, OneOrManyWithParens, Query, RefreshModeKind, + RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, TableVersion, Tag, + WrappedCollection, }; use crate::parser::ParserError; @@ -170,6 +171,10 @@ pub struct CreateTableBuilder { pub initialize: Option, /// Whether operations require a user identity. pub require_user: bool, + /// Redshift `DISTSTYLE` option. + pub diststyle: Option, + /// Redshift `DISTKEY` option. + pub distkey: Option, } impl CreateTableBuilder { @@ -229,6 +234,8 @@ impl CreateTableBuilder { refresh_mode: None, initialize: None, require_user: false, + diststyle: None, + distkey: None, } } /// Set `OR REPLACE` for the CREATE TABLE statement. @@ -504,6 +511,16 @@ impl CreateTableBuilder { self.require_user = require_user; self } + /// Set Redshift `DISTSTYLE` option. + pub fn diststyle(mut self, diststyle: Option) -> Self { + self.diststyle = diststyle; + self + } + /// Set Redshift `DISTKEY` option. + pub fn distkey(mut self, distkey: Option) -> Self { + self.distkey = distkey; + self + } /// Consume the builder and produce a `CreateTable`. pub fn build(self) -> CreateTable { CreateTable { @@ -560,6 +577,8 @@ impl CreateTableBuilder { refresh_mode: self.refresh_mode, initialize: self.initialize, require_user: self.require_user, + diststyle: self.diststyle, + distkey: self.distkey, } } } @@ -635,6 +654,8 @@ impl From for CreateTableBuilder { refresh_mode: table.refresh_mode, initialize: table.initialize, require_user: table.require_user, + diststyle: table.diststyle, + distkey: table.distkey, } } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index eda2822600..bc47e8412f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -70,7 +70,8 @@ pub use self::ddl::{ ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTrigger, CreateView, Deduplicate, - DeferrableInitial, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, + DeferrableInitial, DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, + DropOperatorClass, DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index f4bdf85a39..295d92380d 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -581,6 +581,8 @@ impl Spanned for CreateTable { refresh_mode: _, initialize: _, require_user: _, + diststyle: _, // enum, no span + distkey: _, // Ident, todo } = self; union_spans( diff --git a/src/keywords.rs b/src/keywords.rs index f1dbcd9378..5ea07792d2 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -334,7 +334,9 @@ define_keywords!( DISCONNECT, DISTINCT, DISTINCTROW, + DISTKEY, DISTRIBUTE, + DISTSTYLE, DIV, DO, DOMAIN, @@ -378,6 +380,7 @@ define_keywords!( ESCAPE, ESCAPED, ESTIMATE, + EVEN, EVENT, EVERY, EVOLVE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index eba9b32dcd..7274d2c544 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8056,6 +8056,23 @@ impl<'a> Parser<'a> { } } + /// Parse Redshift `DISTSTYLE { AUTO | EVEN | KEY | ALL }`. + /// + /// See + fn parse_dist_style(&mut self) -> Result { + let token = self.next_token(); + match &token.token { + Token::Word(w) => match w.keyword { + Keyword::AUTO => Ok(DistStyle::Auto), + Keyword::EVEN => Ok(DistStyle::Even), + Keyword::KEY => Ok(DistStyle::Key), + Keyword::ALL => Ok(DistStyle::All), + _ => self.expected("AUTO, EVEN, KEY, or ALL", token), + }, + _ => self.expected("AUTO, EVEN, KEY, or ALL", token), + } + } + /// Parse Hive formats. pub fn parse_hive_formats(&mut self) -> Result, ParserError> { let mut hive_format: Option = None; @@ -8326,6 +8343,21 @@ impl<'a> Parser<'a> { let strict = self.parse_keyword(Keyword::STRICT); + // Redshift: DISTSTYLE, DISTKEY + let diststyle = if self.parse_keyword(Keyword::DISTSTYLE) { + Some(self.parse_dist_style()?) + } else { + None + }; + let distkey = if self.parse_keyword(Keyword::DISTKEY) { + self.expect_token(&Token::LParen)?; + let column = self.parse_identifier()?; + self.expect_token(&Token::RParen)?; + Some(column) + } else { + None + }; + // Parse optional `AS ( query )` let query = if self.parse_keyword(Keyword::AS) { Some(self.parse_query()?) @@ -8365,6 +8397,8 @@ impl<'a> Parser<'a> { .table_options(create_table_config.table_options) .primary_key(primary_key) .strict(strict) + .diststyle(diststyle) + .distkey(distkey) .build()) } diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index b9ae26491f..51965d2e89 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -784,6 +784,8 @@ fn test_duckdb_union_datatype() { refresh_mode: None, initialize: None, require_user: Default::default(), + diststyle: Default::default(), + distkey: Default::default(), }), stmt ); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index cf9ae89809..93f541b0b9 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1969,6 +1969,8 @@ fn parse_create_table_with_valid_options() { refresh_mode: None, initialize: None, require_user: false, + diststyle: None, + distkey: None, }) ); } @@ -2137,6 +2139,8 @@ fn parse_create_table_with_identity_column() { refresh_mode: None, initialize: None, require_user: false, + diststyle: None, + distkey: None, }), ); } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index f8c7381366..dfd151f717 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6322,6 +6322,8 @@ fn parse_trigger_related_functions() { refresh_mode: None, initialize: None, require_user: false, + diststyle: None, + distkey: None, } ); diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 90652ff48f..a4d3b0df56 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -452,3 +452,19 @@ fn parse_vacuum() { _ => unreachable!(), } } + +#[test] +fn test_create_table_diststyle_distkey() { + redshift().verified_stmt( + "CREATE TEMPORARY TABLE tmp_sbk_summary_pp DISTSTYLE KEY DISTKEY(bet_id) AS SELECT 1 AS bet_id", + ); +} + +#[test] +fn test_create_table_diststyle() { + redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE AUTO"); + redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE EVEN"); + redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE KEY DISTKEY(c1)"); + redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE ALL"); +} + From 483ab1d04aa3362b12c3723e54523c1da68bd042 Mon Sep 17 00:00:00 2001 From: Andriy Romanov Date: Wed, 18 Feb 2026 16:22:21 -0800 Subject: [PATCH 2/2] cargo fmt --- src/ast/mod.rs | 20 ++++++++++---------- src/ast/spans.rs | 4 ++-- tests/sqlparser_redshift.rs | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index bc47e8412f..9c95988f47 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -71,16 +71,16 @@ pub use self::ddl::{ CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, - DropOperatorClass, - DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, GeneratedAs, - GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, - IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, - KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem, - OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition, - PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, - TagsColumnOption, TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef, - UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, - UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, + DropOperatorClass, DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, + ForValues, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, + IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, + IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, + OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, + Owner, Partition, PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, + ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, + UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, + UserDefinedTypeStorage, ViewColumnDef, }; pub use self::dml::{ Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 295d92380d..b24543ad9e 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -581,8 +581,8 @@ impl Spanned for CreateTable { refresh_mode: _, initialize: _, require_user: _, - diststyle: _, // enum, no span - distkey: _, // Ident, todo + diststyle: _, // enum, no span + distkey: _, // Ident, todo } = self; union_spans( diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index a4d3b0df56..03dfda2c04 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -467,4 +467,3 @@ fn test_create_table_diststyle() { redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE KEY DISTKEY(c1)"); redshift().verified_stmt("CREATE TABLE t1 (c1 INT) DISTSTYLE ALL"); } -