From b7740723c499f811d025465ff89fc37ee7d1089d Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 24 Oct 2026 12:33:15 +0200 Subject: [PATCH 1/3] Parse PostgreSQL DROP GROUP statements --- src/ast/mod.rs | 3 +++ src/dialect/mod.rs | 5 ++++ src/dialect/postgresql.rs | 4 +++ src/parser/mod.rs | 10 +++++--- tests/sqlparser_postgres.rs | 50 +++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 87618bbc5f..2cfbb0687e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8785,6 +8785,8 @@ pub enum ObjectType { Database, /// A role. Role, + /// A user group. + Group, /// A sequence. Sequence, /// A stage. @@ -8810,6 +8812,7 @@ impl fmt::Display for ObjectType { ObjectType::Schema => "SCHEMA", ObjectType::Database => "DATABASE", ObjectType::Role => "ROLE", + ObjectType::Group => "GROUP", ObjectType::Sequence => "SEQUENCE", ObjectType::Stage => "STAGE", ObjectType::Type => "TYPE", diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 5cfff36965..a884e69069 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -423,6 +423,11 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `DROP GROUP`. + fn supports_drop_group(&self) -> bool { + false + } + /// Returns true if the dialect treats `ALTER USER` as a synonym for `ALTER ROLE`. /// /// In PostgreSQL, `ALTER USER` and `ALTER ROLE` are synonyms that accept the same diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index e8202cfdac..485b9978aa 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -351,6 +351,10 @@ impl Dialect for PostgreSqlDialect { true } + fn supports_drop_group(&self) -> bool { + true + } + fn supports_alter_user_as_alter_role(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5ecd6e437a..1a528655bb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8089,6 +8089,8 @@ impl<'a> Parser<'a> { ObjectType::Index } else if self.parse_keyword(Keyword::ROLE) { ObjectType::Role + } else if self.dialect.supports_drop_group() && self.parse_keyword(Keyword::GROUP) { + ObjectType::Group } else if self.parse_keyword(Keyword::SCHEMA) { ObjectType::Schema } else if self.parse_keyword(Keyword::DATABASE) { @@ -8132,7 +8134,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, GROUP, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW, USER or WAREHOUSE after DROP", self.peek_token_ref(), ); }; @@ -8148,9 +8150,11 @@ impl<'a> Parser<'a> { if cascade && restrict { return parser_err!("Cannot specify both CASCADE and RESTRICT in DROP", loc); } - if object_type == ObjectType::Role && (cascade || restrict || purge) { + if matches!(object_type, ObjectType::Role | ObjectType::Group) + && (cascade || restrict || purge) + { return parser_err!( - "Cannot specify CASCADE, RESTRICT, or PURGE in DROP ROLE", + format_args!("Cannot specify CASCADE, RESTRICT, or PURGE in DROP {object_type}"), loc ); } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 74072e9f51..409a4b2765 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10975,6 +10975,45 @@ fn parse_create_foreign_data_wrapper() { } } +#[test] +fn parse_drop_group() { + for (sql, expected_names, expected_if_exists) in [ + ("DROP GROUP staff", vec!["staff"], false), + ( + "DROP GROUP IF EXISTS staff, workers", + vec!["staff", "workers"], + true, + ), + ] { + match pg().verified_stmt(sql) { + Statement::Drop { + object_type, + if_exists, + names, + .. + } => { + assert_eq!(object_type, ObjectType::Group); + assert_eq_vec(&expected_names, &names); + assert_eq!(if_exists, expected_if_exists); + } + other => panic!("expected DROP statement, got {other:?}"), + } + } + pg().verified_stmt(r#"DROP GROUP "Mixed Name", "GROUP""#); +} + +#[test] +fn parse_drop_group_rejects_drop_behaviors() { + for behavior in ["CASCADE", "RESTRICT", "PURGE"] { + assert_eq!( + pg().parse_sql_statements(&format!("DROP GROUP staff {behavior}")), + Err(ParserError::ParserError( + "Cannot specify CASCADE, RESTRICT, or PURGE in DROP GROUP".to_string() + )) + ); + } +} + #[test] fn foreign_data_wrapper_display_and_span() { let ident = |value: &str, start: u64, end: u64| Ident { @@ -11073,3 +11112,14 @@ fn parse_alter_operator_family_order_by_followed_by_search() { ] ); } + +#[test] +fn parse_drop_group_is_dialect_gated() { + assert_eq!( + all_dialects_where(|d| !d.supports_drop_group()) + .parse_sql_statements("DROP GROUP staff"), + Err(ParserError::ParserError( + "Expected: COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, GROUP, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW, USER or WAREHOUSE after DROP, found: GROUP".to_string() + )) + ); +} From 29bcd8e3882d9c89d25ba48da09540505fab5be3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 24 Oct 2026 12:39:09 +0200 Subject: [PATCH 2/3] Link PostgreSQL DROP GROUP documentation --- src/dialect/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index a884e69069..18f319c2a9 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -424,6 +424,8 @@ pub trait Dialect: Debug + Any { } /// Returns true if the dialect supports `DROP GROUP`. + /// + /// fn supports_drop_group(&self) -> bool { false } From 5427133ddb757d1f5ae26a9b553cdd52090e279c Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 24 Oct 2026 13:39:02 +0200 Subject: [PATCH 3/3] Add fuzz seeds for pg drop group --- fuzz/fuzz_seeds/postgres_2712_drop_group.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fuzz/fuzz_seeds/postgres_2712_drop_group.sql diff --git a/fuzz/fuzz_seeds/postgres_2712_drop_group.sql b/fuzz/fuzz_seeds/postgres_2712_drop_group.sql new file mode 100644 index 0000000000..d40197a6b0 --- /dev/null +++ b/fuzz/fuzz_seeds/postgres_2712_drop_group.sql @@ -0,0 +1,3 @@ +DROP GROUP staff; +DROP GROUP IF EXISTS staff, workers; +DROP GROUP "Mixed Name", "GROUP"