Skip to content

Commit b1a6ab3

Browse files
LucaCappelletti94iffyio
authored andcommitted
Added support for MATCH syntax and unified column option ForeignKey (apache#2062)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent e41e401 commit b1a6ab3

File tree

8 files changed

+179
-63
lines changed

8 files changed

+179
-63
lines changed

src/ast/ddl.rs

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ use sqlparser_derive::{Visit, VisitMut};
3030

3131
use crate::ast::value::escape_single_quote_string;
3232
use crate::ast::{
33-
display_comma_separated, display_separated, ArgMode, AttachedToken, CommentDef,
34-
ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind,
35-
CreateTableOptions, CreateViewParams, DataType, Expr, FileFormat, FunctionBehavior,
36-
FunctionCalledOnNull, FunctionDesc, FunctionDeterminismSpecifier, FunctionParallel,
37-
HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident,
38-
InitializeKind, MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens,
39-
OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy,
40-
SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableConstraint, TableVersion,
33+
display_comma_separated, display_separated,
34+
table_constraints::{ForeignKeyConstraint, TableConstraint},
35+
ArgMode, AttachedToken, CommentDef, ConditionalStatements, CreateFunctionBody,
36+
CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, CreateViewParams, DataType, Expr,
37+
FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDesc, FunctionDeterminismSpecifier,
38+
FunctionParallel, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat,
39+
HiveSetLocation, Ident, InitializeKind, MySQLColumnPosition, ObjectName, OnCommit,
40+
OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind,
41+
RowAccessPolicy, SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableVersion,
4142
Tag, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value,
4243
ValueWithSpan, WrappedCollection,
4344
};
@@ -1559,20 +1560,14 @@ pub enum ColumnOption {
15591560
is_primary: bool,
15601561
characteristics: Option<ConstraintCharacteristics>,
15611562
},
1562-
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
1563-
/// <foreign_table> (<referred_columns>)
1563+
/// A referential integrity constraint (`REFERENCES <foreign_table> (<referred_columns>)
1564+
/// [ MATCH { FULL | PARTIAL | SIMPLE } ]
15641565
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
15651566
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
1566-
/// }
1567+
/// }
15671568
/// [<constraint_characteristics>]
15681569
/// `).
1569-
ForeignKey {
1570-
foreign_table: ObjectName,
1571-
referred_columns: Vec<Ident>,
1572-
on_delete: Option<ReferentialAction>,
1573-
on_update: Option<ReferentialAction>,
1574-
characteristics: Option<ConstraintCharacteristics>,
1575-
},
1570+
ForeignKey(ForeignKeyConstraint),
15761571
/// `CHECK (<expr>)`
15771572
Check(Expr),
15781573
/// Dialect-specific options, such as:
@@ -1643,6 +1638,12 @@ pub enum ColumnOption {
16431638
Invisible,
16441639
}
16451640

1641+
impl From<ForeignKeyConstraint> for ColumnOption {
1642+
fn from(fk: ForeignKeyConstraint) -> Self {
1643+
ColumnOption::ForeignKey(fk)
1644+
}
1645+
}
1646+
16461647
impl fmt::Display for ColumnOption {
16471648
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16481649
use ColumnOption::*;
@@ -1669,24 +1670,25 @@ impl fmt::Display for ColumnOption {
16691670
}
16701671
Ok(())
16711672
}
1672-
ForeignKey {
1673-
foreign_table,
1674-
referred_columns,
1675-
on_delete,
1676-
on_update,
1677-
characteristics,
1678-
} => {
1679-
write!(f, "REFERENCES {foreign_table}")?;
1680-
if !referred_columns.is_empty() {
1681-
write!(f, " ({})", display_comma_separated(referred_columns))?;
1673+
ForeignKey(constraint) => {
1674+
write!(f, "REFERENCES {}", constraint.foreign_table)?;
1675+
if !constraint.referred_columns.is_empty() {
1676+
write!(
1677+
f,
1678+
" ({})",
1679+
display_comma_separated(&constraint.referred_columns)
1680+
)?;
16821681
}
1683-
if let Some(action) = on_delete {
1682+
if let Some(match_kind) = &constraint.match_kind {
1683+
write!(f, " {match_kind}")?;
1684+
}
1685+
if let Some(action) = &constraint.on_delete {
16841686
write!(f, " ON DELETE {action}")?;
16851687
}
1686-
if let Some(action) = on_update {
1688+
if let Some(action) = &constraint.on_update {
16871689
write!(f, " ON UPDATE {action}")?;
16881690
}
1689-
if let Some(characteristics) = characteristics {
1691+
if let Some(characteristics) = &constraint.characteristics {
16901692
write!(f, " {characteristics}")?;
16911693
}
16921694
Ok(())

src/ast/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,31 @@ pub enum CastKind {
673673
DoubleColon,
674674
}
675675

676+
/// `MATCH` type for constraint references
677+
///
678+
/// See: <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-REFERENCES>
679+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
680+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
681+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
682+
pub enum ConstraintReferenceMatchKind {
683+
/// `MATCH FULL`
684+
Full,
685+
/// `MATCH PARTIAL`
686+
Partial,
687+
/// `MATCH SIMPLE`
688+
Simple,
689+
}
690+
691+
impl fmt::Display for ConstraintReferenceMatchKind {
692+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
693+
match self {
694+
Self::Full => write!(f, "MATCH FULL"),
695+
Self::Partial => write!(f, "MATCH PARTIAL"),
696+
Self::Simple => write!(f, "MATCH SIMPLE"),
697+
}
698+
}
699+
}
700+
676701
/// `EXTRACT` syntax variants.
677702
///
678703
/// In Snowflake dialect, the `EXTRACT` expression can support either the `from` syntax

src/ast/spans.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -741,19 +741,7 @@ impl Spanned for ColumnOption {
741741
ColumnOption::Ephemeral(expr) => expr.as_ref().map_or(Span::empty(), |e| e.span()),
742742
ColumnOption::Alias(expr) => expr.span(),
743743
ColumnOption::Unique { .. } => Span::empty(),
744-
ColumnOption::ForeignKey {
745-
foreign_table,
746-
referred_columns,
747-
on_delete,
748-
on_update,
749-
characteristics,
750-
} => union_spans(
751-
core::iter::once(foreign_table.span())
752-
.chain(referred_columns.iter().map(|i| i.span))
753-
.chain(on_delete.iter().map(|i| i.span()))
754-
.chain(on_update.iter().map(|i| i.span()))
755-
.chain(characteristics.iter().map(|i| i.span())),
756-
),
744+
ColumnOption::ForeignKey(constraint) => constraint.span(),
757745
ColumnOption::Check(expr) => expr.span(),
758746
ColumnOption::DialectSpecific(_) => Span::empty(),
759747
ColumnOption::CharacterSet(object_name) => object_name.span(),

src/ast/table_constraints.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
//! SQL Abstract Syntax Tree (AST) types for table constraints
1919
2020
use crate::ast::{
21-
display_comma_separated, display_separated, ConstraintCharacteristics, Expr, Ident,
22-
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, ObjectName,
23-
ReferentialAction,
21+
display_comma_separated, display_separated, ConstraintCharacteristics,
22+
ConstraintReferenceMatchKind, Expr, Ident, IndexColumn, IndexOption, IndexType,
23+
KeyOrIndexDisplay, NullsDistinctOption, ObjectName, ReferentialAction,
2424
};
2525
use crate::tokenizer::Span;
2626
use core::fmt;
@@ -189,7 +189,7 @@ impl crate::ast::Spanned for CheckConstraint {
189189
}
190190

191191
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
192-
/// REFERENCES <foreign_table> (<referred_columns>)
192+
/// REFERENCES <foreign_table> (<referred_columns>) [ MATCH { FULL | PARTIAL | SIMPLE } ]
193193
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
194194
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
195195
/// }`).
@@ -206,6 +206,7 @@ pub struct ForeignKeyConstraint {
206206
pub referred_columns: Vec<Ident>,
207207
pub on_delete: Option<ReferentialAction>,
208208
pub on_update: Option<ReferentialAction>,
209+
pub match_kind: Option<ConstraintReferenceMatchKind>,
209210
pub characteristics: Option<ConstraintCharacteristics>,
210211
}
211212

@@ -223,6 +224,9 @@ impl fmt::Display for ForeignKeyConstraint {
223224
if !self.referred_columns.is_empty() {
224225
write!(f, "({})", display_comma_separated(&self.referred_columns))?;
225226
}
227+
if let Some(match_kind) = &self.match_kind {
228+
write!(f, " {match_kind}")?;
229+
}
226230
if let Some(action) = &self.on_delete {
227231
write!(f, " ON DELETE {action}")?;
228232
}

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ define_keywords!(
713713
PARAMETER,
714714
PARQUET,
715715
PART,
716+
PARTIAL,
716717
PARTITION,
717718
PARTITIONED,
718719
PARTITIONS,
@@ -885,6 +886,7 @@ define_keywords!(
885886
SHOW,
886887
SIGNED,
887888
SIMILAR,
889+
SIMPLE,
888890
SKIP,
889891
SLOW,
890892
SMALLINT,

src/parser/mod.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7971,7 +7971,7 @@ impl<'a> Parser<'a> {
79717971
}
79727972

79737973
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
7974-
let name = self.parse_identifier()?;
7974+
let col_name = self.parse_identifier()?;
79757975
let data_type = if self.is_column_type_sqlite_unspecified() {
79767976
DataType::Unspecified
79777977
} else {
@@ -7996,7 +7996,7 @@ impl<'a> Parser<'a> {
79967996
};
79977997
}
79987998
Ok(ColumnDef {
7999-
name,
7999+
name: col_name,
80008000
data_type,
80018001
options,
80028002
})
@@ -8096,10 +8096,15 @@ impl<'a> Parser<'a> {
80968096
// PostgreSQL allows omitting the column list and
80978097
// uses the primary key column of the foreign table by default
80988098
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
8099+
let mut match_kind = None;
80998100
let mut on_delete = None;
81008101
let mut on_update = None;
81018102
loop {
8102-
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
8103+
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
8104+
match_kind = Some(self.parse_match_kind()?);
8105+
} else if on_delete.is_none()
8106+
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
8107+
{
81038108
on_delete = Some(self.parse_referential_action()?);
81048109
} else if on_update.is_none()
81058110
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
@@ -8111,13 +8116,20 @@ impl<'a> Parser<'a> {
81118116
}
81128117
let characteristics = self.parse_constraint_characteristics()?;
81138118

8114-
Ok(Some(ColumnOption::ForeignKey {
8115-
foreign_table,
8116-
referred_columns,
8117-
on_delete,
8118-
on_update,
8119-
characteristics,
8120-
}))
8119+
Ok(Some(
8120+
ForeignKeyConstraint {
8121+
name: None, // Column-level constraints don't have names
8122+
index_name: None, // Not applicable for column-level constraints
8123+
columns: vec![], // Not applicable for column-level constraints
8124+
foreign_table,
8125+
referred_columns,
8126+
on_delete,
8127+
on_update,
8128+
match_kind,
8129+
characteristics,
8130+
}
8131+
.into(),
8132+
))
81218133
} else if self.parse_keyword(Keyword::CHECK) {
81228134
self.expect_token(&Token::LParen)?;
81238135
// since `CHECK` requires parentheses, we can parse the inner expression in ParserState::Normal
@@ -8391,6 +8403,18 @@ impl<'a> Parser<'a> {
83918403
}
83928404
}
83938405

8406+
pub fn parse_match_kind(&mut self) -> Result<ConstraintReferenceMatchKind, ParserError> {
8407+
if self.parse_keyword(Keyword::FULL) {
8408+
Ok(ConstraintReferenceMatchKind::Full)
8409+
} else if self.parse_keyword(Keyword::PARTIAL) {
8410+
Ok(ConstraintReferenceMatchKind::Partial)
8411+
} else if self.parse_keyword(Keyword::SIMPLE) {
8412+
Ok(ConstraintReferenceMatchKind::Simple)
8413+
} else {
8414+
self.expected("one of FULL, PARTIAL or SIMPLE", self.peek_token())
8415+
}
8416+
}
8417+
83948418
pub fn parse_constraint_characteristics(
83958419
&mut self,
83968420
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
@@ -8501,10 +8525,15 @@ impl<'a> Parser<'a> {
85018525
self.expect_keyword_is(Keyword::REFERENCES)?;
85028526
let foreign_table = self.parse_object_name(false)?;
85038527
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
8528+
let mut match_kind = None;
85048529
let mut on_delete = None;
85058530
let mut on_update = None;
85068531
loop {
8507-
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
8532+
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
8533+
match_kind = Some(self.parse_match_kind()?);
8534+
} else if on_delete.is_none()
8535+
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
8536+
{
85088537
on_delete = Some(self.parse_referential_action()?);
85098538
} else if on_update.is_none()
85108539
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
@@ -8526,6 +8555,7 @@ impl<'a> Parser<'a> {
85268555
referred_columns,
85278556
on_delete,
85288557
on_update,
8558+
match_kind,
85298559
characteristics,
85308560
}
85318561
.into(),

0 commit comments

Comments
 (0)