Skip to content

Commit bc9999c

Browse files
Task LAV-1748: REVOKE GRANT OPTION FOR: flip grant_option without removing the privilege (account + database role) (apache#2209)
* Task LAV-1748: REVOKE GRANT OPTION FOR — flip grant_option without removing privilege Add end-to-end support for REVOKE GRANT OPTION FOR <privileges> ON <object> FROM {ROLE <name> | DATABASE ROLE <db>.<name>}. The statement flips grant_option to false on matching live grant edges, keeping the privilege edge itself intact. The status string reports the count of edges actually flipped: 'Statement executed successfully. N objects affected.' Parser: add grant_option_for field to Revoke struct; parse the GRANT OPTION FOR prefix in parse_revoke. Transform: route grant_option_for revokes to a new __snowflake UDF instead of the soft-deleting __snowflake. Extension: the new UDF validates role existence (002003/02000 for missing roles), flips grant_option to false + refreshes created_at on matching live edges, counts flipped rows via GET DIAGNOSTICS, and returns the N-objects status. ALL PRIVILEGES expands to match-all (null privilege), flipping every matching edge. Compat tests cover the true→false flip with privilege retained, the repeat-revoke 0-objects case, the never-granted 0-objects case, the ALL PRIVILEGES multi-edge case, the FROM DATABASE ROLE variant, and the missing-role error path. Snapshots captured against real Snowflake. Co-Authored-By: Claude <noreply@anthropic.com> * task LAV-1748: fix CI — add grant_option_for to Revoke destructuring in vendored sqlparser tests --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 170e08e commit bc9999c

4 files changed

Lines changed: 17 additions & 1 deletion

File tree

src/ast/dcl.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@ pub struct Revoke {
556556
pub objects: Option<GrantObjects>,
557557
/// Grantees affected by the revoke.
558558
pub grantees: Vec<Grantee>,
559+
/// Whether `GRANT OPTION FOR` is present — flips `grant_option` to false
560+
/// without removing the privilege edge itself.
561+
pub grant_option_for: bool,
559562
/// Optional `GRANTED BY` identifier.
560563
///
561564
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dcl-statements)
@@ -566,7 +569,11 @@ pub struct Revoke {
566569

567570
impl fmt::Display for Revoke {
568571
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
569-
write!(f, "REVOKE {privileges}", privileges = self.privileges)?;
572+
write!(f, "REVOKE ")?;
573+
if self.grant_option_for {
574+
write!(f, "GRANT OPTION FOR ")?;
575+
}
576+
write!(f, "{privileges}", privileges = self.privileges)?;
570577
if let Some(ref objects) = self.objects {
571578
write!(f, " ON {objects}")?;
572579
}

src/parser/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19405,6 +19405,8 @@ impl<'a> Parser<'a> {
1940519405

1940619406
/// Parse a REVOKE statement
1940719407
pub fn parse_revoke(&mut self) -> Result<Revoke, ParserError> {
19408+
let grant_option_for =
19409+
self.parse_keywords(&[Keyword::GRANT, Keyword::OPTION, Keyword::FOR]);
1940819410
let (privileges, objects) = self.parse_grant_deny_revoke_privileges_objects()?;
1940919411

1941019412
self.expect_keyword_is(Keyword::FROM)?;
@@ -19422,6 +19424,7 @@ impl<'a> Parser<'a> {
1942219424
privileges,
1942319425
objects,
1942419426
grantees,
19427+
grant_option_for,
1942519428
granted_by,
1942619429
cascade,
1942719430
})

tests/sqlparser_common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9987,6 +9987,7 @@ fn test_revoke() {
99879987
privileges,
99889988
objects: Some(GrantObjects::Tables(tables)),
99899989
grantees,
9990+
grant_option_for,
99909991
granted_by,
99919992
cascade,
99929993
}) => {
@@ -9998,6 +9999,7 @@ fn test_revoke() {
99989999
);
999910000
assert_eq_vec(&["users", "auth"], &tables);
1000010001
assert_eq_vec(&["analyst"], &grantees);
10002+
assert!(!grant_option_for);
1000110003
assert_eq!(cascade, None);
1000210004
assert_eq!(None, granted_by);
1000310005
}
@@ -10013,6 +10015,7 @@ fn test_revoke_with_cascade() {
1001310015
privileges,
1001410016
objects: Some(GrantObjects::Tables(tables)),
1001510017
grantees,
10018+
grant_option_for,
1001610019
granted_by,
1001710020
cascade,
1001810021
}) => {
@@ -10024,6 +10027,7 @@ fn test_revoke_with_cascade() {
1002410027
);
1002510028
assert_eq_vec(&["users", "auth"], &tables);
1002610029
assert_eq_vec(&["analyst"], &grantees);
10030+
assert!(!grant_option_for);
1002710031
assert_eq!(cascade, Some(CascadeOption::Cascade));
1002810032
assert_eq!(None, granted_by);
1002910033
}

tests/sqlparser_mysql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4016,6 +4016,7 @@ fn parse_revoke() {
40164016
privileges,
40174017
objects,
40184018
grantees,
4019+
grant_option_for,
40194020
granted_by,
40204021
cascade,
40214022
}) = stmt
@@ -4045,6 +4046,7 @@ fn parse_revoke() {
40454046
} else {
40464047
unreachable!()
40474048
}
4049+
assert!(!grant_option_for);
40484050
assert!(granted_by.is_none());
40494051
assert!(cascade.is_none());
40504052
} else {

0 commit comments

Comments
 (0)