Skip to content

Commit a0d4594

Browse files
author
Roman Borschel
committed
Fix parsing EXECUTE (...) with a more general string expression, rather than just a variable.
Statements like "EXEC (@SQL)" as well as "EXEC ('SELECT * FROM ' + @tableName)" should both parse successfully and should both parse equivalently to e.g. EXECUTE IMMEDIATE '<sql>' on BigQuery and Snowflake, meaning the sql expression is parsed as the first (and only) "parameter".
1 parent 913cf0e commit a0d4594

File tree

4 files changed

+32
-33
lines changed

4 files changed

+32
-33
lines changed

src/ast/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4456,7 +4456,7 @@ pub enum Statement {
44564456
name: Option<ObjectName>,
44574457
/// Parameter expressions passed to execute.
44584458
parameters: Vec<Expr>,
4459-
/// Whether parentheses were present.
4459+
/// Whether parentheses were present around `parameters`.
44604460
has_parentheses: bool,
44614461
/// Is this an `EXECUTE IMMEDIATE`.
44624462
immediate: bool,
@@ -5911,7 +5911,8 @@ impl fmt::Display for Statement {
59115911
default,
59125912
} => {
59135913
let (open, close) = if *has_parentheses {
5914-
("(", ")")
5914+
// Space before `(` only when there is no name directly preceding it.
5915+
(if name.is_some() { "(" } else { " (" }, ")")
59155916
} else {
59165917
(if parameters.is_empty() { "" } else { " " }, "")
59175918
};

src/parser/mod.rs

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18881,31 +18881,21 @@ impl<'a> Parser<'a> {
1888118881

1888218882
/// Parse a SQL `EXECUTE` statement
1888318883
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
18884-
// Track whether the procedure/expression name itself was wrapped in parens,
18885-
// i.e. `EXEC (@sql)` (dynamic string execution) vs `EXEC sp_name`.
18886-
// When the name has parens there are no additional parameters.
18887-
let name = if self.dialect.supports_execute_immediate()
18888-
&& self.parse_keyword(Keyword::IMMEDIATE)
18889-
{
18884+
let immediate =
18885+
self.dialect.supports_execute_immediate() && self.parse_keyword(Keyword::IMMEDIATE);
18886+
18887+
// When `EXEC` is immediately followed by `(`, the content is a dynamic-SQL
18888+
// expression — e.g. `EXEC (@sql)`, `EXEC ('SELECT ...')`, or
18889+
// `EXEC ('SELECT ... FROM ' + @tbl + ' WHERE ...')`.
18890+
// Skip name parsing; the expression ends up in `parameters` via the
18891+
// `has_parentheses` path below, consistent with `EXECUTE IMMEDIATE <expr>`.
18892+
let name = if immediate || matches!(self.peek_token_ref().token, Token::LParen) {
1889018893
None
1889118894
} else {
18892-
let has_parentheses = self.consume_token(&Token::LParen);
18893-
let name = self.parse_object_name(false)?;
18894-
if has_parentheses {
18895-
self.expect_token(&Token::RParen)?;
18896-
}
18897-
Some((name, has_parentheses))
18895+
Some(self.parse_object_name(false)?)
1889818896
};
1889918897

18900-
let name_had_parentheses = name.as_ref().map(|(_, p)| *p).unwrap_or(false);
18901-
18902-
// Only look for a parameter list when the name was NOT wrapped in parens.
18903-
// `EXEC (@sql)` is dynamic SQL execution and takes no parameters here.
18904-
let has_parentheses = if name_had_parentheses {
18905-
false
18906-
} else {
18907-
self.consume_token(&Token::LParen)
18908-
};
18898+
let has_parentheses = self.consume_token(&Token::LParen);
1890918899

1891018900
let end_kws = &[Keyword::USING, Keyword::OUTPUT, Keyword::DEFAULT];
1891118901
let end_token = match (has_parentheses, self.peek_token().token) {
@@ -18915,18 +18905,12 @@ impl<'a> Parser<'a> {
1891518905
(false, _) => Token::SemiColon,
1891618906
};
1891718907

18918-
let parameters = if name_had_parentheses {
18919-
vec![]
18920-
} else {
18921-
self.parse_comma_separated0(Parser::parse_expr, end_token)?
18922-
};
18908+
let parameters = self.parse_comma_separated0(Parser::parse_expr, end_token)?;
1892318909

1892418910
if has_parentheses {
1892518911
self.expect_token(&Token::RParen)?;
1892618912
}
1892718913

18928-
let name = name.map(|(n, _)| n);
18929-
1893018914
let into = if self.parse_keyword(Keyword::INTO) {
1893118915
self.parse_comma_separated(Self::parse_identifier)?
1893218916
} else {
@@ -18944,7 +18928,7 @@ impl<'a> Parser<'a> {
1894418928
let default = self.parse_keyword(Keyword::DEFAULT);
1894518929

1894618930
Ok(Statement::Execute {
18947-
immediate: name.is_none(),
18931+
immediate,
1894818932
name,
1894918933
parameters,
1895018934
has_parentheses,

tests/sqlparser_common.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12294,8 +12294,8 @@ fn parse_execute_stored_procedure() {
1229412294
}
1229512295
_ => unreachable!(),
1229612296
}
12297-
// Test optional parentheses around procedure name
12298-
ms_and_generic().one_statement_parses_to("EXEC ('name')", "EXECUTE 'name'");
12297+
// Parenthesised form is dynamic SQL; the expression ends up in parameters.
12298+
ms_and_generic().one_statement_parses_to("EXEC ('name')", "EXECUTE ('name')");
1229912299
}
1230012300

1230112301
#[test]

tests/sqlparser_mssql.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2819,6 +2819,20 @@ fn test_exec_dynamic_sql() {
28192819
assert_eq!(stmts.len(), 2);
28202820
}
28212821

2822+
#[test]
2823+
fn test_exec_dynamic_sql_string_concat() {
2824+
// EXEC with string concatenation: EXEC ('...' + @var + '...')
2825+
let stmts = tsql()
2826+
.parse_sql_statements("EXEC ('SELECT * FROM ' + @TableName + ' WHERE 1=1')")
2827+
.expect("EXEC with string concatenation should parse");
2828+
assert_eq!(stmts.len(), 1);
2829+
assert!(
2830+
matches!(&stmts[0], Statement::Execute { .. }),
2831+
"expected Execute, got: {:?}",
2832+
stmts[0]
2833+
);
2834+
}
2835+
28222836
// MSSQL OUTPUT clause on INSERT/UPDATE/DELETE
28232837
// https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
28242838
#[test]

0 commit comments

Comments
 (0)