diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 96b340714c..096c6eca29 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -535,3 +535,167 @@ impl From for crate::ast::Statement { crate::ast::Statement::Revoke(v) } } + +/// The options of [`AlterDefaultPrivileges`], kept in the order written. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum DefaultPrivilegesOption { + /// `FOR ROLE [, ...]`. + ForRole(Vec), + /// `IN SCHEMA [, ...]`. + InSchema(Vec), +} + +impl fmt::Display for DefaultPrivilegesOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DefaultPrivilegesOption::ForRole(roles) => { + write!(f, "FOR ROLE {}", display_comma_separated(roles)) + } + DefaultPrivilegesOption::InSchema(schemas) => { + write!(f, "IN SCHEMA {}", display_comma_separated(schemas)) + } + } + } +} + +/// `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 { + /// The `FOR ROLE` and `IN SCHEMA` options, in written order. + pub options: 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")?; + for option in &self.options { + write!(f, " {option}")?; + } + 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, + /// `LARGE OBJECTS` + LargeObjects, +} + +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", + DefaultPrivilegesObjectType::LargeObjects => "LARGE OBJECTS", + }) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index eb5bc6cc75..1519623e2e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -58,8 +58,9 @@ pub use self::data_type::{ ExactNumberInfo, IntervalFields, MapBracketKind, StructBracketKind, TimezoneInfo, }; pub use self::dcl::{ - AlterRoleOperation, CreateRole, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles, - SetConfigValue, Use, + AlterDefaultPrivileges, AlterDefaultPrivilegesOperation, AlterRoleOperation, CreateRole, + DefaultPrivilegesObjectType, DefaultPrivilegesOption, Grant, ResetConfig, Revoke, RoleOption, + SecondaryRoles, SetConfigValue, Use, }; pub use self::ddl::{ Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation, AlterConnectorOwner, @@ -4636,6 +4637,10 @@ pub enum Statement { /// ``` Revoke(Revoke), /// ```sql + /// ALTER DEFAULT PRIVILEGES + /// ``` + AlterDefaultPrivileges(AlterDefaultPrivileges), + /// ```sql /// DEALLOCATE [ PREPARE ] { name | ALL } /// ``` /// @@ -6146,6 +6151,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 8267c71798..d8779f32b7 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -28,26 +28,32 @@ use core::iter; use crate::tokenizer::Span; use super::{ - comments, dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation, - AlterIndexOperation, AlterTableOperation, Analyze, Array, Assignment, AssignmentTarget, - AttachedToken, BeginEndStatements, CaseStatement, CloseCursor, ClusteredIndex, ColumnDef, - ColumnOption, ColumnOptionDef, ConditionalStatementBlock, ConditionalStatements, - ConflictTarget, ConnectByKind, ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, - CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeConstraintElement, - ExcludeSelectItem, Expr, ExprWithAlias, Fetch, ForValues, FromTable, Function, FunctionArg, - FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, - HavingBound, IfStatement, IlikeSelectItem, InTarget, IndexColumn, Insert, Interpolate, - InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, - LimitClause, MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr, - MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList, - NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, - OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition, - PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement, - RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, - ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement, - Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, - TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, - ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill, + comments, + dcl::{ + AlterDefaultPrivileges, AlterDefaultPrivilegesOperation, DefaultPrivilegesOption, + SecondaryRoles, + }, + value::ValueWithSpan, + AccessExpr, AlterColumnOperation, AlterIndexOperation, AlterTableOperation, Analyze, Array, + Assignment, AssignmentTarget, AttachedToken, BeginEndStatements, CaseStatement, CloseCursor, + ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef, ConditionalStatementBlock, + ConditionalStatements, ConflictTarget, ConnectByKind, ConstraintCharacteristics, CopySource, + CreateIndex, CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, + ExcludeConstraintElement, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, ForValues, FromTable, + Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, + FunctionArguments, GranteeName, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, + InTarget, IndexColumn, Insert, Interpolate, InterpolateExpr, Join, JoinConstraint, + JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause, MatchRecognizePattern, Measure, + Merge, MergeAction, MergeClause, MergeInsertExpr, MergeInsertKind, MergeUpdateExpr, + MergeUpdateKind, NamedParenthesizedList, NamedWindowDefinition, ObjectName, ObjectNamePart, + Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy, OrderByExpr, + OrderByKind, OutputClause, Parens, Partition, PartitionBoundValue, PivotValueSource, + ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction, + RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, + SetExpr, Setting, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, + TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered, + TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef, WhileStatement, + WildcardAdditionalOptions, With, WithFill, }; /// Given an iterator of spans, return the [Span::union] of all spans. @@ -467,6 +473,7 @@ impl Spanned for Statement { Statement::Grant { .. } => Span::empty(), Statement::Deny { .. } => Span::empty(), Statement::Revoke { .. } => Span::empty(), + Statement::AlterDefaultPrivileges(privileges) => privileges.span(), Statement::Deallocate { .. } => Span::empty(), Statement::Execute { .. } => Span::empty(), Statement::Prepare { .. } => Span::empty(), @@ -528,6 +535,34 @@ impl Spanned for Statement { } } +impl Spanned for AlterDefaultPrivileges { + fn span(&self) -> Span { + let options = self.options.iter().map(|option| match option { + DefaultPrivilegesOption::ForRole(roles) => { + union_spans(roles.iter().map(|role| role.span)) + } + DefaultPrivilegesOption::InSchema(schemas) => { + union_spans(schemas.iter().map(Spanned::span)) + } + }); + let grantees = match &self.operation { + AlterDefaultPrivilegesOperation::Grant { grantees, .. } + | AlterDefaultPrivilegesOperation::Revoke { grantees, .. } => grantees, + }; + union_spans( + options.chain( + grantees + .iter() + .filter_map(|grantee| grantee.name.as_ref()) + .map(|name| match name { + GranteeName::ObjectName(name) => name.span(), + GranteeName::UserHost { user, host } => user.span.union(&host.span), + }), + ), + ) + } +} + impl Spanned for Use { fn span(&self) -> Span { match self { diff --git a/src/keywords.rs b/src/keywords.rs index 7e93822416..267bb49f84 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -914,6 +914,7 @@ define_keywords!( ROLLBACK, ROLLUP, ROOT, + ROUTINES, ROW, ROWGROUPSIZE, ROWID, @@ -1101,6 +1102,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 276b252ed2..00b95b3043 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11376,6 +11376,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, @@ -18206,14 +18210,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]) { @@ -18745,6 +18742,118 @@ 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 mut options = Vec::new(); + let mut for_roles = false; + let mut in_schemas = false; + loop { + if self.parse_keyword(Keyword::FOR) { + if for_roles { + return Err(ParserError::ParserError( + "FOR ROLE specified more than once".to_string(), + )); + } + self.expect_one_of_keywords(&[Keyword::ROLE, Keyword::USER])?; + for_roles = true; + options.push(DefaultPrivilegesOption::ForRole( + self.parse_comma_separated(|p| p.parse_identifier())?, + )); + } else if self.parse_keywords(&[Keyword::IN, Keyword::SCHEMA]) { + if in_schemas { + return Err(ParserError::ParserError( + "IN SCHEMA specified more than once".to_string(), + )); + } + in_schemas = true; + options.push(DefaultPrivilegesOption::InSchema( + self.parse_comma_separated(|p| p.parse_object_name(false))?, + )); + } else { + break; + } + } + + 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 { options, 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 { + if self.parse_keywords(&[Keyword::LARGE, Keyword::OBJECTS]) { + return Ok(DefaultPrivilegesObjectType::LargeObjects); + } + 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, SCHEMAS or LARGE OBJECTS", + 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 0a3866d29f..2bf59872b0 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10265,3 +10265,189 @@ fn parse_geometric_typed_literal_error_propagates() { ParserError::ParserError("Expected: a value, found: (".to_string()) ); } + +#[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", + ); + + // Options keep the order written. + for (sql, options) in [ + ( + "ALTER DEFAULT PRIVILEGES IN SCHEMA s FOR ROLE r GRANT SELECT ON TABLES TO app", + vec![ + DefaultPrivilegesOption::InSchema(vec![ObjectName::from(Ident::new("s"))]), + DefaultPrivilegesOption::ForRole(vec![Ident::new("r")]), + ], + ), + ( + "ALTER DEFAULT PRIVILEGES FOR ROLE r IN SCHEMA s GRANT SELECT ON TABLES TO app", + vec![ + DefaultPrivilegesOption::ForRole(vec![Ident::new("r")]), + DefaultPrivilegesOption::InSchema(vec![ObjectName::from(Ident::new("s"))]), + ], + ), + ] { + let mut stmts = pg().parse_sql_statements(sql).unwrap(); + match stmts.pop().unwrap() { + Statement::AlterDefaultPrivileges(stmt) => assert_eq!(stmt.options, options, "{sql}"), + _ => unreachable!(), + } + } + // Reversed option order round-trips unchanged. + pg().verified_stmt("ALTER DEFAULT PRIVILEGES IN SCHEMA public FOR ROLE postgres GRANT SELECT ON TABLES TO app_user"); + // `LARGE OBJECTS` is a valid object type for both `GRANT` and `REVOKE`. + pg().verified_stmt("ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO app"); + pg().verified_stmt( + "ALTER DEFAULT PRIVILEGES REVOKE GRANT OPTION FOR SELECT ON LARGE OBJECTS FROM app CASCADE", + ); + // Repeated options are rejected. + assert_eq!( + pg().parse_sql_statements( + "ALTER DEFAULT PRIVILEGES FOR ROLE a FOR ROLE b GRANT SELECT ON TABLES TO app" + ) + .unwrap_err(), + ParserError::ParserError("FOR ROLE specified more than once".to_string()), + ); + assert_eq!( + pg().parse_sql_statements( + "ALTER DEFAULT PRIVILEGES IN SCHEMA s IN SCHEMA t GRANT SELECT ON TABLES TO app" + ) + .unwrap_err(), + ParserError::ParserError("IN SCHEMA specified more than once".to_string()), + ); + + // `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()), + ); +} + +#[test] +fn parse_alter_default_privileges_rejects_malformed_statements() { + for (sql, expected) in [ + ( + "ALTER DEFAULT PRIVILEGES FOR ALL GRANT SELECT ON TABLES TO app", + "Expected: one of ROLE or USER, found: ALL", + ), + ( + "ALTER DEFAULT PRIVILEGES FOR ROLE", + "Expected: identifier, found: EOF", + ), + ( + "ALTER DEFAULT PRIVILEGES IN SCHEMA", + "Expected: identifier, found: EOF", + ), + ( + "ALTER DEFAULT PRIVILEGES GRANT ON TABLES TO app", + "Expected: a privilege keyword, found: ON", + ), + ( + "ALTER DEFAULT PRIVILEGES GRANT SELECT TO app", + "Expected: ON, found: TO", + ), + ( + "ALTER DEFAULT PRIVILEGES GRANT SELECT ON INDEXES TO app", + "Expected: TABLES, SEQUENCES, FUNCTIONS, ROUTINES, TYPES, SCHEMAS or LARGE OBJECTS, found: INDEXES", + ), + ( + "ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES", + "Expected: TO, found: EOF", + ), + ( + "ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO", + "Expected: identifier, found: EOF", + ), + ] { + assert_eq!( + pg().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError(expected.to_string()), + ); + } +} + +#[test] +fn display_alter_default_privileges_propagates_write_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 = pg().verified_stmt( + "ALTER DEFAULT PRIVILEGES FOR ROLE r IN SCHEMA s GRANT SELECT ON TABLES TO g WITH GRANT OPTION", + ); + for rejected in ["D", "r", "g", "W"] { + let mut writer = RejectingWriter(rejected); + assert_eq!(write!(writer, "{grant}"), Err(fmt::Error)); + } + + let revoke = pg().verified_stmt( + "ALTER DEFAULT PRIVILEGES REVOKE GRANT OPTION FOR SELECT ON TABLES FROM g CASCADE", + ); + for rejected in ["K", "N", "g", "CASCADE"] { + let mut writer = RejectingWriter(rejected); + assert_eq!(write!(writer, "{revoke}"), Err(fmt::Error)); + } +} + +#[test] +fn parse_alter_default_privileges_spans() { + for (sql, start, end) in [ + ( + "ALTER DEFAULT PRIVILEGES FOR ROLE r IN SCHEMA s GRANT SELECT ON TABLES TO g", + 35, + 76, + ), + ( + "ALTER DEFAULT PRIVILEGES REVOKE SELECT ON TABLES FROM g", + 55, + 56, + ), + ( + "ALTER DEFAULT PRIVILEGES FOR ROLE r GRANT SELECT ON TABLES TO PUBLIC", + 35, + 36, + ), + ] { + let statements = Parser::parse_sql(&PostgreSqlDialect {}, sql).unwrap(); + assert_eq!( + statements[0].span(), + Span::new(Location::new(1, start), Location::new(1, end)), + "span for {sql}" + ); + } + let sql = "ALTER DEFAULT PRIVILEGES REVOKE SELECT ON TABLES FROM 'bob'@'localhost'"; + let statements = Parser::parse_sql(&sqlparser::dialect::GenericDialect {}, sql).unwrap(); + assert_eq!( + statements[0].span(), + Span::new(Location::new(1, 55), Location::new(1, 72)) + ); +}