Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/fuzz_seeds/postgres_grant_role_membership.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GRANT bypasser, auditor TO anon, authenticated GRANTED BY admin
36 changes: 36 additions & 0 deletions src/ast/dcl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,39 @@ impl From<Revoke> for crate::ast::Statement {
crate::ast::Statement::Revoke(v)
}
}

/// `GRANT role [, ...] TO grantee [, ...] [GRANTED BY grantor]`
///
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-grant.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 GrantRole {
/// Roles whose membership is granted.
pub roles: Vec<Ident>,
/// Roles receiving the membership.
pub grantees: Vec<Grantee>,
/// Optional `GRANTED BY` identifier.
pub granted_by: Option<Ident>,
}

impl fmt::Display for GrantRole {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"GRANT {} TO {}",
display_comma_separated(&self.roles),
display_comma_separated(&self.grantees)
)?;
if let Some(granted_by) = &self.granted_by {
write!(f, " GRANTED BY {granted_by}")?;
}
Ok(())
}
}

impl From<GrantRole> for crate::ast::Statement {
fn from(v: GrantRole) -> Self {
crate::ast::Statement::GrantRole(v)
}
}
9 changes: 7 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ pub use self::data_type::{
ExactNumberInfo, IntervalFields, MapBracketKind, StructBracketKind, TimezoneInfo, TypeModifier,
};
pub use self::dcl::{
AlterRoleOperation, CreateRole, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles,
SetConfigValue, Use,
AlterRoleOperation, CreateRole, Grant, GrantRole, ResetConfig, Revoke, RoleOption,
SecondaryRoles, SetConfigValue, Use,
};
pub use self::ddl::{
AggregateModifyKind, Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation,
Expand Down Expand Up @@ -4669,6 +4669,10 @@ pub enum Statement {
/// ```
Grant(Grant),
/// ```sql
/// GRANT roles TO grantees
/// ```
GrantRole(GrantRole),
/// ```sql
/// DENY privileges ON object TO grantees
/// ```
Deny(DenyStatement),
Expand Down Expand Up @@ -6183,6 +6187,7 @@ impl fmt::Display for Statement {
Statement::Grant(grant) => write!(f, "{grant}"),
Statement::Deny(s) => write!(f, "{s}"),
Statement::Revoke(revoke) => write!(f, "{revoke}"),
Statement::GrantRole(grant) => write!(f, "{grant}"),
Statement::Deallocate { name, prepare } => write!(
f,
"DEALLOCATE {prepare}{name}",
Expand Down
2 changes: 2 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ impl Spanned for Values {
/// - [Statement::Assert]
/// - [Statement::Grant]
/// - [Statement::Revoke]
/// - [Statement::GrantRole]
/// - [Statement::Deallocate]
/// - [Statement::Execute]
/// - [Statement::Prepare]
Expand Down Expand Up @@ -469,6 +470,7 @@ impl Spanned for Statement {
Statement::Grant { .. } => Span::empty(),
Statement::Deny { .. } => Span::empty(),
Statement::Revoke { .. } => Span::empty(),
Statement::GrantRole { .. } => Span::empty(),
Statement::Deallocate { .. } => Span::empty(),
Statement::Execute { .. } => Span::empty(),
Statement::Prepare { .. } => Span::empty(),
Expand Down
47 changes: 46 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,11 @@ impl<'a> Parser<'a> {
Keyword::SET => self.parse_set(),
Keyword::SHOW => self.parse_show(),
Keyword::USE => self.parse_use(),
Keyword::GRANT => self.parse_grant().map(Into::into),
Keyword::GRANT => self.parse_privileges_or_roles(
Keyword::TO,
Parser::parse_grant,
Parser::parse_grant_role,
),
Keyword::DENY => {
self.prev_token();
self.parse_deny()
Expand Down Expand Up @@ -18441,6 +18445,47 @@ impl<'a> Parser<'a> {
})
}

/// Parse a `GRANT` of role membership, after the `GRANT` keyword.
pub fn parse_grant_role(&mut self) -> Result<GrantRole, ParserError> {
let roles = self.parse_comma_separated(Parser::parse_identifier)?;
self.expect_keyword_is(Keyword::TO)?;
let grantees = self.parse_grantees()?;
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
Some(self.parse_identifier()?)
} else {
None
};
Ok(GrantRole {
roles,
grantees,
granted_by,
})
}

/// Parses a statement body with `privileges`, and with `roles` instead when that fails and
/// the body opens with a role list followed by `keyword`.
fn parse_privileges_or_roles<P: Into<Statement>, R: Into<Statement>>(
&mut self,
keyword: Keyword,
privileges: fn(&mut Self) -> Result<P, ParserError>,
roles: fn(&mut Self) -> Result<R, ParserError>,
) -> Result<Statement, ParserError> {
let index = self.index;
let privileges_err = match privileges(self) {
Ok(statement) => return Ok(statement.into()),
Err(err) => err,
};
self.index = index;
let is_role_list = self.parse_comma_separated(Parser::parse_identifier).is_ok()
&& self.parse_keyword(keyword);
self.index = index;
if is_role_list {
roles(self).map(Into::into)
} else {
Err(privileges_err)
}
}

fn parse_grantees(&mut self) -> Result<Vec<Grantee>, ParserError> {
let mut values = vec![];
let mut grantee_type = GranteesType::None;
Expand Down
74 changes: 74 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22386,3 +22386,77 @@ fn parse_grant_function_signature() {
ParserError::ParserError("Expected: ), found: EOF".to_string())
);
}

#[test]
fn parse_grant_role() {
let role_grantee = |name: &str| Grantee {
grantee_type: GranteesType::None,
name: Some(GranteeName::ObjectName(ObjectName::from(vec![Ident::new(
name,
)]))),
};

let sql = "GRANT bypasser, auditor TO anon, authenticated GRANTED BY admin";
assert_eq!(
all_dialects().verified_stmt(sql),
Statement::GrantRole(GrantRole {
roles: vec![Ident::new("bypasser"), Ident::new("auditor")],
grantees: vec![role_grantee("anon"), role_grantee("authenticated")],
granted_by: Some(Ident::new("admin")),
})
);

all_dialects().verified_stmt("GRANT bypasser TO anon");
assert_eq!(
all_dialects()
.parse_sql_statements("GRANT bypasser ON t TO anon")
.unwrap_err(),
ParserError::ParserError("Expected: a privilege keyword, found: bypasser".to_string())
);

assert_matches!(
all_dialects().verified_stmt(r#"GRANT "select" TO anon"#),
Statement::GrantRole(_)
);
assert_matches!(
all_dialects().verified_stmt("GRANT SELECT TO anon"),
Statement::Grant(_)
);
for (sql, err) in [
("GRANT bypasser TO", "Expected: identifier, found: EOF"),
(
"GRANT bypasser TO anon GRANTED BY 1",
"Expected: identifier, found: 1",
),
("GRANT 1 TO anon", "Expected: a privilege keyword, found: 1"),
] {
assert_eq!(
all_dialects().parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError(err.to_string()),
"{sql}"
);
}
}

#[test]
fn display_grant_role_propagates_formatter_errors() {
use core::fmt::{self, Write};

struct RejectingWriter(&'static str);

impl Write for RejectingWriter {
fn write_str(&mut self, value: &str) -> fmt::Result {
if value.contains(self.0) {
Err(fmt::Error)
} else {
Ok(())
}
}
}

let grant = verified_stmt("GRANT bypasser TO anon GRANTED BY admin");
for rejected in ["anon", "admin"] {
let mut writer = RejectingWriter(rejected);
assert_eq!(write!(writer, "{grant}"), Err(fmt::Error), "{rejected}");
}
}
Loading