mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-08 01:15:00 +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>,
|
||||
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
|
||||
is_primary: bool,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
||||
/// REFERENCES <foreign_table> (<referred_columns>)
|
||||
|
@ -398,6 +399,7 @@ pub enum TableConstraint {
|
|||
referred_columns: Vec<Ident>,
|
||||
on_delete: Option<ReferentialAction>,
|
||||
on_update: Option<ReferentialAction>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
|
||||
Check {
|
||||
|
@ -454,13 +456,22 @@ impl fmt::Display for TableConstraint {
|
|||
name,
|
||||
columns,
|
||||
is_primary,
|
||||
} => write!(
|
||||
f,
|
||||
"{}{} ({})",
|
||||
display_constraint_name(name),
|
||||
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
|
||||
display_comma_separated(columns)
|
||||
),
|
||||
characteristics,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"{}{} ({})",
|
||||
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 {
|
||||
name,
|
||||
columns,
|
||||
|
@ -468,6 +479,7 @@ impl fmt::Display for TableConstraint {
|
|||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
|
@ -483,6 +495,9 @@ impl fmt::Display for TableConstraint {
|
|||
if let Some(action) = on_update {
|
||||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TableConstraint::Check { name, expr } => {
|
||||
|
@ -713,20 +728,24 @@ pub enum ColumnOption {
|
|||
NotNull,
|
||||
/// `DEFAULT <restricted-expr>`
|
||||
Default(Expr),
|
||||
/// `{ PRIMARY KEY | UNIQUE }`
|
||||
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
|
||||
Unique {
|
||||
is_primary: bool,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
||||
/// <foreign_table> (<referred_columns>)
|
||||
/// { [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>,
|
||||
},
|
||||
/// `CHECK (<expr>)`
|
||||
Check(Expr),
|
||||
|
@ -764,14 +783,22 @@ impl fmt::Display for ColumnOption {
|
|||
Null => write!(f, "NULL"),
|
||||
NotNull => write!(f, "NOT NULL"),
|
||||
Default(expr) => write!(f, "DEFAULT {expr}"),
|
||||
Unique { is_primary } => {
|
||||
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })
|
||||
Unique {
|
||||
is_primary,
|
||||
characteristics,
|
||||
} => {
|
||||
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ForeignKey {
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
} => {
|
||||
write!(f, "REFERENCES {foreign_table}")?;
|
||||
if !referred_columns.is_empty() {
|
||||
|
@ -783,6 +810,9 @@ impl fmt::Display for ColumnOption {
|
|||
if let Some(action) = on_update {
|
||||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Check(expr) => write!(f, "CHECK ({expr})"),
|
||||
|
@ -874,6 +904,84 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
|||
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> =
|
||||
/// { 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::ddl::{
|
||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
|
||||
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
|
||||
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
||||
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
|
|
|
@ -210,6 +210,7 @@ define_keywords!(
|
|||
DECIMAL,
|
||||
DECLARE,
|
||||
DEFAULT,
|
||||
DEFERRABLE,
|
||||
DEFERRED,
|
||||
DELAYED,
|
||||
DELETE,
|
||||
|
@ -250,6 +251,7 @@ define_keywords!(
|
|||
ENDPOINT,
|
||||
END_FRAME,
|
||||
END_PARTITION,
|
||||
ENFORCED,
|
||||
ENGINE,
|
||||
ENUM,
|
||||
EPOCH,
|
||||
|
@ -343,6 +345,7 @@ define_keywords!(
|
|||
INDEX,
|
||||
INDICATOR,
|
||||
INHERIT,
|
||||
INITIALLY,
|
||||
INNER,
|
||||
INOUT,
|
||||
INPUTFORMAT,
|
||||
|
|
|
@ -4478,9 +4478,17 @@ impl<'a> Parser<'a> {
|
|||
} else if self.parse_keyword(Keyword::DEFAULT) {
|
||||
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
|
||||
} 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) {
|
||||
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) {
|
||||
let foreign_table = self.parse_object_name(false)?;
|
||||
// PostgreSQL allows omitting the column list and
|
||||
|
@ -4499,11 +4507,14 @@ impl<'a> Parser<'a> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
|
||||
Ok(Some(ColumnOption::ForeignKey {
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
}))
|
||||
} else if self.parse_keyword(Keyword::CHECK) {
|
||||
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(
|
||||
&mut self,
|
||||
) -> Result<Option<TableConstraint>, ParserError> {
|
||||
|
@ -4681,10 +4733,12 @@ impl<'a> Parser<'a> {
|
|||
.or(name);
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
Ok(Some(TableConstraint::Unique {
|
||||
name,
|
||||
columns,
|
||||
is_primary,
|
||||
characteristics,
|
||||
}))
|
||||
}
|
||||
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
|
||||
|
@ -4706,6 +4760,9 @@ impl<'a> Parser<'a> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let characteristics = self.parse_constraint_characteristics()?;
|
||||
|
||||
Ok(Some(TableConstraint::ForeignKey {
|
||||
name,
|
||||
columns,
|
||||
|
@ -4713,6 +4770,7 @@ impl<'a> Parser<'a> {
|
|||
referred_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
characteristics,
|
||||
}))
|
||||
}
|
||||
Token::Word(w) if w.keyword == Keyword::CHECK => {
|
||||
|
|
|
@ -2555,7 +2555,10 @@ fn parse_create_table() {
|
|||
},
|
||||
ColumnOptionDef {
|
||||
name: Some("pkey".into()),
|
||||
option: ColumnOption::Unique { is_primary: true },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
|
@ -2563,7 +2566,10 @@ fn parse_create_table() {
|
|||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique { is_primary: false },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: false,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
|
@ -2582,6 +2588,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["a".into(), "b".into()],
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
characteristics: None,
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
@ -2596,6 +2603,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec![],
|
||||
on_delete: Some(ReferentialAction::Cascade),
|
||||
on_update: Some(ReferentialAction::NoAction),
|
||||
characteristics: None,
|
||||
},
|
||||
},],
|
||||
},
|
||||
|
@ -2611,6 +2619,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Restrict),
|
||||
on_update: None,
|
||||
characteristics: None,
|
||||
},
|
||||
TableConstraint::ForeignKey {
|
||||
name: Some("fkey2".into()),
|
||||
|
@ -2619,6 +2628,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::NoAction),
|
||||
on_update: Some(ReferentialAction::Restrict),
|
||||
characteristics: None,
|
||||
},
|
||||
TableConstraint::ForeignKey {
|
||||
name: None,
|
||||
|
@ -2627,6 +2637,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["lat".into()],
|
||||
on_delete: Some(ReferentialAction::Cascade),
|
||||
on_update: Some(ReferentialAction::SetDefault),
|
||||
characteristics: None,
|
||||
},
|
||||
TableConstraint::ForeignKey {
|
||||
name: None,
|
||||
|
@ -2635,6 +2646,7 @@ fn parse_create_table() {
|
|||
referred_columns: vec!["longitude".into()],
|
||||
on_delete: None,
|
||||
on_update: Some(ReferentialAction::SetNull),
|
||||
characteristics: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
@ -2656,6 +2668,269 @@ fn parse_create_table() {
|
|||
.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]
|
||||
fn parse_create_table_hive_array() {
|
||||
// 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![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique { is_primary: true },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
|
@ -484,7 +487,8 @@ fn parse_create_table_unique_key() {
|
|||
vec![TableConstraint::Unique {
|
||||
name: Some(Ident::new("bar_key")),
|
||||
columns: vec![Ident::new("bar")],
|
||||
is_primary: false
|
||||
is_primary: false,
|
||||
characteristics: None,
|
||||
}],
|
||||
constraints
|
||||
);
|
||||
|
@ -497,7 +501,10 @@ fn parse_create_table_unique_key() {
|
|||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique { is_primary: true },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
|
@ -707,7 +714,10 @@ fn parse_quote_identifiers() {
|
|||
collation: None,
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique { is_primary: true },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
}],
|
||||
}],
|
||||
columns
|
||||
|
|
|
@ -208,7 +208,10 @@ fn parse_create_table_auto_increment() {
|
|||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique { is_primary: true },
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue