Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,14 @@ pub struct CreateTable {
/// <https://www.postgresql.org/docs/current/ddl-inherit.html>
/// <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-INHERITS>
pub inherits: Option<Vec<ObjectName>>,
/// PostgreSQL `PARTITION OF` clause to create a partition of a parent table.
/// Contains the parent table name.
/// <https://www.postgresql.org/docs/current/sql-createtable.html>
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
pub partition_of: Option<ObjectName>,
/// PostgreSQL partition bound specification for PARTITION OF.
/// <https://www.postgresql.org/docs/current/sql-createtable.html>
pub for_values: Option<ForValues>,
/// SQLite "STRICT" clause.
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
/// then strict typing rules apply to that table.
Expand Down Expand Up @@ -2783,6 +2791,9 @@ impl fmt::Display for CreateTable {
dynamic = if self.dynamic { "DYNAMIC " } else { "" },
name = self.name,
)?;
if let Some(partition_of) = &self.partition_of {
write!(f, " PARTITION OF {partition_of}")?;
}
if let Some(on_cluster) = &self.on_cluster {
write!(f, " ON CLUSTER {on_cluster}")?;
}
Expand All @@ -2797,12 +2808,19 @@ impl fmt::Display for CreateTable {
Indent(DisplayCommaSeparated(&self.constraints)).fmt(f)?;
NewLine.fmt(f)?;
f.write_str(")")?;
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
} else if self.query.is_none()
&& self.like.is_none()
&& self.clone.is_none()
&& self.partition_of.is_none()
{
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
f.write_str(" ()")?;
} else if let Some(CreateTableLikeKind::Parenthesized(like_in_columns_list)) = &self.like {
write!(f, " ({like_in_columns_list})")?;
}
if let Some(for_values) = &self.for_values {
write!(f, " {for_values}")?;
}

// Hive table comment should be after column definitions, please refer to:
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
Expand Down Expand Up @@ -3044,6 +3062,76 @@ impl fmt::Display for CreateTable {
}
}

/// PostgreSQL partition bound specification for PARTITION OF.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// PostgreSQL partition bound specification for PARTITION OF.
/// PostgreSQL partition bound specification for `PARTITION OF`.

///
/// Specifies partition bounds for a child partition table.
///
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.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 ForValues {
/// `FOR VALUES IN (expr, ...)`
In(Vec<Expr>),
/// `FOR VALUES FROM (expr|MINVALUE|MAXVALUE, ...) TO (expr|MINVALUE|MAXVALUE, ...)`
From {
from: Vec<PartitionBoundValue>,
to: Vec<PartitionBoundValue>,
},
/// `FOR VALUES WITH (MODULUS n, REMAINDER r)`
With { modulus: u64, remainder: u64 },
/// `DEFAULT`
Default,
}

impl fmt::Display for ForValues {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ForValues::In(values) => {
write!(f, "FOR VALUES IN ({})", display_comma_separated(values))
}
ForValues::From { from, to } => {
write!(
f,
"FOR VALUES FROM ({}) TO ({})",
display_comma_separated(from),
display_comma_separated(to)
)
}
ForValues::With { modulus, remainder } => {
write!(
f,
"FOR VALUES WITH (MODULUS {modulus}, REMAINDER {remainder})"
)
}
ForValues::Default => write!(f, "DEFAULT"),
}
}
}

/// A value in a partition bound specification.
///
/// Used in RANGE partition bounds where values can be expressions,
/// MINVALUE (negative infinity), or MAXVALUE (positive infinity).
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum PartitionBoundValue {
Expr(Expr),
MinValue,
MaxValue,
}

impl fmt::Display for PartitionBoundValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PartitionBoundValue::Expr(expr) => write!(f, "{expr}"),
PartitionBoundValue::MinValue => write!(f, "MINVALUE"),
PartitionBoundValue::MaxValue => write!(f, "MAXVALUE"),
}
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
Expand Down
24 changes: 22 additions & 2 deletions src/ast/helpers/stmt_create_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use sqlparser_derive::{Visit, VisitMut};

use crate::ast::{
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions, Expr,
FileFormat, HiveDistributionStyle, HiveFormat, Ident, InitializeKind, ObjectName, OnCommit,
OneOrManyWithParens, Query, RefreshModeKind, RowAccessPolicy, Statement,
FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident, InitializeKind, ObjectName,
OnCommit, OneOrManyWithParens, Query, RefreshModeKind, RowAccessPolicy, Statement,
StorageSerializationPolicy, TableConstraint, TableVersion, Tag, WrappedCollection,
};

