Skip to content

Commit 16cedc5

Browse files
Snowflake: CREATE PROCEDURE EXECUTE AS { CALLER | OWNER }
1 parent a09d0ae commit 16cedc5

6 files changed

Lines changed: 112 additions & 1 deletion

File tree

src/ast/ddl.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,30 @@ impl fmt::Display for ProcedureParam {
15351535
}
15361536
}
15371537

1538+
/// Snowflake `EXECUTE AS { CALLER | OWNER }` rights on a stored procedure.
1539+
///
1540+
/// When the clause is omitted the procedure runs with owner's rights, so an
1541+
/// absent clause (`None` on `Statement::CreateProcedure`) is equivalent to
1542+
/// `Owner`; `Caller` is the only value that changes the default behaviour.
1543+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
1544+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1545+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1546+
pub enum ProcedureExecuteAs {
1547+
/// `EXECUTE AS CALLER` — run with the caller's rights.
1548+
Caller,
1549+
/// `EXECUTE AS OWNER` — run with the owner's rights (Snowflake default).
1550+
Owner,
1551+
}
1552+
1553+
impl fmt::Display for ProcedureExecuteAs {
1554+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1555+
match self {
1556+
ProcedureExecuteAs::Caller => write!(f, "CALLER"),
1557+
ProcedureExecuteAs::Owner => write!(f, "OWNER"),
1558+
}
1559+
}
1560+
}
1561+
15381562
/// SQL column definition
15391563
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
15401564
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

src/ast/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ pub use self::ddl::{
8080
IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType,
8181
KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem,
8282
OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition,
83-
PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
83+
PartitionBoundValue, ProcedureExecuteAs, ProcedureParam, ReferentialAction, RenameTableNameKind,
84+
ReplicaIdentity,
8485
TagsColumnOption, TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
8586
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
8687
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
@@ -4730,6 +4731,11 @@ pub enum Statement {
47304731
returns: Option<DataType>,
47314732
/// Optional language identifier.
47324733
language: Option<Ident>,
4734+
/// Optional `EXECUTE AS { CALLER | OWNER }` rights clause (Snowflake).
4735+
///
4736+
/// `None` means the clause was omitted, which Snowflake treats as
4737+
/// owner's rights — i.e. absent is equivalent to `Some(Owner)`.
4738+
execute_as: Option<ProcedureExecuteAs>,
47334739
/// Procedure body statements.
47344740
body: ConditionalStatements,
47354741
},
@@ -6071,6 +6077,7 @@ impl fmt::Display for Statement {
60716077
params,
60726078
returns,
60736079
language,
6080+
execute_as,
60746081
body,
60756082
} => {
60766083
let modifier = if *or_alter {
@@ -6096,6 +6103,10 @@ impl fmt::Display for Statement {
60966103
write!(f, " LANGUAGE {language}")?;
60976104
}
60986105

6106+
if let Some(execute_as) = execute_as {
6107+
write!(f, " EXECUTE AS {execute_as}")?;
6108+
}
6109+
60996110
write!(f, " AS {body}")
61006111
}
61016112
Statement::CreateMacro {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ define_keywords!(
196196
CACHE,
197197
CALL,
198198
CALLED,
199+
CALLER,
199200
CANONICAL,
200201
CARDINALITY,
201202
CASCADE,

src/parser/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20443,6 +20443,21 @@ impl<'a> Parser<'a> {
2044320443
None
2044420444
};
2044520445

20446+
// Snowflake's `EXECUTE AS { CALLER | OWNER }` rights clause sits among
20447+
// the create options before the body `AS`. Only the two-keyword
20448+
// `EXECUTE AS` sequence is the rights clause; a standalone `AS`
20449+
// delimits the body below.
20450+
let execute_as = if self.parse_keywords(&[Keyword::EXECUTE, Keyword::AS]) {
20451+
if self.parse_keyword(Keyword::CALLER) {
20452+
Some(ProcedureExecuteAs::Caller)
20453+
} else {
20454+
self.expect_keyword_is(Keyword::OWNER)?;
20455+
Some(ProcedureExecuteAs::Owner)
20456+
}
20457+
} else {
20458+
None
20459+
};
20460+
2044620461
self.expect_keyword_is(Keyword::AS)?;
2044720462

2044820463
// Snowflake encloses the procedure body in a dollar-quoted string
@@ -20466,6 +20481,7 @@ impl<'a> Parser<'a> {
2046620481
params,
2046720482
returns,
2046820483
language,
20484+
execute_as,
2046920485
body,
2047020486
})
2047120487
}

tests/sqlparser_mssql.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ fn parse_create_procedure() {
204204
span: Span::empty(),
205205
}]),
206206
language: None,
207+
execute_as: None,
207208
}
208209
)
209210
}

