mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-23 15:34:09 +00:00
Add support for constraint characteristics clause (#1099)
This commit is contained in:
parent
1fb9f3efdf
commit
c86508bae5
7 changed files with 480 additions and 22 deletions
130
src/ast/ddl.rs
130
src/ast/ddl.rs
|
@ -385,6 +385,7 @@ pub enum TableConstraint {
|
||||||
columns: Vec<Ident>,
|
columns: Vec<Ident>,
|
||||||
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
|
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
|
||||||
is_primary: bool,
|
is_primary: bool,
|
||||||
|
characteristics: Option<ConstraintCharacteristics>,
|
||||||
},
|
},
|
||||||
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
||||||
/// REFERENCES <foreign_table> (<referred_columns>)
|
/// REFERENCES <foreign_table> (<referred_columns>)
|
||||||
|
@ -398,6 +399,7 @@ pub enum TableConstraint {
|
||||||
referred_columns: Vec<Ident>,
|
referred_columns: Vec<Ident>,
|
||||||
on_delete: Option<ReferentialAction>,
|
on_delete: Option<ReferentialAction>,
|
||||||
on_update: Option<ReferentialAction>,
|
on_update: Option<ReferentialAction>,
|
||||||
|
characteristics: Option<ConstraintCharacteristics>,
|
||||||
},
|
},
|
||||||
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
|
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
|
||||||
Check {
|
Check {
|
||||||
|
@ -454,13 +456,22 @@ impl fmt::Display for TableConstraint {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
is_primary,
|
is_primary,
|
||||||
} => write!(
|
characteristics,
|
||||||
f,
|
} => {
|
||||||
"{}{} ({})",
|
write!(
|
||||||
display_constraint_name(name),
|
f,
|
||||||
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
|
"{}{} ({})",
|
||||||
display_comma_separated(columns)
|
display_constraint_name(name),
|
||||||
),
|
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
|
||||||
|
display_comma_separated(columns)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(characteristics) = characteristics {
|
||||||
|
write!(f, " {}", characteristics)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
TableConstraint::ForeignKey {
|
TableConstraint::ForeignKey {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
|
@ -468,6 +479,7 @@ impl fmt::Display for TableConstraint {
|
||||||
referred_columns,
|
referred_columns,
|
||||||
on_delete,
|
on_delete,
|
||||||
on_update,
|
on_update,
|
||||||
|
characteristics,
|
||||||
} => {
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
@ -483,6 +495,9 @@ impl fmt::Display for TableConstraint {
|
||||||
if let Some(action) = on_update {
|
if let Some(action) = on_update {
|
||||||
write!(f, " ON UPDATE {action}")?;
|
write!(f, " ON UPDATE {action}")?;
|
||||||
}
|
}
|
||||||
|
if let Some(characteristics) = characteristics {
|
||||||
|
write!(f, " {}", characteristics)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
TableConstraint::Check { name, expr } => {
|
TableConstraint::Check { name, expr } => {
|
||||||
|
@ -713,20 +728,24 @@ pub enum ColumnOption {
|
||||||
NotNull,
|
NotNull,
|
||||||
/// `DEFAULT <restricted-expr>`
|
/// `DEFAULT <restricted-expr>`
|
||||||
Default(Expr),
|
Default(Expr),
|
||||||
/// `{ PRIMARY KEY | UNIQUE }`
|
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
|
||||||
Unique {
|
Unique {
|
||||||
is_primary: bool,
|
is_primary: bool,
|
||||||
|
characteristics: Option<ConstraintCharacteristics>,
|
||||||
},
|
},
|
||||||
/// 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 DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||||
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
|
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
|
||||||
/// }`).
|
/// }
|
||||||
|
/// [<constraint_characteristics>]
|
||||||
|
/// `).
|
||||||
ForeignKey {
|
ForeignKey {
|
||||||
foreign_table: ObjectName,
|
foreign_table: ObjectName,
|
||||||
referred_columns: Vec<Ident>,
|
referred_columns: Vec<Ident>,
|
||||||
on_delete: Option<ReferentialAction>,
|
on_delete: Option<ReferentialAction>,
|
||||||
on_update: Option<ReferentialAction>,
|
on_update: Option<ReferentialAction>,
|
||||||
|
characteristics: Option<ConstraintCharacteristics>,
|
||||||
},
|
},
|
||||||
/// `CHECK (<expr>)`
|
/// `CHECK (<expr>)`
|
||||||
Check(Expr),
|
Check(Expr),
|
||||||
|
@ -764,14 +783,22 @@ impl fmt::Display for ColumnOption {
|
||||||
Null => write!(f, "NULL"),
|
Null => write!(f, "NULL"),
|
||||||
NotNull => write!(f, "NOT NULL"),
|
NotNull => write!(f, "NOT NULL"),
|
||||||
Default(expr) => write!(f, "DEFAULT {expr}"),
|
Default(expr) => write!(f, "DEFAULT {expr}"),
|
||||||
Unique { is_primary } => {
|
Unique {
|
||||||
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })
|
is_primary,
|
||||||
|
characteristics,
|
||||||
|
} => {
|
||||||
|
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
|
||||||
|
if let Some(characteristics) = characteristics {
|
||||||
|
write!(f, " {}", characteristics)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
ForeignKey {
|
ForeignKey {
|
||||||
foreign_table,
|
foreign_table,
|
||||||
referred_columns,
|
referred_columns,
|
||||||
on_delete,
|
on_delete,
|
||||||
on_update,
|
on_update,
|
||||||
|
characteristics,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "REFERENCES {foreign_table}")?;
|
write!(f, "REFERENCES {foreign_table}")?;
|
||||||
if !referred_columns.is_empty() {
|
if !referred_columns.is_empty() {
|
||||||
|
@ -783,6 +810,9 @@ impl fmt::Display for ColumnOption {
|
||||||
if let Some(action) = on_update {
|
if let Some(action) = on_update {
|
||||||
write!(f, " ON UPDATE {action}")?;
|
write!(f, " ON UPDATE {action}")?;
|
||||||
}
|
}
|
||||||
|
if let Some(characteristics) = characteristics {
|
||||||
|
write!(f, " {}", characteristics)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Check(expr) => write!(f, "CHECK ({expr})"),
|
Check(expr) => write!(f, "CHECK ({expr})"),
|
||||||
|
@ -874,6 +904,84 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
||||||
ConstraintName(name)
|
ConstraintName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
|
||||||
|
///
|
||||||
|
/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct ConstraintCharacteristics {
|
||||||
|
/// `[ DEFERRABLE | NOT DEFERRABLE ]`
|
||||||
|
pub deferrable: Option<bool>,
|
||||||
|
/// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
|
||||||
|
pub initially: Option<DeferrableInitial>,
|
||||||
|
/// `[ ENFORCED | NOT ENFORCED ]`
|
||||||
|
pub enforced: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 DeferrableInitial {
|
||||||
|
/// `INITIALLY IMMEDIATE`
|
||||||
|
Immediate,
|
||||||
|
/// `INITIALLY DEFERRED`
|
||||||
|
Deferred,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstraintCharacteristics {
|
||||||
|
fn deferrable_text(&self) -> Option<&'static str> {
|
||||||
|
self.deferrable.map(|deferrable| {
|
||||||
|
if deferrable {
|
||||||
|
"DEFERRABLE"
|
||||||
|
} else {
|
||||||
|
"NOT DEFERRABLE"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initially_immediate_text(&self) -> Option<&'static str> {
|
||||||
|
self.initially
|
||||||
|
.map(|initially_immediate| match initially_immediate {
|
||||||
|
DeferrableInitial::Immediate => "INITIALLY IMMEDIATE",
|
||||||
|
DeferrableInitial::Deferred => "INITIALLY DEFERRED",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enforced_text(&self) -> Option<&'static str> {
|
||||||
|
self.enforced.map(
|
||||||
|
|enforced| {
|
||||||
|
if enforced {
|
||||||
|
"ENFORCED"
|
||||||
|
} else {
|
||||||
|
"NOT ENFORCED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ConstraintCharacteristics {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let deferrable = self.deferrable_text();
|
||||||
|
let initially_immediate = self.initially_immediate_text();
|
||||||
|
let enforced = self.enforced_text();
|
||||||
|
|
||||||
|
match (deferrable, initially_immediate, enforced) {
|
||||||
|
(None, None, None) => Ok(()),
|
||||||
|
(None, None, Some(enforced)) => write!(f, "{enforced}"),
|
||||||
|
(None, Some(initial), None) => write!(f, "{initial}"),
|
||||||
|
(None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"),
|
||||||
|
(Some(deferrable), None, None) => write!(f, "{deferrable}"),
|
||||||
|
(Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"),
|
||||||
|
(Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"),
|
||||||
|
(Some(deferrable), Some(initial), Some(enforced)) => {
|
||||||
|
write!(f, "{deferrable} {initial} {enforced}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `<referential_action> =
|
/// `<referential_action> =
|
||||||
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
|
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
|
||||||
///
|
///
|
||||||
|
|
|
@ -32,8 +32,9 @@ pub use self::data_type::{
|
||||||
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue};
|
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue};
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||||
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
|
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
|
||||||
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
||||||
|
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||||
};
|
};
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
|
|
|
@ -210,6 +210,7 @@ define_keywords!(
|
||||||
DECIMAL,
|
DECIMAL,
|
||||||
DECLARE,
|
DECLARE,
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
DEFERRABLE,
|
||||||
DEFERRED,
|
DEFERRED,
|
||||||
DELAYED,
|
DELAYED,
|
||||||
DELETE,
|
DELETE,
|
||||||
|
@ -250,6 +251,7 @@ define_keywords!(
|
||||||
ENDPOINT,
|
ENDPOINT,
|
||||||
END_FRAME,
|
END_FRAME,
|
||||||
END_PARTITION,
|
END_PARTITION,
|
||||||
|
ENFORCED,
|
||||||
ENGINE,
|
ENGINE,
|
||||||
ENUM,
|
ENUM,
|
||||||
EPOCH,
|
EPOCH,
|
||||||
|
@ -343,6 +345,7 @@ define_keywords!(
|
||||||
INDEX,
|
INDEX,
|
||||||
INDICATOR,
|
INDICATOR,
|
||||||
INHERIT,
|
INHERIT,
|
||||||
|
INITIALLY,
|
||||||
INNER,
|
INNER,
|
||||||
INOUT,
|
INOUT,
|
||||||
INPUTFORMAT,
|
INPUTFORMAT,
|
||||||
|
|
|
@ -4478,9 +4478,17 @@ impl<'a> Parser<'a> {
|
||||||
} else if self.parse_keyword(Keyword::DEFAULT) {
|
} else if self.parse_keyword(Keyword::DEFAULT) {
|
||||||
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
|
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
|
||||||
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
|
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
|
||||||
Ok(Some(ColumnOption::Unique { is_primary: true }))
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
|
Ok(Some(ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics,
|
||||||
|
}))
|
||||||
} else if self.parse_keyword(Keyword::UNIQUE) {
|
} else if self.parse_keyword(Keyword::UNIQUE) {
|
||||||
Ok(Some(ColumnOption::Unique { is_primary: false }))
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
|
Ok(Some(ColumnOption::Unique {
|
||||||
|
is_primary: false,
|
||||||
|
characteristics,
|
||||||
|
}))
|
||||||
} else if self.parse_keyword(Keyword::REFERENCES) {
|
} else if self.parse_keyword(Keyword::REFERENCES) {
|
||||||
let foreign_table = self.parse_object_name(false)?;
|
let foreign_table = self.parse_object_name(false)?;
|
||||||
// PostgreSQL allows omitting the column list and
|
// PostgreSQL allows omitting the column list and
|
||||||
|
@ -4499,11 +4507,14 @@ impl<'a> Parser<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
|
|
||||||
Ok(Some(ColumnOption::ForeignKey {
|
Ok(Some(ColumnOption::ForeignKey {
|
||||||
foreign_table,
|
foreign_table,
|
||||||
referred_columns,
|
referred_columns,
|
||||||
on_delete,
|
on_delete,
|
||||||
on_update,
|
on_update,
|
||||||
|
characteristics,
|
||||||
}))
|
}))
|
||||||
} else if self.parse_keyword(Keyword::CHECK) {
|
} else if self.parse_keyword(Keyword::CHECK) {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
@ -4658,6 +4669,47 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_constraint_characteristics(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
|
||||||
|
let mut cc = ConstraintCharacteristics {
|
||||||
|
deferrable: None,
|
||||||
|
initially: None,
|
||||||
|
enforced: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE])
|
||||||
|
{
|
||||||
|
cc.deferrable = Some(false);
|
||||||
|
} else if cc.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) {
|
||||||
|
cc.deferrable = Some(true);
|
||||||
|
} else if cc.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) {
|
||||||
|
if self.parse_keyword(Keyword::DEFERRED) {
|
||||||
|
cc.initially = Some(DeferrableInitial::Deferred);
|
||||||
|
} else if self.parse_keyword(Keyword::IMMEDIATE) {
|
||||||
|
cc.initially = Some(DeferrableInitial::Immediate);
|
||||||
|
} else {
|
||||||
|
self.expected("one of DEFERRED or IMMEDIATE", self.peek_token())?;
|
||||||
|
}
|
||||||
|
} else if cc.enforced.is_none() && self.parse_keyword(Keyword::ENFORCED) {
|
||||||
|
cc.enforced = Some(true);
|
||||||
|
} else if cc.enforced.is_none()
|
||||||
|
&& self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED])
|
||||||
|
{
|
||||||
|
cc.enforced = Some(false);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.deferrable.is_some() || cc.initially.is_some() || cc.enforced.is_some() {
|
||||||
|
Ok(Some(cc))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_optional_table_constraint(
|
pub fn parse_optional_table_constraint(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Option<TableConstraint>, ParserError> {
|
) -> Result<Option<TableConstraint>, ParserError> {
|
||||||
|
@ -4681,10 +4733,12 @@ impl<'a> Parser<'a> {
|
||||||
.or(name);
|
.or(name);
|
||||||
|
|
||||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||||
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
Ok(Some(TableConstraint::Unique {
|
Ok(Some(TableConstraint::Unique {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
is_primary,
|
is_primary,
|
||||||
|
characteristics,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
|
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
|
||||||
|
@ -4706,6 +4760,9 @@ impl<'a> Parser<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
|
|
||||||
Ok(Some(TableConstraint::ForeignKey {
|
Ok(Some(TableConstraint::ForeignKey {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
|
@ -4713,6 +4770,7 @@ impl<'a> Parser<'a> {
|
||||||
referred_columns,
|
referred_columns,
|
||||||
on_delete,
|
on_delete,
|
||||||
on_update,
|
on_update,
|
||||||
|
characteristics,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Token::Word(w) if w.keyword == Keyword::CHECK => {
|
Token::Word(w) if w.keyword == Keyword::CHECK => {
|
||||||
|
|
|
@ -2555,7 +2555,10 @@ fn parse_create_table() {
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: Some("pkey".into()),
|
name: Some("pkey".into()),
|
||||||
option: ColumnOption::Unique { is_primary: true },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -2563,7 +2566,10 @@ fn parse_create_table() {
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
option: ColumnOption::Unique { is_primary: false },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: false,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -2582,6 +2588,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec!["a".into(), "b".into()],
|
referred_columns: vec!["a".into(), "b".into()],
|
||||||
on_delete: None,
|
on_delete: None,
|
||||||
on_update: None,
|
on_update: None,
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -2596,6 +2603,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec![],
|
referred_columns: vec![],
|
||||||
on_delete: Some(ReferentialAction::Cascade),
|
on_delete: Some(ReferentialAction::Cascade),
|
||||||
on_update: Some(ReferentialAction::NoAction),
|
on_update: Some(ReferentialAction::NoAction),
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
},],
|
},],
|
||||||
},
|
},
|
||||||
|
@ -2611,6 +2619,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec!["lat".into()],
|
referred_columns: vec!["lat".into()],
|
||||||
on_delete: Some(ReferentialAction::Restrict),
|
on_delete: Some(ReferentialAction::Restrict),
|
||||||
on_update: None,
|
on_update: None,
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
TableConstraint::ForeignKey {
|
TableConstraint::ForeignKey {
|
||||||
name: Some("fkey2".into()),
|
name: Some("fkey2".into()),
|
||||||
|
@ -2619,6 +2628,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec!["lat".into()],
|
referred_columns: vec!["lat".into()],
|
||||||
on_delete: Some(ReferentialAction::NoAction),
|
on_delete: Some(ReferentialAction::NoAction),
|
||||||
on_update: Some(ReferentialAction::Restrict),
|
on_update: Some(ReferentialAction::Restrict),
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
TableConstraint::ForeignKey {
|
TableConstraint::ForeignKey {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -2627,6 +2637,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec!["lat".into()],
|
referred_columns: vec!["lat".into()],
|
||||||
on_delete: Some(ReferentialAction::Cascade),
|
on_delete: Some(ReferentialAction::Cascade),
|
||||||
on_update: Some(ReferentialAction::SetDefault),
|
on_update: Some(ReferentialAction::SetDefault),
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
TableConstraint::ForeignKey {
|
TableConstraint::ForeignKey {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -2635,6 +2646,7 @@ fn parse_create_table() {
|
||||||
referred_columns: vec!["longitude".into()],
|
referred_columns: vec!["longitude".into()],
|
||||||
on_delete: None,
|
on_delete: None,
|
||||||
on_update: Some(ReferentialAction::SetNull),
|
on_update: Some(ReferentialAction::SetNull),
|
||||||
|
characteristics: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -2656,6 +2668,269 @@ fn parse_create_table() {
|
||||||
.contains("Expected constraint details after CONSTRAINT <name>"));
|
.contains("Expected constraint details after CONSTRAINT <name>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_constraint_characteristics() {
|
||||||
|
let sql = "CREATE TABLE uk_cities (\
|
||||||
|
name VARCHAR(100) NOT NULL,\
|
||||||
|
lat DOUBLE NULL,\
|
||||||
|
lng DOUBLE,
|
||||||
|
constraint fkey foreign key (lat) references othertable3 (lat) on delete restrict deferrable initially deferred,\
|
||||||
|
constraint fkey2 foreign key (lat) references othertable4(lat) on delete no action on update restrict deferrable initially immediate, \
|
||||||
|
foreign key (lat) references othertable4(lat) on update set default on delete cascade not deferrable initially deferred not enforced, \
|
||||||
|
FOREIGN KEY (lng) REFERENCES othertable4 (longitude) ON UPDATE SET NULL enforced not deferrable initially immediate
|
||||||
|
)";
|
||||||
|
let ast = one_statement_parses_to(
|
||||||
|
sql,
|
||||||
|
"CREATE TABLE uk_cities (\
|
||||||
|
name VARCHAR(100) NOT NULL, \
|
||||||
|
lat DOUBLE NULL, \
|
||||||
|
lng DOUBLE, \
|
||||||
|
CONSTRAINT fkey FOREIGN KEY (lat) REFERENCES othertable3(lat) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED, \
|
||||||
|
CONSTRAINT fkey2 FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE NO ACTION ON UPDATE RESTRICT DEFERRABLE INITIALLY IMMEDIATE, \
|
||||||
|
FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE CASCADE ON UPDATE SET DEFAULT NOT DEFERRABLE INITIALLY DEFERRED NOT ENFORCED, \
|
||||||
|
FOREIGN KEY (lng) REFERENCES othertable4(longitude) ON UPDATE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE ENFORCED)",
|
||||||
|
);
|
||||||
|
match ast {
|
||||||
|
Statement::CreateTable {
|
||||||
|
name,
|
||||||
|
columns,
|
||||||
|
constraints,
|
||||||
|
with_options,
|
||||||
|
if_not_exists: false,
|
||||||
|
external: false,
|
||||||
|
file_format: None,
|
||||||
|
location: None,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!("uk_cities", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
columns,
|
||||||
|
vec![
|
||||||
|
ColumnDef {
|
||||||
|
name: "name".into(),
|
||||||
|
data_type: DataType::Varchar(Some(CharacterLength::IntegerLength {
|
||||||
|
length: 100,
|
||||||
|
unit: None,
|
||||||
|
})),
|
||||||
|
collation: None,
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::NotNull,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
ColumnDef {
|
||||||
|
name: "lat".into(),
|
||||||
|
data_type: DataType::Double,
|
||||||
|
collation: None,
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::Null,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
ColumnDef {
|
||||||
|
name: "lng".into(),
|
||||||
|
data_type: DataType::Double,
|
||||||
|
collation: None,
|
||||||
|
options: vec![],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
constraints,
|
||||||
|
vec![
|
||||||
|
TableConstraint::ForeignKey {
|
||||||
|
name: Some("fkey".into()),
|
||||||
|
columns: vec!["lat".into()],
|
||||||
|
foreign_table: ObjectName(vec!["othertable3".into()]),
|
||||||
|
referred_columns: vec!["lat".into()],
|
||||||
|
on_delete: Some(ReferentialAction::Restrict),
|
||||||
|
on_update: None,
|
||||||
|
characteristics: Some(ConstraintCharacteristics {
|
||||||
|
deferrable: Some(true),
|
||||||
|
initially: Some(DeferrableInitial::Deferred),
|
||||||
|
enforced: None
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
TableConstraint::ForeignKey {
|
||||||
|
name: Some("fkey2".into()),
|
||||||
|
columns: vec!["lat".into()],
|
||||||
|
foreign_table: ObjectName(vec!["othertable4".into()]),
|
||||||
|
referred_columns: vec!["lat".into()],
|
||||||
|
on_delete: Some(ReferentialAction::NoAction),
|
||||||
|
on_update: Some(ReferentialAction::Restrict),
|
||||||
|
characteristics: Some(ConstraintCharacteristics {
|
||||||
|
deferrable: Some(true),
|
||||||
|
initially: Some(DeferrableInitial::Immediate),
|
||||||
|
enforced: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
TableConstraint::ForeignKey {
|
||||||
|
name: None,
|
||||||
|
columns: vec!["lat".into()],
|
||||||
|
foreign_table: ObjectName(vec!["othertable4".into()]),
|
||||||
|
referred_columns: vec!["lat".into()],
|
||||||
|
on_delete: Some(ReferentialAction::Cascade),
|
||||||
|
on_update: Some(ReferentialAction::SetDefault),
|
||||||
|
characteristics: Some(ConstraintCharacteristics {
|
||||||
|
deferrable: Some(false),
|
||||||
|
initially: Some(DeferrableInitial::Deferred),
|
||||||
|
enforced: Some(false),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
TableConstraint::ForeignKey {
|
||||||
|
name: None,
|
||||||
|
columns: vec!["lng".into()],
|
||||||
|
foreign_table: ObjectName(vec!["othertable4".into()]),
|
||||||
|
referred_columns: vec!["longitude".into()],
|
||||||
|
on_delete: None,
|
||||||
|
on_update: Some(ReferentialAction::SetNull),
|
||||||
|
characteristics: Some(ConstraintCharacteristics {
|
||||||
|
deferrable: Some(false),
|
||||||
|
initially: Some(DeferrableInitial::Immediate),
|
||||||
|
enforced: Some(true),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = parse_sql_statements("CREATE TABLE t (
|
||||||
|
a int NOT NULL,
|
||||||
|
FOREIGN KEY (a) REFERENCES othertable4(a) ON DELETE CASCADE ON UPDATE SET DEFAULT DEFERRABLE INITIALLY IMMEDIATE NOT DEFERRABLE, \
|
||||||
|
)");
|
||||||
|
assert!(res
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Expected \',\' or \')\' after column definition, found: NOT"));
|
||||||
|
|
||||||
|
let res = parse_sql_statements("CREATE TABLE t (
|
||||||
|
a int NOT NULL,
|
||||||
|
FOREIGN KEY (a) REFERENCES othertable4(a) ON DELETE CASCADE ON UPDATE SET DEFAULT NOT ENFORCED INITIALLY DEFERRED ENFORCED, \
|
||||||
|
)");
|
||||||
|
assert!(res
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Expected \',\' or \')\' after column definition, found: ENFORCED"));
|
||||||
|
|
||||||
|
let res = parse_sql_statements("CREATE TABLE t (
|
||||||
|
a int NOT NULL,
|
||||||
|
FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE CASCADE ON UPDATE SET DEFAULT INITIALLY DEFERRED INITIALLY IMMEDIATE, \
|
||||||
|
)");
|
||||||
|
assert!(res
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Expected \',\' or \')\' after column definition, found: INITIALLY"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_column_constraint_characteristics() {
|
||||||
|
fn test_combo(
|
||||||
|
syntax: &str,
|
||||||
|
deferrable: Option<bool>,
|
||||||
|
initially: Option<DeferrableInitial>,
|
||||||
|
enforced: Option<bool>,
|
||||||
|
) {
|
||||||
|
let message = if syntax.is_empty() {
|
||||||
|
"No clause"
|
||||||
|
} else {
|
||||||
|
syntax
|
||||||
|
};
|
||||||
|
|
||||||
|
let sql = format!("CREATE TABLE t (a int UNIQUE {})", syntax);
|
||||||
|
let expected_clause = if syntax.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(" {syntax}")
|
||||||
|
};
|
||||||
|
let expected = format!("CREATE TABLE t (a INT UNIQUE{})", expected_clause);
|
||||||
|
let ast = one_statement_parses_to(&sql, &expected);
|
||||||
|
|
||||||
|
let expected_value = if deferrable.is_some() || initially.is_some() || enforced.is_some() {
|
||||||
|
Some(ConstraintCharacteristics {
|
||||||
|
deferrable,
|
||||||
|
initially,
|
||||||
|
enforced,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match ast {
|
||||||
|
Statement::CreateTable { columns, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
columns,
|
||||||
|
vec![ColumnDef {
|
||||||
|
name: "a".into(),
|
||||||
|
data_type: DataType::Int(None),
|
||||||
|
collation: None,
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: false,
|
||||||
|
characteristics: expected_value
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"{message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for deferrable in [None, Some(true), Some(false)] {
|
||||||
|
for initially in [
|
||||||
|
None,
|
||||||
|
Some(DeferrableInitial::Immediate),
|
||||||
|
Some(DeferrableInitial::Deferred),
|
||||||
|
] {
|
||||||
|
for enforced in [None, Some(true), Some(false)] {
|
||||||
|
let deferrable_text =
|
||||||
|
deferrable.map(|d| if d { "DEFERRABLE" } else { "NOT DEFERRABLE" });
|
||||||
|
let initially_text = initially.map(|i| match i {
|
||||||
|
DeferrableInitial::Immediate => "INITIALLY IMMEDIATE",
|
||||||
|
DeferrableInitial::Deferred => "INITIALLY DEFERRED",
|
||||||
|
});
|
||||||
|
let enforced_text = enforced.map(|e| if e { "ENFORCED" } else { "NOT ENFORCED" });
|
||||||
|
|
||||||
|
let syntax = [deferrable_text, initially_text, enforced_text]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
test_combo(&syntax, deferrable, initially, enforced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = parse_sql_statements(
|
||||||
|
"CREATE TABLE t (a int NOT NULL UNIQUE DEFERRABLE INITIALLY BADVALUE)",
|
||||||
|
);
|
||||||
|
assert!(res
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Expected one of DEFERRED or IMMEDIATE, found: BADVALUE"));
|
||||||
|
|
||||||
|
let res = parse_sql_statements(
|
||||||
|
"CREATE TABLE t (a int NOT NULL UNIQUE INITIALLY IMMEDIATE DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
);
|
||||||
|
res.expect_err("INITIALLY {IMMEDIATE|DEFERRED} setting should only be allowed once");
|
||||||
|
|
||||||
|
let res = parse_sql_statements(
|
||||||
|
"CREATE TABLE t (a int NOT NULL UNIQUE DEFERRABLE INITIALLY DEFERRED NOT DEFERRABLE)",
|
||||||
|
);
|
||||||
|
res.expect_err("[NOT] DEFERRABLE setting should only be allowed once");
|
||||||
|
|
||||||
|
let res = parse_sql_statements(
|
||||||
|
"CREATE TABLE t (a int NOT NULL UNIQUE DEFERRABLE INITIALLY DEFERRED ENFORCED NOT ENFORCED)",
|
||||||
|
);
|
||||||
|
res.expect_err("[NOT] ENFORCED setting should only be allowed once");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_hive_array() {
|
fn parse_create_table_hive_array() {
|
||||||
// Parsing [] type arrays does not work in MsSql since [ is used in is_delimited_identifier_start
|
// Parsing [] type arrays does not work in MsSql since [ is used in is_delimited_identifier_start
|
||||||
|
|
|
@ -451,7 +451,10 @@ fn parse_create_table_auto_increment() {
|
||||||
options: vec![
|
options: vec![
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
option: ColumnOption::Unique { is_primary: true },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -484,7 +487,8 @@ fn parse_create_table_unique_key() {
|
||||||
vec![TableConstraint::Unique {
|
vec![TableConstraint::Unique {
|
||||||
name: Some(Ident::new("bar_key")),
|
name: Some(Ident::new("bar_key")),
|
||||||
columns: vec![Ident::new("bar")],
|
columns: vec![Ident::new("bar")],
|
||||||
is_primary: false
|
is_primary: false,
|
||||||
|
characteristics: None,
|
||||||
}],
|
}],
|
||||||
constraints
|
constraints
|
||||||
);
|
);
|
||||||
|
@ -497,7 +501,10 @@ fn parse_create_table_unique_key() {
|
||||||
options: vec![
|
options: vec![
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
option: ColumnOption::Unique { is_primary: true },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -707,7 +714,10 @@ fn parse_quote_identifiers() {
|
||||||
collation: None,
|
collation: None,
|
||||||
options: vec![ColumnOptionDef {
|
options: vec![ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
option: ColumnOption::Unique { is_primary: true },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
columns
|
columns
|
||||||
|
|
|
@ -208,7 +208,10 @@ fn parse_create_table_auto_increment() {
|
||||||
options: vec![
|
options: vec![
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
option: ColumnOption::Unique { is_primary: true },
|
option: ColumnOption::Unique {
|
||||||
|
is_primary: true,
|
||||||
|
characteristics: None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
name: None,
|
name: None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue