Skip to content

Commit 9e05f19

Browse files
iffyioayman-sigma
authored andcommitted
Add support for EXECUTE IMMEDIATE (apache#1717)
1 parent 303dfb2 commit 9e05f19

File tree

7 files changed

+122
-35
lines changed

7 files changed

+122
-35
lines changed

src/ast/mod.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,18 +3287,21 @@ pub enum Statement {
32873287
/// Note: this is a PostgreSQL-specific statement.
32883288
Deallocate { name: Ident, prepare: bool },
32893289
/// ```sql
3290-
/// EXECUTE name [ ( parameter [, ...] ) ] [USING <expr>]
3290+
/// An `EXECUTE` statement
32913291
/// ```
32923292
///
3293-
/// Note: this statement is supported by Postgres and MSSQL, with slight differences in syntax.
3294-
///
32953293
/// Postgres: <https://www.postgresql.org/docs/current/sql-execute.html>
32963294
/// MSSQL: <https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/execute-a-stored-procedure>
3295+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
3296+
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
32973297
Execute {
3298-
name: ObjectName,
3298+
name: Option<ObjectName>,
32993299
parameters: Vec<Expr>,
33003300
has_parentheses: bool,
3301-
using: Vec<Expr>,
3301+
/// Is this an `EXECUTE IMMEDIATE`
3302+
immediate: bool,
3303+
into: Vec<Ident>,
3304+
using: Vec<ExprWithAlias>,
33023305
},
33033306
/// ```sql
33043307
/// PREPARE name [ ( data_type [, ...] ) ] AS statement
@@ -4907,18 +4910,26 @@ impl fmt::Display for Statement {
49074910
name,
49084911
parameters,
49094912
has_parentheses,
4913+
immediate,
4914+
into,
49104915
using,
49114916
} => {
49124917
let (open, close) = if *has_parentheses {
49134918
("(", ")")
49144919
} else {
49154920
(if parameters.is_empty() { "" } else { " " }, "")
49164921
};
4917-
write!(
4918-
f,
4919-
"EXECUTE {name}{open}{}{close}",
4920-
display_comma_separated(parameters),
4921-
)?;
4922+
write!(f, "EXECUTE")?;
4923+
if *immediate {
4924+
write!(f, " IMMEDIATE")?;
4925+
}
4926+
if let Some(name) = name {
4927+
write!(f, " {name}")?;
4928+
}
4929+
write!(f, "{open}{}{close}", display_comma_separated(parameters),)?;
4930+
if !into.is_empty() {
4931+
write!(f, " INTO {}", display_comma_separated(into))?;
4932+
}
49224933
if !using.is_empty() {
49234934
write!(f, " USING {}", display_comma_separated(using))?;
49244935
};

src/dialect/bigquery.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ impl Dialect for BigQueryDialect {
110110
true
111111
}
112112

113+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
114+
fn supports_execute_immediate(&self) -> bool {
115+
true
116+
}
117+
113118
// See <https://cloud.google.com/bigquery/docs/access-historical-data>
114119
fn supports_timestamp_versioning(&self) -> bool {
115120
true

src/dialect/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ pub trait Dialect: Debug + Any {
261261
false
262262
}
263263

264+
/// Returns true if the dialect supports `EXECUTE IMMEDIATE` statements.
265+
fn supports_execute_immediate(&self) -> bool {
266+
false
267+
}
268+
264269
/// Returns true if the dialect supports the MATCH_RECOGNIZE operation.
265270
fn supports_match_recognize(&self) -> bool {
266271
false

src/dialect/snowflake.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ impl Dialect for SnowflakeDialect {
9696
true
9797
}
9898

99+
/// See <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
100+
fn supports_execute_immediate(&self) -> bool {
101+
true
102+
}
103+
99104
fn supports_match_recognize(&self) -> bool {
100105
true
101106
}

src/parser/mod.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13859,7 +13859,14 @@ impl<'a> Parser<'a> {
1385913859
}
1386013860

1386113861
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
13862-
let name = self.parse_object_name(false)?;
13862+
let name = if self.dialect.supports_execute_immediate()
13863+
&& self.parse_keyword(Keyword::IMMEDIATE)
13864+
{
13865+
None
13866+
} else {
13867+
let name = self.parse_object_name(false)?;
13868+
Some(name)
13869+
};
1386313870

1386413871
let has_parentheses = self.consume_token(&Token::LParen);
1386513872

@@ -13876,19 +13883,24 @@ impl<'a> Parser<'a> {
1387613883
self.expect_token(&Token::RParen)?;
1387713884
}
1387813885

13879-
let mut using = vec![];
13880-
if self.parse_keyword(Keyword::USING) {
13881-
using.push(self.parse_expr()?);
13886+
let into = if self.parse_keyword(Keyword::INTO) {
13887+
self.parse_comma_separated(Self::parse_identifier)?
13888+
} else {
13889+
vec![]
13890+
};
1388213891

13883-
while self.consume_token(&Token::Comma) {
13884-
using.push(self.parse_expr()?);
13885-
}
13892+
let using = if self.parse_keyword(Keyword::USING) {
13893+
self.parse_comma_separated(Self::parse_expr_with_alias)?
13894+
} else {
13895+
vec![]
1388613896
};
1388713897

1388813898
Ok(Statement::Execute {
13899+
immediate: name.is_none(),
1388913900
name,
1389013901
parameters,
1389113902
has_parentheses,
13903+
into,
1389213904
using,
1389313905
})
1389413906
}

tests/sqlparser_common.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10748,7 +10748,7 @@ fn parse_call() {
1074810748
#[test]
1074910749
fn parse_execute_stored_procedure() {
1075010750
let expected = Statement::Execute {
10751-
name: ObjectName::from(vec![
10751+
name: Some(ObjectName::from(vec![
1075210752
Ident {
1075310753
value: "my_schema".to_string(),
1075410754
quote_style: None,
@@ -10759,13 +10759,15 @@ fn parse_execute_stored_procedure() {
1075910759
quote_style: None,
1076010760
span: Span::empty(),
1076110761
},
10762-
]),
10762+
])),
1076310763
parameters: vec![
1076410764
Expr::Value(Value::NationalStringLiteral("param1".to_string())),
1076510765
Expr::Value(Value::NationalStringLiteral("param2".to_string())),
1076610766
],
1076710767
has_parentheses: false,
10768+
immediate: false,
1076810769
using: vec![],
10770+
into: vec![],
1076910771
};
1077010772
assert_eq!(
1077110773
// Microsoft SQL Server does not use parentheses around arguments for EXECUTE
@@ -10782,6 +10784,41 @@ fn parse_execute_stored_procedure() {
1078210784
);
1078310785
}
1078410786

10787+
#[test]
10788+
fn parse_execute_immediate() {
10789+
let dialects = all_dialects_where(|d| d.supports_execute_immediate());
10790+
10791+
let expected = Statement::Execute {
10792+
parameters: vec![Expr::Value(Value::SingleQuotedString(
10793+
"SELECT 1".to_string(),
10794+
))],
10795+
immediate: true,
10796+
using: vec![ExprWithAlias {
10797+
expr: Expr::Value(number("1")),
10798+
alias: Some(Ident::new("b")),
10799+
}],
10800+
into: vec![Ident::new("a")],
10801+
name: None,
10802+
has_parentheses: false,
10803+
};
10804+
10805+
let stmt = dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a USING 1 AS b");
10806+
assert_eq!(expected, stmt);
10807+
10808+
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a, b USING 1 AS x, y");
10809+
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' USING 1 AS x, y");
10810+
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a, b");
10811+
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1'");
10812+
dialects.verified_stmt("EXECUTE 'SELECT 1'");
10813+
10814+
assert_eq!(
10815+
ParserError::ParserError("Expected: identifier, found: ,".to_string()),
10816+
dialects
10817+
.parse_sql_statements("EXECUTE IMMEDIATE 'SELECT 1' USING 1 AS, y")
10818+
.unwrap_err()
10819+
);
10820+
}
10821+
1078510822
#[test]
1078610823
fn parse_create_table_collate() {
1078710824
pg_and_generic().verified_stmt("CREATE TABLE tbl (foo INT, bar TEXT COLLATE \"de_DE\")");

tests/sqlparser_postgres.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,24 +1659,28 @@ fn parse_execute() {
16591659
assert_eq!(
16601660
stmt,
16611661
Statement::Execute {
1662-
name: ObjectName::from(vec!["a".into()]),
1662+
name: Some(ObjectName::from(vec!["a".into()])),
16631663
parameters: vec![],
16641664
has_parentheses: false,
1665-
using: vec![]
1665+
using: vec![],
1666+
immediate: false,
1667+
into: vec![]
16661668
}
16671669
);
16681670

16691671
let stmt = pg_and_generic().verified_stmt("EXECUTE a(1, 't')");
16701672
assert_eq!(
16711673
stmt,
16721674
Statement::Execute {
1673-
name: ObjectName::from(vec!["a".into()]),
1675+
name: Some(ObjectName::from(vec!["a".into()])),
16741676
parameters: vec![
16751677
Expr::Value(number("1")),
16761678
Expr::Value(Value::SingleQuotedString("t".to_string()))
16771679
],
16781680
has_parentheses: true,
1679-
using: vec![]
1681+
using: vec![],
1682+
immediate: false,
1683+
into: vec![]
16801684
}
16811685
);
16821686

@@ -1685,23 +1689,31 @@ fn parse_execute() {
16851689
assert_eq!(
16861690
stmt,
16871691
Statement::Execute {
1688-
name: ObjectName::from(vec!["a".into()]),
1692+
name: Some(ObjectName::from(vec!["a".into()])),
16891693
parameters: vec![],
16901694
has_parentheses: false,
16911695
using: vec![
1692-
Expr::Cast {
1693-
kind: CastKind::Cast,
1694-
expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))),
1695-
data_type: DataType::SmallInt(None),
1696-
format: None
1696+
ExprWithAlias {
1697+
expr: Expr::Cast {
1698+
kind: CastKind::Cast,
1699+
expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))),
1700+
data_type: DataType::SmallInt(None),
1701+
format: None
1702+
},
1703+
alias: None
16971704
},
1698-
Expr::Cast {
1699-
kind: CastKind::Cast,
1700-
expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))),
1701-
data_type: DataType::SmallInt(None),
1702-
format: None
1705+
ExprWithAlias {
1706+
expr: Expr::Cast {
1707+
kind: CastKind::Cast,
1708+
expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))),
1709+
data_type: DataType::SmallInt(None),
1710+
format: None
1711+
},
1712+
alias: None
17031713
},
1704-
]
1714+
],
1715+
immediate: false,
1716+
into: vec![]
17051717
}
17061718
);
17071719
}

0 commit comments

Comments
 (0)