Skip to content

Commit b482562

Browse files
authored
Add support for EXECUTE IMMEDIATE (#1717)
1 parent 3e90a18 commit b482562

File tree

7 files changed

+122
-35
lines changed

7 files changed

+122
-35
lines changed

src/ast/mod.rs

+21-10
Original file line numberDiff line numberDiff line change
@@ -3269,18 +3269,21 @@ pub enum Statement {
32693269
/// Note: this is a PostgreSQL-specific statement.
32703270
Deallocate { name: Ident, prepare: bool },
32713271
/// ```sql
3272-
/// EXECUTE name [ ( parameter [, ...] ) ] [USING <expr>]
3272+
/// An `EXECUTE` statement
32733273
/// ```
32743274
///
3275-
/// Note: this statement is supported by Postgres and MSSQL, with slight differences in syntax.
3276-
///
32773275
/// Postgres: <https://www.postgresql.org/docs/current/sql-execute.html>
32783276
/// MSSQL: <https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/execute-a-stored-procedure>
3277+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
3278+
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
32793279
Execute {
3280-
name: ObjectName,
3280+
name: Option<ObjectName>,
32813281
parameters: Vec<Expr>,
32823282
has_parentheses: bool,
3283-
using: Vec<Expr>,
3283+
/// Is this an `EXECUTE IMMEDIATE`
3284+
immediate: bool,
3285+
into: Vec<Ident>,
3286+
using: Vec<ExprWithAlias>,
32843287
},
32853288
/// ```sql
32863289
/// PREPARE name [ ( data_type [, ...] ) ] AS statement
@@ -4889,18 +4892,26 @@ impl fmt::Display for Statement {
48894892
name,
48904893
parameters,
48914894
has_parentheses,
4895+
immediate,
4896+
into,
48924897
using,
48934898
} => {
48944899
let (open, close) = if *has_parentheses {
48954900
("(", ")")
48964901
} else {
48974902
(if parameters.is_empty() { "" } else { " " }, "")
48984903
};
4899-
write!(
4900-
f,
4901-
"EXECUTE {name}{open}{}{close}",
4902-
display_comma_separated(parameters),
4903-
)?;
4904+
write!(f, "EXECUTE")?;
4905+
if *immediate {
4906+
write!(f, " IMMEDIATE")?;
4907+
}
4908+
if let Some(name) = name {
4909+
write!(f, " {name}")?;
4910+
}
4911+
write!(f, "{open}{}{close}", display_comma_separated(parameters),)?;
4912+
if !into.is_empty() {
4913+
write!(f, " INTO {}", display_comma_separated(into))?;
4914+
}
49044915
if !using.is_empty() {
49054916
write!(f, " USING {}", display_comma_separated(using))?;
49064917
};

src/dialect/bigquery.rs

+5
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

+5
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

+5
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

+19-7
Original file line numberDiff line numberDiff line change
@@ -13849,7 +13849,14 @@ impl<'a> Parser<'a> {
1384913849
}
1385013850

1385113851
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
13852-
let name = self.parse_object_name(false)?;
13852+
let name = if self.dialect.supports_execute_immediate()
13853+
&& self.parse_keyword(Keyword::IMMEDIATE)
13854+
{
13855+
None
13856+
} else {
13857+
let name = self.parse_object_name(false)?;
13858+
Some(name)
13859+
};
1385313860

1385413861
let has_parentheses = self.consume_token(&Token::LParen);
1385513862

@@ -13866,19 +13873,24 @@ impl<'a> Parser<'a> {
1386613873
self.expect_token(&Token::RParen)?;
1386713874
}
1386813875

13869-
let mut using = vec![];
13870-
if self.parse_keyword(Keyword::USING) {
13871-
using.push(self.parse_expr()?);
13876+
let into = if self.parse_keyword(Keyword::INTO) {
13877+
self.parse_comma_separated(Self::parse_identifier)?
13878+
} else {
13879+
vec![]
13880+
};
1387213881

13873-
while self.consume_token(&Token::Comma) {
13874-
using.push(self.parse_expr()?);
13875-
}
13882+
let using = if self.parse_keyword(Keyword::USING) {
13883+
self.parse_comma_separated(Self::parse_expr_with_alias)?
13884+
} else {
13885+
vec![]
1387613886
};
1387713887

1387813888
Ok(Statement::Execute {
13889+
immediate: name.is_none(),
1387913890
name,
1388013891
parameters,
1388113892
has_parentheses,
13893+
into,
1388213894
using,
1388313895
})
1388413896
}

tests/sqlparser_common.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -10745,7 +10745,7 @@ fn parse_call() {
1074510745
#[test]
1074610746
fn parse_execute_stored_procedure() {
1074710747
let expected = Statement::Execute {
10748-
name: ObjectName::from(vec![
10748+
name: Some(ObjectName::from(vec![
1074910749
Ident {
1075010750
value: "my_schema".to_string(),
1075110751
quote_style: None,
@@ -10756,13 +10756,15 @@ fn parse_execute_stored_procedure() {
1075610756
quote_style: None,
1075710757
span: Span::empty(),
1075810758
},
10759-
]),
10759+
])),
1076010760
parameters: vec![
1076110761
Expr::Value(Value::NationalStringLiteral("param1".to_string())),
1076210762
Expr::Value(Value::NationalStringLiteral("param2".to_string())),
1076310763
],
1076410764
has_parentheses: false,
10765+
immediate: false,
1076510766
using: vec![],
10767+
into: vec![],
1076610768
};
1076710769
assert_eq!(
1076810770
// Microsoft SQL Server does not use parentheses around arguments for EXECUTE
@@ -10779,6 +10781,41 @@ fn parse_execute_stored_procedure() {
1077910781
);
1078010782
}
1078110783

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

tests/sqlparser_postgres.rs

+28-16
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)