diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 3a38fbaae..6f5010f04 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4188,3 +4188,62 @@ impl fmt::Display for OperatorPurpose { } } } + +/// `DROP 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 DropOperator { + /// `IF EXISTS` clause + pub if_exists: bool, + /// One or more operators to drop with their signatures + pub operators: Vec, + /// `CASCADE or RESTRICT` + pub drop_behavior: Option, +} + +/// Operator signature for a `DROP OPERATOR` statement +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct DropOperatorSignature { + /// Operator name + pub name: ObjectName, + /// Left operand type + pub left_type: Option, + /// Right operand type + pub right_type: DataType, +} + +impl fmt::Display for DropOperatorSignature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} (", self.name)?; + if let Some(left_type) = &self.left_type { + write!(f, "{}", left_type)?; + } else { + write!(f, "NONE")?; + } + write!(f, ", {})", self.right_type) + } +} + +impl fmt::Display for DropOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DROP OPERATOR")?; + if self.if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", display_comma_separated(&self.operators))?; + if let Some(drop_behavior) = &self.drop_behavior { + write!(f, " {}", drop_behavior)?; + } + Ok(()) + } +} + +impl Spanned for DropOperator { + fn span(&self) -> Span { + Span::empty() + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 482c38132..4b9eea6f5 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -67,14 +67,15 @@ pub use self::ddl::{ ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, 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, - UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, + DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorSignature, 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, UserDefinedTypeSqlDefinitionOption, + UserDefinedTypeStorage, ViewColumnDef, }; pub use self::dml::{Delete, Insert, Update}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -3573,6 +3574,12 @@ pub enum Statement { /// DropExtension(DropExtension), /// ```sql + /// DROP OPERATOR [ IF EXISTS ] name ( { left_type | NONE } , right_type ) [, ...] [ CASCADE | RESTRICT ] + /// ``` + /// Note: this is a PostgreSQL-specific statement. + /// + DropOperator(DropOperator), + /// ```sql /// FETCH /// ``` /// Retrieve rows from a query using a cursor @@ -4835,6 +4842,7 @@ impl fmt::Display for Statement { Statement::CreateIndex(create_index) => create_index.fmt(f), Statement::CreateExtension(create_extension) => write!(f, "{create_extension}"), Statement::DropExtension(drop_extension) => write!(f, "{drop_extension}"), + Statement::DropOperator(drop_operator) => write!(f, "{drop_operator}"), 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 cfaaf8f09..8876e3697 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -366,6 +366,7 @@ impl Spanned for Statement { Statement::CreateRole(create_role) => create_role.span(), Statement::CreateExtension(create_extension) => create_extension.span(), Statement::DropExtension(drop_extension) => drop_extension.span(), + Statement::DropOperator(drop_operator) => drop_operator.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 f835f5417..e0c317cd2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6767,9 +6767,11 @@ impl<'a> Parser<'a> { return self.parse_drop_trigger(); } else if self.parse_keyword(Keyword::EXTENSION) { return self.parse_drop_extension(); + } else if self.parse_keyword(Keyword::OPERATOR) { + return self.parse_drop_operator(); } else { return self.expected( - "CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP", + "CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP", self.peek_token(), ); }; @@ -7525,6 +7527,46 @@ impl<'a> Parser<'a> { })) } + /// Parse a[Statement::DropOperator] statement. + /// + pub fn parse_drop_operator(&mut self) -> Result { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let operators = self.parse_comma_separated(|p| p.parse_drop_operator_signature())?; + let drop_behavior = self.parse_optional_drop_behavior(); + Ok(Statement::DropOperator(DropOperator { + if_exists, + operators, + drop_behavior, + })) + } + + /// Parse an operator signature for a [Statement::DropOperator] + /// Format: `name ( { left_type | NONE } , right_type )` + fn parse_drop_operator_signature(&mut self) -> Result { + let name = self.parse_operator_name()?; + self.expect_token(&Token::LParen)?; + + // Parse left operand type (or NONE for prefix operators) + let left_type = if self.parse_keyword(Keyword::NONE) { + None + } else { + Some(self.parse_data_type()?) + }; + + self.expect_token(&Token::Comma)?; + + // Parse right operand type (always required) + let right_type = self.parse_data_type()?; + + self.expect_token(&Token::RParen)?; + + Ok(DropOperatorSignature { + name, + left_type, + right_type, + }) + } + //TODO: Implement parsing for Skewed pub fn parse_hive_distribution(&mut self) -> Result { if self.parse_keywords(&[Keyword::PARTITIONED, Keyword::BY]) { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index fbfa66588..c6ac83ce0 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6763,6 +6763,94 @@ fn parse_create_operator() { assert!(pg().parse_sql_statements("CREATE OPERATOR > ())").is_err()); } +#[test] +fn parse_drop_operator() { + use sqlparser::ast::{DataType, DropBehavior, DropOperator, DropOperatorSignature}; + + // Test DROP OPERATOR with NONE for prefix operator + let sql = "DROP OPERATOR ~ (NONE, BIT)"; + assert_eq!( + pg().verified_stmt(sql), + Statement::DropOperator(DropOperator { + if_exists: false, + operators: vec![DropOperatorSignature { + name: ObjectName::from(vec![Ident::new("~")]), + left_type: None, + right_type: DataType::Bit(None), + }], + drop_behavior: None, + }) + ); + + for if_exist in [true, false] { + for cascading in [ + None, + Some(DropBehavior::Cascade), + Some(DropBehavior::Restrict), + ] { + for op in &["<", ">", "<=", ">=", "<>", "||", "&&", "<<", ">>"] { + let sql = format!( + "DROP OPERATOR{} {op} (INTEGER, INTEGER){}", + if if_exist { " IF EXISTS" } else { "" }, + match cascading { + Some(cascading) => format!(" {cascading}"), + None => String::new(), + } + ); + assert_eq!( + pg().verified_stmt(&sql), + Statement::DropOperator(DropOperator { + if_exists: if_exist, + operators: vec![DropOperatorSignature { + name: ObjectName::from(vec![Ident::new(*op)]), + left_type: Some(DataType::Integer(None)), + right_type: DataType::Integer(None), + }], + drop_behavior: cascading, + }) + ); + } + } + } + + // Test DROP OPERATOR with schema-qualified operator name + let sql = "DROP OPERATOR myschema.@@ (TEXT, TEXT)"; + assert_eq!( + pg().verified_stmt(sql), + Statement::DropOperator(DropOperator { + if_exists: false, + operators: vec![DropOperatorSignature { + name: ObjectName::from(vec![Ident::new("myschema"), Ident::new("@@")]), + left_type: Some(DataType::Text), + right_type: DataType::Text, + }], + drop_behavior: None, + }) + ); + + // Test DROP OPERATOR with multiple operators, IF EXISTS and CASCADE + let sql = "DROP OPERATOR IF EXISTS + (INTEGER, INTEGER), - (INTEGER, INTEGER) CASCADE"; + assert_eq!( + pg().verified_stmt(sql), + Statement::DropOperator(DropOperator { + if_exists: true, + operators: vec![ + DropOperatorSignature { + name: ObjectName::from(vec![Ident::new("+")]), + left_type: Some(DataType::Integer(None)), + right_type: DataType::Integer(None), + }, + DropOperatorSignature { + name: ObjectName::from(vec![Ident::new("-")]), + left_type: Some(DataType::Integer(None)), + right_type: DataType::Integer(None), + } + ], + drop_behavior: Some(DropBehavior::Cascade), + }) + ); +} + #[test] fn parse_create_operator_family() { for index_method in &["btree", "hash", "gist", "gin", "spgist", "brin"] {