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
52 changes: 27 additions & 25 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ use sqlparser_derive::{Visit, VisitMut};

use crate::ast::value::escape_single_quote_string;
use crate::ast::{
display_comma_separated, display_separated, table_constraints::TableConstraint, ArgMode,
CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing,
display_comma_separated, display_separated,
table_constraints::{ForeignKeyConstraint, TableConstraint},
ArgMode, CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing,
CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior,
FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InitializeKind, MySQLColumnPosition,
Expand Down Expand Up @@ -1558,20 +1559,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 @@ -1642,6 +1637,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 @@ -1668,24 +1669,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 @@ -655,6 +655,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 @@ -822,19 +822,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
81 changes: 60 additions & 21 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7919,7 +7919,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 @@ -7929,22 +7929,22 @@ impl<'a> Parser<'a> {
loop {
if self.parse_keyword(Keyword::CONSTRAINT) {
let name = Some(self.parse_identifier()?);
if let Some(option) = self.parse_optional_column_option()? {
if let Some(option) = self.parse_optional_column_option(&col_name)? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this change implies, it looks like we'd be storing the column name twice in the AST node which doesn't seem ideal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed to standardize ForeignKeyConstraint and avoid having a duplicated struct to represent the same type of concept. I could possibly leave the columns vector of the ForeignKeyConstraint empty, but when I use that struct in code that works on top of the AST it is quite useful to have the column ident defined there, and not have to handle the case of a special ForeignKeyConstraint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will note that it is the same change needed for PR #2064, which I did separately but the goal is identical - to standardize the structs used to represent constraints in the columns and table.

options.push(ColumnOptionDef { name, option });
} else {
return self.expected(
"constraint details after CONSTRAINT <name>",
self.peek_token(),
);
}
} else if let Some(option) = self.parse_optional_column_option()? {
} else if let Some(option) = self.parse_optional_column_option(&col_name)? {
options.push(ColumnOptionDef { name: None, option });
} else {
break;
};
}
Ok(ColumnDef {
name,
name: col_name,
data_type,
options,
})
Expand Down Expand Up @@ -7973,20 +7973,26 @@ impl<'a> Parser<'a> {
}
}

pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
pub fn parse_optional_column_option(
&mut self,
column_ident: &Ident,
) -> Result<Option<ColumnOption>, ParserError> {
if let Some(option) = self.dialect.parse_column_option(self)? {
return option;
}

self.with_state(
ColumnDefinition,
|parser| -> Result<Option<ColumnOption>, ParserError> {
parser.parse_optional_column_option_inner()
parser.parse_optional_column_option_inner(column_ident)
},
)
}

fn parse_optional_column_option_inner(&mut self) -> Result<Option<ColumnOption>, ParserError> {
fn parse_optional_column_option_inner(
&mut self,
column_ident: &Ident,
) -> Result<Option<ColumnOption>, ParserError> {
if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
Ok(Some(ColumnOption::CharacterSet(
self.parse_object_name(false)?,
Expand Down Expand Up @@ -8044,10 +8050,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 @@ -8059,13 +8070,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![column_ident.clone()],
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 @@ -8339,6 +8357,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 @@ -8449,10 +8479,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 @@ -8474,6 +8509,7 @@ impl<'a> Parser<'a> {
referred_columns,
on_delete,
on_update,
match_kind,
characteristics,
}
.into(),
Expand Down Expand Up @@ -9046,7 +9082,7 @@ impl<'a> Parser<'a> {
let new_name = self.parse_identifier()?;
let data_type = self.parse_data_type()?;
let mut options = vec![];
while let Some(option) = self.parse_optional_column_option()? {
while let Some(option) = self.parse_optional_column_option(&new_name)? {
options.push(option);
}

Expand All @@ -9064,7 +9100,7 @@ impl<'a> Parser<'a> {
let col_name = self.parse_identifier()?;
let data_type = self.parse_data_type()?;
let mut options = vec![];
while let Some(option) = self.parse_optional_column_option()? {
while let Some(option) = self.parse_optional_column_option(&col_name)? {
options.push(option);
}

Expand Down Expand Up @@ -11325,7 +11361,7 @@ impl<'a> Parser<'a> {
/// Parses a column definition within a view.
fn parse_view_column(&mut self) -> Result<ViewColumnDef, ParserError> {
let name = self.parse_identifier()?;
let options = self.parse_view_column_options()?;
let options = self.parse_view_column_options(&name)?;
let data_type = if dialect_of!(self is ClickHouseDialect) {
Some(self.parse_data_type()?)
} else {
Expand All @@ -11338,10 +11374,13 @@ impl<'a> Parser<'a> {
})
}

fn parse_view_column_options(&mut self) -> Result<Option<ColumnOptions>, ParserError> {
fn parse_view_column_options(
&mut self,
column_ident: &Ident,
) -> Result<Option<ColumnOptions>, ParserError> {
let mut options = Vec::new();
loop {
let option = self.parse_optional_column_option()?;
let option = self.parse_optional_column_option(column_ident)?;
if let Some(option) = option {
options.push(option);
} else {
Expand Down
Loading