This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 1114d6a2 Add PostgreSQL Operator DDL Support (#2096)
1114d6a2 is described below

commit 1114d6a2bcfc6f3cdb3f2f195278d075e669faf4
Author: Luca Cappelletti <[email protected]>
AuthorDate: Wed Nov 19 15:19:39 2025 +0100

    Add PostgreSQL Operator DDL Support (#2096)
    
    Co-authored-by: Ifeanyi Ubah <[email protected]>
---
 src/ast/ddl.rs              | 238 ++++++++++++++++++++++-
 src/ast/mod.rs              |  31 ++-
 src/ast/spans.rs            |  26 ++-
 src/keywords.rs             |  10 +
 src/parser/mod.rs           | 284 ++++++++++++++++++++++++++++
 tests/sqlparser_postgres.rs | 451 +++++++++++++++++++++++++++++++++++++++-----
 6 files changed, 987 insertions(+), 53 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 286b16a4..3a38fbaa 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -19,7 +19,13 @@
 //! (commonly referred to as Data Definition Language, or DDL)
 
 #[cfg(not(feature = "std"))]
-use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
+use alloc::{
+    boxed::Box,
+    format,
+    string::{String, ToString},
+    vec,
+    vec::Vec,
+};
 use core::fmt::{self, Display, Write};
 
 #[cfg(feature = "serde")]
@@ -3952,3 +3958,233 @@ impl Spanned for DropFunction {
         Span::empty()
     }
 }
+
+/// CREATE OPERATOR statement
+/// See <https://www.postgresql.org/docs/current/sql-createoperator.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 CreateOperator {
+    /// Operator name (can be schema-qualified)
+    pub name: ObjectName,
+    /// FUNCTION or PROCEDURE parameter (function name)
+    pub function: ObjectName,
+    /// Whether PROCEDURE keyword was used (vs FUNCTION)
+    pub is_procedure: bool,
+    /// LEFTARG parameter (left operand type)
+    pub left_arg: Option<DataType>,
+    /// RIGHTARG parameter (right operand type)
+    pub right_arg: Option<DataType>,
+    /// COMMUTATOR parameter (commutator operator)
+    pub commutator: Option<ObjectName>,
+    /// NEGATOR parameter (negator operator)
+    pub negator: Option<ObjectName>,
+    /// RESTRICT parameter (restriction selectivity function)
+    pub restrict: Option<ObjectName>,
+    /// JOIN parameter (join selectivity function)
+    pub join: Option<ObjectName>,
+    /// HASHES flag
+    pub hashes: bool,
+    /// MERGES flag
+    pub merges: bool,
+}
+
+/// CREATE OPERATOR FAMILY statement
+/// See <https://www.postgresql.org/docs/current/sql-createopfamily.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 CreateOperatorFamily {
+    /// Operator family name (can be schema-qualified)
+    pub name: ObjectName,
+    /// Index method (btree, hash, gist, gin, etc.)
+    pub using: Ident,
+}
+
+/// CREATE OPERATOR CLASS statement
+/// See <https://www.postgresql.org/docs/current/sql-createopclass.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 CreateOperatorClass {
+    /// Operator class name (can be schema-qualified)
+    pub name: ObjectName,
+    /// Whether this is the default operator class for the type
+    pub default: bool,
+    /// The data type
+    pub for_type: DataType,
+    /// Index method (btree, hash, gist, gin, etc.)
+    pub using: Ident,
+    /// Optional operator family name
+    pub family: Option<ObjectName>,
+    /// List of operator class items (operators, functions, storage)
+    pub items: Vec<OperatorClassItem>,
+}
+
+impl fmt::Display for CreateOperator {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "CREATE OPERATOR {} (", self.name)?;
+
+        let function_keyword = if self.is_procedure {
+            "PROCEDURE"
+        } else {
+            "FUNCTION"
+        };
+        let mut params = vec![format!("{} = {}", function_keyword, 
self.function)];
+
+        if let Some(left_arg) = &self.left_arg {
+            params.push(format!("LEFTARG = {}", left_arg));
+        }
+        if let Some(right_arg) = &self.right_arg {
+            params.push(format!("RIGHTARG = {}", right_arg));
+        }
+        if let Some(commutator) = &self.commutator {
+            params.push(format!("COMMUTATOR = {}", commutator));
+        }
+        if let Some(negator) = &self.negator {
+            params.push(format!("NEGATOR = {}", negator));
+        }
+        if let Some(restrict) = &self.restrict {
+            params.push(format!("RESTRICT = {}", restrict));
+        }
+        if let Some(join) = &self.join {
+            params.push(format!("JOIN = {}", join));
+        }
+        if self.hashes {
+            params.push("HASHES".to_string());
+        }
+        if self.merges {
+            params.push("MERGES".to_string());
+        }
+
+        write!(f, "{}", params.join(", "))?;
+        write!(f, ")")
+    }
+}
+
+impl fmt::Display for CreateOperatorFamily {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "CREATE OPERATOR FAMILY {} USING {}",
+            self.name, self.using
+        )
+    }
+}
+
+impl fmt::Display for CreateOperatorClass {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "CREATE OPERATOR CLASS {}", self.name)?;
+        if self.default {
+            write!(f, " DEFAULT")?;
+        }
+        write!(f, " FOR TYPE {} USING {}", self.for_type, self.using)?;
+        if let Some(family) = &self.family {
+            write!(f, " FAMILY {}", family)?;
+        }
+        write!(f, " AS {}", display_comma_separated(&self.items))
+    }
+}
+
+/// Operator argument types for CREATE OPERATOR CLASS
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct OperatorArgTypes {
+    pub left: DataType,
+    pub right: DataType,
+}
+
+impl fmt::Display for OperatorArgTypes {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}, {}", self.left, self.right)
+    }
+}
+
+/// An item in a CREATE OPERATOR CLASS statement
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum OperatorClassItem {
+    /// OPERATOR clause
+    Operator {
+        strategy_number: u32,
+        operator_name: ObjectName,
+        /// Optional operator argument types
+        op_types: Option<OperatorArgTypes>,
+        /// FOR SEARCH or FOR ORDER BY
+        purpose: Option<OperatorPurpose>,
+    },
+    /// FUNCTION clause
+    Function {
+        support_number: u32,
+        /// Optional function argument types for the operator class
+        op_types: Option<Vec<DataType>>,
+        function_name: ObjectName,
+        /// Function argument types
+        argument_types: Vec<DataType>,
+    },
+    /// STORAGE clause
+    Storage { storage_type: DataType },
+}
+
+/// Purpose of an operator in an operator class
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum OperatorPurpose {
+    ForSearch,
+    ForOrderBy { sort_family: ObjectName },
+}
+
+impl fmt::Display for OperatorClassItem {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            OperatorClassItem::Operator {
+                strategy_number,
+                operator_name,
+                op_types,
+                purpose,
+            } => {
+                write!(f, "OPERATOR {strategy_number} {operator_name}")?;
+                if let Some(types) = op_types {
+                    write!(f, " ({types})")?;
+                }
+                if let Some(purpose) = purpose {
+                    write!(f, " {purpose}")?;
+                }
+                Ok(())
+            }
+            OperatorClassItem::Function {
+                support_number,
+                op_types,
+                function_name,
+                argument_types,
+            } => {
+                write!(f, "FUNCTION {support_number}")?;
+                if let Some(types) = op_types {
+                    write!(f, " ({})", display_comma_separated(types))?;
+                }
+                write!(f, " {function_name}")?;
+                if !argument_types.is_empty() {
+                    write!(f, "({})", 
display_comma_separated(argument_types))?;
+                }
+                Ok(())
+            }
+            OperatorClassItem::Storage { storage_type } => {
+                write!(f, "STORAGE {storage_type}")
+            }
+        }
+    }
+}
+
+impl fmt::Display for OperatorPurpose {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            OperatorPurpose::ForSearch => write!(f, "FOR SEARCH"),
+            OperatorPurpose::ForOrderBy { sort_family } => {
+                write!(f, "FOR ORDER BY {sort_family}")
+            }
+        }
+    }
+}
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index aa3fb082..482c3813 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -65,11 +65,12 @@ pub use self::ddl::{
     AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, 
AlterTypeRenameValue,
     ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, 
ColumnPolicy,
     ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, 
CreateDomain,
-    CreateExtension, CreateFunction, CreateIndex, CreateTable, CreateTrigger, 
CreateView,
-    Deduplicate, DeferrableInitial, DropBehavior, DropExtension, DropFunction, 
DropTrigger,
-    GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
-    IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, 
IndexColumn,
-    IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, 
Owner, Partition,
+    CreateExtension, CreateFunction, CreateIndex, CreateOperator, 
CreateOperatorClass,
+    CreateOperatorFamily, CreateTable, CreateTrigger, CreateView, Deduplicate, 
DeferrableInitial,
+    DropBehavior, DropExtension, DropFunction, DropTrigger, GeneratedAs, 
GeneratedExpressionMode,
+    IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, 
IdentityPropertyKind,
+    IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, 
KeyOrIndexDisplay, Msck,
+    NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorPurpose, 
Owner, Partition,
     ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, 
TagsColumnOption,
     TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
     UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, 
UserDefinedTypeRepresentation,
@@ -3347,6 +3348,21 @@ pub enum Statement {
     /// See 
[Hive](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362034#LanguageManualDDL-CreateDataConnectorCreateConnector)
     CreateConnector(CreateConnector),
     /// ```sql
+    /// CREATE OPERATOR
+    /// ```
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createoperator.html)
+    CreateOperator(CreateOperator),
+    /// ```sql
+    /// CREATE OPERATOR FAMILY
+    /// ```
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createopfamily.html)
+    CreateOperatorFamily(CreateOperatorFamily),
+    /// ```sql
+    /// CREATE OPERATOR CLASS
+    /// ```
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createopclass.html)
+    CreateOperatorClass(CreateOperatorClass),
+    /// ```sql
     /// ALTER TABLE
     /// ```
     AlterTable(AlterTable),
