Skip to content

Commit 2d37749

Browse files
committed
test: realign EXCLUDE tests to current APIs and expand coverage
Update the existing EXCLUDE tests to the current upstream APIs: - `Statement::AlterTable` is a tuple variant wrapping `AlterTable` - `AlterTableOperation::AddConstraint` is a struct variant with `{ constraint, not_valid }` - `Value::Number` takes `BigDecimal` under `--all-features`; use the `number()` helper so the tests compile in CI's feature matrix Expand coverage following upstream review: - `NOT DEFERRABLE INITIALLY IMMEDIATE` complement to the existing `DEFERRABLE INITIALLY DEFERRED` case - Operator class: `col text_pattern_ops WITH =` - Ordering qualifiers: `ASC NULLS LAST`, `DESC NULLS FIRST` - Parenthesised function expression as element: `(lower(name))` - Schema-qualified operator: `OPERATOR(pg_catalog.=)` - Tighter error assertions on missing `WITH` and missing operator - Negative test for non-PostgreSQL dialects (and smoke test that `exclude` remains a legal column name in MySQL and SQLite)
1 parent dddb2ce commit 2d37749

1 file changed

Lines changed: 167 additions & 41 deletions

File tree

tests/sqlparser_postgres.rs

Lines changed: 167 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ mod test_utils;
2424

