Skip to content

Commit 678234e

Browse files
Merge foreign-key-match into future-main
2 parents b5ac814 + 353b07d commit 678234e

File tree

9 files changed

+354
-67
lines changed

9 files changed

+354
-67
lines changed

src/ast/ddl.rs

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ use sqlparser_derive::{Visit, VisitMut};
3131
use crate::ast::helpers::attached_token::AttachedToken;
3232
use crate::ast::table_constraints::TableConstraint;
3333
use crate::ast::value::escape_single_quote_string;
34+
use crate::ast::{CreateViewParams, FunctionDesc, HiveSetLocation};
3435
use crate::ast::{
35-
display_comma_separated, display_separated, ArgMode, CommentDef, ConditionalStatements,
36-
CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions,
37-
CreateViewParams, DataType, Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull,
38-
FunctionDesc, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
39-
HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident, InitializeKind,
40-
MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg,
41-
OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy, SequenceOptions,
42-
Spanned, SqlOption, StorageSerializationPolicy, TableVersion, Tag, TriggerEvent,
43-
TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value, ValueWithSpan,
44-
WrappedCollection,
36+
display_comma_separated, display_separated, table_constraints::ForeignKeyConstraint, ArgMode,
37+
CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing,
38+
CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior,
39+
FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
40+
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InitializeKind, MySQLColumnPosition,
41+
ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect,
42+
Query, RefreshModeKind, RowAccessPolicy, SequenceOptions, Spanned, SqlOption,
43+
StorageSerializationPolicy, TableVersion, Tag, TriggerEvent, TriggerExecBody, TriggerObject,
44+
TriggerPeriod, TriggerReferencing, Value, ValueWithSpan, WrappedCollection,
4545
};
4646
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
4747
use crate::keywords::Keyword;
@@ -1176,12 +1176,19 @@ pub struct ProcedureParam {
11761176
pub name: Ident,
11771177
pub data_type: DataType,
11781178
pub mode: Option<ArgMode>,
1179+
pub default: Option<Expr>,
11791180
}
11801181

