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]

Reply via email to