diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 3c50a81c0..9eed010d3 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -526,3 +526,149 @@ impl From for crate::ast::Statement { crate::ast::Statement::Revoke(v) } } + +/// `ALTER DEFAULT PRIVILEGES`, which applies to objects created later rather +/// than to existing ones. +/// +/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.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 AlterDefaultPrivileges { + /// `FOR ROLE [, ...]`, empty when the clause is absent. + pub for_roles: Vec, + /// `IN SCHEMA [, ...]`, empty when the clause is absent. + pub in_schemas: Vec, + /// The abbreviated `GRANT` or `REVOKE` that follows. + pub operation: AlterDefaultPrivilegesOperation, +} + +impl fmt::Display for AlterDefaultPrivileges { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("ALTER DEFAULT PRIVILEGES")?; + if !self.for_roles.is_empty() { + write!(f, " FOR ROLE {}", display_comma_separated(&self.for_roles))?; + } + if !self.in_schemas.is_empty() { + write!( + f, + " IN SCHEMA {}", + display_comma_separated(&self.in_schemas) + )?; + } + write!(f, " {}", self.operation) + } +} + +impl From for crate::ast::Statement { + fn from(v: AlterDefaultPrivileges) -> Self { + crate::ast::Statement::AlterDefaultPrivileges(v) + } +} + +/// The abbreviated `GRANT` or `REVOKE` of an [`AlterDefaultPrivileges`], naming +/// an object type rather than named objects and so unable to reuse [`Grant`]. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterDefaultPrivilegesOperation { + /// `GRANT ON TO [WITH GRANT OPTION]` + Grant { + /// The privileges being granted. + privileges: Privileges, + /// The kind of future object the privileges apply to. + object_type: DefaultPrivilegesObjectType, + /// The roles receiving the privileges. + grantees: Vec, + /// Whether `WITH GRANT OPTION` is present. + with_grant_option: bool, + }, + /// `REVOKE [GRANT OPTION FOR] ON FROM [CASCADE | RESTRICT]` + Revoke { + /// Whether `GRANT OPTION FOR` is present. + grant_option_for: bool, + /// The privileges being revoked. + privileges: Privileges, + /// The kind of future object the privileges apply to. + object_type: DefaultPrivilegesObjectType, + /// The roles losing the privileges. + grantees: Vec, + /// Optional `CASCADE`/`RESTRICT` behaviour. + cascade: Option, + }, +} + +impl fmt::Display for AlterDefaultPrivilegesOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterDefaultPrivilegesOperation::Grant { + privileges, + object_type, + grantees, + with_grant_option, + } => { + write!( + f, + "GRANT {privileges} ON {object_type} TO {}", + display_comma_separated(grantees) + )?; + if *with_grant_option { + f.write_str(" WITH GRANT OPTION")?; + } + } + AlterDefaultPrivilegesOperation::Revoke { + grant_option_for, + privileges, + object_type, + grantees, + cascade, + } => { + f.write_str("REVOKE ")?; + if *grant_option_for { + f.write_str("GRANT OPTION FOR ")?; + } + write!( + f, + "{privileges} ON {object_type} FROM {}", + display_comma_separated(grantees) + )?; + if let Some(cascade) = cascade { + write!(f, " {cascade}")?; + } + } + } + Ok(()) + } +} + +/// The kind of future object an [`AlterDefaultPrivileges`] applies to. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum DefaultPrivilegesObjectType { + /// `TABLES`, which also covers views and foreign tables. + Tables, + /// `SEQUENCES` + Sequences, + /// `FUNCTIONS` + Functions, + /// `ROUTINES`, a synonym of `FUNCTIONS`. + Routines, + /// `TYPES` + Types, + /// `SCHEMAS` + Schemas, +} + +impl fmt::Display for DefaultPrivilegesObjectType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + DefaultPrivilegesObjectType::Tables => "TABLES", + DefaultPrivilegesObjectType::Sequences => "SEQUENCES", + DefaultPrivilegesObjectType::Functions => "FUNCTIONS", + DefaultPrivilegesObjectType::Routines => "ROUTINES", + DefaultPrivilegesObjectType::Types => "TYPES", + DefaultPrivilegesObjectType::Schemas => "SCHEMAS", + }) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8a9a67a74..e97562eb4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -58,7 +58,8 @@ pub use self::data_type::{ ExactNumberInfo, IntervalFields, MapBracketKind, StructBracketKind, TimezoneInfo, }; pub use self::dcl::{ - AlterRoleOperation, CreateRole, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles, + AlterDefaultPrivileges, AlterDefaultPrivilegesOperation, AlterRoleOperation, CreateRole, + DefaultPrivilegesObjectType, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles, SetConfigValue, Use, }; pub use self::ddl::{ @@ -4587,6 +4588,10 @@ pub enum Statement { /// ``` Revoke(Revoke), /// ```sql + /// ALTER DEFAULT PRIVILEGES + /// ``` + AlterDefaultPrivileges(AlterDefaultPrivileges), + /// ```sql /// DEALLOCATE [ PREPARE ] { name | ALL } /// ``` /// @@ -6090,6 +6095,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::AlterDefaultPrivileges(stmt) => write!(f, "{stmt}"), Statement::Deallocate { name, prepare } => write!( f, "DEALLOCATE {prepare}{name}", diff --git a/src/ast/spans.rs b/src/ast/spans.rs index e1fa0c752..593c9296d 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -301,6 +301,7 @@ impl Spanned for Values { /// - [Statement::Assert] /// - [Statement::Grant] /// - [Statement::Revoke] +/// - [Statement::AlterDefaultPrivileges] /// - [Statement::Deallocate] /// - [Statement::Execute] /// - [Statement::Prepare] @@ -466,6 +467,7 @@ impl Spanned for Statement { Statement::Grant { .. } => Span::empty(), Statement::Deny { .. } => Span::empty(), Statement::Revoke { .. } => Span::empty(), + Statement::AlterDefaultPrivileges { .. } => Span::empty(), Statement::Deallocate { .. } => Span::empty(), Statement::Execute { .. } => Span::empty(), Statement::Prepare { .. } => Span::empty(), diff --git a/src/keywords.rs b/src/keywords.rs index 0c50703c3..e16e15a3d 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -908,6 +908,7 @@ define_keywords!( ROLLBACK, ROLLUP, ROOT, + ROUTINES, ROW, ROWGROUPSIZE, ROWID, @@ -1095,6 +1096,7 @@ define_keywords!( TSVECTOR, TUPLE, TYPE, + TYPES, TYPMOD_IN, TYPMOD_OUT, UBIGINT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2d51d4717..93ed31bbc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11141,6 +11141,10 @@ impl<'a> Parser<'a> { return self.parse_alter_text_search().map(Into::into); } + if self.parse_keywords(&[Keyword::DEFAULT, Keyword::PRIVILEGES]) { + return self.parse_alter_default_privileges().map(Into::into); + } + let object_type = self.expect_one_of_keywords(&[ Keyword::VIEW, Keyword::TYPE, @@ -17793,14 +17797,7 @@ impl<'a> Parser<'a> { pub fn parse_grant_deny_revoke_privileges_objects( &mut self, ) -> Result<(Privileges, Option), ParserError> { - let privileges = if self.parse_keyword(Keyword::ALL) { - Privileges::All { - with_privileges_keyword: self.parse_keyword(Keyword::PRIVILEGES), - } - } else { - let actions = self.parse_actions_list()?; - Privileges::Actions(actions) - }; + let privileges = self.parse_privileges()?; let objects = if self.parse_keyword(Keyword::ON) { if self.parse_keywords(&[Keyword::ALL, Keyword::TABLES, Keyword::IN, Keyword::SCHEMA]) { @@ -18328,6 +18325,102 @@ impl<'a> Parser<'a> { }) } + /// Parse `ALTER DEFAULT PRIVILEGES`, whose keywords are already consumed. + /// + /// See [PostgreSQL docs](https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html). + pub fn parse_alter_default_privileges( + &mut self, + ) -> Result { + // `FOR USER` is a synonym of `FOR ROLE` and is normalised to it. + let for_roles = if self.parse_keyword(Keyword::FOR) { + self.expect_one_of_keywords(&[Keyword::ROLE, Keyword::USER])?; + self.parse_comma_separated(|p| p.parse_identifier())? + } else { + vec![] + }; + + let in_schemas = if self.parse_keywords(&[Keyword::IN, Keyword::SCHEMA]) { + self.parse_comma_separated(|p| p.parse_object_name(false))? + } else { + vec![] + }; + + let is_grant = if self.parse_keyword(Keyword::GRANT) { + true + } else if self.parse_keyword(Keyword::REVOKE) { + false + } else { + return self.expected_ref("GRANT or REVOKE", self.peek_token_ref()); + }; + + let grant_option_for = + !is_grant && self.parse_keywords(&[Keyword::GRANT, Keyword::OPTION, Keyword::FOR]); + let privileges = self.parse_privileges()?; + self.expect_keyword_is(Keyword::ON)?; + let object_type = self.parse_default_privileges_object_type()?; + self.expect_keyword_is(if is_grant { Keyword::TO } else { Keyword::FROM })?; + let grantees = self.parse_grantees()?; + + let operation = if is_grant { + AlterDefaultPrivilegesOperation::Grant { + privileges, + object_type, + grantees, + with_grant_option: self.parse_keywords(&[ + Keyword::WITH, + Keyword::GRANT, + Keyword::OPTION, + ]), + } + } else { + AlterDefaultPrivilegesOperation::Revoke { + grant_option_for, + privileges, + object_type, + grantees, + cascade: self.parse_cascade_option(), + } + }; + + Ok(AlterDefaultPrivileges { + for_roles, + in_schemas, + operation, + }) + } + + /// Parse `ALL [PRIVILEGES]` or a comma separated privilege list. + fn parse_privileges(&mut self) -> Result { + if self.parse_keyword(Keyword::ALL) { + Ok(Privileges::All { + with_privileges_keyword: self.parse_keyword(Keyword::PRIVILEGES), + }) + } else { + Ok(Privileges::Actions(self.parse_actions_list()?)) + } + } + + fn parse_default_privileges_object_type( + &mut self, + ) -> Result { + for (keyword, object_type) in [ + (Keyword::TABLES, DefaultPrivilegesObjectType::Tables), + (Keyword::SEQUENCES, DefaultPrivilegesObjectType::Sequences), + (Keyword::FUNCTIONS, DefaultPrivilegesObjectType::Functions), + (Keyword::ROUTINES, DefaultPrivilegesObjectType::Routines), + (Keyword::TYPES, DefaultPrivilegesObjectType::Types), + (Keyword::SCHEMAS, DefaultPrivilegesObjectType::Schemas), + ] { + if self.parse_keyword(keyword) { + return Ok(object_type); + } + } + self.expected_ref( + "TABLES, SEQUENCES, FUNCTIONS, ROUTINES, TYPES or SCHEMAS", + self.peek_token_ref(), + ) + } + /// Parse an REPLACE statement pub fn parse_replace( &mut self, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a7128eafd..2818b3bb0 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9663,3 +9663,33 @@ fn parse_right_deep_join_chain() { // NATURAL JOIN followed by a constrained join must stay left-associative. pg().verified_stmt("SELECT * FROM t0 NATURAL JOIN t1 INNER JOIN t2 ON true"); } + +#[test] +fn parse_alter_default_privileges() { + // What `pg_dump -s` emits for a default ACL. + pg().verified_stmt("ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT SELECT ON TABLES TO app_user"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES FOR ROLE a, b IN SCHEMA s1, s2 GRANT SELECT, INSERT ON TABLES TO app WITH GRANT OPTION"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT ALL PRIVILEGES ON SEQUENCES TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON ROUTINES TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT USAGE ON TYPES TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT USAGE, CREATE ON SCHEMAS TO app"); + pg().verified_stmt("ALTER DEFAULT PRIVILEGES REVOKE SELECT ON TABLES FROM app"); + pg().verified_stmt( + "ALTER DEFAULT PRIVILEGES REVOKE GRANT OPTION FOR ALL ON TABLES FROM app CASCADE", + ); + // `FOR USER` normalises to `FOR ROLE`. + pg().one_statement_parses_to( + "ALTER DEFAULT PRIVILEGES FOR USER bob GRANT SELECT ON TABLES TO app", + "ALTER DEFAULT PRIVILEGES FOR ROLE bob GRANT SELECT ON TABLES TO app", + ); + + // `TABLES` is a keyword only here, so a table named `tables` still parses. + pg().verified_stmt("GRANT SELECT ON TABLES TO app"); + assert_eq!( + pg().parse_sql_statements("ALTER DEFAULT PRIVILEGES FOR ROLE a") + .unwrap_err(), + ParserError::ParserError("Expected: GRANT or REVOKE, found: EOF".to_string()), + ); +}