diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0fa3841fd5..054ea86605 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -7779,6 +7779,16 @@ pub enum GrantObjects { /// The target schema names. schemas: Vec, }, + /// Grant privileges on `ALL PROCEDURES IN SCHEMA [, ...]` + AllProceduresInSchema { + /// The target schema names. + schemas: Vec, + }, + /// Grant privileges on `ALL ROUTINES IN SCHEMA [, ...]` + AllRoutinesInSchema { + /// The target schema names. + schemas: Vec, + }, /// Grant privileges on `FUTURE SCHEMAS IN DATABASE [, ...]` FutureSchemasInDatabase { /// The target database names. @@ -7847,6 +7857,13 @@ pub enum GrantObjects { /// For example: /// `GRANT EXECUTE ON FUNCTION foo(p_id uuid), bar() TO role1` Functions(Vec), + /// Grant privileges on routines, which name functions and procedures alike. + /// + /// For example: + /// `GRANT EXECUTE ON ROUTINE foo(p_id uuid), bar() TO role1` + /// + /// [PostgreSQL](https://www.postgresql.org/docs/current/sql-grant.html) + Routines(Vec), } impl fmt::Display for GrantObjects { @@ -7915,6 +7932,20 @@ impl fmt::Display for GrantObjects { display_comma_separated(schemas) ) } + GrantObjects::AllProceduresInSchema { schemas } => { + write!( + f, + "ALL PROCEDURES IN SCHEMA {}", + display_comma_separated(schemas) + ) + } + GrantObjects::AllRoutinesInSchema { schemas } => { + write!( + f, + "ALL ROUTINES IN SCHEMA {}", + display_comma_separated(schemas) + ) + } GrantObjects::FutureSchemasInDatabase { databases } => { write!( f, @@ -7984,6 +8015,9 @@ impl fmt::Display for GrantObjects { GrantObjects::Functions(functions) => { write!(f, "FUNCTION {}", display_comma_separated(functions)) } + GrantObjects::Routines(routines) => { + write!(f, "ROUTINE {}", display_comma_separated(routines)) + } } } } diff --git a/src/keywords.rs b/src/keywords.rs index b35644befb..561919b6e1 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -845,6 +845,7 @@ define_keywords!( PRIVILEGES, PROC, PROCEDURE, + PROCEDURES, PROCESSLIST, PROFILE, PROGRAM, @@ -934,6 +935,8 @@ define_keywords!( ROLLBACK, ROLLUP, ROOT, + ROUTINE, + ROUTINES, ROW, ROWGROUPSIZE, ROWID, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 90296755ab..49adbf0228 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18687,6 +18687,24 @@ impl<'a> Parser<'a> { Some(GrantObjects::AllFunctionsInSchema { schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, }) + } else if self.parse_keywords(&[ + Keyword::ALL, + Keyword::PROCEDURES, + Keyword::IN, + Keyword::SCHEMA, + ]) { + Some(GrantObjects::AllProceduresInSchema { + schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, + }) + } else if self.parse_keywords(&[ + Keyword::ALL, + Keyword::ROUTINES, + Keyword::IN, + Keyword::SCHEMA, + ]) { + Some(GrantObjects::AllRoutinesInSchema { + schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, + }) } else if self.parse_keywords(&[ Keyword::FUTURE, Keyword::SCHEMAS, @@ -18780,6 +18798,10 @@ impl<'a> Parser<'a> { Some(GrantObjects::Procedures( self.parse_comma_separated(Parser::parse_function_desc)?, )) + } else if self.parse_keyword(Keyword::ROUTINE) { + Some(GrantObjects::Routines( + self.parse_comma_separated(Parser::parse_function_desc)?, + )) } else { let object_type = self.parse_one_of_keywords(&[ Keyword::SEQUENCE, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 379e37a660..658dfeca86 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -22744,3 +22744,57 @@ fn display_create_procedure_parameter_list_propagates_write_errors() { let mut writer = RejectingWriter(" ("); assert_eq!(write!(writer, "{statement}"), Err(fmt::Error)); } + +#[test] +fn parse_grant_routines() { + let objects = |sql: &str| match verified_stmt(sql) { + Statement::Grant(grant) => grant.objects, + Statement::Revoke(revoke) => revoke.objects, + stmt => panic!("unexpected statement: {stmt:?}"), + }; + let name = |name: &str| ObjectName::from(vec![Ident::new(name)]); + + assert_eq!( + objects("GRANT EXECUTE ON ROUTINE f(INT) TO bob"), + Some(GrantObjects::Routines(vec![FunctionDesc { + name: name("f"), + args: Some(vec![OperateFunctionArg::unnamed(DataType::Int(None))]), + }])) + ); + assert_eq!( + objects("REVOKE ALL ON ROUTINE f, g() FROM bob"), + Some(GrantObjects::Routines(vec![ + FunctionDesc { + name: name("f"), + args: None, + }, + FunctionDesc { + name: name("g"), + args: Some(vec![]), + }, + ])) + ); + assert_eq!( + objects("GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA s TO bob"), + Some(GrantObjects::AllProceduresInSchema { + schemas: vec![name("s")], + }) + ); + assert_eq!( + objects("REVOKE EXECUTE ON ALL ROUTINES IN SCHEMA s, t FROM bob"), + Some(GrantObjects::AllRoutinesInSchema { + schemas: vec![name("s"), name("t")], + }) + ); + + verified_stmt("REVOKE ALL ON ROUTINE public.f(p_id UUID), g() FROM bob"); + verified_stmt("REVOKE EXECUTE ON ALL PROCEDURES IN SCHEMA s, t FROM bob"); + verified_stmt("GRANT EXECUTE ON ALL ROUTINES IN SCHEMA s TO bob"); + + assert_eq!( + all_dialects() + .parse_sql_statements("GRANT EXECUTE ON ROUTINE f(INT") + .unwrap_err(), + ParserError::ParserError("Expected: ), found: EOF".to_string()) + ); +}