From 2c06645716f0dea7baf6e9e688268f22b0e1918e Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 7 Aug 2026 08:20:23 +0200 Subject: [PATCH] Add support for PostgreSQL DROP RULE --- src/ast/ddl.rs | 44 +++++++++++++++++++++++++++++++++++++++ src/ast/mod.rs | 8 ++++++- src/ast/spans.rs | 1 + src/parser/mod.rs | 24 ++++++++++++++++++++- tests/sqlparser_common.rs | 31 +++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index a0e69ad8a..707099751 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -5994,3 +5994,47 @@ impl From for crate::ast::Statement { crate::ast::Statement::AlterPolicy(v) } } + +/// DROP RULE statement. +/// +/// ```sql +/// DROP RULE [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ] +/// ``` +/// +/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-droprule.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 DropRule { + /// `true` when `IF EXISTS` was present. + pub if_exists: bool, + /// Name of the rule to drop. + pub name: Ident, + /// Name of the table the rule applies to. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + pub table_name: ObjectName, + /// Optional drop behavior (`CASCADE` or `RESTRICT`). + pub drop_behavior: Option, +} + +impl fmt::Display for DropRule { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "DROP RULE {if_exists}{name} ON {table_name}", + if_exists = if self.if_exists { "IF EXISTS " } else { "" }, + name = self.name, + table_name = self.table_name + )?; + if let Some(behavior) = &self.drop_behavior { + write!(f, " {behavior}")?; + } + Ok(()) + } +} + +impl From for crate::ast::Statement { + fn from(v: DropRule) -> Self { + crate::ast::Statement::DropRule(v) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8a9a67a74..7b085784b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -76,7 +76,7 @@ pub use self::ddl::{ CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTextSearch, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, - DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, + DropOperatorFamily, DropOperatorSignature, DropPolicy, DropRule, DropTrigger, ForValues, FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, @@ -4018,6 +4018,11 @@ pub enum Statement { /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-droppolicy.html) DropPolicy(DropPolicy), /// ```sql + /// DROP RULE + /// ``` + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-droprule.html) + DropRule(DropRule), + /// ```sql /// DROP CONNECTOR /// ``` /// See [Hive](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362034#LanguageManualDDL-DropConnector) @@ -5797,6 +5802,7 @@ impl fmt::Display for Statement { Ok(()) } Statement::DropPolicy(policy) => write!(f, "{policy}"), + Statement::DropRule(rule) => write!(f, "{rule}"), Statement::DropConnector { if_exists, name } => { write!( f, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a34fe66d9..a55a2b0ef 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -489,6 +489,7 @@ impl Spanned for Statement { Statement::AlterPolicy { .. } => Span::empty(), Statement::AlterConnector { .. } => Span::empty(), Statement::DropPolicy { .. } => Span::empty(), + Statement::DropRule { .. } => Span::empty(), Statement::DropConnector { .. } => Span::empty(), Statement::ShowCatalogs { .. } => Span::empty(), Statement::ShowDatabases { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b2b3f42bb..2cf9e842c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7607,6 +7607,8 @@ impl<'a> Parser<'a> { return self.parse_drop_function().map(Into::into); } else if self.parse_keyword(Keyword::POLICY) { return self.parse_drop_policy().map(Into::into); + } else if self.parse_keyword(Keyword::RULE) { + return self.parse_drop_rule().map(Into::into); } else if self.parse_keyword(Keyword::CONNECTOR) { return self.parse_drop_connector(); } else if self.parse_keyword(Keyword::DOMAIN) { @@ -7630,7 +7632,7 @@ impl<'a> Parser<'a> { }; } else { return self.expected_ref( - "COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW, USER or WAREHOUSE after DROP", + "COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, RULE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW, USER or WAREHOUSE after DROP", self.peek_token_ref(), ); }; @@ -7710,6 +7712,26 @@ impl<'a> Parser<'a> { drop_behavior, }) } + + /// ```sql + /// DROP RULE [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ] + /// ``` + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-droprule.html) + fn parse_drop_rule(&mut self) -> Result { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_identifier()?; + self.expect_keyword_is(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; + let drop_behavior = self.parse_optional_drop_behavior(); + Ok(DropRule { + if_exists, + name, + table_name, + drop_behavior, + }) + } + /// ```sql /// DROP CONNECTOR [IF EXISTS] name /// ``` diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 0800bc41f..517dbb0a2 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -19679,3 +19679,34 @@ fn parse_function_arg_call_chain_no_exponential_blowup() { rx.recv_timeout(Duration::from_secs(5)) .expect("parser should reject this quickly, not loop exponentially"); } + +#[test] +fn parse_drop_rule() { + let sql = "DROP RULE r ON t"; + match all_dialects().verified_stmt(sql) { + Statement::DropRule(DropRule { + if_exists, + name, + table_name, + drop_behavior, + }) => { + assert!(!if_exists); + assert_eq!(name.to_string(), "r"); + assert_eq!(table_name.to_string(), "t"); + assert_eq!(drop_behavior, None); + } + _ => unreachable!("Expected DROP RULE"), + } + + all_dialects().verified_stmt("DROP RULE IF EXISTS r ON s1.t CASCADE"); + all_dialects().verified_stmt("DROP RULE r ON t RESTRICT"); + + // the table name is mandatory + assert_eq!( + all_dialects() + .parse_sql_statements("DROP RULE r") + .unwrap_err() + .to_string(), + "sql parser error: Expected: ON, found: EOF" + ); +}