From d6a6a440a3afe3cf0bfb8ff1840f0c21b8139ba1 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 16:15:16 +0100 Subject: [PATCH 1/4] Added support for CREATE OPERATOR * statements --- src/ast/ddl.rs | 230 ++++++++++++++++++++++++++++ src/ast/mod.rs | 44 ++++-- src/ast/spans.rs | 26 +++- src/keywords.rs | 2 + src/parser/mod.rs | 289 ++++++++++++++++++++++++++++++++++++ tests/sqlparser_postgres.rs | 116 +++++++++++++++ 6 files changed, 692 insertions(+), 15 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index fd481213f..f9554dce0 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3613,3 +3613,233 @@ impl Spanned for DropFunction { Span::empty() } } + +/// CREATE OPERATOR statement +/// See +#[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, + /// RIGHTARG parameter (right operand type) + pub right_arg: Option, + /// COMMUTATOR parameter (commutator operator) + pub commutator: Option, + /// NEGATOR parameter (negator operator) + pub negator: Option, + /// RESTRICT parameter (restriction selectivity function) + pub restrict: Option, + /// JOIN parameter (join selectivity function) + pub join: Option, + /// HASHES flag + pub hashes: bool, + /// MERGES flag + pub merges: bool, +} + +/// CREATE OPERATOR FAMILY statement +/// See +#[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 +#[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, + /// List of operator class items (operators, functions, storage) + pub items: Vec, +} + +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, + /// FOR SEARCH or FOR ORDER BY + purpose: Option, + }, + /// FUNCTION clause + Function { + support_number: u32, + /// Optional function argument types for the operator class + op_types: Option>, + function_name: ObjectName, + /// Function argument types + argument_types: Vec, + }, + /// 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 176d36545..90acf429b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -65,13 +65,14 @@ pub use self::ddl::{ 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, ProcedureParam, ReferentialAction, RenameTableNameKind, - ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, + 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, UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{Delete, Insert, Update}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -2787,10 +2788,11 @@ impl fmt::Display for Declare { } /// Sql options of a `CREATE TABLE` statement. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum CreateTableOptions { + #[default] None, /// Options specified using the `WITH` keyword. /// e.g. `WITH (description = "123")` @@ -2819,12 +2821,6 @@ pub enum CreateTableOptions { TableProperties(Vec), } -impl Default for CreateTableOptions { - fn default() -> Self { - Self::None - } -} - impl fmt::Display for CreateTableOptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -3340,6 +3336,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), @@ -4886,6 +4897,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 7d2a00095..5f95e7513 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; @@ -367,6 +368,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 { @@ -2536,3 +2542,21 @@ ALTER TABLE users assert_eq!(stmt_span.end, (4, 11).into()); } } + +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() + } +} diff --git a/src/keywords.rs b/src/keywords.rs index 319c57827..d69848944 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -194,6 +194,7 @@ define_keywords!( CHECK, CHECKSUM, CIRCLE, + CLASS, CLEANPATH, CLEAR, CLOB, @@ -381,6 +382,7 @@ define_keywords!( FAIL, FAILOVER, FALSE, + FAMILY, FETCH, FIELDS, FILE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9a01e510b..6b86b9b91 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4791,6 +4791,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 { @@ -6421,6 +6430,286 @@ impl<'a> Parser<'a> { })) } + /// Helper function to parse an operator name (which can contain special characters) + /// Operator names can be schema-qualified (e.g., schema.operator) + fn parse_operator_name(&mut self) -> Result { + let mut name_parts = vec![]; + loop { + let token = self.next_token(); + let part = ObjectNamePart::Identifier(Ident::new(token.to_string())); + name_parts.push(part); + + if !self.consume_token(&Token::Period) { + break; + } + } + Ok(ObjectName(name_parts)) + } + + /// Parse a CREATE OPERATOR statement + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createoperator.html) + pub fn parse_create_operator(&mut self) -> Result { + // Parse the operator name (can be schema-qualified) + // Operators can contain special characters like +, -, *, /, <, >, =, ~, !, @, #, %, ^, &, |, `, ? + // See https://www.postgresql.org/docs/current/sql-createoperator.html + let name = self.parse_operator_name()?; + + // Expect opening parenthesis + self.expect_token(&Token::LParen)?; + + let mut function: Option = None; + let mut is_procedure = false; + let mut left_arg: Option = None; + let mut right_arg: Option = None; + let mut commutator: Option = None; + let mut negator: Option = None; + let mut restrict: Option = None; + let mut join: Option = None; + let mut hashes = false; + let mut merges = false; + + loop { + // Parse parameter name + let param_name = self.parse_identifier()?; + let param_name_upper = param_name.value.to_uppercase(); + + // Check if this is a flag (HASHES or MERGES) - no '=' expected + match param_name_upper.as_str() { + "HASHES" => { + hashes = true; + } + "MERGES" => { + merges = true; + } + "FUNCTION" | "PROCEDURE" => { + self.expect_token(&Token::Eq)?; + let func_name = self.parse_object_name(false)?; + function = Some(func_name); + is_procedure = param_name_upper == "PROCEDURE"; + } + "LEFTARG" => { + self.expect_token(&Token::Eq)?; + let data_type = self.parse_data_type()?; + left_arg = Some(data_type); + } + "RIGHTARG" => { + self.expect_token(&Token::Eq)?; + let data_type = self.parse_data_type()?; + right_arg = Some(data_type); + } + "COMMUTATOR" => { + self.expect_token(&Token::Eq)?; + let op_name = if self.parse_keyword(Keyword::OPERATOR) { + self.expect_token(&Token::LParen)?; + let op = self.parse_operator_name()?; + self.expect_token(&Token::RParen)?; + op + } else { + self.parse_operator_name()? + }; + commutator = Some(op_name); + } + "NEGATOR" => { + self.expect_token(&Token::Eq)?; + let op_name = if self.parse_keyword(Keyword::OPERATOR) { + self.expect_token(&Token::LParen)?; + let op = self.parse_operator_name()?; + self.expect_token(&Token::RParen)?; + op + } else { + self.parse_operator_name()? + }; + negator = Some(op_name); + } + "RESTRICT" => { + self.expect_token(&Token::Eq)?; + let func_name = self.parse_object_name(false)?; + restrict = Some(func_name); + } + "JOIN" => { + self.expect_token(&Token::Eq)?; + let func_name = self.parse_object_name(false)?; + join = Some(func_name); + } + _ => { + return Err(ParserError::ParserError(format!( + "Unknown CREATE OPERATOR parameter: {}", + param_name_upper + ))) + } + } + + // Check for comma or closing parenthesis + 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 CREATE OPERATOR FAMILY statement + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createopfamily.html) + pub fn parse_create_operator_family(&mut self) -> Result { + 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 CREATE OPERATOR CLASS statement + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createopclass.html) + pub fn parse_create_operator_class(&mut self) -> Result { + 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 { // 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 9ba0fb978..74fc3f631 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6650,3 +6650,119 @@ fn parse_foreign_key_match_with_actions() { pg_and_generic().verified_stmt(sql); } + +#[test] +fn parse_create_operator() { + // Test a basic CREATE OPERATOR statement + let sql = "CREATE OPERATOR < (PROCEDURE = \"cas_lt\", LEFTARG = CAS, RIGHTARG = CAS, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel)"; + let statement = pg().verified_stmt(sql); + + // Verify the parsed statement + match statement { + Statement::CreateOperator(CreateOperator { + name, + function, + is_procedure, + left_arg, + right_arg, + commutator, + negator, + restrict, + join, + hashes, + merges, + }) => { + assert_eq!(name.to_string(), "<"); + assert_eq!(function.to_string(), "\"cas_lt\""); + assert!(is_procedure); + assert_eq!(left_arg.as_ref().unwrap().to_string(), "CAS"); + assert_eq!(right_arg.as_ref().unwrap().to_string(), "CAS"); + assert_eq!(commutator.as_ref().unwrap().to_string(), ">"); + assert_eq!(negator.as_ref().unwrap().to_string(), ">="); + assert_eq!(restrict.as_ref().unwrap().to_string(), "scalarltsel"); + assert_eq!(join.as_ref().unwrap().to_string(), "scalarltjoinsel"); + assert!(!hashes); + assert!(!merges); + } + _ => panic!("Expected Statement::CreateOperator, got {:?}", statement), + } +} + +#[test] +fn parse_create_operator_family() { + let sql = "CREATE OPERATOR FAMILY CAS_btree_ops USING btree"; + let statement = pg().verified_stmt(sql); + + match statement { + Statement::CreateOperatorFamily(CreateOperatorFamily { name, using }) => { + assert_eq!(name.to_string(), "CAS_btree_ops"); + assert_eq!(using.to_string(), "btree"); + } + _ => panic!( + "Expected Statement::CreateOperatorFamily, got {:?}", + statement + ), + } +} + +#[test] +fn parse_create_operator_class() { + let sql = "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)"; + let statement = pg().verified_stmt(sql); + + match statement { + Statement::CreateOperatorClass(CreateOperatorClass { + name, + default, + for_type, + using, + family, + items, + }) => { + assert_eq!(name.to_string(), "CAS_btree_ops"); + assert!(default); + assert_eq!(for_type.to_string(), "CAS"); + assert_eq!(using.to_string(), "btree"); + assert_eq!(family.unwrap().to_string(), "CAS_btree_ops"); + assert_eq!(items.len(), 6); + + // Check first operator + match &items[0] { + OperatorClassItem::Operator { + strategy_number, + operator_name, + op_types, + purpose, + } => { + assert_eq!(*strategy_number, 1); + assert_eq!(operator_name.to_string(), "<"); + assert!(op_types.is_none()); + assert!(purpose.is_none()); + } + _ => panic!("Expected Operator"), + } + + // Check function + match &items[5] { + OperatorClassItem::Function { + support_number, + op_types, + function_name, + argument_types, + } => { + assert_eq!(*support_number, 1); + assert!(op_types.is_none()); + assert_eq!(function_name.to_string(), "cas_cmp"); + assert_eq!(argument_types.len(), 2); + assert_eq!(argument_types[0].to_string(), "CAS"); + assert_eq!(argument_types[1].to_string(), "CAS"); + } + _ => panic!("Expected Function"), + } + } + _ => panic!( + "Expected Statement::CreateOperatorClass, got {:?}", + statement + ), + } +} From 535870b14e97fc2bd239c4d5a851da5bb88ff988 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 16:26:17 +0100 Subject: [PATCH 2/4] Refactored parsing to using keywords instead of strings --- src/keywords.rs | 8 ++++++++ src/parser/mod.rs | 45 +++++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/keywords.rs b/src/keywords.rs index d69848944..0426f9f25 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -214,6 +214,7 @@ define_keywords!( COMMENT, COMMIT, COMMITTED, + COMMUTATOR, COMPATIBLE, COMPRESSION, COMPUPDATE, @@ -444,6 +445,7 @@ define_keywords!( GROUPS, GZIP, HASH, + HASHES, HAVING, HEADER, HEAP, @@ -536,7 +538,10 @@ define_keywords!( LATERAL, LEAD, LEADING, + LEAKPROOF, + LEAST, LEFT, + LEFTARG, LEVEL, LIKE, LIKE_REGEX, @@ -590,6 +595,7 @@ define_keywords!( MEDIUMTEXT, MEMBER, MERGE, + MERGES, MESSAGE, METADATA, METHOD, @@ -627,6 +633,7 @@ define_keywords!( NATURAL, NCHAR, NCLOB, + NEGATOR, NEST, NESTED, NETWORK, @@ -834,6 +841,7 @@ define_keywords!( RETURNS, REVOKE, RIGHT, + RIGHTARG, RLIKE, RM, ROLE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6b86b9b91..2831d1461 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6470,35 +6470,45 @@ impl<'a> Parser<'a> { let mut merges = false; loop { - // Parse parameter name - let param_name = self.parse_identifier()?; - let param_name_upper = param_name.value.to_uppercase(); + // Parse parameter name as keyword + 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, + ])?; // Check if this is a flag (HASHES or MERGES) - no '=' expected - match param_name_upper.as_str() { - "HASHES" => { + match keyword { + Keyword::HASHES => { hashes = true; } - "MERGES" => { + Keyword::MERGES => { merges = true; } - "FUNCTION" | "PROCEDURE" => { + Keyword::FUNCTION | Keyword::PROCEDURE => { self.expect_token(&Token::Eq)?; let func_name = self.parse_object_name(false)?; function = Some(func_name); - is_procedure = param_name_upper == "PROCEDURE"; + is_procedure = keyword == Keyword::PROCEDURE; } - "LEFTARG" => { + Keyword::LEFTARG => { self.expect_token(&Token::Eq)?; let data_type = self.parse_data_type()?; left_arg = Some(data_type); } - "RIGHTARG" => { + Keyword::RIGHTARG => { self.expect_token(&Token::Eq)?; let data_type = self.parse_data_type()?; right_arg = Some(data_type); } - "COMMUTATOR" => { + Keyword::COMMUTATOR => { self.expect_token(&Token::Eq)?; let op_name = if self.parse_keyword(Keyword::OPERATOR) { self.expect_token(&Token::LParen)?; @@ -6510,7 +6520,7 @@ impl<'a> Parser<'a> { }; commutator = Some(op_name); } - "NEGATOR" => { + Keyword::NEGATOR => { self.expect_token(&Token::Eq)?; let op_name = if self.parse_keyword(Keyword::OPERATOR) { self.expect_token(&Token::LParen)?; @@ -6522,22 +6532,17 @@ impl<'a> Parser<'a> { }; negator = Some(op_name); } - "RESTRICT" => { + Keyword::RESTRICT => { self.expect_token(&Token::Eq)?; let func_name = self.parse_object_name(false)?; restrict = Some(func_name); } - "JOIN" => { + Keyword::JOIN => { self.expect_token(&Token::Eq)?; let func_name = self.parse_object_name(false)?; join = Some(func_name); } - _ => { - return Err(ParserError::ParserError(format!( - "Unknown CREATE OPERATOR parameter: {}", - param_name_upper - ))) - } + _ => unreachable!("unexpected keyword in CREATE OPERATOR"), } // Check for comma or closing parenthesis From c317547f7a8e224168ef73cf2667b21dc36ed685 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 16:29:15 +0100 Subject: [PATCH 3/4] Fixed no-std error --- src/ast/ddl.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index f9554dce0..bab375022 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")] From 994344dcf6f2e43807882a0ce577380bdf5d2f21 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 16:31:06 +0100 Subject: [PATCH 4/4] Fixed clippy code smell --- src/ast/spans.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 5f95e7513..749aeac4c 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2351,6 +2351,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}; @@ -2542,21 +2560,3 @@ ALTER TABLE users assert_eq!(stmt_span.end, (4, 11).into()); } } - -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() - } -}