11811182
impl fmt::Display for ProcedureParam {
11821183
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
11831184
if let Some(mode) = &self.mode {
1184-
write!(f, "{mode} {} {}", self.name, self.data_type)
1185+
if let Some(default) = &self.default {
1186+
write!(f, "{mode} {} {} = {}", self.name, self.data_type, default)
1187+
} else {
1188+
write!(f, "{mode} {} {}", self.name, self.data_type)
1189+
}
1190+
} else if let Some(default) = &self.default {
1191+
write!(f, "{} {} = {}", self.name, self.data_type, default)
11851192
} else {
11861193
write!(f, "{} {}", self.name, self.data_type)
11871194
}
@@ -1554,20 +1561,14 @@ pub enum ColumnOption {
15541561
is_primary: bool,
15551562
characteristics: Option<ConstraintCharacteristics>,
15561563
},
1557-
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
1558-
/// <foreign_table> (<referred_columns>)
1564+
/// A referential integrity constraint (`REFERENCES <foreign_table> (<referred_columns>)
1565+
/// [ MATCH { FULL | PARTIAL | SIMPLE } ]
15591566
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
15601567
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
1561-
/// }
1568+
/// }
15621569
/// [<constraint_characteristics>]
15631570
/// `).
1564-
ForeignKey {
1565-
foreign_table: ObjectName,
1566-
referred_columns: Vec<Ident>,
1567-
on_delete: Option<ReferentialAction>,
1568-
on_update: Option<ReferentialAction>,
1569-
characteristics: Option<ConstraintCharacteristics>,
1570-
},
1571+
ForeignKey(ForeignKeyConstraint),
15711572
/// `CHECK (<expr>)`
15721573
Check(Expr),
15731574
/// Dialect-specific options, such as:
@@ -1638,6 +1639,12 @@ pub enum ColumnOption {
16381639
Invisible,
16391640
}
16401641

1642+
impl From<ForeignKeyConstraint> for ColumnOption {
1643+
fn from(fk: ForeignKeyConstraint) -> Self {
1644+
ColumnOption::ForeignKey(fk)
1645+
}
1646+
}
1647+
16411648
impl fmt::Display for ColumnOption {
16421649
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16431650
use ColumnOption::*;
@@ -1664,24 +1671,25 @@ impl fmt::Display for ColumnOption {
16641671
}
16651672
Ok(())
16661673
}
1667-
ForeignKey {
1668-
foreign_table,
1669-
referred_columns,
1670-
on_delete,
1671-
on_update,
1672-
characteristics,
1673-
} => {
1674-
write!(f, "REFERENCES {foreign_table}")?;
1675-
if !referred_columns.is_empty() {
1676-
write!(f, " ({})", display_comma_separated(referred_columns))?;
1674+
ForeignKey(constraint) => {
1675+
write!(f, "REFERENCES {}", constraint.foreign_table)?;
1676+
if !constraint.referred_columns.is_empty() {
1677+
write!(
1678+
f,
1679+
" ({})",
1680+
display_comma_separated(&constraint.referred_columns)
1681+
)?;
1682+
}
1683+
if let Some(match_kind) = &constraint.match_kind {
1684+
write!(f, " {match_kind}")?;
16771685
}
1678-
if let Some(action) = on_delete {
1686+
if let Some(action) = &constraint.on_delete {
16791687
write!(f, " ON DELETE {action}")?;
16801688
}
1681-
if let Some(action) = on_update {
1689+
if let Some(action) = &constraint.on_update {
16821690
write!(f, " ON UPDATE {action}")?;
16831691
}
1684-
if let Some(characteristics) = characteristics {
1692+
if let Some(characteristics) = &constraint.characteristics {
16851693
write!(f, " {characteristics}")?;
16861694
}
16871695
Ok(())

src/ast/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,40 @@ pub enum CastKind {
657657
DoubleColon,
658658
}
659659

660+
/// The MATCH option for foreign key constraints.
661+
///
662+
/// Specifies how to match composite foreign keys against the referenced table.
663+
/// A value inserted into the referencing column(s) is matched against the values
664+
/// of the referenced table and referenced columns using the given match type.
665+
///
666+
/// See: <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-REFERENCES>
667+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
668+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
669+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
670+
pub enum MatchKind {
671+
/// `MATCH FULL` - Will not allow one column of a multicolumn foreign key to be null
672+
/// unless all foreign key columns are null; if they are all null, the row is not
673+
/// required to have a match in the referenced table.
674+
Full,
675+
/// `MATCH PARTIAL` - Not yet implemented by most databases (part of SQL standard).
676+
/// Would allow partial matches in multicolumn foreign keys.
677+
Partial,
678+
/// `MATCH SIMPLE` - The default behavior. Allows any of the foreign key columns
679+
/// to be null; if any of them are null, the row is not required to have a match
680+
/// in the referenced table.
681+
Simple,
682+
}
683+
684+
impl fmt::Display for MatchKind {
685+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
686+
match self {
687+
MatchKind::Full => write!(f, "MATCH FULL"),
688+
MatchKind::Partial => write!(f, "MATCH PARTIAL"),
689+
MatchKind::Simple => write!(f, "MATCH SIMPLE"),
690+
}
691+
}
692+
}
693+
660694
/// `EXTRACT` syntax variants.
661695
///
662696
/// 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: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
2020
use crate::ast::{
2121
display_comma_separated, display_separated, ConstraintCharacteristics, Expr, Ident,
22-
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, ObjectName,
23-
ReferentialAction,
22+
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, MatchKind, NullsDistinctOption,
23+
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<MatchKind>,
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: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7927,10 +7927,17 @@ impl<'a> Parser<'a> {
79277927
};
79287928
let name = self.parse_identifier()?;
79297929
let data_type = self.parse_data_type()?;
7930+
let default = if self.consume_token(&Token::Eq) {
7931+
Some(self.parse_expr()?)
7932+
} else {
7933+
None
7934+
};
7935+
79307936
Ok(ProcedureParam {
79317937
name,
79327938
data_type,
79337939
mode,
7940+
default,
79347941
})
79357942
}
79367943

@@ -8060,10 +8067,15 @@ impl<'a> Parser<'a> {
80608067
// PostgreSQL allows omitting the column list and
80618068
// uses the primary key column of the foreign table by default
80628069
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
8070+
let mut match_kind = None;
80638071
let mut on_delete = None;
80648072
let mut on_update = None;
80658073
loop {
8066-
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
8074+
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
8075+
match_kind = Some(self.parse_match_kind()?);
8076+
} else if on_delete.is_none()
8077+
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
8078+
{
80678079
on_delete = Some(self.parse_referential_action()?);
80688080
} else if on_update.is_none()
80698081
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
@@ -8075,13 +8087,20 @@ impl<'a> Parser<'a> {
80758087
}
80768088
let characteristics = self.parse_constraint_characteristics()?;
80778089

8078-
Ok(Some(ColumnOption::ForeignKey {
8079-
foreign_table,
8080-
referred_columns,
8081-
on_delete,
8082-
on_update,
8083-
characteristics,
8084-
}))
8090+
Ok(Some(
8091+
ForeignKeyConstraint {
8092+
name: None, // Column-level constraints don't have names
8093+
index_name: None, // Not applicable for column-level constraints
8094+
columns: vec![], // Column is implicit for column-level constraints
8095+
foreign_table,
8096+
referred_columns,
8097+
on_delete,
8098+
on_update,
8099+
match_kind,
8100+
characteristics,
8101+
}
8102+
.into(),
8103+
))
80858104
} else if self.parse_keyword(Keyword::CHECK) {
80868105
self.expect_token(&Token::LParen)?;
80878106
// since `CHECK` requires parentheses, we can parse the inner expression in ParserState::Normal
@@ -8355,6 +8374,18 @@ impl<'a> Parser<'a> {
83558374
}
83568375
}
83578376

8377+
pub fn parse_match_kind(&mut self) -> Result<MatchKind, ParserError> {
8378+
if self.parse_keyword(Keyword::FULL) {
8379+
Ok(MatchKind::Full)
8380+
} else if self.parse_keyword(Keyword::PARTIAL) {
8381+
Ok(MatchKind::Partial)
8382+
} else if self.parse_keyword(Keyword::SIMPLE) {
8383+
Ok(MatchKind::Simple)
8384+
} else {
8385+
self.expected("one of FULL, PARTIAL or SIMPLE", self.peek_token())
8386+
}
8387+
}
8388+
83588389
pub fn parse_constraint_characteristics(
83598390
&mut self,
83608391
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
@@ -8465,10 +8496,15 @@ impl<'a> Parser<'a> {
84658496
self.expect_keyword_is(Keyword::REFERENCES)?;
84668497
let foreign_table = self.parse_object_name(false)?;
84678498
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
8499+
let mut match_kind = None;
84688500
let mut on_delete = None;
84698501
let mut on_update = None;
84708502
loop {
8471-
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
8503+
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
8504+
match_kind = Some(self.parse_match_kind()?);
8505+
} else if on_delete.is_none()
8506+
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
8507+
{
84728508
on_delete = Some(self.parse_referential_action()?);
84738509
} else if on_update.is_none()
84748510
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
@@ -8490,6 +8526,7 @@ impl<'a> Parser<'a> {
84908526
referred_columns,
84918527
on_delete,
84928528
on_update,
8529+
match_kind,
84938530
characteristics,
84948531
}
84958532
.into(),

0 commit comments

Comments
 (0)