Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 33 additions & 31 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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::*;
Expand All @@ -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(())
Expand Down
25 changes: 25 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 1 addition & 13 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
12 changes: 8 additions & 4 deletions src/ast/table_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>]
/// }`).
Expand All @@ -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>,
}

Expand All @@ -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}")?;
}
Expand Down
2 changes: 2 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ define_keywords!(
PARAMETER,
PARQUET,
PART,
PARTIAL,
PARTITION,
PARTITIONED,
PARTITIONS,
Expand Down Expand Up @@ -885,6 +886,7 @@ define_keywords!(
SHOW,
SIGNED,
SIMILAR,
SIMPLE,
SKIP,
SLOW,
SMALLINT,
Expand Down
52 changes: 41 additions & 11 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -7965,7 +7965,7 @@ impl<'a> Parser<'a> {
};
}
Ok(ColumnDef {
name,
name: col_name,
data_type,
options,
})
Expand Down Expand Up @@ -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])
Expand All @@ -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
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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])
Expand All @@ -8495,6 +8524,7 @@ impl<'a> Parser<'a> {
referred_columns,
on_delete,
on_update,
match_kind,
characteristics,
}
.into(),
Expand Down
Loading