Add support for constraint characteristics clause (#1099)

This commit is contained in:
Daniel Imfeld 2024-01-24 09:26:19 -10:00 committed by GitHub
parent 1fb9f3efdf
commit c86508bae5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 480 additions and 22 deletions

View file

@ -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 }`
///

View file

@ -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};

View file

@ -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,

View file

@ -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 => {

View file

@ -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

View file

@ -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

View file

@ -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,