Skip to content

Commit c0ca7b8

Browse files
committed
Add PostgreSQL PARTITION OF syntax support (apache#2127)
1 parent aa1bba5 commit c0ca7b8

5 files changed

Lines changed: 331 additions & 25 deletions

File tree

src/ast/ddl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3072,7 +3072,7 @@ impl fmt::Display for CreateTable {
30723072
}
30733073
}
30743074

3075-
/// PostgreSQL partition bound specification for PARTITION OF.
3075+
/// PostgreSQL partition bound specification for `PARTITION OF`.
30763076
///
30773077
/// Specifies partition bounds for a child partition table.
30783078
///

src/ast/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,9 @@ pub use self::ddl::{
7575
NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem,
7676
OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition, PartitionBoundValue,
7777
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
78-
TriggerObjectKind, Truncate,
79-
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
80-
UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
81-
UserDefinedTypeStorage, ViewColumnDef,
78+
TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
79+
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
80+
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
8281
};
8382
pub use self::dml::{
8483
Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
@@ -8782,7 +8781,9 @@ impl fmt::Display for FunctionBehavior {
87828781
}
87838782
}
87848783

8785-
/// Specifies whether the function is SECURITY DEFINER or SECURITY INVOKER.
8784+
/// Security attribute for functions: SECURITY DEFINER or SECURITY INVOKER.
8785+
///
8786+
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createfunction.html)
87868787
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
87878788
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87888789
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/spans.rs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ use super::{
4141
MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
4242
MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
4343
ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
44-
OrderByExpr, OrderByKind, OutputClause, Partition, PartitionBoundValue, PivotValueSource, ProjectionSelect, Query,
45-
RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
46-
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
47-
SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
48-
TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Value, Values,
49-
ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill,
44+
OrderByExpr, OrderByKind, OutputClause, Partition, PartitionBoundValue, PivotValueSource,
45+
ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
46+
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
47+
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
48+
TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins, Update,
49+
UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
50+
WildcardAdditionalOptions, With, WithFill,
5051
};
5152

5253
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -547,13 +548,13 @@ impl Spanned for CreateTable {
547548
clone,
548549
comment: _, // todo, no span
549550
on_commit: _,
550-
on_cluster: _, // todo, clickhouse specific
551-
primary_key: _, // todo, clickhouse specific
552-
order_by: _, // todo, clickhouse specific
553-
partition_by: _, // todo, BigQuery specific
554-
cluster_by: _, // todo, BigQuery specific
555-
clustered_by: _, // todo, Hive specific
556-
inherits: _, // todo, PostgreSQL specific
551+
on_cluster: _, // todo, clickhouse specific
552+
primary_key: _, // todo, clickhouse specific
553+
order_by: _, // todo, clickhouse specific
554+
partition_by: _, // todo, BigQuery specific
555+
cluster_by: _, // todo, BigQuery specific
556+
clustered_by: _, // todo, Hive specific
557+
inherits: _, // todo, PostgreSQL specific
557558
partition_of,
558559
for_values,
559560
strict: _, // bool
@@ -651,6 +652,33 @@ impl Spanned for TableConstraint {
651652
}
652653
}
653654

