diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 884b378db..e928dbac7 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -129,6 +129,9 @@ pub struct CreateTable { pub default_charset: Option, pub collation: Option, pub on_commit: Option, + /// Datafusion "WITH ORDER" clause + /// + pub with_order: Vec>, /// ClickHouse "ON CLUSTER" clause: /// pub on_cluster: Option, @@ -405,6 +408,14 @@ impl Display for CreateTable { write!(f, " WITH AGGREGATION POLICY {with_aggregation_policy}",)?; } + if !self.with_order.is_empty() { + write!(f, " WITH ORDER (")?; + for order_by in &self.with_order { + write!(f, "{}", display_comma_separated(order_by))?; + } + write!(f, ")")?; + } + if let Some(row_access_policy) = &self.with_row_access_policy { write!(f, " {row_access_policy}",)?; } diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index 82532b291..e66ec547c 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -10,8 +10,8 @@ use sqlparser_derive::{Visit, VisitMut}; use super::super::dml::CreateTable; use crate::ast::{ ClusteredBy, ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, - ObjectName, OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, - TableConstraint, TableEngine, Tag, WrappedCollection, + ObjectName, OnCommit, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SqlOption, + Statement, TableConstraint, TableEngine, Tag, WrappedCollection, }; use crate::parser::ParserError; @@ -75,6 +75,7 @@ pub struct CreateTableBuilder { pub on_commit: Option, pub on_cluster: Option, pub primary_key: Option>, + pub with_order: Vec>, pub order_by: Option>, pub partition_by: Option>, pub cluster_by: Option>>, @@ -136,6 +137,7 @@ impl CreateTableBuilder { max_data_extension_time_in_days: None, default_ddl_collation: None, with_aggregation_policy: None, + with_order: vec![], with_row_access_policy: None, with_tags: None, } @@ -273,6 +275,11 @@ impl CreateTableBuilder { self } + pub fn with_order(mut self, with_order: Vec>) -> Self { + self.with_order = with_order; + self + } + pub fn order_by(mut self, order_by: Option>) -> Self { self.order_by = order_by; self @@ -397,6 +404,7 @@ impl CreateTableBuilder { max_data_extension_time_in_days: self.max_data_extension_time_in_days, default_ddl_collation: self.default_ddl_collation, with_aggregation_policy: self.with_aggregation_policy, + with_order: self.with_order, with_row_access_policy: self.with_row_access_policy, with_tags: self.with_tags, }) @@ -452,6 +460,7 @@ impl TryFrom for CreateTableBuilder { max_data_extension_time_in_days, default_ddl_collation, with_aggregation_policy, + with_order, with_row_access_policy, with_tags, }) => Ok(Self { @@ -496,6 +505,7 @@ impl TryFrom for CreateTableBuilder { default_ddl_collation, with_aggregation_policy, with_row_access_policy, + with_order, with_tags, volatile, }), diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index c8f1c00d9..d2582c725 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -79,6 +79,10 @@ impl Dialect for GenericDialect { true } + fn supports_with_order_expr(&self) -> bool { + true + } + fn allow_extract_custom(&self) -> bool { true } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 2a74d9925..fe0601339 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -199,6 +199,11 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialects supports `WITH ORDER` expressions. + fn supports_with_order_expr(&self) -> bool { + false + } + /// Returns true if the dialect supports CONNECT BY. fn supports_connect_by(&self) -> bool { false diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 977372656..a81a916d9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5589,7 +5589,19 @@ impl<'a> Parser<'a> { let clustered_by = self.parse_optional_clustered_by()?; let hive_formats = self.parse_hive_formats()?; // PostgreSQL supports `WITH ( options )`, before `AS` - let with_options = self.parse_options(Keyword::WITH)?; + let mut with_options: Vec = vec![]; + let mut order_exprs: Vec> = vec![]; + if self.dialect.supports_with_order_expr() + && self.parse_keywords(&[Keyword::WITH, Keyword::ORDER]) + { + self.expect_token(&Token::LParen)?; + let order_by = self.parse_comma_separated(Parser::parse_order_by_expr)?; + order_exprs.push(order_by); + self.expect_token(&Token::RParen)?; + } else { + with_options = self.parse_options(Keyword::WITH)?; + } + let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?; let engine = if self.parse_keyword(Keyword::ENGINE) { @@ -5738,6 +5750,7 @@ impl<'a> Parser<'a> { .cluster_by(create_table_config.cluster_by) .options(create_table_config.options) .primary_key(primary_key) + .with_order(order_exprs) .strict(strict) .build()) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index fbe97171b..9bd4c379d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8801,6 +8801,43 @@ fn parse_uncache_table() { ); } +#[test] +fn parse_create_table_with_order() { + let sql = "CREATE TABLE test (foo INT, bar VARCHAR(256)) WITH ORDER (foo ASC)"; + let ast = all_dialects_where(|d| d.supports_with_order_expr()).verified_stmt(sql); + match ast { + Statement::CreateTable(CreateTable { with_order, .. }) => { + assert_eq!( + with_order, + vec![vec![OrderByExpr { + expr: Expr::Identifier(Ident::from("foo")), + asc: Some(true), + nulls_first: None, + with_fill: None, + }]] + ); + } + _ => unreachable!(), + } + + let sql = "CREATE TABLE test (foo INT, bar VARCHAR(256)) WITH ORDER (bar DESC NULLS FIRST)"; + let ast = all_dialects_where(|d| d.supports_with_order_expr()).verified_stmt(sql); + match ast { + Statement::CreateTable(CreateTable { with_order, .. }) => { + assert_eq!( + with_order, + vec![vec![OrderByExpr { + expr: Expr::Identifier(Ident::from("bar")), + asc: Some(false), + nulls_first: Some(true), + with_fill: None, + }]] + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_deeply_nested_parens_hits_recursion_limits() { let sql = "(".repeat(1000); diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 12368a88c..aa9c36b83 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -751,6 +751,7 @@ fn test_duckdb_union_datatype() { max_data_extension_time_in_days: Default::default(), default_ddl_collation: Default::default(), with_aggregation_policy: Default::default(), + with_order: Default::default(), with_row_access_policy: Default::default(), with_tags: Default::default() }), diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index d96211823..74e6ef2cd 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4864,6 +4864,7 @@ fn parse_trigger_related_functions() { with_aggregation_policy: None, with_row_access_policy: None, with_tags: None, + with_order: vec![], } );