@@ -24,7 +24,7 @@ mod test_utils;
2424
2525use helpers:: attached_token:: AttachedToken ;
2626use sqlparser:: ast:: * ;
27- use sqlparser:: dialect:: { GenericDialect , PostgreSqlDialect } ;
27+ use sqlparser:: dialect:: { Dialect , GenericDialect , MySqlDialect , PostgreSqlDialect , SQLiteDialect } ;
2828use sqlparser:: parser:: ParserError ;
2929use sqlparser:: tokenizer:: Span ;
3030use test_utils:: * ;
@@ -9136,8 +9136,7 @@ fn parse_pg_analyze() {
91369136
91379137#[ test]
91389138fn 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]
92519248fn 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]
92869273fn 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]
93449330fn 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]
93689448fn 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]
93749458fn 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