mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
Added support for MATCH syntax and unified column option ForeignKey (#2062)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
parent
4490c8c55c
commit
c8531d41a1
8 changed files with 179 additions and 63 deletions
|
|
@ -30,14 +30,15 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
|
||||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, ArgMode, AttachedToken, CommentDef,
|
||||
ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind,
|
||||
CreateTableOptions, CreateViewParams, DataType, Expr, FileFormat, FunctionBehavior,
|
||||
FunctionCalledOnNull, FunctionDesc, FunctionDeterminismSpecifier, FunctionParallel,
|
||||
HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident,
|
||||
InitializeKind, MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens,
|
||||
OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy,
|
||||
SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableConstraint, TableVersion,
|
||||
display_comma_separated, display_separated,
|
||||
table_constraints::{ForeignKeyConstraint, TableConstraint},
|
||||
ArgMode, AttachedToken, CommentDef, ConditionalStatements, CreateFunctionBody,
|
||||
CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, CreateViewParams, DataType, Expr,
|
||||
FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDesc, FunctionDeterminismSpecifier,
|
||||
FunctionParallel, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat,
|
||||
HiveSetLocation, Ident, InitializeKind, MySQLColumnPosition, ObjectName, OnCommit,
|
||||
OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind,
|
||||
RowAccessPolicy, SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableVersion,
|
||||
Tag, TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value,
|
||||
ValueWithSpan, WrappedCollection,
|
||||
};
|
||||
|
|
@ -1559,20 +1560,14 @@ pub enum ColumnOption {
|
|||
is_primary: bool,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
||||
/// <foreign_table> (<referred_columns>)
|
||||
/// A referential integrity constraint (`REFERENCES <foreign_table> (<referred_columns>)
|
||||
/// [ MATCH { FULL | PARTIAL | SIMPLE } ]
|
||||
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
|
||||
/// }
|
||||
/// }
|
||||
/// [<constraint_characteristics>]
|
||||
/// `).
|
||||
ForeignKey {
|
||||
foreign_table: ObjectName,
|
||||
referred_columns: Vec<Ident>,
|
||||
on_delete: Option<ReferentialAction>,
|
||||
on_update: Option<ReferentialAction>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
ForeignKey(ForeignKeyConstraint),
|
||||
/// `CHECK (<expr>)`
|
||||
Check(Expr),
|
||||
/// Dialect-specific options, such as:
|
||||
|
|
@ -1643,6 +1638,12 @@ pub enum ColumnOption {
|
|||
Invisible,
|
||||
}
|
||||
|
||||
impl From<ForeignKeyConstraint> for ColumnOption {
|
||||
fn from(fk: ForeignKeyConstraint) -> Self {
|
||||
ColumnOption::ForeignKey(fk)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ColumnOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ColumnOption::*;
|
||||
|
|
@ -1669,24 +1670,25 @@ impl fmt::Display for ColumnOption {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
ForeignKey {
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
} => {
|
||||
write!(f, "REFERENCES {foreign_table}")?;
|
||||
if !referred_columns.is_empty() {
|
||||
write!(f, " ({})", display_comma_separated(referred_columns))?;
|
||||
ForeignKey(constraint) => {
|
||||
write!(f, "REFERENCES {}", constraint.foreign_table)?;
|
||||
if !constraint.referred_columns.is_empty() {
|
||||
write!(
|
||||
f,
|
||||
" ({})",
|
||||
display_comma_separated(&constraint.referred_columns)
|
||||
)?;
|
||||
}
|
||||
if let Some(action) = on_delete {
|
||||
if let Some(match_kind) = &constraint.match_kind {
|
||||
write!(f, " {match_kind}")?;
|
||||
}
|
||||
if let Some(action) = &constraint.on_delete {
|
||||
write!(f, " ON DELETE {action}")?;
|
||||
}
|
||||
if let Some(action) = on_update {
|
||||
if let Some(action) = &constraint.on_update {
|
||||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
if let Some(characteristics) = &constraint.characteristics {
|
||||
write!(f, " {characteristics}")?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -657,6 +657,31 @@ pub enum CastKind {
|
|||
DoubleColon,
|
||||
}
|
||||
|
||||
/// `MATCH` type for constraint references
|
||||
///
|
||||
/// See: <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-REFERENCES>
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum ConstraintReferenceMatchKind {
|
||||
/// `MATCH FULL`
|
||||
Full,
|
||||
/// `MATCH PARTIAL`
|
||||
Partial,
|
||||
/// `MATCH SIMPLE`
|
||||
Simple,
|
||||
}
|
||||
|
||||
impl fmt::Display for ConstraintReferenceMatchKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Full => write!(f, "MATCH FULL"),
|
||||
Self::Partial => write!(f, "MATCH PARTIAL"),
|
||||
Self::Simple => write!(f, "MATCH SIMPLE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `EXTRACT` syntax variants.
|
||||
///
|
||||
/// In Snowflake dialect, the `EXTRACT` expression can support either the `from` syntax
|
||||
|
|
|
|||
|
|
@ -741,19 +741,7 @@ impl Spanned for ColumnOption {
|
|||
ColumnOption::Ephemeral(expr) => expr.as_ref().map_or(Span::empty(), |e| e.span()),
|
||||
ColumnOption::Alias(expr) => expr.span(),
|
||||
ColumnOption::Unique { .. } => Span::empty(),
|
||||
ColumnOption::ForeignKey {
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
} => union_spans(
|
||||
core::iter::once(foreign_table.span())
|
||||
.chain(referred_columns.iter().map(|i| i.span))
|
||||
.chain(on_delete.iter().map(|i| i.span()))
|
||||
.chain(on_update.iter().map(|i| i.span()))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
ColumnOption::ForeignKey(constraint) => constraint.span(),
|
||||
ColumnOption::Check(expr) => expr.span(),
|
||||
ColumnOption::DialectSpecific(_) => Span::empty(),
|
||||
ColumnOption::CharacterSet(object_name) => object_name.span(),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
//! SQL Abstract Syntax Tree (AST) types for table constraints
|
||||
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, ConstraintCharacteristics, Expr, Ident,
|
||||
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, ObjectName,
|
||||
ReferentialAction,
|
||||
display_comma_separated, display_separated, ConstraintCharacteristics,
|
||||
ConstraintReferenceMatchKind, Expr, Ident, IndexColumn, IndexOption, IndexType,
|
||||
KeyOrIndexDisplay, NullsDistinctOption, ObjectName, ReferentialAction,
|
||||
};
|
||||
use crate::tokenizer::Span;
|
||||
use core::fmt;
|
||||
|
|
@ -189,7 +189,7 @@ impl crate::ast::Spanned for CheckConstraint {
|
|||
}
|
||||
|
||||
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
||||
/// REFERENCES <foreign_table> (<referred_columns>)
|
||||
/// REFERENCES <foreign_table> (<referred_columns>) [ MATCH { FULL | PARTIAL | SIMPLE } ]
|
||||
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
|
||||
/// }`).
|
||||
|
|
@ -206,6 +206,7 @@ pub struct ForeignKeyConstraint {
|
|||
pub referred_columns: Vec<Ident>,
|
||||
pub on_delete: Option<ReferentialAction>,
|
||||
pub on_update: Option<ReferentialAction>,
|
||||
pub match_kind: Option<ConstraintReferenceMatchKind>,
|
||||
pub characteristics: Option<ConstraintCharacteristics>,
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +224,9 @@ impl fmt::Display for ForeignKeyConstraint {
|
|||
if !self.referred_columns.is_empty() {
|
||||
write!(f, "({})", display_comma_separated(&self.referred_columns))?;
|
||||
}
|
||||
if let Some(match_kind) = &self.match_kind {
|
||||
write!(f, " {match_kind}")?;
|
||||
}
|
||||
if let Some(action) = &self.on_delete {
|
||||
write!(f, " ON DELETE {action}")?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -713,6 +713,7 @@ define_keywords!(
|
|||
PARAMETER,
|
||||
PARQUET,
|
||||
PART,
|
||||
PARTIAL,
|
||||
PARTITION,
|
||||
PARTITIONED,
|
||||
PARTITIONS,
|
||||
|
|
@ -885,6 +886,7 @@ define_keywords!(
|
|||
SHOW,
|
||||
SIGNED,
|
||||
SIMILAR,
|
||||
SIMPLE,
|
||||
SKIP,
|
||||
SLOW,
|
||||
SMALLINT,
|
||||
|
|
|
|||
|
|
@ -7940,7 +7940,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
|
||||
let name = self.parse_identifier()?;
|
||||
let col_name = self.parse_identifier()?;
|
||||
let data_type = if self.is_column_type_sqlite_unspecified() {
|
||||
DataType::Unspecified
|
||||
} else {
|
||||
|
|
@ -7965,7 +7965,7 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
}
|
||||
Ok(ColumnDef {
|
||||
name,
|
||||
name: col_name,
|
||||
data_type,
|
||||
options,
|
||||
})
|
||||
|
|
@ -8065,10 +8065,15 @@ impl<'a> Parser<'a> {
|
|||
// PostgreSQL allows omitting the column list and
|
||||
// uses the primary key column of the foreign table by default
|
||||
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
|
||||
let mut match_kind = None;
|
||||
let mut on_delete = None;
|
||||
let mut on_update = None;
|
||||
loop {
|
||||
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
|
||||
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
|
||||
match_kind = Some(self.parse_match_kind()?);
|
||||
} else if on_delete.is_none()
|
||||
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
|
||||
{
|
||||
on_delete = Some(self.parse_referential_action()?);
|
||||
} else if on_update.is_none()
|
||||
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
|
||||
|
|
@ -8080,13 +8085,20 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
|
||||
Ok(Some(ColumnOption::ForeignKey {
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
}))
|
||||
Ok(Some(
|
||||
ForeignKeyConstraint {
|
||||
name: None, // Column-level constraints don't have names
|
||||
index_name: None, // Not applicable for column-level constraints
|
||||
columns: vec![], // Not applicable for column-level constraints
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
match_kind,
|
||||
characteristics,
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
} else if self.parse_keyword(Keyword::CHECK) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
// since `CHECK` requires parentheses, we can parse the inner expression in ParserState::Normal
|
||||
|
|
@ -8360,6 +8372,18 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_match_kind(&mut self) -> Result<ConstraintReferenceMatchKind, ParserError> {
|
||||
if self.parse_keyword(Keyword::FULL) {
|
||||
Ok(ConstraintReferenceMatchKind::Full)
|
||||
} else if self.parse_keyword(Keyword::PARTIAL) {
|
||||
Ok(ConstraintReferenceMatchKind::Partial)
|
||||
} else if self.parse_keyword(Keyword::SIMPLE) {
|
||||
Ok(ConstraintReferenceMatchKind::Simple)
|
||||
} else {
|
||||
self.expected("one of FULL, PARTIAL or SIMPLE", self.peek_token())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_constraint_characteristics(
|
||||
&mut self,
|
||||
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
|
||||
|
|
@ -8470,10 +8494,15 @@ impl<'a> Parser<'a> {
|
|||
self.expect_keyword_is(Keyword::REFERENCES)?;
|
||||
let foreign_table = self.parse_object_name(false)?;
|
||||
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
|
||||
let mut match_kind = None;
|
||||
let mut on_delete = None;
|
||||
let mut on_update = None;
|
||||
loop {
|
||||
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
|
||||
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
|
||||
match_kind = Some(self.parse_match_kind()?);
|
||||
} else if on_delete.is_none()
|
||||
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
|
||||
{
|
||||
on_delete = Some(self.parse_referential_action()?);
|
||||
} else if on_update.is_none()
|
||||
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
|
||||
|
|
@ -8495,6 +8524,7 @@ impl<'a> Parser<'a> {
|
|||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
match_kind,
|
||||
characteristics,
|
||||
}
|
||||
.into(),
|
||||
|
|
|
|||
|
|
@ -3790,13 +3790,17 @@ fn parse_create_table() {
|
|||
data_type: DataType::Int(None),
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::ForeignKey {
|
||||
option: ColumnOption::ForeignKey(ForeignKeyConstraint {
|
||||
name: None,
|
||||
index_name: None,
|
||||
columns: vec![],
|
||||
foreign_table: ObjectName::from(vec!["othertable".into()]),
|
||||
referred_columns: vec!["a".into(), "b".into()],
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
},
|
||||
}),
|
||||
}],
|
||||
},
|
||||
ColumnDef {
|
||||
|
|
@ -3804,13 +3808,17 @@ fn parse_create_table() {
|
|||
data_type: DataType::Int(None),
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::ForeignKey {
|
||||
option: ColumnOption::ForeignKey(ForeignKeyConstraint {
|
||||
name: None,
|
||||
index_name: None,
|
||||
columns: vec![],
|
||||
foreign_table: ObjectName::from(vec!["othertable2".into()]),
|
||||
referred_columns: vec![],
|
||||
on_delete: Some(ReferentialAction::Cascade),
|
||||
on_update: Some(ReferentialAction::NoAction),
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
},
|
||||
}),
|
||||
},],
|
||||
},
|
||||
]
|
||||
|
|
@ -3826,6 +3834,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Restrict),
|
||||
on_update: None,
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
}
|
||||
.into(),
|
||||
|
|
@ -3837,6 +3846,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::NoAction),
|
||||
on_update: Some(ReferentialAction::Restrict),
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
}
|
||||
.into(),
|
||||
|
|
@ -3848,6 +3858,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Cascade),
|
||||
on_update: Some(ReferentialAction::SetDefault),
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
}
|
||||
.into(),
|
||||
|
|
@ -3859,6 +3870,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["longitude".into()],
|
||||
on_delete: None,
|
||||
on_update: Some(ReferentialAction::SetNull),
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
}
|
||||
.into(),
|
||||
|
|
@ -3957,6 +3969,7 @@ fn parse_create_table_with_constraint_characteristics() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Restrict),
|
||||
on_update: None,
|
||||
match_kind: None,
|
||||
characteristics: Some(ConstraintCharacteristics {
|
||||
deferrable: Some(true),
|
||||
initially: Some(DeferrableInitial::Deferred),
|
||||
|
|
@ -3972,6 +3985,7 @@ fn parse_create_table_with_constraint_characteristics() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::NoAction),
|
||||
on_update: Some(ReferentialAction::Restrict),
|
||||
match_kind: None,
|
||||
characteristics: Some(ConstraintCharacteristics {
|
||||
deferrable: Some(true),
|
||||
initially: Some(DeferrableInitial::Immediate),
|
||||
|
|
@ -3987,6 +4001,7 @@ fn parse_create_table_with_constraint_characteristics() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Cascade),
|
||||
on_update: Some(ReferentialAction::SetDefault),
|
||||
match_kind: None,
|
||||
characteristics: Some(ConstraintCharacteristics {
|
||||
deferrable: Some(false),
|
||||
initially: Some(DeferrableInitial::Deferred),
|
||||
|
|
@ -4002,6 +4017,7 @@ fn parse_create_table_with_constraint_characteristics() {
|
|||
referred_columns: vec!["longitude".into()],
|
||||
on_delete: None,
|
||||
on_update: Some(ReferentialAction::SetNull),
|
||||
match_kind: None,
|
||||
characteristics: Some(ConstraintCharacteristics {
|
||||
deferrable: Some(false),
|
||||
initially: Some(DeferrableInitial::Immediate),
|
||||
|
|
|
|||
|
|
@ -6438,6 +6438,7 @@ fn parse_alter_table_constraint_not_valid() {
|
|||
referred_columns: vec!["ref".into()],
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
match_kind: None,
|
||||
characteristics: None,
|
||||
}
|
||||
.into(),
|
||||
|
|
@ -6603,3 +6604,51 @@ fn parse_alter_schema() {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_foreign_key_match() {
|
||||
let test_cases = [
|
||||
("MATCH FULL", ConstraintReferenceMatchKind::Full),
|
||||
("MATCH SIMPLE", ConstraintReferenceMatchKind::Simple),
|
||||
("MATCH PARTIAL", ConstraintReferenceMatchKind::Partial),
|
||||
];
|
||||
|
||||
for (match_clause, expected_kind) in test_cases {
|
||||
// Test column-level foreign key
|
||||
let sql = format!("CREATE TABLE t (id INT REFERENCES other_table (id) {match_clause})");
|
||||
let statement = pg_and_generic().verified_stmt(&sql);
|
||||
match statement {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
match &columns[0].options[0].option {
|
||||
ColumnOption::ForeignKey(constraint) => {
|
||||
assert_eq!(constraint.match_kind, Some(expected_kind));
|
||||
}
|
||||
_ => panic!("Expected ColumnOption::ForeignKey"),
|
||||
}
|
||||
}
|
||||
_ => unreachable!("{:?} should parse to Statement::CreateTable", sql),
|
||||
}
|
||||
|
||||
// Test table-level foreign key constraint
|
||||
let sql = format!(
|
||||
"CREATE TABLE t (id INT, FOREIGN KEY (id) REFERENCES other_table(id) {match_clause})"
|
||||
);
|
||||
let statement = pg_and_generic().verified_stmt(&sql);
|
||||
match statement {
|
||||
Statement::CreateTable(CreateTable { constraints, .. }) => match &constraints[0] {
|
||||
TableConstraint::ForeignKey(constraint) => {
|
||||
assert_eq!(constraint.match_kind, Some(expected_kind));
|
||||
}
|
||||
_ => panic!("Expected TableConstraint::ForeignKey"),
|
||||
},
|
||||
_ => unreachable!("{:?} should parse to Statement::CreateTable", sql),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_foreign_key_match_with_actions() {
|
||||
let sql = "CREATE TABLE orders (order_id INT REFERENCES another_table (id) MATCH FULL ON DELETE CASCADE ON UPDATE RESTRICT, customer_id INT, CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id) MATCH SIMPLE ON DELETE SET NULL ON UPDATE CASCADE)";
|
||||
|
||||
pg_and_generic().verified_stmt(sql);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue