diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 3c50a81c0..3163c7646 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -486,10 +486,14 @@ impl From for crate::ast::Statement { } /// REVOKE privileges ON objects FROM grantees +/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-revoke.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 Revoke { + /// Whether `GRANT OPTION FOR` is present, withdrawing the right to grant + /// the privileges rather than the privileges themselves. + pub grant_option_for: bool, /// Privileges to revoke. pub privileges: Privileges, /// Optional objects from which to revoke. @@ -506,7 +510,11 @@ pub struct Revoke { impl fmt::Display for Revoke { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "REVOKE {privileges}", privileges = self.privileges)?; + write!(f, "REVOKE ")?; + if self.grant_option_for { + write!(f, "GRANT OPTION FOR ")?; + } + write!(f, "{}", self.privileges)?; if let Some(ref objects) = self.objects { write!(f, " ON {objects}")?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b2b3f42bb..41004ce2d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18339,6 +18339,9 @@ impl<'a> Parser<'a> { /// Parse a REVOKE statement pub fn parse_revoke(&mut self) -> Result { + let grant_option_for = + self.parse_keywords(&[Keyword::GRANT, Keyword::OPTION, Keyword::FOR]); + let (privileges, objects) = self.parse_grant_deny_revoke_privileges_objects()?; self.expect_keyword_is(Keyword::FROM)?; @@ -18353,6 +18356,7 @@ impl<'a> Parser<'a> { let cascade = self.parse_cascade_option(); Ok(Revoke { + grant_option_for, privileges, objects, grantees, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 0800bc41f..97b48cb29 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -10096,6 +10096,7 @@ fn test_revoke() { let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst"; match verified_stmt(sql) { Statement::Revoke(Revoke { + grant_option_for: false, privileges, objects: Some(GrantObjects::Tables(tables)), grantees, @@ -10120,8 +10121,9 @@ fn test_revoke() { #[test] fn test_revoke_with_cascade() { let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst CASCADE"; - match all_dialects_except(|d| d.is::()).verified_stmt(sql) { + match verified_stmt(sql) { Statement::Revoke(Revoke { + grant_option_for: false, privileges, objects: Some(GrantObjects::Tables(tables)), grantees, @@ -10143,6 +10145,26 @@ fn test_revoke_with_cascade() { } } +#[test] +fn test_revoke_grant_option_for() { + let Statement::Revoke(mut revoke) = verified_stmt("REVOKE GRANT OPTION FOR SELECT ON t FROM r") + else { + unreachable!() + }; + assert!(revoke.grant_option_for); + + // Clearing the flag must yield exactly the plain revoke. + revoke.grant_option_for = false; + assert_eq!( + Statement::Revoke(revoke), + verified_stmt("REVOKE SELECT ON t FROM r") + ); + + verified_stmt("REVOKE GRANT OPTION FOR ALL ON t FROM r"); + verified_stmt("REVOKE GRANT OPTION FOR SELECT ON t FROM r CASCADE"); + verified_stmt("REVOKE GRANT OPTION FOR SELECT ON t FROM r RESTRICT"); +} + #[test] fn parse_merge() { let sql = "MERGE INTO s.bar AS dest USING (SELECT * FROM s.foo) AS stg ON dest.D = stg.D AND dest.E = stg.E WHEN NOT MATCHED THEN INSERT (A, B, C) VALUES (stg.A, stg.B, stg.C) WHEN MATCHED AND dest.A = 'a' THEN UPDATE SET dest.F = stg.F, dest.G = stg.G WHEN MATCHED THEN DELETE"; diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 797a12551..0df8393e4 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4013,6 +4013,7 @@ fn parse_revoke() { let sql = "REVOKE ALL ON db1.* FROM 'jeffrey'@'%'"; let stmt = mysql_and_generic().verified_stmt(sql); if let Statement::Revoke(Revoke { + grant_option_for: false, privileges, objects, grantees,