mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-29 18:34:04 +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
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue