From 04d8502677a85b2c166aabedf25c6bc94a039a0b Mon Sep 17 00:00:00 2001 From: Ramnivas Laddad Date: Wed, 18 Dec 2024 15:58:26 -0800 Subject: [PATCH 1/2] Adds support for pg DROP EXTENSION ``` DROP EXTENSION [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] ``` https://www.postgresql.org/docs/current/sql-dropextension.html --- src/ast/mod.rs | 24 ++++++++++++++++++++++++ src/ast/spans.rs | 1 + src/parser/mod.rs | 20 +++++++++++++++++++- tests/sqlparser_postgres.rs | 16 ++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6e3f20472..29afd8e19 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2759,6 +2759,15 @@ pub enum Statement { version: Option, }, /// ```sql + /// DROP EXTENSION [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + /// ``` + DropExtension { + names: Vec, + if_exists: bool, + /// `CASCADE` or `RESTRICT` + cascade_or_restrict: Option, + }, + /// ```sql /// FETCH /// ``` /// Retrieve rows from a query using a cursor @@ -4025,6 +4034,21 @@ impl fmt::Display for Statement { Ok(()) } + Statement::DropExtension { + names, + if_exists, + cascade_or_restrict, + } => { + write!(f, "DROP EXTENSION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", display_comma_separated(names))?; + if let Some(cascade_or_restrict) = cascade_or_restrict { + write!(f, " {cascade_or_restrict}")?; + } + Ok(()) + } Statement::CreateRole { names, if_not_exists, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index c2c7c14f0..b46649860 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -429,6 +429,7 @@ impl Spanned for Statement { Statement::DropSecret { .. } => Span::empty(), Statement::Declare { .. } => Span::empty(), Statement::CreateExtension { .. } => Span::empty(), + Statement::DropExtension { .. } => Span::empty(), Statement::Fetch { .. } => Span::empty(), Statement::Flush { .. } => Span::empty(), Statement::Discard { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 94d63cf80..ef18ca3e2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5196,9 +5196,11 @@ impl<'a> Parser<'a> { return self.parse_drop_secret(temporary, persistent); } else if self.parse_keyword(Keyword::TRIGGER) { return self.parse_drop_trigger(); + } else if self.parse_keyword(Keyword::EXTENSION) { + return self.parse_drop_extension(); } else { return self.expected( - "TABLE, VIEW, INDEX, ROLE, SCHEMA, DATABASE, FUNCTION, PROCEDURE, STAGE, TRIGGER, SECRET, SEQUENCE, or TYPE after DROP", + "TABLE, VIEW, INDEX, ROLE, SCHEMA, DATABASE, FUNCTION, PROCEDURE, STAGE, TRIGGER, SECRET, SEQUENCE, TYPE, or EXTENSION after DROP", self.peek_token(), ); }; @@ -5861,6 +5863,22 @@ impl<'a> Parser<'a> { }) } + pub fn parse_drop_extension(&mut self) -> Result { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let names = self.parse_comma_separated(|p| p.parse_identifier(false))?; + let cascade_or_restrict = + self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]); + Ok(Statement::DropExtension { + names, + if_exists, + cascade_or_restrict: cascade_or_restrict.map(|k| match k { + Keyword::CASCADE => ReferentialAction::Cascade, + Keyword::RESTRICT => ReferentialAction::Restrict, + _ => unreachable!(), + }), + }) + } + //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 aaf4e65db..832581277 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -662,6 +662,22 @@ fn parse_create_extension() { .verified_stmt("CREATE EXTENSION extension_name WITH SCHEMA schema_name VERSION version"); } +#[test] +fn parse_drop_extension() { + pg_and_generic().verified_stmt("DROP EXTENSION extension_name"); + pg_and_generic().verified_stmt("DROP EXTENSION extension_name CASCADE"); + pg_and_generic().verified_stmt("DROP EXTENSION extension_name RESTRICT"); + pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 CASCADE"); + pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 RESTRICT"); + + pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name"); + pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name CASCADE"); + pg_and_generic() + .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 CASCADE"); + pg_and_generic() + .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 RESTRICT"); +} + #[test] fn parse_alter_table_alter_column() { pg().one_statement_parses_to( From ccf298a433f2ecaf3df7adc7c88cf2b193d4439c Mon Sep 17 00:00:00 2001 From: Ramnivas Laddad Date: Tue, 24 Dec 2024 10:20:23 -0800 Subject: [PATCH 2/2] Address review comments --- src/ast/mod.rs | 3 ++ src/parser/mod.rs | 13 +++-- tests/sqlparser_postgres.rs | 102 +++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 29afd8e19..992738568 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2760,6 +2760,9 @@ pub enum Statement { }, /// ```sql /// DROP EXTENSION [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + /// + /// Note: this is a PostgreSQL-specific statement. + /// https://www.postgresql.org/docs/current/sql-dropextension.html /// ``` DropExtension { names: Vec, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ef18ca3e2..f0e8a4f9e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5863,6 +5863,7 @@ impl<'a> Parser<'a> { }) } + /// Parse a PostgreSQL-specific [Statement::DropExtension] statement. pub fn parse_drop_extension(&mut self) -> Result { let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let names = self.parse_comma_separated(|p| p.parse_identifier(false))?; @@ -5871,11 +5872,13 @@ impl<'a> Parser<'a> { Ok(Statement::DropExtension { names, if_exists, - cascade_or_restrict: cascade_or_restrict.map(|k| match k { - Keyword::CASCADE => ReferentialAction::Cascade, - Keyword::RESTRICT => ReferentialAction::Restrict, - _ => unreachable!(), - }), + cascade_or_restrict: cascade_or_restrict + .map(|k| match k { + Keyword::CASCADE => Ok(ReferentialAction::Cascade), + Keyword::RESTRICT => Ok(ReferentialAction::Restrict), + _ => self.expected("CASCADE or RESTRICT", self.peek_token()), + }) + .transpose()?, }) } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 832581277..79d77d4d7 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -664,18 +664,96 @@ fn parse_create_extension() { #[test] fn parse_drop_extension() { - pg_and_generic().verified_stmt("DROP EXTENSION extension_name"); - pg_and_generic().verified_stmt("DROP EXTENSION extension_name CASCADE"); - pg_and_generic().verified_stmt("DROP EXTENSION extension_name RESTRICT"); - pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 CASCADE"); - pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 RESTRICT"); - - pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name"); - pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name CASCADE"); - pg_and_generic() - .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 CASCADE"); - pg_and_generic() - .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 RESTRICT"); + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION extension_name"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: false, + cascade_or_restrict: None, + } + ); + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION extension_name CASCADE"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: false, + cascade_or_restrict: Some(ReferentialAction::Cascade), + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION extension_name RESTRICT"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: false, + cascade_or_restrict: Some(ReferentialAction::Restrict), + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 CASCADE"), + Statement::DropExtension { + names: vec!["extension_name".into(), "extension_name2".into()], + if_exists: false, + cascade_or_restrict: Some(ReferentialAction::Cascade), + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION extension_name, extension_name2 RESTRICT"), + Statement::DropExtension { + names: vec!["extension_name".into(), "extension_name2".into()], + if_exists: false, + cascade_or_restrict: Some(ReferentialAction::Restrict), + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: true, + cascade_or_restrict: None, + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name CASCADE"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: true, + cascade_or_restrict: Some(ReferentialAction::Cascade), + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP EXTENSION IF EXISTS extension_name RESTRICT"), + Statement::DropExtension { + names: vec!["extension_name".into()], + if_exists: true, + cascade_or_restrict: Some(ReferentialAction::Restrict), + } + ); + + assert_eq!( + pg_and_generic() + .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 CASCADE"), + Statement::DropExtension { + names: vec!["extension_name1".into(), "extension_name2".into()], + if_exists: true, + cascade_or_restrict: Some(ReferentialAction::Cascade), + } + ); + + assert_eq!( + pg_and_generic() + .verified_stmt("DROP EXTENSION IF EXISTS extension_name1, extension_name2 RESTRICT"), + Statement::DropExtension { + names: vec!["extension_name1".into(), "extension_name2".into()], + if_exists: true, + cascade_or_restrict: Some(ReferentialAction::Restrict), + } + ); } #[test]