-
Notifications
You must be signed in to change notification settings - Fork 710
feat: add PostgreSQL EXCLUDE constraint parsing #2307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d266014
a957bb7
6f47488
17cbba9
a0cacb2
a189d8f
dddb2ce
2d37749
90803e0
442e196
d8e35d9
e02613e
837b5a0
228c969
abfc1c6
460c098
f699bea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,4 +18,4 @@ Cargo.lock | |
|
|
||
| *.swp | ||
|
|
||
| .DS_store | ||
| .DS_store.worktrees/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,13 +20,13 @@ | |||||||||||||||||
| use crate::ast::{ | ||||||||||||||||||
| display_comma_separated, display_separated, ConstraintCharacteristics, | ||||||||||||||||||
| ConstraintReferenceMatchKind, Expr, Ident, IndexColumn, IndexOption, IndexType, | ||||||||||||||||||
| KeyOrIndexDisplay, NullsDistinctOption, ObjectName, ReferentialAction, | ||||||||||||||||||
| KeyOrIndexDisplay, NullsDistinctOption, ObjectName, OrderByOptions, ReferentialAction, | ||||||||||||||||||
| }; | ||||||||||||||||||
| use crate::tokenizer::Span; | ||||||||||||||||||
| use core::fmt; | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(not(feature = "std"))] | ||||||||||||||||||
| use alloc::{boxed::Box, vec::Vec}; | ||||||||||||||||||
| use alloc::{boxed::Box, string::String, vec::Vec}; | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(feature = "serde")] | ||||||||||||||||||
| use serde::{Deserialize, Serialize}; | ||||||||||||||||||
|
|
@@ -117,6 +117,12 @@ pub enum TableConstraint { | |||||||||||||||||
| /// | ||||||||||||||||||
| /// [1]: https://www.postgresql.org/docs/current/sql-altertable.html | ||||||||||||||||||
| UniqueUsingIndex(ConstraintUsingIndex), | ||||||||||||||||||
| /// PostgreSQL `EXCLUDE` constraint. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// `[ CONSTRAINT <name> ] EXCLUDE [ USING <index_method> ] ( <element> WITH <operator> [, ...] ) [ INCLUDE (<cols>) ] [ WHERE (<predicate>) ]` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// See <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE> | ||||||||||||||||||
| Exclusion(ExclusionConstraint), | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
does this make more sense if the constraint is called 'EXCLUDE' rather than 'EXCLUSION'? |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl From<UniqueConstraint> for TableConstraint { | ||||||||||||||||||
|
|
@@ -155,6 +161,12 @@ impl From<FullTextOrSpatialConstraint> for TableConstraint { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl From<ExclusionConstraint> for TableConstraint { | ||||||||||||||||||
| fn from(constraint: ExclusionConstraint) -> Self { | ||||||||||||||||||
| TableConstraint::Exclusion(constraint) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl fmt::Display for TableConstraint { | ||||||||||||||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||||||||
| match self { | ||||||||||||||||||
|
|
@@ -166,6 +178,7 @@ impl fmt::Display for TableConstraint { | |||||||||||||||||
| TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f), | ||||||||||||||||||
| TableConstraint::PrimaryKeyUsingIndex(c) => c.fmt_with_keyword(f, "PRIMARY KEY"), | ||||||||||||||||||
| TableConstraint::UniqueUsingIndex(c) => c.fmt_with_keyword(f, "UNIQUE"), | ||||||||||||||||||
| TableConstraint::Exclusion(constraint) => constraint.fmt(f), | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -603,3 +616,123 @@ impl crate::ast::Spanned for ConstraintUsingIndex { | |||||||||||||||||
| start.union(&end) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// The operator that follows `WITH` in an `EXCLUDE` element. | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a link to the docs? Otherwise I think this struct wouldn't be very descriptive on its own |
||||||||||||||||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||||||||||||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||||||||||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||||||||||||||
| pub enum ExclusionOperator { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| /// A single operator token, e.g. `=`, `&&`, `<->`. | ||||||||||||||||||
| Token(String), | ||||||||||||||||||
| /// Postgres schema-qualified form: `OPERATOR(schema.op)`. | ||||||||||||||||||
| PgCustom(Vec<String>), | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl fmt::Display for ExclusionOperator { | ||||||||||||||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||||||||
| match self { | ||||||||||||||||||
| ExclusionOperator::Token(token) => f.write_str(token), | ||||||||||||||||||
| ExclusionOperator::PgCustom(parts) => { | ||||||||||||||||||
| write!(f, "OPERATOR({})", display_separated(parts, ".")) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// One element in an `EXCLUDE` constraint's element list. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// `{ column_name | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] WITH <operator>` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// See <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE> | ||||||||||||||||||
|
Comment on lines
+642
to
+646
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||||||||||||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||||||||||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||||||||||||||
| pub struct ExclusionElement { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| /// The index expression or column name. | ||||||||||||||||||
| pub expr: Expr, | ||||||||||||||||||
| /// Optional operator class (e.g. `gist_geometry_ops_nd`). | ||||||||||||||||||
| pub operator_class: Option<ObjectName>, | ||||||||||||||||||
| /// Ordering options (ASC/DESC, NULLS FIRST/LAST). | ||||||||||||||||||
| pub order: OrderByOptions, | ||||||||||||||||||
| /// The exclusion operator. | ||||||||||||||||||
| pub operator: ExclusionOperator, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl fmt::Display for ExclusionElement { | ||||||||||||||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||||||||
| write!(f, "{}", self.expr)?; | ||||||||||||||||||
| if let Some(opclass) = &self.operator_class { | ||||||||||||||||||
| write!(f, " {opclass}")?; | ||||||||||||||||||
| } | ||||||||||||||||||
| write!(f, "{} WITH {}", self.order, self.operator) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl crate::ast::Spanned for ExclusionElement { | ||||||||||||||||||
| fn span(&self) -> Span { | ||||||||||||||||||
| let mut span = self.expr.span(); | ||||||||||||||||||
| if let Some(opclass) = &self.operator_class { | ||||||||||||||||||
| span = span.union(&opclass.span()); | ||||||||||||||||||
| } | ||||||||||||||||||
| span | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// A PostgreSQL `EXCLUDE` constraint. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// `[ CONSTRAINT <name> ] EXCLUDE [ USING <index_method> ] ( <element> WITH <operator> [, ...] ) [ INCLUDE (<cols>) ] [ WHERE (<predicate>) ]` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// See <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE> | ||||||||||||||||||
|
Comment on lines
+681
to
+685
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||||||||||||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||||||||||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||||||||||||||
| pub struct ExclusionConstraint { | ||||||||||||||||||
| /// Optional constraint name. | ||||||||||||||||||
| pub name: Option<Ident>, | ||||||||||||||||||
| /// Optional index method (e.g. `gist`, `spgist`). | ||||||||||||||||||
| pub index_method: Option<Ident>, | ||||||||||||||||||
| /// The list of index expressions with their exclusion operators. | ||||||||||||||||||
| pub elements: Vec<ExclusionElement>, | ||||||||||||||||||
| /// Optional list of additional columns to include in the index. | ||||||||||||||||||
| pub include: Vec<Ident>, | ||||||||||||||||||
| /// Optional `WHERE` predicate to restrict the constraint to a subset of rows. | ||||||||||||||||||
| pub where_clause: Option<Box<Expr>>, | ||||||||||||||||||
| /// Optional constraint characteristics like `DEFERRABLE`. | ||||||||||||||||||
| pub characteristics: Option<ConstraintCharacteristics>, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl fmt::Display for ExclusionConstraint { | ||||||||||||||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||||||||
| use crate::ast::ddl::display_constraint_name; | ||||||||||||||||||
| write!(f, "{}EXCLUDE", display_constraint_name(&self.name))?; | ||||||||||||||||||
| if let Some(method) = &self.index_method { | ||||||||||||||||||
| write!(f, " USING {method}")?; | ||||||||||||||||||
| } | ||||||||||||||||||
| write!(f, " ({})", display_comma_separated(&self.elements))?; | ||||||||||||||||||
| if !self.include.is_empty() { | ||||||||||||||||||
| write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?; | ||||||||||||||||||
| } | ||||||||||||||||||
| if let Some(predicate) = &self.where_clause { | ||||||||||||||||||
| write!(f, " WHERE ({predicate})")?; | ||||||||||||||||||
| } | ||||||||||||||||||
| if let Some(characteristics) = &self.characteristics { | ||||||||||||||||||
| write!(f, " {characteristics}")?; | ||||||||||||||||||
| } | ||||||||||||||||||
| Ok(()) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl crate::ast::Spanned for ExclusionConstraint { | ||||||||||||||||||
| fn span(&self) -> Span { | ||||||||||||||||||
| Span::union_iter( | ||||||||||||||||||
| self.name | ||||||||||||||||||
| .iter() | ||||||||||||||||||
| .map(|i| i.span) | ||||||||||||||||||
| .chain(self.index_method.iter().map(|i| i.span)) | ||||||||||||||||||
| .chain(self.elements.iter().map(|e| e.span())) | ||||||||||||||||||
| .chain(self.include.iter().map(|i| i.span)) | ||||||||||||||||||
| .chain(self.where_clause.iter().map(|e| e.span())) | ||||||||||||||||||
| .chain(self.characteristics.iter().map(|c| c.span())), | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.