This is an automated email from the ASF dual-hosted git repository. github-bot pushed a commit to branch gh-readonly-queue/main/pr-2109-89938b9fcbbb983ea4ad7b35213cbc7285ddfb8f in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 367aa6e8d0fe696c16dbeb9a89f7305860e2e7c2 Author: Luca Cappelletti <[email protected]> AuthorDate: Mon Dec 1 16:09:52 2025 +0100 Added support for `DROP OPERATOR CLASS` syntax (#2109) --- src/ast/ddl.rs | 37 ++++++++++++++++ src/ast/mod.rs | 11 ++++- src/ast/spans.rs | 1 + src/parser/mod.rs | 21 ++++++++- tests/sqlparser_postgres.rs | 103 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 160 insertions(+), 13 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1719c6c0..0df53c14 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4288,3 +4288,40 @@ impl Spanned for DropOperatorFamily { Span::empty() } } + +/// `DROP OPERATOR CLASS` statement +/// See <https://www.postgresql.org/docs/current/sql-dropopclass.html> +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct DropOperatorClass { + /// `IF EXISTS` clause + pub if_exists: bool, + /// One or more operator classes to drop + pub names: Vec<ObjectName>, + /// Index method (btree, hash, gist, gin, etc.) + pub using: Ident, + /// `CASCADE or RESTRICT` + pub drop_behavior: Option<DropBehavior>, +} + +impl fmt::Display for DropOperatorClass { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DROP OPERATOR CLASS")?; + if self.if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", display_comma_separated(&self.names))?; + write!(f, " USING {}", self.using)?; + if let Some(drop_behavior) = &self.drop_behavior { + write!(f, " {}", drop_behavior)?; + } + Ok(()) + } +} + +impl Spanned for DropOperatorClass { + fn span(&self) -> Span { + Span::empty() + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 63a7bebc..44d50c13 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -67,7 +67,7 @@ pub use self::ddl::{ ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, - DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorFamily, + DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, DropOperatorSignature, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, @@ -3586,6 +3586,12 @@ pub enum Statement { /// <https://www.postgresql.org/docs/current/sql-dropopfamily.html> DropOperatorFamily(DropOperatorFamily), /// ```sql + /// DROP OPERATOR CLASS [ IF EXISTS ] name USING index_method [ CASCADE | RESTRICT ] + /// ``` + /// Note: this is a PostgreSQL-specific statement. + /// <https://www.postgresql.org/docs/current/sql-dropopclass.html> + DropOperatorClass(DropOperatorClass), + /// ```sql /// FETCH /// ``` /// Retrieve rows from a query using a cursor @@ -4853,6 +4859,9 @@ impl fmt::Display for Statement { Statement::DropOperatorFamily(drop_operator_family) => { write!(f, "{drop_operator_family}") } + Statement::DropOperatorClass(drop_operator_class) => { + write!(f, "{drop_operator_class}") + } Statement::CreateRole(create_role) => write!(f, "{create_role}"), Statement::CreateSecret { or_replace, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 994cee97..684cc5b0 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -377,6 +377,7 @@ impl Spanned for Statement { Statement::DropExtension(drop_extension) => drop_extension.span(), Statement::DropOperator(drop_operator) => drop_operator.span(), Statement::DropOperatorFamily(drop_operator_family) => drop_operator_family.span(), + Statement::DropOperatorClass(drop_operator_class) => drop_operator_class.span(), Statement::CreateSecret { .. } => Span::empty(), Statement::CreateServer { .. } => Span::empty(), Statement::CreateConnector { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f3daf628..13c3c5b3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6773,9 +6773,11 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::EXTENSION) { return self.parse_drop_extension(); } else if self.parse_keyword(Keyword::OPERATOR) { - // Check if this is DROP OPERATOR FAMILY + // Check if this is DROP OPERATOR FAMILY or DROP OPERATOR CLASS return if self.parse_keyword(Keyword::FAMILY) { self.parse_drop_operator_family() + } else if self.parse_keyword(Keyword::CLASS) { + self.parse_drop_operator_class() } else { self.parse_drop_operator() }; @@ -7594,6 +7596,23 @@ impl<'a> Parser<'a> { })) } + /// Parse a [Statement::DropOperatorClass] + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-dropopclass.html) + pub fn parse_drop_operator_class(&mut self) -> Result<Statement, ParserError> { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let names = self.parse_comma_separated(|p| p.parse_object_name(false))?; + self.expect_keyword(Keyword::USING)?; + let using = self.parse_identifier()?; + let drop_behavior = self.parse_optional_drop_behavior(); + Ok(Statement::DropOperatorClass(DropOperatorClass { + if_exists, + names, + using, + drop_behavior, + })) + } + //TODO: Implement parsing for Skewed pub fn parse_hive_distribution(&mut self) -> Result<HiveDistributionStyle, ParserError> { if self.parse_keywords(&[Keyword::PARTITIONED, Keyword::BY]) { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 14b11052..96e04145 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -23,7 +23,9 @@ mod test_utils; use helpers::attached_token::AttachedToken; -use sqlparser::ast::{DataType, DropBehavior, DropOperator, DropOperatorSignature}; +use sqlparser::ast::{ + DataType, DropBehavior, DropOperator, DropOperatorClass, DropOperatorSignature, +}; use sqlparser::tokenizer::Span; use test_utils::*; @@ -6908,6 +6910,14 @@ fn parse_drop_operator() { drop_behavior: Some(DropBehavior::Cascade), }) ); + + // Test error: DROP OPERATOR with no operators + let sql = "DROP OPERATOR (INTEGER, INTEGER)"; + assert!(pg().parse_sql_statements(sql).is_err()); + + // Test error: DROP OPERATOR IF EXISTS with no operators + let sql = "DROP OPERATOR IF EXISTS (INTEGER, INTEGER)"; + assert!(pg().parse_sql_statements(sql).is_err()); } #[test] @@ -6963,13 +6973,84 @@ fn parse_drop_operator_family() { } } } + + // Test error: DROP OPERATOR FAMILY with no names + let sql = "DROP OPERATOR FAMILY USING btree"; + assert!(pg_and_generic().parse_sql_statements(sql).is_err()); + + // Test error: DROP OPERATOR FAMILY IF EXISTS with no names + let sql = "DROP OPERATOR FAMILY IF EXISTS USING btree"; + assert!(pg_and_generic().parse_sql_statements(sql).is_err()); +} + +#[test] +fn parse_drop_operator_class() { + for if_exists in [true, false] { + for drop_behavior in [ + None, + Some(DropBehavior::Cascade), + Some(DropBehavior::Restrict), + ] { + for index_method in &["btree", "hash", "gist", "gin", "spgist", "brin"] { + for (names_str, names_vec) in [ + ( + "widget_ops", + vec![ObjectName::from(vec![Ident::new("widget_ops")])], + ), + ( + "myschema.int4_ops", + vec![ObjectName::from(vec![ + Ident::new("myschema"), + Ident::new("int4_ops"), + ])], + ), + ( + "ops1, ops2, schema.ops3", + vec![ + ObjectName::from(vec![Ident::new("ops1")]), + ObjectName::from(vec![Ident::new("ops2")]), + ObjectName::from(vec![Ident::new("schema"), Ident::new("ops3")]), + ], + ), + ] { + let sql = format!( + "DROP OPERATOR CLASS{} {} USING {}{}", + if if_exists { " IF EXISTS" } else { "" }, + names_str, + index_method, + match drop_behavior { + Some(behavior) => format!(" {}", behavior), + None => String::new(), + } + ); + assert_eq!( + pg_and_generic().verified_stmt(&sql), + Statement::DropOperatorClass(DropOperatorClass { + if_exists, + names: names_vec.clone(), + using: Ident::new(*index_method), + drop_behavior, + }) + ); + } + } + } + } + + // Test error: DROP OPERATOR CLASS with no names + let sql = "DROP OPERATOR CLASS USING btree"; + assert!(pg_and_generic().parse_sql_statements(sql).is_err()); + + // Test error: DROP OPERATOR CLASS IF EXISTS with no names + let sql = "DROP OPERATOR CLASS IF EXISTS USING btree"; + assert!(pg_and_generic().parse_sql_statements(sql).is_err()); } #[test] fn parse_create_operator_family() { for index_method in &["btree", "hash", "gist", "gin", "spgist", "brin"] { assert_eq!( - pg().verified_stmt(&format!( + pg_and_generic().verified_stmt(&format!( "CREATE OPERATOR FAMILY my_family USING {index_method}" )), Statement::CreateOperatorFamily(CreateOperatorFamily { @@ -6978,7 +7059,7 @@ fn parse_create_operator_family() { }) ); assert_eq!( - pg().verified_stmt(&format!( + pg_and_generic().verified_stmt(&format!( "CREATE OPERATOR FAMILY myschema.test_family USING {index_method}" )), Statement::CreateOperatorFamily(CreateOperatorFamily { @@ -7004,7 +7085,7 @@ fn parse_create_operator_class() { let sql = format!( "CREATE OPERATOR CLASS {class_name} {default_clause}FOR TYPE INT4 USING btree{family_clause} AS OPERATOR 1 <" ); - match pg().verified_stmt(&sql) { + match pg_and_generic().verified_stmt(&sql) { Statement::CreateOperatorClass(CreateOperatorClass { name, default, @@ -7034,7 +7115,7 @@ fn parse_create_operator_class() { } // Test comprehensive operator class with all fields - match pg().verified_stmt("CREATE OPERATOR CLASS CAS_btree_ops DEFAULT FOR TYPE CAS USING btree FAMILY CAS_btree_ops AS OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 =, OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 cas_cmp(CAS, CAS)") { + match pg_and_generic().verified_stmt("CREATE OPERATOR CLASS CAS_btree_ops DEFAULT FOR TYPE CAS USING btree FAMILY CAS_btree_ops AS OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 =, OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 cas_cmp(CAS, CAS)") { Statement::CreateOperatorClass(CreateOperatorClass { name, default: true, @@ -7053,7 +7134,7 @@ fn parse_create_operator_class() { } // Test operator with argument types - match pg().verified_stmt( + match pg_and_generic().verified_stmt( "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING gist AS OPERATOR 1 < (INT4, INT4)", ) { Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) => { @@ -7078,7 +7159,7 @@ fn parse_create_operator_class() { } // Test operator FOR SEARCH - match pg().verified_stmt( + match pg_and_generic().verified_stmt( "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING gist AS OPERATOR 1 < FOR SEARCH", ) { Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) => { @@ -7122,7 +7203,7 @@ fn parse_create_operator_class() { } // Test function with operator class arg types - match pg().verified_stmt("CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 (INT4, INT4) btcmp(INT4, INT4)") { + match pg_and_generic().verified_stmt("CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 (INT4, INT4) btcmp(INT4, INT4)") { Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. @@ -7145,11 +7226,11 @@ fn parse_create_operator_class() { } // Test function with no arguments (empty parentheses normalizes to no parentheses) - pg().one_statement_parses_to( + pg_and_generic().one_statement_parses_to( "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 my_func()", "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 my_func", ); - match pg().verified_stmt( + match pg_and_generic().verified_stmt( "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 my_func", ) { Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) => { @@ -7174,7 +7255,7 @@ fn parse_create_operator_class() { } // Test multiple items including STORAGE - match pg().verified_stmt("CREATE OPERATOR CLASS gist_ops FOR TYPE geometry USING gist AS OPERATOR 1 <<, FUNCTION 1 gist_consistent(internal, geometry, INT4), STORAGE box") { + match pg_and_generic().verified_stmt("CREATE OPERATOR CLASS gist_ops FOR TYPE geometry USING gist AS OPERATOR 1 <<, FUNCTION 1 gist_consistent(internal, geometry, INT4), STORAGE box") { Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
