From b2522d1aebf72903b1aebf122c3ddddc9d105adc 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 8e798524c9..44aed1ed94 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8749,6 +8749,8 @@ pub enum ObjectType { Database, /// A role. Role, + /// A user group. + Group, /// A sequence. Sequence, /// A stage. @@ -8774,6 +8776,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 c867b76f61..1aa833448e 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -421,6 +421,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 9e5257b30f..ba54743583 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 3efb22a349..f8b5ca1154 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8021,6 +8021,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) { @@ -8064,7 +8066,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(), ); }; @@ -8080,9 +8082,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 aa9252125b..a0bead0611 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10875,3 +10875,53 @@ fn parse_create_aggregate_rejects_output_arguments_and_defaults() { ); } } + +#[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 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 956f882eee60fdc89a4e5e3a8927f68b781e5856 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 1aa833448e..72341d067a 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -422,6 +422,8 @@ pub trait Dialect: Debug + Any { } /// Returns true if the dialect supports `DROP GROUP`. + /// + /// fn supports_drop_group(&self) -> bool { false } From 0214e6ad8ddea3858fa78fac002b1a38d15bcfe6 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"