tests/sqlparser_snowflake.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5405,6 +5405,64 @@ END $$"#;
54055405
assert_eq!(stmts[0], stmts_plain[0]);
54065406
}
54075407

5408+
/// `EXECUTE AS { CALLER | OWNER }` rights clause parses, is recoverable from
5409+
/// the AST, and round-trips through `Display`. An omitted clause is reported
5410+
/// as owner's-rights (`None`).
5411+
#[test]
5412+
fn test_create_procedure_execute_as() {
5413+
fn execute_as_of(sql: &str) -> Option<ProcedureExecuteAs> {
5414+
let stmts = snowflake()
5415+
.parse_sql_statements(sql)
5416+
.expect("EXECUTE AS procedure should parse");
5417+
match &stmts[0] {
5418+
Statement::CreateProcedure { execute_as, .. } => *execute_as,
5419+
other => panic!("expected CreateProcedure, got {other:?}"),
5420+
}
5421+
}
5422+
5423+
let caller = r#"CREATE OR REPLACE PROCEDURE p(a INT) RETURNS VARCHAR LANGUAGE SQL EXECUTE AS CALLER AS $$
5424+
BEGIN
5425+
RETURN 'x';
5426+
END $$"#;
5427+
assert_eq!(execute_as_of(caller), Some(ProcedureExecuteAs::Caller));
5428+
5429+
let owner = r#"CREATE OR REPLACE PROCEDURE p(a INT) RETURNS VARCHAR LANGUAGE SQL EXECUTE AS OWNER AS $$
5430+
BEGIN
5431+
RETURN 'x';
5432+
END $$"#;
5433+
assert_eq!(execute_as_of(owner), Some(ProcedureExecuteAs::Owner));
5434+
5435+
let omitted = r#"CREATE OR REPLACE PROCEDURE p(a INT) RETURNS VARCHAR LANGUAGE SQL AS $$
5436+
BEGIN
5437+
RETURN 'x';
5438+
END $$"#;
5439+
assert_eq!(execute_as_of(omitted), None);
5440+
5441+
// A `CALLER` declaration is distinguishable from `OWNER`/omitted.
5442+
assert_ne!(execute_as_of(caller), execute_as_of(owner));
5443+
assert_ne!(execute_as_of(caller), execute_as_of(omitted));
5444+
5445+
// parse → Display → parse round-trips for all three forms.
5446+
for sql in [caller, owner, omitted] {
5447+
let first = snowflake().parse_sql_statements(sql).unwrap();
5448+
let rendered = first[0].to_string();
5449+
let second = snowflake()
5450+
.parse_sql_statements(&rendered)
5451+
.expect("re-parse of Display output should succeed");
5452+
assert_eq!(first[0], second[0]);
5453+
}
5454+
5455+
// The omitted form does not gain a spurious `EXECUTE AS` rendering.
5456+
let rendered_omitted = snowflake().parse_sql_statements(omitted).unwrap()[0].to_string();
5457+
assert!(!rendered_omitted.contains("EXECUTE AS"));
5458+
assert!(snowflake().parse_sql_statements(caller).unwrap()[0]
5459+
.to_string()
5460+
.contains("EXECUTE AS CALLER"));
5461+
assert!(snowflake().parse_sql_statements(owner).unwrap()[0]
5462+
.to_string()
5463+
.contains("EXECUTE AS OWNER"));
5464+
}
5465+
54085466
/// `LET var := expr` (without data type) inside `BEGIN...END`.
54095467
#[test]
54105468
fn test_scripting_let_without_type() {

0 commit comments

Comments
 (0)