Skip to content

Commit 2b64f60

Browse files
committed
MSSQL: support EXEC (@SQL) dynamic SQL execution
parse_execute() was consuming a second parameter list after already parsing the parenthesised name expression, causing parse failures on any token that immediately followed EXEC (@SQL). Fixed by tracking whether the name was itself wrapped in parens; when it is, skip the parameter-list scan and leave no tokens consumed for the caller to mis-interpret. Adds test_exec_dynamic_sql covering both the standalone form and the case where a subsequent statement follows on the next line.
1 parent e81eb14 commit 2b64f60

2 files changed

Lines changed: 43 additions & 3 deletions

File tree

src/parser/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18536,6 +18536,9 @@ impl<'a> Parser<'a> {
1853618536

1853718537
/// Parse a SQL `EXECUTE` statement
1853818538
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
18539+
// Track whether the procedure/expression name itself was wrapped in parens,
18540+
// i.e. `EXEC (@sql)` (dynamic string execution) vs `EXEC sp_name`.
18541+
// When the name has parens there are no additional parameters.
1853918542
let name = if self.dialect.supports_execute_immediate()
1854018543
&& self.parse_keyword(Keyword::IMMEDIATE)
1854118544
{
@@ -18546,10 +18549,18 @@ impl<'a> Parser<'a> {
1854618549
if has_parentheses {
1854718550
self.expect_token(&Token::RParen)?;
1854818551
}
18549-
Some(name)
18552+
Some((name, has_parentheses))
1855018553
};
1855118554

18552-
let has_parentheses = self.consume_token(&Token::LParen);
18555+
let name_had_parentheses = name.as_ref().map(|(_, p)| *p).unwrap_or(false);
18556+
18557+
// Only look for a parameter list when the name was NOT wrapped in parens.
18558+
// `EXEC (@sql)` is dynamic SQL execution and takes no parameters here.
18559+
let has_parentheses = if name_had_parentheses {
18560+
false
18561+
} else {
18562+
self.consume_token(&Token::LParen)
18563+
};
1855318564

1855418565
let end_kws = &[Keyword::USING, Keyword::OUTPUT, Keyword::DEFAULT];
1855518566
let end_token = match (has_parentheses, self.peek_token().token) {
@@ -18559,12 +18570,18 @@ impl<'a> Parser<'a> {
1855918570
(false, _) => Token::SemiColon,
1856018571
};
1856118572

18562-
let parameters = self.parse_comma_separated0(Parser::parse_expr, end_token)?;
18573+
let parameters = if name_had_parentheses {
18574+
vec![]
18575+
} else {
18576+
self.parse_comma_separated0(Parser::parse_expr, end_token)?
18577+
};
1856318578

1856418579
if has_parentheses {
1856518580
self.expect_token(&Token::RParen)?;
1856618581
}
1856718582

18583+
let name = name.map(|(n, _)| n);
18584+
1856818585
let into = if self.parse_keyword(Keyword::INTO) {
1856918586
self.parse_comma_separated(Self::parse_identifier)?
1857018587
} else {

tests/sqlparser_mssql.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,3 +2730,26 @@ fn parse_mssql_tran_shorthand() {
27302730
// ROLLBACK TRAN normalizes to ROLLBACK (same as ROLLBACK TRANSACTION)
27312731
ms().one_statement_parses_to("ROLLBACK TRAN", "ROLLBACK");
27322732
}
2733+
2734+
#[test]
2735+
fn test_exec_dynamic_sql() {
2736+
// EXEC (@sql) executes a dynamic SQL string held in a variable.
2737+
// It must parse as a single Execute statement and not attempt to parse
2738+
// parameters after the closing paren.
2739+
let stmts = tsql()
2740+
.parse_sql_statements("EXEC (@sql)")
2741+
.expect("EXEC (@sql) should parse");
2742+
assert_eq!(stmts.len(), 1);
2743+
assert!(
2744+
matches!(&stmts[0], Statement::Execute { .. }),
2745+
"expected Execute, got: {:?}",
2746+
stmts[0]
2747+
);
2748+
2749+
// Verify that a statement following EXEC (@sql) on the next line is parsed
2750+
// as a separate statement and not consumed as a parameter.
2751+
let stmts = tsql()
2752+
.parse_sql_statements("EXEC (@sql)\nDROP TABLE #tmp")
2753+
.expect("EXEC (@sql) followed by DROP TABLE should parse");
2754+
assert_eq!(stmts.len(), 2);
2755+
}

0 commit comments

Comments
 (0)