@@ -4901,6 +4917,11 @@ impl fmt::Display for Statement {
                 Ok(())
             }
             Statement::CreateConnector(create_connector) => 
create_connector.fmt(f),
+            Statement::CreateOperator(create_operator) => 
create_operator.fmt(f),
+            Statement::CreateOperatorFamily(create_operator_family) => {
+                create_operator_family.fmt(f)
+            }
+            Statement::CreateOperatorClass(create_operator_class) => 
create_operator_class.fmt(f),
             Statement::AlterTable(alter_table) => write!(f, "{alter_table}"),
             Statement::AlterIndex { name, operation } => {
                 write!(f, "ALTER INDEX {name} {operation}")
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 3a4f1d02..cfaaf8f0 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -17,7 +17,8 @@
 
 use crate::ast::{
     ddl::AlterSchema, query::SelectItemQualifiedWildcardKind, 
AlterSchemaOperation, AlterTable,
-    ColumnOptions, CreateView, ExportData, Owner, TypedString,
+    ColumnOptions, CreateOperator, CreateOperatorClass, CreateOperatorFamily, 
CreateView,
+    ExportData, Owner, TypedString,
 };
 use core::iter;
 
@@ -368,6 +369,11 @@ impl Spanned for Statement {
             Statement::CreateSecret { .. } => Span::empty(),
             Statement::CreateServer { .. } => Span::empty(),
             Statement::CreateConnector { .. } => Span::empty(),
+            Statement::CreateOperator(create_operator) => 
create_operator.span(),
+            Statement::CreateOperatorFamily(create_operator_family) => {
+                create_operator_family.span()
+            }
+            Statement::CreateOperatorClass(create_operator_class) => 
create_operator_class.span(),
             Statement::AlterTable(alter_table) => alter_table.span(),
             Statement::AlterIndex { name, operation } => 
name.span().union(&operation.span()),
             Statement::AlterView {
@@ -2357,6 +2363,24 @@ impl Spanned for AlterTable {
     }
 }
 
+impl Spanned for CreateOperator {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+impl Spanned for CreateOperatorFamily {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+impl Spanned for CreateOperatorClass {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
 #[cfg(test)]
 pub mod tests {
     use crate::dialect::{Dialect, GenericDialect, SnowflakeDialect};
diff --git a/src/keywords.rs b/src/keywords.rs
index 7ff42b41..834d3495 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -197,6 +197,7 @@ define_keywords!(
     CHECK,
     CHECKSUM,
     CIRCLE,
+    CLASS,
     CLEANPATH,
     CLEAR,
     CLOB,
@@ -217,6 +218,7 @@ define_keywords!(
     COMMENT,
     COMMIT,
     COMMITTED,
+    COMMUTATOR,
     COMPATIBLE,
     COMPRESSION,
     COMPUPDATE,
@@ -385,6 +387,7 @@ define_keywords!(
     FAIL,
     FAILOVER,
     FALSE,
+    FAMILY,
     FETCH,
     FIELDS,
     FILE,
@@ -446,6 +449,7 @@ define_keywords!(
     GROUPS,
     GZIP,
     HASH,
+    HASHES,
     HAVING,
     HEADER,
     HEAP,
@@ -539,7 +543,10 @@ define_keywords!(
     LATERAL,
     LEAD,
     LEADING,
+    LEAKPROOF,
+    LEAST,
     LEFT,
+    LEFTARG,
     LEVEL,
     LIKE,
     LIKE_REGEX,
@@ -594,6 +601,7 @@ define_keywords!(
     MEDIUMTEXT,
     MEMBER,
     MERGE,
+    MERGES,
     MESSAGE,
     METADATA,
     METHOD,
@@ -632,6 +640,7 @@ define_keywords!(
     NATURAL,
     NCHAR,
     NCLOB,
+    NEGATOR,
     NEST,
     NESTED,
     NETWORK,
@@ -844,6 +853,7 @@ define_keywords!(
     RETURNS,
     REVOKE,
     RIGHT,
+    RIGHTARG,
     RLIKE,
     RM,
     ROLE,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1ab4626f..f835f541 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4792,6 +4792,15 @@ impl<'a> Parser<'a> {
             self.parse_create_procedure(or_alter)
         } else if self.parse_keyword(Keyword::CONNECTOR) {
             self.parse_create_connector()
+        } else if self.parse_keyword(Keyword::OPERATOR) {
+            // Check if this is CREATE OPERATOR FAMILY or CREATE OPERATOR CLASS
+            if self.parse_keyword(Keyword::FAMILY) {
+                self.parse_create_operator_family()
+            } else if self.parse_keyword(Keyword::CLASS) {
+                self.parse_create_operator_class()
+            } else {
+                self.parse_create_operator()
+            }
         } else if self.parse_keyword(Keyword::SERVER) {
             self.parse_pg_create_server()
         } else {
@@ -6436,6 +6445,281 @@ impl<'a> Parser<'a> {
         }))
     }
 
+    /// Parse an operator name, which can contain special characters like +, 
-, <, >, =
+    /// that are tokenized as operator tokens rather than identifiers.
+    /// This is used for PostgreSQL CREATE OPERATOR statements.
+    ///
+    /// Examples: `+`, `myschema.+`, `pg_catalog.<=`
+    fn parse_operator_name(&mut self) -> Result<ObjectName, ParserError> {
+        let mut parts = vec![];
+        loop {
+            parts.push(ObjectNamePart::Identifier(Ident::new(
+                self.next_token().to_string(),
+            )));
+            if !self.consume_token(&Token::Period) {
+                break;
+            }
+        }
+        Ok(ObjectName(parts))
+    }
+
+    /// Parse a [Statement::CreateOperator]
+    ///
+    /// [PostgreSQL 
Documentation](https://www.postgresql.org/docs/current/sql-createoperator.html)
+    pub fn parse_create_operator(&mut self) -> Result<Statement, ParserError> {
+        let name = self.parse_operator_name()?;
+        self.expect_token(&Token::LParen)?;
+
+        let mut function: Option<ObjectName> = None;
+        let mut is_procedure = false;
+        let mut left_arg: Option<DataType> = None;
+        let mut right_arg: Option<DataType> = None;
+        let mut commutator: Option<ObjectName> = None;
+        let mut negator: Option<ObjectName> = None;
+        let mut restrict: Option<ObjectName> = None;
+        let mut join: Option<ObjectName> = None;
+        let mut hashes = false;
+        let mut merges = false;
+
+        loop {
+            let keyword = self.expect_one_of_keywords(&[
+                Keyword::FUNCTION,
+                Keyword::PROCEDURE,
+                Keyword::LEFTARG,
+                Keyword::RIGHTARG,
+                Keyword::COMMUTATOR,
+                Keyword::NEGATOR,
+                Keyword::RESTRICT,
+                Keyword::JOIN,
+                Keyword::HASHES,
+                Keyword::MERGES,
+            ])?;
+
+            match keyword {
+                Keyword::HASHES if !hashes => {
+                    hashes = true;
+                }
+                Keyword::MERGES if !merges => {
+                    merges = true;
+                }
+                Keyword::FUNCTION | Keyword::PROCEDURE if function.is_none() 
=> {
+                    self.expect_token(&Token::Eq)?;
+                    function = Some(self.parse_object_name(false)?);
+                    is_procedure = keyword == Keyword::PROCEDURE;
+                }
+                Keyword::LEFTARG if left_arg.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    left_arg = Some(self.parse_data_type()?);
+                }
+                Keyword::RIGHTARG if right_arg.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    right_arg = Some(self.parse_data_type()?);
+                }
+                Keyword::COMMUTATOR if commutator.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    if self.parse_keyword(Keyword::OPERATOR) {
+                        self.expect_token(&Token::LParen)?;
+                        commutator = Some(self.parse_operator_name()?);
+                        self.expect_token(&Token::RParen)?;
+                    } else {
+                        commutator = Some(self.parse_operator_name()?);
+                    }
+                }
+                Keyword::NEGATOR if negator.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    if self.parse_keyword(Keyword::OPERATOR) {
+                        self.expect_token(&Token::LParen)?;
+                        negator = Some(self.parse_operator_name()?);
+                        self.expect_token(&Token::RParen)?;
+                    } else {
+                        negator = Some(self.parse_operator_name()?);
+                    }
+                }
+                Keyword::RESTRICT if restrict.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    restrict = Some(self.parse_object_name(false)?);
+                }
+                Keyword::JOIN if join.is_none() => {
+                    self.expect_token(&Token::Eq)?;
+                    join = Some(self.parse_object_name(false)?);
+                }
+                _ => {
+                    return Err(ParserError::ParserError(format!(
+                        "Duplicate or unexpected keyword {:?} in CREATE 
OPERATOR",
+                        keyword
+                    )))
+                }
+            }
+
+            if !self.consume_token(&Token::Comma) {
+                break;
+            }
+        }
+
+        // Expect closing parenthesis
+        self.expect_token(&Token::RParen)?;
+
+        // FUNCTION is required
+        let function = function.ok_or_else(|| {
+            ParserError::ParserError("CREATE OPERATOR requires FUNCTION 
parameter".to_string())
+        })?;
+
+        Ok(Statement::CreateOperator(CreateOperator {
+            name,
+            function,
+            is_procedure,
+            left_arg,
+            right_arg,
+            commutator,
+            negator,
+            restrict,
+            join,
+            hashes,
+            merges,
+        }))
+    }
+
+    /// Parse a [Statement::CreateOperatorFamily]
+    ///
+    /// [PostgreSQL 
Documentation](https://www.postgresql.org/docs/current/sql-createopfamily.html)
+    pub fn parse_create_operator_family(&mut self) -> Result<Statement, 
ParserError> {
+        let name = self.parse_object_name(false)?;
+        self.expect_keyword(Keyword::USING)?;
+        let using = self.parse_identifier()?;
+
+        Ok(Statement::CreateOperatorFamily(CreateOperatorFamily {
+            name,
+            using,
+        }))
+    }
+
+    /// Parse a [Statement::CreateOperatorClass]
+    ///
+    /// [PostgreSQL 
Documentation](https://www.postgresql.org/docs/current/sql-createopclass.html)
+    pub fn parse_create_operator_class(&mut self) -> Result<Statement, 
ParserError> {
+        let name = self.parse_object_name(false)?;
+        let default = self.parse_keyword(Keyword::DEFAULT);
+        self.expect_keywords(&[Keyword::FOR, Keyword::TYPE])?;
+        let for_type = self.parse_data_type()?;
+        self.expect_keyword(Keyword::USING)?;
+        let using = self.parse_identifier()?;
+
+        let family = if self.parse_keyword(Keyword::FAMILY) {
+            Some(self.parse_object_name(false)?)
+        } else {
+            None
+        };
+
+        self.expect_keyword(Keyword::AS)?;
+
+        let mut items = vec![];
+        loop {
+            if self.parse_keyword(Keyword::OPERATOR) {
+                let strategy_number = self.parse_literal_uint()? as u32;
+                let operator_name = self.parse_operator_name()?;
+
+                // Optional operator argument types
+                let op_types = if self.consume_token(&Token::LParen) {
+                    let left = self.parse_data_type()?;
+                    self.expect_token(&Token::Comma)?;
+                    let right = self.parse_data_type()?;
+                    self.expect_token(&Token::RParen)?;
+                    Some(OperatorArgTypes { left, right })
+                } else {
+                    None
+                };
+
+                // Optional purpose
+                let purpose = if self.parse_keyword(Keyword::FOR) {
+                    if self.parse_keyword(Keyword::SEARCH) {
+                        Some(OperatorPurpose::ForSearch)
+                    } else if self.parse_keywords(&[Keyword::ORDER, 
Keyword::BY]) {
+                        let sort_family = self.parse_object_name(false)?;
+                        Some(OperatorPurpose::ForOrderBy { sort_family })
+                    } else {
+                        return self.expected("SEARCH or ORDER BY after FOR", 
self.peek_token());
+                    }
+                } else {
+                    None
+                };
+
+                items.push(OperatorClassItem::Operator {
+                    strategy_number,
+                    operator_name,
+                    op_types,
+                    purpose,
+                });
+            } else if self.parse_keyword(Keyword::FUNCTION) {
+                let support_number = self.parse_literal_uint()? as u32;
+
+                // Optional operator types
+                let op_types =
+                    if self.consume_token(&Token::LParen) && self.peek_token() 
!= Token::RParen {
+                        let mut types = vec![];
+                        loop {
+                            types.push(self.parse_data_type()?);
+                            if !self.consume_token(&Token::Comma) {
+                                break;
+                            }
+                        }
+                        self.expect_token(&Token::RParen)?;
+                        Some(types)
+                    } else if self.consume_token(&Token::LParen) {
+                        self.expect_token(&Token::RParen)?;
+                        Some(vec![])
+                    } else {
+                        None
+                    };
+
+                let function_name = self.parse_object_name(false)?;
+
+                // Function argument types
+                let argument_types = if self.consume_token(&Token::LParen) {
+                    let mut types = vec![];
+                    loop {
+                        if self.peek_token() == Token::RParen {
+                            break;
+                        }
+                        types.push(self.parse_data_type()?);
+                        if !self.consume_token(&Token::Comma) {
+                            break;
+                        }
+                    }
+                    self.expect_token(&Token::RParen)?;
+                    types
+                } else {
+                    vec![]
+                };
+
+                items.push(OperatorClassItem::Function {
+                    support_number,
+                    op_types,
+                    function_name,
+                    argument_types,
+                });
+            } else if self.parse_keyword(Keyword::STORAGE) {
+                let storage_type = self.parse_data_type()?;
+                items.push(OperatorClassItem::Storage { storage_type });
+            } else {
+                break;
+            }
+
+            // Check for comma separator
+            if !self.consume_token(&Token::Comma) {
+                break;
+            }
+        }
+
+        Ok(Statement::CreateOperatorClass(CreateOperatorClass {
+            name,
+            default,
+            for_type,
+            using,
+            family,
+            items,
+        }))
+    }
+
     pub fn parse_drop(&mut self) -> Result<Statement, ParserError> {
         // MySQL dialect supports `TEMPORARY`
         let temporary = dialect_of!(self is MySqlDialect | GenericDialect | 
DuckDbDialect)
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 3bdf6d18..fbfa6658 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6553,7 +6553,9 @@ fn parse_create_server() {
 
 #[test]
 fn parse_alter_schema() {
-    match pg_and_generic().verified_stmt("ALTER SCHEMA foo RENAME TO bar") {
+    // Test RENAME operation
+    let stmt = pg_and_generic().verified_stmt("ALTER SCHEMA foo RENAME TO 
bar");
+    match stmt {
         Statement::AlterSchema(AlterSchema { operations, .. }) => {
             assert_eq!(
                 operations,
@@ -6565,52 +6567,26 @@ fn parse_alter_schema() {
         _ => unreachable!(),
     }
 
-    match pg_and_generic().verified_stmt("ALTER SCHEMA foo OWNER TO bar") {
-        Statement::AlterSchema(AlterSchema { operations, .. }) => {
-            assert_eq!(
-                operations,
-                vec![AlterSchemaOperation::OwnerTo {
-                    owner: Owner::Ident("bar".into())
-                }]
-            );
-        }
-        _ => unreachable!(),
-    }
-
-    match pg_and_generic().verified_stmt("ALTER SCHEMA foo OWNER TO 
CURRENT_ROLE") {
-        Statement::AlterSchema(AlterSchema { operations, .. }) => {
-            assert_eq!(
-                operations,
-                vec![AlterSchemaOperation::OwnerTo {
-                    owner: Owner::CurrentRole
-                }]
-            );
-        }
-        _ => unreachable!(),
-    }
-
-    match pg_and_generic().verified_stmt("ALTER SCHEMA foo OWNER TO 
CURRENT_USER") {
-        Statement::AlterSchema(AlterSchema { operations, .. }) => {
-            assert_eq!(
-                operations,
-                vec![AlterSchemaOperation::OwnerTo {
-                    owner: Owner::CurrentUser
-                }]
-            );
-        }
-        _ => unreachable!(),
-    }
-
-    match pg_and_generic().verified_stmt("ALTER SCHEMA foo OWNER TO 
SESSION_USER") {
-        Statement::AlterSchema(AlterSchema { operations, .. }) => {
-            assert_eq!(
-                operations,
-                vec![AlterSchemaOperation::OwnerTo {
-                    owner: Owner::SessionUser
-                }]
-            );
+    // Test OWNER TO operations with different owner types
+    for (owner_clause, expected_owner) in &[
+        ("bar", Owner::Ident("bar".into())),
+        ("CURRENT_ROLE", Owner::CurrentRole),
+        ("CURRENT_USER", Owner::CurrentUser),
+        ("SESSION_USER", Owner::SessionUser),
+    ] {
+        let sql = format!("ALTER SCHEMA foo OWNER TO {}", owner_clause);
+        let stmt = pg_and_generic().verified_stmt(&sql);
+        match stmt {
+            Statement::AlterSchema(AlterSchema { operations, .. }) => {
+                assert_eq!(
+                    operations,
+                    vec![AlterSchemaOperation::OwnerTo {
+                        owner: expected_owner.clone()
+                    }]
+                );
+            }
+            _ => unreachable!(),
         }
-        _ => unreachable!(),
     }
 }
 
@@ -6661,3 +6637,386 @@ fn parse_foreign_key_match_with_actions() {
 
     pg_and_generic().verified_stmt(sql);
 }
+
+#[test]
+fn parse_create_operator() {
+    let sql = "CREATE OPERATOR myschema.@@ (PROCEDURE = myschema.my_proc, 
LEFTARG = TIMESTAMP WITH TIME ZONE, RIGHTARG = VARCHAR(255), COMMUTATOR = 
schema.>, NEGATOR = schema.<=, RESTRICT = myschema.sel_func, JOIN = 
myschema.join_func, HASHES, MERGES)";
+    assert_eq!(
+        pg().verified_stmt(sql),
+        Statement::CreateOperator(CreateOperator {
+            name: ObjectName::from(vec![Ident::new("myschema"), 
Ident::new("@@")]),
+            function: ObjectName::from(vec![Ident::new("myschema"), 
Ident::new("my_proc")]),
+            is_procedure: true,
+            left_arg: Some(DataType::Timestamp(None, 
TimezoneInfo::WithTimeZone)),
+            right_arg: 
Some(DataType::Varchar(Some(CharacterLength::IntegerLength {
+                length: 255,
+                unit: None
+            }))),
+            commutator: Some(ObjectName::from(vec![
+                Ident::new("schema"),
+                Ident::new(">")
+            ])),
+            negator: Some(ObjectName::from(vec![
+                Ident::new("schema"),
+                Ident::new("<=")
+            ])),
+            restrict: Some(ObjectName::from(vec![
+                Ident::new("myschema"),
+                Ident::new("sel_func")
+            ])),
+            join: Some(ObjectName::from(vec![
+                Ident::new("myschema"),
+                Ident::new("join_func")
+            ])),
+            hashes: true,
+            merges: true,
+        })
+    );
+
+    for op_symbol in &[
+        "-", "*", "/", "<", ">", "=", "<=", ">=", "<>", "~", "!", "@", "#", 
"%", "^", "&", "|",
+        "<<", ">>", "&&",
+    ] {
+        assert_eq!(
+            pg().verified_stmt(&format!("CREATE OPERATOR {op_symbol} (FUNCTION 
= f)")),
+            Statement::CreateOperator(CreateOperator {
+                name: ObjectName::from(vec![Ident::new(*op_symbol)]),
+                function: ObjectName::from(vec![Ident::new("f")]),
+                is_procedure: false,
+                left_arg: None,
+                right_arg: None,
+                commutator: None,
+                negator: None,
+                restrict: None,
+                join: None,
+                hashes: false,
+                merges: false,
+            })
+        );
+    }
+
+    pg().one_statement_parses_to(
+        "CREATE OPERATOR != (FUNCTION = func)",
+        "CREATE OPERATOR <> (FUNCTION = func)",
+    );
+
+    for (name, expected_name) in [
+        (
+            "s1.+",
+            ObjectName::from(vec![Ident::new("s1"), Ident::new("+")]),
+        ),
+        (
+            "s2.-",
+            ObjectName::from(vec![Ident::new("s2"), Ident::new("-")]),
+        ),
+        (
+            "s1.s3.*",
+            ObjectName::from(vec![Ident::new("s1"), Ident::new("s3"), 
Ident::new("*")]),
+        ),
+    ] {
+        match pg().verified_stmt(&format!("CREATE OPERATOR {name} (FUNCTION = 
f)")) {
+            Statement::CreateOperator(CreateOperator {
+                name,
+                hashes: false,
+                merges: false,
+                ..
+            }) => {
+                assert_eq!(name, expected_name);
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    pg().one_statement_parses_to(
+        "CREATE OPERATOR + (FUNCTION = f, COMMUTATOR = OPERATOR(>), NEGATOR = 
OPERATOR(>=))",
+        "CREATE OPERATOR + (FUNCTION = f, COMMUTATOR = >, NEGATOR = >=)",
+    );
+
+    // Test all duplicate clause errors
+    for field in &[
+        "FUNCTION = f2",
+        "PROCEDURE = p",
+        "LEFTARG = INT4, LEFTARG = INT4",
+        "RIGHTARG = INT4, RIGHTARG = INT4",
+        "COMMUTATOR = -, COMMUTATOR = *",
+        "NEGATOR = -, NEGATOR = *",
+        "RESTRICT = f1, RESTRICT = f2",
+        "JOIN = f1, JOIN = f2",
+        "HASHES, HASHES",
+        "MERGES, MERGES",
+    ] {
+        assert!(pg()
+            .parse_sql_statements(&format!("CREATE OPERATOR + (FUNCTION = f, 
{field})"))
+            .is_err());
+    }
+
+    // Test missing FUNCTION/PROCEDURE error
+    assert!(pg()
+        .parse_sql_statements("CREATE OPERATOR + (LEFTARG = INT4)")
+        .is_err());
+
+    // Test empty parameter list error
+    assert!(pg().parse_sql_statements("CREATE OPERATOR + ()").is_err());
+
+    // Test nested empty parentheses error
+    assert!(pg().parse_sql_statements("CREATE OPERATOR > (()").is_err());
+    assert!(pg().parse_sql_statements("CREATE OPERATOR > ())").is_err());
+}
+
+#[test]
+fn parse_create_operator_family() {
+    for index_method in &["btree", "hash", "gist", "gin", "spgist", "brin"] {
+        assert_eq!(
+            pg().verified_stmt(&format!(
+                "CREATE OPERATOR FAMILY my_family USING {index_method}"
+            )),
+            Statement::CreateOperatorFamily(CreateOperatorFamily {
+                name: ObjectName::from(vec![Ident::new("my_family")]),
+                using: Ident::new(*index_method),
+            })
+        );
+        assert_eq!(
+            pg().verified_stmt(&format!(
+                "CREATE OPERATOR FAMILY myschema.test_family USING 
{index_method}"
+            )),
+            Statement::CreateOperatorFamily(CreateOperatorFamily {
+                name: ObjectName::from(vec![Ident::new("myschema"), 
Ident::new("test_family")]),
+                using: Ident::new(*index_method),
+            })
+        );
+    }
+}
+
+#[test]
+fn parse_create_operator_class() {
+    // Test all combinations of DEFAULT flag and FAMILY clause with different 
name qualifications
+    for (is_default, default_clause) in [(false, ""), (true, "DEFAULT ")] {
+        for (has_family, family_clause) in [(false, ""), (true, " FAMILY 
int4_family")] {
+            for (class_name, expected_name) in [
+                ("int4_ops", ObjectName::from(vec![Ident::new("int4_ops")])),
+                (
+                    "myschema.test_ops",
+                    ObjectName::from(vec![Ident::new("myschema"), 
Ident::new("test_ops")]),
+                ),
+            ] {
+                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) {
+                    Statement::CreateOperatorClass(CreateOperatorClass {
+                        name,
+                        default,
+                        ref for_type,
+                        ref using,
+                        ref family,
+                        ref items,
+                    }) => {
+                        assert_eq!(name, expected_name);
+                        assert_eq!(default, is_default);
+                        assert_eq!(for_type, &DataType::Int4(None));
+                        assert_eq!(using, &Ident::new("btree"));
+                        assert_eq!(
+                            family,
+                            &if has_family {
+                                
Some(ObjectName::from(vec![Ident::new("int4_family")]))
+                            } else {
+                                None
+                            }
+                        );
+                        assert_eq!(items.len(), 1);
+                    }
+                    _ => panic!("Expected CreateOperatorClass statement"),
+                }
+            }
+        }
+    }
+
+    // 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)") {
+        Statement::CreateOperatorClass(CreateOperatorClass {
+            name,
+            default: true,
+            ref for_type,
+            ref using,
+            ref family,
+            ref items,
+        }) => {
+            assert_eq!(name, 
ObjectName::from(vec![Ident::new("CAS_btree_ops")]));
+            assert_eq!(for_type, 
&DataType::Custom(ObjectName::from(vec![Ident::new("CAS")]), vec![]));
+            assert_eq!(using, &Ident::new("btree"));
+            assert_eq!(family, 
&Some(ObjectName::from(vec![Ident::new("CAS_btree_ops")])));
+            assert_eq!(items.len(), 6);
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // Test operator with argument types
+    match pg().verified_stmt(
+        "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING gist AS OPERATOR 1 
< (INT4, INT4)",
+    ) {
+        Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) 
=> {
+            assert_eq!(items.len(), 1);
+            match &items[0] {
+                OperatorClassItem::Operator {
+                    strategy_number: 1,
+                    ref operator_name,
+                    op_types:
+                        Some(OperatorArgTypes {
+                            left: DataType::Int4(None),
+                            right: DataType::Int4(None),
+                        }),
+                    purpose: None,
+                } => {
+                    assert_eq!(operator_name, 
&ObjectName::from(vec![Ident::new("<")]));
+                }
+                _ => panic!("Expected Operator item with arg types"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // Test operator FOR SEARCH
+    match pg().verified_stmt(
+        "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING gist AS OPERATOR 1 
< FOR SEARCH",
+    ) {
+        Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) 
=> {
+            assert_eq!(items.len(), 1);
+            match &items[0] {
+                OperatorClassItem::Operator {
+                    strategy_number: 1,
+                    ref operator_name,
+                    op_types: None,
+                    purpose: Some(OperatorPurpose::ForSearch),
+                } => {
+                    assert_eq!(operator_name, 
&ObjectName::from(vec![Ident::new("<")]));
+                }
+                _ => panic!("Expected Operator item FOR SEARCH"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // Test operator FOR ORDER BY
+    match pg().verified_stmt("CREATE OPERATOR CLASS test_ops FOR TYPE INT4 
USING gist AS OPERATOR 2 <<-> FOR ORDER BY float_ops") {
+        Statement::CreateOperatorClass(CreateOperatorClass {
+            ref items,
+            ..
+        }) => {
+            assert_eq!(items.len(), 1);
+            match &items[0] {
+                OperatorClassItem::Operator {
+                    strategy_number: 2,
+                    ref operator_name,
+                    op_types: None,
+                    purpose: Some(OperatorPurpose::ForOrderBy { ref 
sort_family }),
+                } => {
+                    assert_eq!(operator_name, 
&ObjectName::from(vec![Ident::new("<<->")]));
+                    assert_eq!(sort_family, 
&ObjectName::from(vec![Ident::new("float_ops")]));
+                }
+                _ => panic!("Expected Operator item FOR ORDER BY"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // 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)") {
+        Statement::CreateOperatorClass(CreateOperatorClass {
+            ref items,
+            ..
+        }) => {
+            assert_eq!(items.len(), 1);
+            match &items[0] {
+                OperatorClassItem::Function {
+                    support_number: 1,
+                    op_types: Some(_),
+                    ref function_name,
+                    ref argument_types,
+                } => {
+                    assert_eq!(function_name, 
&ObjectName::from(vec![Ident::new("btcmp")]));
+                    assert_eq!(argument_types.len(), 2);
+                }
+                _ => panic!("Expected Function item with op_types"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // Test function with no arguments (empty parentheses normalizes to no 
parentheses)
+    pg().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(
+        "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 
1 my_func",
+    ) {
+        Statement::CreateOperatorClass(CreateOperatorClass { ref items, .. }) 
=> {
+            assert_eq!(items.len(), 1);
+            match &items[0] {
+                OperatorClassItem::Function {
+                    support_number: 1,
+                    op_types: None,
+                    ref function_name,
+                    ref argument_types,
+                } => {
+                    assert_eq!(
+                        function_name,
+                        &ObjectName::from(vec![Ident::new("my_func")])
+                    );
+                    assert_eq!(argument_types.len(), 0);
+                }
+                _ => panic!("Expected Function item without op_types and no 
arguments"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // 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") {
+        Statement::CreateOperatorClass(CreateOperatorClass {
+            ref items,
+            ..
+        }) => {
+            assert_eq!(items.len(), 3);
+            // Check operator item
+            match &items[0] {
+                OperatorClassItem::Operator {
+                    strategy_number: 1,
+                    ref operator_name,
+                    ..
+                } => {
+                    assert_eq!(operator_name, 
&ObjectName::from(vec![Ident::new("<<")]));
+                }
+                _ => panic!("Expected Operator item"),
+            }
+            // Check function item
+            match &items[1] {
+                OperatorClassItem::Function {
+                    support_number: 1,
+                    ref function_name,
+                    ref argument_types,
+                    ..
+                } => {
+                    assert_eq!(function_name, 
&ObjectName::from(vec![Ident::new("gist_consistent")]));
+                    assert_eq!(argument_types.len(), 3);
+                }
+                _ => panic!("Expected Function item"),
+            }
+            // Check storage item
+            match &items[2] {
+                OperatorClassItem::Storage { ref storage_type } => {
+                    assert_eq!(storage_type, 
&DataType::Custom(ObjectName::from(vec![Ident::new("box")]), vec![]));
+                }
+                _ => panic!("Expected Storage item"),
+            }
+        }
+        _ => panic!("Expected CreateOperatorClass statement"),
+    }
+
+    // Test nested empty parentheses error in function arguments
+    assert!(pg()
+        .parse_sql_statements(
+            "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS 
FUNCTION 1 cas_cmp(()"
+        )
+        .is_err());
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to