655+
impl Spanned for PartitionBoundValue {
656+
fn span(&self) -> Span {
657+
match self {
658+
PartitionBoundValue::Expr(expr) => expr.span(),
659+
// MINVALUE and MAXVALUE are keywords without tracked spans
660+
PartitionBoundValue::MinValue => Span::empty(),
661+
PartitionBoundValue::MaxValue => Span::empty(),
662+
}
663+
}
664+
}
665+
666+
impl Spanned for ForValues {
667+
fn span(&self) -> Span {
668+
match self {
669+
ForValues::In(exprs) => union_spans(exprs.iter().map(|e| e.span())),
670+
ForValues::From { from, to } => union_spans(
671+
from.iter()
672+
.map(|v| v.span())
673+
.chain(to.iter().map(|v| v.span())),
674+
),
675+
// WITH (MODULUS n, REMAINDER r) - u64 values have no spans
676+
ForValues::With { .. } => Span::empty(),
677+
ForValues::Default => Span::empty(),
678+
}
679+
}
680+
}
681+
654682
impl Spanned for CreateIndex {
655683
fn span(&self) -> Span {
656684
let CreateIndex {

src/parser/mod.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7919,9 +7919,16 @@ impl<'a> Parser<'a> {
79197919
let table_name = self.parse_object_name(allow_unquoted_hyphen)?;
79207920

79217921
// PostgreSQL PARTITION OF for child partition tables
7922-
let partition_of = if dialect_of!(self is PostgreSqlDialect | GenericDialect)
7923-
&& self.parse_keywords(&[Keyword::PARTITION, Keyword::OF])
7924-
{
7922+
// Note: This is a PostgreSQL-specific feature, but the dialect check was intentionally
7923+
// removed to allow GenericDialect and other dialects to parse this syntax. This enables
7924+
// multi-dialect SQL tools to work with PostgreSQL-specific DDL statements.
7925+
//
7926+
// PARTITION OF can be combined with other table definition clauses in the AST,
7927+
// though PostgreSQL itself prohibits PARTITION OF with AS SELECT or LIKE clauses.
7928+
// The parser accepts these combinations for flexibility; semantic validation
7929+
// is left to downstream tools.
7930+
// Child partitions can have their own constraints and indexes.
7931+
let partition_of = if self.parse_keywords(&[Keyword::PARTITION, Keyword::OF]) {
79257932
Some(self.parse_object_name(allow_unquoted_hyphen)?)
79267933
} else {
79277934
None
@@ -7953,7 +7960,14 @@ impl<'a> Parser<'a> {
79537960

79547961
// PostgreSQL PARTITION OF: partition bound specification
79557962
let for_values = if partition_of.is_some() {
7956-
Some(self.parse_partition_for_values()?)
7963+
if self.peek_keyword(Keyword::FOR) || self.peek_keyword(Keyword::DEFAULT) {
7964+
Some(self.parse_partition_for_values()?)
7965+
} else {
7966+
return self.expected(
7967+
"FOR VALUES or DEFAULT after PARTITION OF",
7968+
self.peek_token(),
7969+
);
7970+
}
79577971
} else {
79587972
None
79597973
};
@@ -8096,7 +8110,7 @@ impl<'a> Parser<'a> {
80968110
}
80978111
}
80988112

8099-
/// Parse PostgreSQL partition bound specification for PARTITION OF.
8113+
/// Parse [ForValues] of a `PARTITION OF` clause.
81008114
///
81018115
/// Parses: `FOR VALUES partition_bound_spec | DEFAULT`
81028116
///
@@ -8111,16 +8125,25 @@ impl<'a> Parser<'a> {
81118125
if self.parse_keyword(Keyword::IN) {
81128126
// FOR VALUES IN (expr, ...)
81138127
self.expect_token(&Token::LParen)?;
8128+
if self.peek_token() == Token::RParen {
8129+
return self.expected("at least one value", self.peek_token());
8130+
}
81148131
let values = self.parse_comma_separated(Parser::parse_expr)?;
81158132
self.expect_token(&Token::RParen)?;
81168133
Ok(ForValues::In(values))
81178134
} else if self.parse_keyword(Keyword::FROM) {
81188135
// FOR VALUES FROM (...) TO (...)
81198136
self.expect_token(&Token::LParen)?;
8137+
if self.peek_token() == Token::RParen {
8138+
return self.expected("at least one value", self.peek_token());
8139+
}
81208140
let from = self.parse_comma_separated(Parser::parse_partition_bound_value)?;
81218141
self.expect_token(&Token::RParen)?;
81228142
self.expect_keyword(Keyword::TO)?;
81238143
self.expect_token(&Token::LParen)?;
8144+
if self.peek_token() == Token::RParen {
8145+
return self.expected("at least one value", self.peek_token());
8146+
}
81248147
let to = self.parse_comma_separated(Parser::parse_partition_bound_value)?;
81258148
self.expect_token(&Token::RParen)?;
81268149
Ok(ForValues::From { from, to })
@@ -8139,7 +8162,7 @@ impl<'a> Parser<'a> {
81398162
}
81408163
}
81418164

8142-
/// Parse a single partition bound value (MINVALUE, MAXVALUE, or expression).
8165+
/// Parse a single [PartitionBoundValue].
81438166
fn parse_partition_bound_value(&mut self) -> Result<PartitionBoundValue, ParserError> {
81448167
if self.parse_keyword(Keyword::MINVALUE) {
81458168
Ok(PartitionBoundValue::MinValue)

0 commit comments

Comments
 (0)