2525
use helpers::attached_token::AttachedToken;
2626
use sqlparser::ast::*;
27-
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect};
27+
use sqlparser::dialect::{Dialect, GenericDialect, MySqlDialect, PostgreSqlDialect, SQLiteDialect};
2828
use sqlparser::parser::ParserError;
2929
use sqlparser::tokenizer::Span;
3030
use test_utils::*;
@@ -9136,8 +9136,7 @@ fn parse_pg_analyze() {
91369136

91379137
#[test]
91389138
fn parse_exclude_constraint_basic() {
9139-
let sql =
9140-
"CREATE TABLE t (room INT, CONSTRAINT no_overlap EXCLUDE USING gist (room WITH =))";
9139+
let sql = "CREATE TABLE t (room INT, CONSTRAINT no_overlap EXCLUDE USING gist (room WITH =))";
91419140
match pg().verified_stmt(sql) {
91429141
Statement::CreateTable(create_table) => {
91439142
assert_eq!(1, create_table.constraints.len());
@@ -9146,9 +9145,13 @@ fn parse_exclude_constraint_basic() {
91469145
assert_eq!(c.name, Some(Ident::new("no_overlap")));
91479146
assert_eq!(c.index_method, Some(Ident::new("gist")));
91489147
assert_eq!(c.elements.len(), 1);
9148+
assert_eq!(c.elements[0].expr, Expr::Identifier(Ident::new("room")));
91499149
assert_eq!(c.elements[0].operator, "=");
9150+
assert!(c.elements[0].operator_class.is_none());
9151+
assert_eq!(c.elements[0].order, OrderByOptions::default());
91509152
assert_eq!(c.include.len(), 0);
91519153
assert!(c.where_clause.is_none());
9154+
assert!(c.characteristics.is_none());
91529155
}
91539156
other => panic!("Expected Exclusion, got {other:?}"),
91549157
}
@@ -9170,15 +9173,9 @@ fn parse_exclude_constraint_multi_element() {
91709173
assert_eq!(c.index_method, Some(Ident::new("gist")));
91719174
assert_eq!(c.elements.len(), 2);
91729175
assert_eq!(c.elements[0].operator, "=");
9173-
assert_eq!(
9174-
c.elements[0].expr,
9175-
Expr::Identifier(Ident::new("room"))
9176-
);
9176+
assert_eq!(c.elements[0].expr, Expr::Identifier(Ident::new("room")));
91779177
assert_eq!(c.elements[1].operator, "&&");
9178-
assert_eq!(
9179-
c.elements[1].expr,
9180-
Expr::Identifier(Ident::new("during"))
9181-
);
9178+
assert_eq!(c.elements[1].expr, Expr::Identifier(Ident::new("during")));
91829179
}
91839180
other => panic!("Expected Exclusion, got {other:?}"),
91849181
}
@@ -9249,8 +9246,7 @@ fn parse_lock_table() {
92499246

92509247
#[test]
92519248
fn parse_exclude_constraint_with_where() {
9252-
let sql =
9253-
"CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =) WHERE (col > 0))";
9249+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =) WHERE (col > 0))";
92549250
match pg().verified_stmt(sql) {
92559251
Statement::CreateTable(create_table) => {
92569252
assert_eq!(1, create_table.constraints.len());
@@ -9259,18 +9255,9 @@ fn parse_exclude_constraint_with_where() {
92599255
assert!(c.where_clause.is_some());
92609256
match c.where_clause.as_ref().unwrap().as_ref() {
92619257
Expr::BinaryOp { left, op, right } => {
9262-
assert_eq!(
9263-
**left,
9264-
Expr::Identifier(Ident::new("col"))
9265-
);
9258+
assert_eq!(**left, Expr::Identifier(Ident::new("col")));
92669259
assert_eq!(*op, BinaryOperator::Gt);
9267-
assert_eq!(
9268-
**right,
9269-
Expr::Value(
9270-
(Value::Number("0".to_string(), false))
9271-
.with_empty_span()
9272-
)
9273-
);
9260+
assert_eq!(**right, Expr::Value(number("0").with_empty_span()));
92749261
}
92759262
other => panic!("Expected BinaryOp, got {other:?}"),
92769263
}
@@ -9284,14 +9271,16 @@ fn parse_exclude_constraint_with_where() {
92849271

92859272
#[test]
92869273
fn parse_exclude_constraint_with_include() {
9287-
let sql =
9288-
"CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =) INCLUDE (col))";
9274+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =) INCLUDE (col))";
92899275
match pg().verified_stmt(sql) {
92909276
Statement::CreateTable(create_table) => {
92919277
assert_eq!(1, create_table.constraints.len());
92929278
match &create_table.constraints[0] {
92939279
TableConstraint::Exclusion(c) => {
9280+
assert_eq!(c.elements.len(), 1);
92949281
assert_eq!(c.include, vec![Ident::new("col")]);
9282+
assert!(c.where_clause.is_none());
9283+
assert!(c.characteristics.is_none());
92959284
}
92969285
other => panic!("Expected Exclusion, got {other:?}"),
92979286
}
@@ -9328,10 +9317,7 @@ fn parse_exclude_constraint_deferrable() {
93289317
TableConstraint::Exclusion(c) => {
93299318
let characteristics = c.characteristics.as_ref().unwrap();
93309319
assert_eq!(characteristics.deferrable, Some(true));
9331-
assert_eq!(
9332-
characteristics.initially,
9333-
Some(DeferrableInitial::Deferred)
9334-
);
9320+
assert_eq!(characteristics.initially, Some(DeferrableInitial::Deferred));
93359321
}
93369322
other => panic!("Expected Exclusion, got {other:?}"),
93379323
}
@@ -9342,18 +9328,18 @@ fn parse_exclude_constraint_deferrable() {
93429328

93439329
#[test]
93449330
fn parse_exclude_constraint_in_alter_table() {
9345-
let sql =
9346-
"ALTER TABLE t ADD CONSTRAINT no_overlap EXCLUDE USING gist (room WITH =)";
9331+
let sql = "ALTER TABLE t ADD CONSTRAINT no_overlap EXCLUDE USING gist (room WITH =)";
93479332
match pg().verified_stmt(sql) {
9348-
Statement::AlterTable { operations, .. } => {
9349-
match &operations[0] {
9350-
AlterTableOperation::AddConstraint(TableConstraint::Exclusion(c)) => {
9351-
assert_eq!(c.name, Some(Ident::new("no_overlap")));
9352-
assert_eq!(c.elements[0].operator, "=");
9353-
}
9354-
other => panic!("Expected AddConstraint(Exclusion), got {other:?}"),
9333+
Statement::AlterTable(alter_table) => match &alter_table.operations[0] {
9334+
AlterTableOperation::AddConstraint {
9335+
constraint: TableConstraint::Exclusion(c),
9336+
..
9337+
} => {
9338+
assert_eq!(c.name, Some(Ident::new("no_overlap")));
9339+
assert_eq!(c.elements[0].operator, "=");
93559340
}
9356-
}
9341+
other => panic!("Expected AddConstraint(Exclusion), got {other:?}"),
9342+
},
93579343
_ => panic!("Expected AlterTable"),
93589344
}
93599345
}
@@ -9364,14 +9350,154 @@ fn roundtrip_exclude_constraint() {
93649350
pg().verified_stmt(sql);
93659351
}
93669352

9353+
#[test]
9354+
fn parse_exclude_constraint_not_deferrable_initially_immediate() {
9355+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =) NOT DEFERRABLE INITIALLY IMMEDIATE)";
9356+
match pg().verified_stmt(sql) {
9357+
Statement::CreateTable(create_table) => match &create_table.constraints[0] {
9358+
TableConstraint::Exclusion(c) => {
9359+
let characteristics = c.characteristics.as_ref().unwrap();
9360+
assert_eq!(characteristics.deferrable, Some(false));
9361+
assert_eq!(
9362+
characteristics.initially,
9363+
Some(DeferrableInitial::Immediate)
9364+
);
9365+
}
9366+
other => panic!("Expected Exclusion, got {other:?}"),
9367+
},
9368+
_ => panic!("Expected CreateTable"),
9369+
}
9370+
}
9371+
9372+
#[test]
9373+
fn parse_exclude_constraint_operator_class() {
9374+
let sql = "CREATE TABLE t (col TEXT, EXCLUDE USING gist (col text_pattern_ops WITH =))";
9375+
match pg().verified_stmt(sql) {
9376+
Statement::CreateTable(create_table) => match &create_table.constraints[0] {
9377+
TableConstraint::Exclusion(c) => {
9378+
assert_eq!(c.elements.len(), 1);
9379+
assert_eq!(
9380+
c.elements[0].operator_class,
9381+
Some(ObjectName::from(vec![Ident::new("text_pattern_ops")]))
9382+
);
9383+
assert_eq!(c.elements[0].operator, "=");
9384+
}
9385+
other => panic!("Expected Exclusion, got {other:?}"),
9386+
},
9387+
_ => panic!("Expected CreateTable"),
9388+
}
9389+
}
9390+
9391+
#[test]
9392+
fn parse_exclude_constraint_asc_nulls_last() {
9393+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING btree (col ASC NULLS LAST WITH =))";
9394+
match pg().verified_stmt(sql) {
9395+
Statement::CreateTable(create_table) => match &create_table.constraints[0] {
9396+
TableConstraint::Exclusion(c) => {
9397+
assert_eq!(c.elements[0].order.asc, Some(true));
9398+
assert_eq!(c.elements[0].order.nulls_first, Some(false));
9399+
}
9400+
other => panic!("Expected Exclusion, got {other:?}"),
9401+
},
9402+
_ => panic!("Expected CreateTable"),
9403+
}
9404+
}
9405+
9406+
#[test]
9407+
fn parse_exclude_constraint_desc_nulls_first() {
9408+
pg().verified_stmt(
9409+
"CREATE TABLE t (col INT, EXCLUDE USING btree (col DESC NULLS FIRST WITH =))",
9410+
);
9411+
}
9412+
9413+
#[test]
9414+
fn parse_exclude_constraint_function_expression() {
9415+
let sql =
9416+
"CREATE TABLE t (name TEXT, EXCLUDE USING gist ((lower(name)) text_pattern_ops WITH =))";
9417+
match pg().verified_stmt(sql) {
9418+
Statement::CreateTable(create_table) => match &create_table.constraints[0] {
9419+
TableConstraint::Exclusion(c) => {
9420+
assert_eq!(c.elements.len(), 1);
9421+
assert!(matches!(c.elements[0].expr, Expr::Nested(_)));
9422+
assert_eq!(
9423+
c.elements[0].operator_class,
9424+
Some(ObjectName::from(vec![Ident::new("text_pattern_ops")]))
9425+
);
9426+
}
9427+
other => panic!("Expected Exclusion, got {other:?}"),
9428+
},
9429+
_ => panic!("Expected CreateTable"),
9430+
}
9431+
}
9432+
9433+
#[test]
9434+
fn parse_exclude_constraint_pg_custom_operator() {
9435+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH OPERATOR(pg_catalog.=)))";
9436+
match pg().verified_stmt(sql) {
9437+
Statement::CreateTable(create_table) => match &create_table.constraints[0] {
9438+
TableConstraint::Exclusion(c) => {
9439+
assert_eq!(c.elements[0].operator, "OPERATOR(pg_catalog.=)");
9440+
}
9441+
other => panic!("Expected Exclusion, got {other:?}"),
9442+
},
9443+
_ => panic!("Expected CreateTable"),
9444+
}
9445+
}
9446+
93679447
#[test]
93689448
fn exclude_missing_with_keyword_errors() {
93699449
let sql = "CREATE TABLE t (CONSTRAINT c EXCLUDE USING gist (col))";
9370-
assert!(pg().parse_sql_statements(sql).is_err());
9450+
let err = pg().parse_sql_statements(sql).unwrap_err();
9451+
assert!(
9452+
err.to_string().contains("Expected: WITH"),
9453+
"unexpected error: {err}"
9454+
);
93719455
}
93729456

93739457
#[test]
93749458
fn exclude_empty_element_list_errors() {
93759459
let sql = "CREATE TABLE t (CONSTRAINT c EXCLUDE USING gist ())";
93769460
assert!(pg().parse_sql_statements(sql).is_err());
93779461
}
9462+
9463+
#[test]
9464+
fn exclude_missing_operator_errors() {
9465+
let sql = "CREATE TABLE t (CONSTRAINT c EXCLUDE USING gist (col WITH))";
9466+
let err = pg().parse_sql_statements(sql).unwrap_err();
9467+
assert!(
9468+
err.to_string().contains("exclusion operator"),
9469+
"unexpected error: {err}"
9470+
);
9471+
}
9472+
9473+
#[test]
9474+
fn exclude_rejected_in_non_postgres_dialects() {
9475+
let sql = "CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =))";
9476+
for dialect in
9477+
all_dialects_except(|d| d.is::<PostgreSqlDialect>() || d.is::<GenericDialect>()).dialects
9478+
{
9479+
let parser = TestedDialects::new(vec![dialect]);
9480+
assert!(
9481+
parser.parse_sql_statements(sql).is_err(),
9482+
"dialect unexpectedly accepted EXCLUDE: {sql}"
9483+
);
9484+
}
9485+
}
9486+
9487+
#[test]
9488+
fn exclude_as_column_name_parses_in_mysql_and_sqlite() {
9489+
// `exclude` must remain usable as an identifier where it is not a
9490+
// reserved keyword; PG reserves it as a constraint keyword.
9491+
let sql = "CREATE TABLE t (exclude INT)";
9492+
for dialect in [
9493+
Box::new(MySqlDialect {}) as Box<dyn Dialect>,
9494+
Box::new(SQLiteDialect {}),
9495+
] {
9496+
let type_name = format!("{dialect:?}");
9497+
let parser = TestedDialects::new(vec![dialect]);
9498+
assert!(
9499+
parser.parse_sql_statements(sql).is_ok(),
9500+
"dialect {type_name} failed to parse `exclude` as column name"
9501+
);
9502+
}
9503+
}

0 commit comments

Comments
 (0)