Expand Down Expand Up @@ -94,6 +94,8 @@ pub struct CreateTableBuilder {
pub cluster_by: Option<WrappedCollection<Vec<Expr>>>,
pub clustered_by: Option<ClusteredBy>,
pub inherits: Option<Vec<ObjectName>>,
pub partition_of: Option<ObjectName>,
pub for_values: Option<ForValues>,
pub strict: bool,
pub copy_grants: bool,
pub enable_schema_evolution: Option<bool>,
Expand Down Expand Up @@ -150,6 +152,8 @@ impl CreateTableBuilder {
cluster_by: None,
clustered_by: None,
inherits: None,
partition_of: None,
for_values: None,
strict: false,
copy_grants: false,
enable_schema_evolution: None,
Expand Down Expand Up @@ -317,6 +321,16 @@ impl CreateTableBuilder {
self
}

pub fn partition_of(mut self, partition_of: Option<ObjectName>) -> Self {
self.partition_of = partition_of;
self
}

pub fn for_values(mut self, for_values: Option<ForValues>) -> Self {
self.for_values = for_values;
self
}

pub fn strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
Expand Down Expand Up @@ -463,6 +477,8 @@ impl CreateTableBuilder {
cluster_by: self.cluster_by,
clustered_by: self.clustered_by,
inherits: self.inherits,
partition_of: self.partition_of,
for_values: self.for_values,
strict: self.strict,
copy_grants: self.copy_grants,
enable_schema_evolution: self.enable_schema_evolution,
Expand Down Expand Up @@ -527,6 +543,8 @@ impl TryFrom<Statement> for CreateTableBuilder {
cluster_by,
clustered_by,
inherits,
partition_of,
for_values,
strict,
copy_grants,
enable_schema_evolution,
Expand Down Expand Up @@ -577,6 +595,8 @@ impl TryFrom<Statement> for CreateTableBuilder {
cluster_by,
clustered_by,
inherits,
partition_of,
for_values,
strict,
iceberg,
copy_grants,
Expand Down
17 changes: 9 additions & 8 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,15 @@ pub use self::ddl::{
CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass,
CreateOperatorFamily, CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial,
DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
DropOperatorSignature, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption,
OperatorArgTypes, OperatorClassItem, OperatorOption, OperatorPurpose, Owner, Partition,
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
DropOperatorSignature, DropTrigger, ForValues, GeneratedAs, GeneratedExpressionMode,
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck,
NullsDistinctOption, OperatorArgTypes, OperatorClassItem, 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,
Expand Down
2 changes: 2 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ impl Spanned for CreateTable {
cluster_by: _, // todo, BigQuery specific
clustered_by: _, // todo, Hive specific
inherits: _, // todo, PostgreSQL specific
partition_of: _, // todo, PostgreSQL specific
for_values: _, // todo, PostgreSQL specific
strict: _, // bool
copy_grants: _, // bool
enable_schema_evolution: _, // bool
Expand Down
2 changes: 2 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ define_keywords!(
MODIFIES,
MODIFY,
MODULE,
MODULUS,
MONITOR,
MONTH,
MONTHS,
Expand Down Expand Up @@ -837,6 +838,7 @@ define_keywords!(
RELAY,
RELEASE,
RELEASES,
REMAINDER,
REMOTE,
REMOVE,
REMOVEQUOTES,
Expand Down
72 changes: 72 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7833,6 +7833,15 @@ impl<'a> Parser<'a> {
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
let table_name = self.parse_object_name(allow_unquoted_hyphen)?;

// PostgreSQL PARTITION OF for child partition tables
let partition_of = if dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::PARTITION, Keyword::OF])
Comment on lines +7836 to +7838
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// PostgreSQL PARTITION OF for child partition tables
let partition_of = if dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::PARTITION, Keyword::OF])
let partition_of = if self.parse_keywords(&[Keyword::PARTITION, Keyword::OF])

If possible we can skiip the dialect check and let the parser accept the PARTITION OF clause whenever it shows up in an input sql

{
Some(self.parse_object_name(allow_unquoted_hyphen)?)
} else {
None
};

// Clickhouse has `ON CLUSTER 'cluster'` syntax for DDLs
let on_cluster = self.parse_optional_on_cluster()?;

Expand All @@ -7857,6 +7866,13 @@ impl<'a> Parser<'a> {
None
};

// PostgreSQL PARTITION OF: partition bound specification
let for_values = if partition_of.is_some() {
Some(self.parse_partition_for_values()?)
} else {
None
};

// SQLite supports `WITHOUT ROWID` at the end of `CREATE TABLE`
let without_rowid = self.parse_keywords(&[Keyword::WITHOUT, Keyword::ROWID]);

Expand Down Expand Up @@ -7934,6 +7950,8 @@ impl<'a> Parser<'a> {
.partition_by(create_table_config.partition_by)
.cluster_by(create_table_config.cluster_by)
.inherits(create_table_config.inherits)
.partition_of(partition_of)
.for_values(for_values)
.table_options(create_table_config.table_options)
.primary_key(primary_key)
.strict(strict)
Expand Down Expand Up @@ -7993,6 +8011,60 @@ impl<'a> Parser<'a> {
}
}

/// Parse PostgreSQL partition bound specification for PARTITION OF.
///
/// Parses: `FOR VALUES partition_bound_spec | DEFAULT`
Comment on lines +8014 to +8016
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Parse PostgreSQL partition bound specification for PARTITION OF.
///
/// Parses: `FOR VALUES partition_bound_spec | DEFAULT`
/// Parse [ForValues] of a `PARTITION OF` clause.

///
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html)
fn parse_partition_for_values(&mut self) -> Result<ForValues, ParserError> {
if self.parse_keyword(Keyword::DEFAULT) {
return Ok(ForValues::Default);
}

self.expect_keywords(&[Keyword::FOR, Keyword::VALUES])?;

if self.parse_keyword(Keyword::IN) {
// FOR VALUES IN (expr, ...)
self.expect_token(&Token::LParen)?;
let values = self.parse_comma_separated(Parser::parse_expr)?;
self.expect_token(&Token::RParen)?;
Ok(ForValues::In(values))
} else if self.parse_keyword(Keyword::FROM) {
// FOR VALUES FROM (...) TO (...)
self.expect_token(&Token::LParen)?;
let from = self.parse_comma_separated(Parser::parse_partition_bound_value)?;
self.expect_token(&Token::RParen)?;
self.expect_keyword(Keyword::TO)?;
self.expect_token(&Token::LParen)?;
let to = self.parse_comma_separated(Parser::parse_partition_bound_value)?;
self.expect_token(&Token::RParen)?;
Ok(ForValues::From { from, to })
} else if self.parse_keyword(Keyword::WITH) {
// FOR VALUES WITH (MODULUS n, REMAINDER r)
self.expect_token(&Token::LParen)?;
self.expect_keyword(Keyword::MODULUS)?;
let modulus = self.parse_literal_uint()?;
self.expect_token(&Token::Comma)?;
self.expect_keyword(Keyword::REMAINDER)?;
let remainder = self.parse_literal_uint()?;
self.expect_token(&Token::RParen)?;
Ok(ForValues::With { modulus, remainder })
} else {
self.expected("IN, FROM, or WITH after FOR VALUES", self.peek_token())
}
}

/// Parse a single partition bound value (MINVALUE, MAXVALUE, or expression).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Parse a single partition bound value (MINVALUE, MAXVALUE, or expression).
/// Parse a single [PartitionBoundValue].

fn parse_partition_bound_value(&mut self) -> Result<PartitionBoundValue, ParserError> {
if self.parse_keyword(Keyword::MINVALUE) {
Ok(PartitionBoundValue::MinValue)
} else if self.parse_keyword(Keyword::MAXVALUE) {
Ok(PartitionBoundValue::MaxValue)
} else {
Ok(PartitionBoundValue::Expr(self.parse_expr()?))
}
}

/// Parse configuration like inheritance, partitioning, clustering information during the table creation.
///
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_2)
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,8 @@ fn test_duckdb_union_datatype() {
cluster_by: Default::default(),
clustered_by: Default::default(),
inherits: Default::default(),
partition_of: Default::default(),
for_values: Default::default(),
strict: Default::default(),
copy_grants: Default::default(),
enable_schema_evolution: Default::default(),
Expand Down
4 changes: 4 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1897,6 +1897,8 @@ fn parse_create_table_with_valid_options() {
cluster_by: None,
clustered_by: None,
inherits: None,
partition_of: None,
for_values: None,
strict: false,
iceberg: false,
copy_grants: false,
Expand Down Expand Up @@ -2064,6 +2066,8 @@ fn parse_create_table_with_identity_column() {
cluster_by: None,
clustered_by: None,
inherits: None,
partition_of: None,
for_values: None,
strict: false,
copy_grants: false,
enable_schema_evolution: None,
Expand Down
Loading