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

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

commit 1da2ff779c0e71932fc882fe7ae4e6d674b8cc76
Author: Andriy Romanov <[email protected]>
AuthorDate: Tue Mar 3 04:13:17 2026 -0800

    Redshift: Added DISTSTYLE and DISTKEY keywords parsing (#2222)
---
 src/ast/ddl.rs                       | 40 ++++++++++++++++++++++++++++++++++++
 src/ast/helpers/stmt_create_table.rs | 29 ++++++++++++++++++++++----
 src/ast/mod.rs                       | 21 ++++++++++---------
 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          | 15 ++++++++++++++
 10 files changed, 138 insertions(+), 14 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 3a951f66..895959a3 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
     /// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
     pub require_user: bool,
+    /// Redshift `DISTSTYLE` option
+    /// 
<https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
+    pub diststyle: Option<DistStyle>,
+    /// Redshift `DISTKEY` option
+    /// 
<https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
+    pub distkey: Option<Ident>,
 }
 
 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 e63c90db..258f9c83 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<InitializeKind>,
     /// Whether operations require a user identity.
     pub require_user: bool,
+    /// Redshift `DISTSTYLE` option.
+    pub diststyle: Option<DistStyle>,
+    /// Redshift `DISTKEY` option.
+    pub distkey: Option<Ident>,
 }
 
 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<DistStyle>) -> Self {
+        self.diststyle = diststyle;
+        self
+    }
+    /// Set Redshift `DISTKEY` option.
+    pub fn distkey(mut self, distkey: Option<Ident>) -> 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<CreateTable> 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 97cc6193..6a2f9bd9 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -70,16 +70,17 @@ 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,
-    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,
+    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,
 };
 pub use self::dml::{
     Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, 
MergeInsertExpr,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index dd62c5ba..7c075197 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -582,6 +582,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 80f679c0..37a82227 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -335,7 +335,9 @@ define_keywords!(
     DISCONNECT,
     DISTINCT,
     DISTINCTROW,
+    DISTKEY,
     DISTRIBUTE,
+    DISTSTYLE,
     DIV,
     DO,
     DOMAIN,
@@ -379,6 +381,7 @@ define_keywords!(
     ESCAPE,
     ESCAPED,
     ESTIMATE,
+    EVEN,
     EVENT,
     EVERY,
     EVOLVE,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 75450f75..2749969c 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8097,6 +8097,23 @@ impl<'a> Parser<'a> {
         }
     }
 
+    /// Parse Redshift `DISTSTYLE { AUTO | EVEN | KEY | ALL }`.
+    ///
+    /// See 
<https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
+    fn parse_dist_style(&mut self) -> Result<DistStyle, ParserError> {
+        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<Option<HiveFormat>, 
ParserError> {
         let mut hive_format: Option<HiveFormat> = None;
@@ -8367,6 +8384,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()?)
@@ -8406,6 +8438,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 a061876d..0512053a 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -788,6 +788,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 9033efe0..aa31b632 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -2006,6 +2006,8 @@ fn parse_create_table_with_valid_options() {
                 refresh_mode: None,
                 initialize: None,
                 require_user: false,
+                diststyle: None,
+                distkey: None,
             })
         );
     }
@@ -2174,6 +2176,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 434c5fd7..7dd624a2 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6418,6 +6418,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 90652ff4..03dfda2c 100644
--- a/tests/sqlparser_redshift.rs
+++ b/tests/sqlparser_redshift.rs
@@ -452,3 +452,18 @@ 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");
+}


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

Reply via email to