Add parsing for GRANT ROLE and GRANT DATABASE ROLE in Snowflake dialect (#1689)

This commit is contained in:
Yoav Cohen 2025-02-03 20:17:47 +01:00 committed by GitHub
parent 257da5a82c
commit ec948eaf6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 94 additions and 54 deletions

View file

@ -3230,7 +3230,7 @@ pub enum Statement {
/// ``` /// ```
Grant { Grant {
privileges: Privileges, privileges: Privileges,
objects: GrantObjects, objects: Option<GrantObjects>,
grantees: Vec<Grantee>, grantees: Vec<Grantee>,
with_grant_option: bool, with_grant_option: bool,
granted_by: Option<Ident>, granted_by: Option<Ident>,
@ -3240,7 +3240,7 @@ pub enum Statement {
/// ``` /// ```
Revoke { Revoke {
privileges: Privileges, privileges: Privileges,
objects: GrantObjects, objects: Option<GrantObjects>,
grantees: Vec<Grantee>, grantees: Vec<Grantee>,
granted_by: Option<Ident>, granted_by: Option<Ident>,
cascade: Option<CascadeOption>, cascade: Option<CascadeOption>,
@ -4785,7 +4785,9 @@ impl fmt::Display for Statement {
granted_by, granted_by,
} => { } => {
write!(f, "GRANT {privileges} ")?; write!(f, "GRANT {privileges} ")?;
write!(f, "ON {objects} ")?; if let Some(objects) = objects {
write!(f, "ON {objects} ")?;
}
write!(f, "TO {}", display_comma_separated(grantees))?; write!(f, "TO {}", display_comma_separated(grantees))?;
if *with_grant_option { if *with_grant_option {
write!(f, " WITH GRANT OPTION")?; write!(f, " WITH GRANT OPTION")?;
@ -4803,7 +4805,9 @@ impl fmt::Display for Statement {
cascade, cascade,
} => { } => {
write!(f, "REVOKE {privileges} ")?; write!(f, "REVOKE {privileges} ")?;
write!(f, "ON {objects} ")?; if let Some(objects) = objects {
write!(f, "ON {objects} ")?;
}
write!(f, "FROM {}", display_comma_separated(grantees))?; write!(f, "FROM {}", display_comma_separated(grantees))?;
if let Some(grantor) = granted_by { if let Some(grantor) = granted_by {
write!(f, " GRANTED BY {grantor}")?; write!(f, " GRANTED BY {grantor}")?;
@ -5503,6 +5507,9 @@ pub enum Action {
Create { Create {
obj_type: Option<ActionCreateObjectType>, obj_type: Option<ActionCreateObjectType>,
}, },
DatabaseRole {
role: ObjectName,
},
Delete, Delete,
EvolveSchema, EvolveSchema,
Execute { Execute {
@ -5536,6 +5543,9 @@ pub enum Action {
}, },
Replicate, Replicate,
ResolveAll, ResolveAll,
Role {
role: Ident,
},
Select { Select {
columns: Option<Vec<Ident>>, columns: Option<Vec<Ident>>,
}, },
@ -5565,6 +5575,7 @@ impl fmt::Display for Action {
write!(f, " {obj_type}")? write!(f, " {obj_type}")?
} }
} }
Action::DatabaseRole { role } => write!(f, "DATABASE ROLE {role}")?,
Action::Delete => f.write_str("DELETE")?, Action::Delete => f.write_str("DELETE")?,
Action::EvolveSchema => f.write_str("EVOLVE SCHEMA")?, Action::EvolveSchema => f.write_str("EVOLVE SCHEMA")?,
Action::Execute { obj_type } => { Action::Execute { obj_type } => {
@ -5591,6 +5602,7 @@ impl fmt::Display for Action {
Action::References { .. } => f.write_str("REFERENCES")?, Action::References { .. } => f.write_str("REFERENCES")?,
Action::Replicate => f.write_str("REPLICATE")?, Action::Replicate => f.write_str("REPLICATE")?,
Action::ResolveAll => f.write_str("RESOLVE ALL")?, Action::ResolveAll => f.write_str("RESOLVE ALL")?,
Action::Role { role } => write!(f, "ROLE {role}")?,
Action::Select { .. } => f.write_str("SELECT")?, Action::Select { .. } => f.write_str("SELECT")?,
Action::Temporary => f.write_str("TEMPORARY")?, Action::Temporary => f.write_str("TEMPORARY")?,
Action::Trigger => f.write_str("TRIGGER")?, Action::Trigger => f.write_str("TRIGGER")?,

View file

@ -12130,7 +12130,7 @@ impl<'a> Parser<'a> {
pub fn parse_grant_revoke_privileges_objects( pub fn parse_grant_revoke_privileges_objects(
&mut self, &mut self,
) -> Result<(Privileges, GrantObjects), ParserError> { ) -> Result<(Privileges, Option<GrantObjects>), ParserError> {
let privileges = if self.parse_keyword(Keyword::ALL) { let privileges = if self.parse_keyword(Keyword::ALL) {
Privileges::All { Privileges::All {
with_privileges_keyword: self.parse_keyword(Keyword::PRIVILEGES), with_privileges_keyword: self.parse_keyword(Keyword::PRIVILEGES),
@ -12140,48 +12140,49 @@ impl<'a> Parser<'a> {
Privileges::Actions(actions) Privileges::Actions(actions)
}; };
self.expect_keyword_is(Keyword::ON)?; let objects = if self.parse_keyword(Keyword::ON) {
if self.parse_keywords(&[Keyword::ALL, Keyword::TABLES, Keyword::IN, Keyword::SCHEMA]) {
let objects = if self.parse_keywords(&[ Some(GrantObjects::AllTablesInSchema {
Keyword::ALL, schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?,
Keyword::TABLES, })
Keyword::IN, } else if self.parse_keywords(&[
Keyword::SCHEMA, Keyword::ALL,
]) { Keyword::SEQUENCES,
GrantObjects::AllTablesInSchema { Keyword::IN,
schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, Keyword::SCHEMA,
} ]) {
} else if self.parse_keywords(&[ Some(GrantObjects::AllSequencesInSchema {
Keyword::ALL, schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?,
Keyword::SEQUENCES, })
Keyword::IN, } else {
Keyword::SCHEMA, let object_type = self.parse_one_of_keywords(&[
]) { Keyword::SEQUENCE,
GrantObjects::AllSequencesInSchema { Keyword::DATABASE,
schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, Keyword::DATABASE,
Keyword::SCHEMA,
Keyword::TABLE,
Keyword::VIEW,
Keyword::WAREHOUSE,
Keyword::INTEGRATION,
Keyword::VIEW,
Keyword::WAREHOUSE,
Keyword::INTEGRATION,
]);
let objects =
self.parse_comma_separated(|p| p.parse_object_name_with_wildcards(false, true));
match object_type {
Some(Keyword::DATABASE) => Some(GrantObjects::Databases(objects?)),
Some(Keyword::SCHEMA) => Some(GrantObjects::Schemas(objects?)),
Some(Keyword::SEQUENCE) => Some(GrantObjects::Sequences(objects?)),
Some(Keyword::WAREHOUSE) => Some(GrantObjects::Warehouses(objects?)),
Some(Keyword::INTEGRATION) => Some(GrantObjects::Integrations(objects?)),
Some(Keyword::VIEW) => Some(GrantObjects::Views(objects?)),
Some(Keyword::TABLE) | None => Some(GrantObjects::Tables(objects?)),
_ => unreachable!(),
}
} }
} else { } else {
let object_type = self.parse_one_of_keywords(&[ None
Keyword::SEQUENCE,
Keyword::DATABASE,
Keyword::SCHEMA,
Keyword::TABLE,
Keyword::VIEW,
Keyword::WAREHOUSE,
Keyword::INTEGRATION,
]);
let objects =
self.parse_comma_separated(|p| p.parse_object_name_with_wildcards(false, true));
match object_type {
Some(Keyword::DATABASE) => GrantObjects::Databases(objects?),
Some(Keyword::SCHEMA) => GrantObjects::Schemas(objects?),
Some(Keyword::SEQUENCE) => GrantObjects::Sequences(objects?),
Some(Keyword::WAREHOUSE) => GrantObjects::Warehouses(objects?),
Some(Keyword::INTEGRATION) => GrantObjects::Integrations(objects?),
Some(Keyword::VIEW) => GrantObjects::Views(objects?),
Some(Keyword::TABLE) | None => GrantObjects::Tables(objects?),
_ => unreachable!(),
}
}; };
Ok((privileges, objects)) Ok((privileges, objects))
@ -12208,6 +12209,9 @@ impl<'a> Parser<'a> {
Ok(Action::AttachPolicy) Ok(Action::AttachPolicy)
} else if self.parse_keywords(&[Keyword::BIND, Keyword::SERVICE, Keyword::ENDPOINT]) { } else if self.parse_keywords(&[Keyword::BIND, Keyword::SERVICE, Keyword::ENDPOINT]) {
Ok(Action::BindServiceEndpoint) Ok(Action::BindServiceEndpoint)
} else if self.parse_keywords(&[Keyword::DATABASE, Keyword::ROLE]) {
let role = self.parse_object_name(false)?;
Ok(Action::DatabaseRole { role })
} else if self.parse_keywords(&[Keyword::EVOLVE, Keyword::SCHEMA]) { } else if self.parse_keywords(&[Keyword::EVOLVE, Keyword::SCHEMA]) {
Ok(Action::EvolveSchema) Ok(Action::EvolveSchema)
} else if self.parse_keywords(&[Keyword::IMPORT, Keyword::SHARE]) { } else if self.parse_keywords(&[Keyword::IMPORT, Keyword::SHARE]) {
@ -12273,6 +12277,9 @@ impl<'a> Parser<'a> {
Ok(Action::Read) Ok(Action::Read)
} else if self.parse_keyword(Keyword::REPLICATE) { } else if self.parse_keyword(Keyword::REPLICATE) {
Ok(Action::Replicate) Ok(Action::Replicate)
} else if self.parse_keyword(Keyword::ROLE) {
let role = self.parse_identifier()?;
Ok(Action::Role { role })
} else if self.parse_keyword(Keyword::SELECT) { } else if self.parse_keyword(Keyword::SELECT) {
Ok(Action::Select { Ok(Action::Select {
columns: parse_columns(self)?, columns: parse_columns(self)?,

View file

@ -8637,7 +8637,7 @@ fn parse_grant() {
granted_by, granted_by,
.. ..
} => match (privileges, objects) { } => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::Tables(objects)) => { (Privileges::Actions(actions), Some(GrantObjects::Tables(objects))) => {
assert_eq!( assert_eq!(
vec![ vec![
Action::Select { columns: None }, Action::Select { columns: None },
@ -8687,7 +8687,7 @@ fn parse_grant() {
with_grant_option, with_grant_option,
.. ..
} => match (privileges, objects) { } => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::AllTablesInSchema { schemas }) => { (Privileges::Actions(actions), Some(GrantObjects::AllTablesInSchema { schemas })) => {
assert_eq!(vec![Action::Insert { columns: None }], actions); assert_eq!(vec![Action::Insert { columns: None }], actions);
assert_eq_vec(&["public"], &schemas); assert_eq_vec(&["public"], &schemas);
assert_eq_vec(&["browser"], &grantees); assert_eq_vec(&["browser"], &grantees);
@ -8707,7 +8707,7 @@ fn parse_grant() {
granted_by, granted_by,
.. ..
} => match (privileges, objects, granted_by) { } => match (privileges, objects, granted_by) {
(Privileges::Actions(actions), GrantObjects::Sequences(objects), None) => { (Privileges::Actions(actions), Some(GrantObjects::Sequences(objects)), None) => {
assert_eq!( assert_eq!(
vec![Action::Usage, Action::Select { columns: None }], vec![Action::Usage, Action::Select { columns: None }],
actions actions
@ -8744,7 +8744,7 @@ fn parse_grant() {
Privileges::All { Privileges::All {
with_privileges_keyword, with_privileges_keyword,
}, },
GrantObjects::Schemas(schemas), Some(GrantObjects::Schemas(schemas)),
) => { ) => {
assert!(!with_privileges_keyword); assert!(!with_privileges_keyword);
assert_eq_vec(&["aa", "b"], &schemas); assert_eq_vec(&["aa", "b"], &schemas);
@ -8761,7 +8761,10 @@ fn parse_grant() {
objects, objects,
.. ..
} => match (privileges, objects) { } => match (privileges, objects) {
(Privileges::Actions(actions), GrantObjects::AllSequencesInSchema { schemas }) => { (
Privileges::Actions(actions),
Some(GrantObjects::AllSequencesInSchema { schemas }),
) => {
assert_eq!(vec![Action::Usage], actions); assert_eq!(vec![Action::Usage], actions);
assert_eq_vec(&["bus"], &schemas); assert_eq_vec(&["bus"], &schemas);
} }
@ -8791,7 +8794,7 @@ fn test_revoke() {
match verified_stmt(sql) { match verified_stmt(sql) {
Statement::Revoke { Statement::Revoke {
privileges, privileges,
objects: GrantObjects::Tables(tables), objects: Some(GrantObjects::Tables(tables)),
grantees, grantees,
granted_by, granted_by,
cascade, cascade,
@ -8817,7 +8820,7 @@ fn test_revoke_with_cascade() {
match all_dialects_except(|d| d.is::<MySqlDialect>()).verified_stmt(sql) { match all_dialects_except(|d| d.is::<MySqlDialect>()).verified_stmt(sql) {
Statement::Revoke { Statement::Revoke {
privileges, privileges,
objects: GrantObjects::Tables(tables), objects: Some(GrantObjects::Tables(tables)),
grantees, grantees,
granted_by, granted_by,
cascade, cascade,

View file

@ -3046,7 +3046,10 @@ fn parse_grant() {
); );
assert_eq!( assert_eq!(
objects, objects,
GrantObjects::Tables(vec![ObjectName::from(vec!["*".into(), "*".into()])]) Some(GrantObjects::Tables(vec![ObjectName::from(vec![
"*".into(),
"*".into()
])]))
); );
assert!(!with_grant_option); assert!(!with_grant_option);
assert!(granted_by.is_none()); assert!(granted_by.is_none());
@ -3087,7 +3090,10 @@ fn parse_revoke() {
); );
assert_eq!( assert_eq!(
objects, objects,
GrantObjects::Tables(vec![ObjectName::from(vec!["db1".into(), "*".into()])]) Some(GrantObjects::Tables(vec![ObjectName::from(vec![
"db1".into(),
"*".into()
])]))
); );
if let [Grantee { if let [Grantee {
grantee_type: GranteesType::None, grantee_type: GranteesType::None,

View file

@ -3263,3 +3263,15 @@ fn test_grant_account_privileges() {
} }
} }
} }
#[test]
fn test_grant_role_to() {
snowflake_and_generic().verified_stmt("GRANT ROLE r1 TO ROLE r2");
snowflake_and_generic().verified_stmt("GRANT ROLE r1 TO USER u1");
}
#[test]
fn test_grant_database_role_to() {
snowflake_and_generic().verified_stmt("GRANT DATABASE ROLE r1 TO ROLE r2");
snowflake_and_generic().verified_stmt("GRANT DATABASE ROLE db1.sc1.r1 TO ROLE db1.sc1.r2");
}