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_options.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GRANT bypasser TO anon WITH ADMIN OPTION, INHERIT FALSE, SET FALSE GRANTED BY admin
47 changes: 46 additions & 1 deletion src/ast/dcl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ impl From<Revoke> for crate::ast::Statement {
}
}

/// `GRANT role [, ...] TO grantee [, ...] [GRANTED BY grantor]`
/// `GRANT role [, ...] TO grantee [, ...] [WITH option [, ...]] [GRANTED BY grantor]`
///
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-grant.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
Expand All @@ -547,6 +547,8 @@ pub struct GrantRole {
pub roles: Vec<Ident>,
/// Roles receiving the membership.
pub grantees: Vec<Grantee>,
/// Options of the membership, from `WITH`.
pub options: Vec<RoleGrantOption>,
/// Optional `GRANTED BY` identifier.
pub granted_by: Option<Ident>,
}
Expand All @@ -559,6 +561,9 @@ impl fmt::Display for GrantRole {
display_comma_separated(&self.roles),
display_comma_separated(&self.grantees)
)?;
if !self.options.is_empty() {
write!(f, " WITH {}", display_comma_separated(&self.options))?;
}
if let Some(granted_by) = &self.granted_by {
write!(f, " GRANTED BY {granted_by}")?;
}
Expand All @@ -571,3 +576,43 @@ impl From<GrantRole> for crate::ast::Statement {
crate::ast::Statement::GrantRole(v)
}
}

/// An option of a role membership, such as `ADMIN OPTION` or `SET FALSE`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct RoleGrantOption {
/// The option name, such as `ADMIN`, `INHERIT` or `SET`.
pub name: Ident,
/// The value it is set to.
pub value: RoleGrantOptionValue,
}

impl fmt::Display for RoleGrantOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.name, self.value)
}
}

/// The value of a [`RoleGrantOption`].
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum RoleGrantOptionValue {
/// `OPTION`, equivalent to `TRUE`.
Option,
/// `TRUE`
True,
/// `FALSE`
False,
}

impl fmt::Display for RoleGrantOptionValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
RoleGrantOptionValue::Option => "OPTION",
RoleGrantOptionValue::True => "TRUE",
RoleGrantOptionValue::False => "FALSE",
})
}
}
4 changes: 2 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, GrantRole, ResetConfig, Revoke, RoleOption,
SecondaryRoles, SetConfigValue, Use,
AlterRoleOperation, CreateRole, Grant, GrantRole, ResetConfig, Revoke, RoleGrantOption,
RoleGrantOptionValue, RoleOption, SecondaryRoles, SetConfigValue, Use,
};
pub use self::ddl::{
AggregateModifyKind, Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation,
Expand Down
18 changes: 18 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18450,6 +18450,11 @@ impl<'a> Parser<'a> {
let roles = self.parse_comma_separated(Parser::parse_identifier)?;
self.expect_keyword_is(Keyword::TO)?;
let grantees = self.parse_grantees()?;
let options = if self.parse_keyword(Keyword::WITH) {
self.parse_comma_separated(Parser::parse_role_grant_option)?
} else {
vec![]
};
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
Some(self.parse_identifier()?)
} else {
Expand All @@ -18458,10 +18463,23 @@ impl<'a> Parser<'a> {
Ok(GrantRole {
roles,
grantees,
options,
granted_by,
})
}

fn parse_role_grant_option(&mut self) -> Result<RoleGrantOption, ParserError> {
let name = self.parse_identifier()?;
let value =
match self.parse_one_of_keywords(&[Keyword::OPTION, Keyword::TRUE, Keyword::FALSE]) {
Some(Keyword::OPTION) => RoleGrantOptionValue::Option,
Some(Keyword::TRUE) => RoleGrantOptionValue::True,
Some(Keyword::FALSE) => RoleGrantOptionValue::False,
_ => return self.expected_ref("OPTION, TRUE or FALSE", self.peek_token_ref()),
};
Ok(RoleGrantOption { name, value })
}

/// 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>>(
Expand Down
64 changes: 64 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22402,6 +22402,7 @@ fn parse_grant_role() {
Statement::GrantRole(GrantRole {
roles: vec![Ident::new("bypasser"), Ident::new("auditor")],
grantees: vec![role_grantee("anon"), role_grantee("authenticated")],
options: vec![],
granted_by: Some(Ident::new("admin")),
})
);
Expand Down Expand Up @@ -22460,3 +22461,66 @@ fn display_grant_role_propagates_formatter_errors() {
assert_eq!(write!(writer, "{grant}"), Err(fmt::Error), "{rejected}");
}
}

#[test]
fn parse_grant_role_options() {
let sql = "GRANT bypasser TO anon WITH ADMIN OPTION, INHERIT FALSE, SET TRUE GRANTED BY admin";
let Statement::GrantRole(GrantRole { options, .. }) = all_dialects().verified_stmt(sql) else {
panic!("Expected GRANT role");
};
assert_eq!(
options,
vec![
RoleGrantOption {
name: Ident::new("ADMIN"),
value: RoleGrantOptionValue::Option,
},
RoleGrantOption {
name: Ident::new("INHERIT"),
value: RoleGrantOptionValue::False,
},
RoleGrantOption {
name: Ident::new("SET"),
value: RoleGrantOptionValue::True,
},
]
);

all_dialects().verified_stmt("GRANT bypasser TO anon WITH SET FALSE");

assert_eq!(
all_dialects()
.parse_sql_statements("GRANT bypasser TO anon WITH ADMIN")
.unwrap_err(),
ParserError::ParserError("Expected: OPTION, TRUE or FALSE, found: EOF".to_string())
);
assert_eq!(
all_dialects()
.parse_sql_statements("GRANT bypasser TO anon WITH 1 OPTION")
.unwrap_err(),
ParserError::ParserError("Expected: identifier, found: 1".to_string())
);
}

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

struct RejectingWriter<'a>(&'a 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 statement = all_dialects().verified_stmt("GRANT bypasser TO anon WITH SET FALSE");
for rejected in [" WITH ", "SET", "FALSE"] {
let mut writer = RejectingWriter(rejected);
assert_eq!(write!(writer, "{statement}"), Err(fmt::Error));
}
}
Loading