mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Add support for "on delete cascade" column option (#170)
Specifically, `FOREIGN KEY REFERENCES <foreign_table> (<referred_columns>)` can now be followed by `ON DELETE <referential_action>` and/or by `ON UPDATE <referential_action>`.
This commit is contained in:
parent
789fcc8521
commit
98f97d09db
5 changed files with 131 additions and 11 deletions
|
@ -155,10 +155,15 @@ pub enum ColumnOption {
|
||||||
is_primary: bool,
|
is_primary: bool,
|
||||||
},
|
},
|
||||||
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
||||||
/// <foreign_table> (<referred_columns>)`).
|
/// <foreign_table> (<referred_columns>)
|
||||||
|
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||||
|
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
|
||||||
|
/// }`).
|
||||||
ForeignKey {
|
ForeignKey {
|
||||||
foreign_table: ObjectName,
|
foreign_table: ObjectName,
|
||||||
referred_columns: Vec<Ident>,
|
referred_columns: Vec<Ident>,
|
||||||
|
on_delete: Option<ReferentialAction>,
|
||||||
|
on_update: Option<ReferentialAction>,
|
||||||
},
|
},
|
||||||
// `CHECK (<expr>)`
|
// `CHECK (<expr>)`
|
||||||
Check(Expr),
|
Check(Expr),
|
||||||
|
@ -177,12 +182,21 @@ impl fmt::Display for ColumnOption {
|
||||||
ForeignKey {
|
ForeignKey {
|
||||||
foreign_table,
|
foreign_table,
|
||||||
referred_columns,
|
referred_columns,
|
||||||
} => write!(
|
on_delete,
|
||||||
f,
|
on_update,
|
||||||
"REFERENCES {} ({})",
|
} => {
|
||||||
foreign_table,
|
write!(f, "REFERENCES {}", foreign_table)?;
|
||||||
display_comma_separated(referred_columns)
|
if !referred_columns.is_empty() {
|
||||||
),
|
write!(f, " ({})", display_comma_separated(referred_columns))?;
|
||||||
|
}
|
||||||
|
if let Some(action) = on_delete {
|
||||||
|
write!(f, " ON DELETE {}", action)?;
|
||||||
|
}
|
||||||
|
if let Some(action) = on_update {
|
||||||
|
write!(f, " ON UPDATE {}", action)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Check(expr) => write!(f, "CHECK ({})", expr),
|
Check(expr) => write!(f, "CHECK ({})", expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,3 +214,28 @@ fn display_constraint_name<'a>(name: &'a Option<Ident>) -> impl fmt::Display + '
|
||||||
}
|
}
|
||||||
ConstraintName(name)
|
ConstraintName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `<referential_action> =
|
||||||
|
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
|
||||||
|
///
|
||||||
|
/// Used in foreign key constraints in `ON UPDATE` and `ON DELETE` options.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ReferentialAction {
|
||||||
|
Restrict,
|
||||||
|
Cascade,
|
||||||
|
SetNull,
|
||||||
|
NoAction,
|
||||||
|
SetDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ReferentialAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
ReferentialAction::Restrict => "RESTRICT",
|
||||||
|
ReferentialAction::Cascade => "CASCADE",
|
||||||
|
ReferentialAction::SetNull => "SET NULL",
|
||||||
|
ReferentialAction::NoAction => "NO ACTION",
|
||||||
|
ReferentialAction::SetDefault => "SET DEFAULT",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ use std::fmt;
|
||||||
|
|
||||||
pub use self::data_type::DataType;
|
pub use self::data_type::DataType;
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, TableConstraint,
|
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, ReferentialAction,
|
||||||
|
TableConstraint,
|
||||||
};
|
};
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
pub use self::query::{
|
pub use self::query::{
|
||||||
|
|
|
@ -51,6 +51,7 @@ macro_rules! define_keywords {
|
||||||
|
|
||||||
define_keywords!(
|
define_keywords!(
|
||||||
ABS,
|
ABS,
|
||||||
|
ACTION,
|
||||||
ADD,
|
ADD,
|
||||||
ASC,
|
ASC,
|
||||||
ALL,
|
ALL,
|
||||||
|
|
|
@ -1038,10 +1038,25 @@ impl Parser {
|
||||||
ColumnOption::Unique { is_primary: false }
|
ColumnOption::Unique { is_primary: false }
|
||||||
} else if self.parse_keyword("REFERENCES") {
|
} else if self.parse_keyword("REFERENCES") {
|
||||||
let foreign_table = self.parse_object_name()?;
|
let foreign_table = self.parse_object_name()?;
|
||||||
let referred_columns = self.parse_parenthesized_column_list(Mandatory)?;
|
// 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)?;
|
||||||
|
let mut on_delete = None;
|
||||||
|
let mut on_update = None;
|
||||||
|
loop {
|
||||||
|
if on_delete.is_none() && self.parse_keywords(vec!["ON", "DELETE"]) {
|
||||||
|
on_delete = Some(self.parse_referential_action()?);
|
||||||
|
} else if on_update.is_none() && self.parse_keywords(vec!["ON", "UPDATE"]) {
|
||||||
|
on_update = Some(self.parse_referential_action()?);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
ColumnOption::ForeignKey {
|
ColumnOption::ForeignKey {
|
||||||
foreign_table,
|
foreign_table,
|
||||||
referred_columns,
|
referred_columns,
|
||||||
|
on_delete,
|
||||||
|
on_update,
|
||||||
}
|
}
|
||||||
} else if self.parse_keyword("CHECK") {
|
} else if self.parse_keyword("CHECK") {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
@ -1055,6 +1070,25 @@ impl Parser {
|
||||||
Ok(ColumnOptionDef { name, option })
|
Ok(ColumnOptionDef { name, option })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_referential_action(&mut self) -> Result<ReferentialAction, ParserError> {
|
||||||
|
if self.parse_keyword("RESTRICT") {
|
||||||
|
Ok(ReferentialAction::Restrict)
|
||||||
|
} else if self.parse_keyword("CASCADE") {
|
||||||
|
Ok(ReferentialAction::Cascade)
|
||||||
|
} else if self.parse_keywords(vec!["SET", "NULL"]) {
|
||||||
|
Ok(ReferentialAction::SetNull)
|
||||||
|
} else if self.parse_keywords(vec!["NO", "ACTION"]) {
|
||||||
|
Ok(ReferentialAction::NoAction)
|
||||||
|
} else if self.parse_keywords(vec!["SET", "DEFAULT"]) {
|
||||||
|
Ok(ReferentialAction::SetDefault)
|
||||||
|
} else {
|
||||||
|
self.expected(
|
||||||
|
"one of RESTRICT, CASCADE, SET NULL, NO ACTION or SET DEFAULT",
|
||||||
|
self.peek_token(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_optional_table_constraint(
|
pub fn parse_optional_table_constraint(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Option<TableConstraint>, ParserError> {
|
) -> Result<Option<TableConstraint>, ParserError> {
|
||||||
|
|
|
@ -893,7 +893,9 @@ fn parse_create_table() {
|
||||||
lat DOUBLE NULL,\
|
lat DOUBLE NULL,\
|
||||||
lng DOUBLE,
|
lng DOUBLE,
|
||||||
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0),
|
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0),
|
||||||
ref INT REFERENCES othertable (a, b))";
|
ref INT REFERENCES othertable (a, b),\
|
||||||
|
ref2 INT references othertable2 on delete cascade on update no action\
|
||||||
|
)";
|
||||||
let ast = one_statement_parses_to(
|
let ast = one_statement_parses_to(
|
||||||
sql,
|
sql,
|
||||||
"CREATE TABLE uk_cities (\
|
"CREATE TABLE uk_cities (\
|
||||||
|
@ -901,7 +903,8 @@ fn parse_create_table() {
|
||||||
lat double NULL, \
|
lat double NULL, \
|
||||||
lng double, \
|
lng double, \
|
||||||
constrained int NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0), \
|
constrained int NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0), \
|
||||||
ref int REFERENCES othertable (a, b))",
|
ref int REFERENCES othertable (a, b), \
|
||||||
|
ref2 int REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION)",
|
||||||
);
|
);
|
||||||
match ast {
|
match ast {
|
||||||
Statement::CreateTable {
|
Statement::CreateTable {
|
||||||
|
@ -978,8 +981,24 @@ fn parse_create_table() {
|
||||||
option: ColumnOption::ForeignKey {
|
option: ColumnOption::ForeignKey {
|
||||||
foreign_table: ObjectName(vec!["othertable".into()]),
|
foreign_table: ObjectName(vec!["othertable".into()]),
|
||||||
referred_columns: vec!["a".into(), "b".into(),],
|
referred_columns: vec!["a".into(), "b".into(),],
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
ColumnDef {
|
||||||
|
name: "ref2".into(),
|
||||||
|
data_type: DataType::Int,
|
||||||
|
collation: None,
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::ForeignKey {
|
||||||
|
foreign_table: ObjectName(vec!["othertable2".into()]),
|
||||||
|
referred_columns: vec![],
|
||||||
|
on_delete: Some(ReferentialAction::Cascade),
|
||||||
|
on_update: Some(ReferentialAction::NoAction),
|
||||||
|
}
|
||||||
|
},]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -996,6 +1015,32 @@ fn parse_create_table() {
|
||||||
.contains("Expected column option, found: GARBAGE"));
|
.contains("Expected column option, found: GARBAGE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_multiple_on_delete_fails() {
|
||||||
|
parse_sql_statements(
|
||||||
|
"\
|
||||||
|
create table X (\
|
||||||
|
y_id int references Y (id) \
|
||||||
|
on delete cascade on update cascade on delete no action\
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.expect_err("should have failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
|
||||||
|
let sql = |options: &str| -> String {
|
||||||
|
format!("create table X (y_id int references Y (id) {})", options)
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_sql_statements(&sql("on update cascade on delete no action"))?;
|
||||||
|
parse_sql_statements(&sql("on delete cascade on update cascade"))?;
|
||||||
|
parse_sql_statements(&sql("on update no action"))?;
|
||||||
|
parse_sql_statements(&sql("on delete restrict"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_with_options() {
|
fn parse_create_table_with_options() {
|
||||||
let sql = "CREATE TABLE t (c int) WITH (foo = 'bar', a = 123)";
|
let sql = "CREATE TABLE t (c int) WITH (foo = 'bar', a = 123)";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue