This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 6f0e803a MSSQL: support EXEC (@sql) dynamic SQL execution (#2234)
6f0e803a is described below
commit 6f0e803aa77414e83aefd326e23231c51b60ae32
Author: Yoabot <[email protected]>
AuthorDate: Fri Feb 27 11:44:59 2026 +0100
MSSQL: support EXEC (@sql) dynamic SQL execution (#2234)
---
src/parser/mod.rs | 23 ++++++++++++++++++++---
tests/sqlparser_mssql.rs | 23 +++++++++++++++++++++++
2 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index bea566bb..75db4d24 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -18583,6 +18583,9 @@ impl<'a> Parser<'a> {
/// Parse a SQL `EXECUTE` statement
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
+ // Track whether the procedure/expression name itself was wrapped in
parens,
+ // i.e. `EXEC (@sql)` (dynamic string execution) vs `EXEC sp_name`.
+ // When the name has parens there are no additional parameters.
let name = if self.dialect.supports_execute_immediate()
&& self.parse_keyword(Keyword::IMMEDIATE)
{
@@ -18593,10 +18596,18 @@ impl<'a> Parser<'a> {
if has_parentheses {
self.expect_token(&Token::RParen)?;
}
- Some(name)
+ Some((name, has_parentheses))
};
- let has_parentheses = self.consume_token(&Token::LParen);
+ let name_had_parentheses = name.as_ref().map(|(_, p)|
*p).unwrap_or(false);
+
+ // Only look for a parameter list when the name was NOT wrapped in
parens.
+ // `EXEC (@sql)` is dynamic SQL execution and takes no parameters here.
+ let has_parentheses = if name_had_parentheses {
+ false
+ } else {
+ self.consume_token(&Token::LParen)
+ };
let end_kws = &[Keyword::USING, Keyword::OUTPUT, Keyword::DEFAULT];
let end_token = match (has_parentheses, self.peek_token().token) {
@@ -18606,12 +18617,18 @@ impl<'a> Parser<'a> {
(false, _) => Token::SemiColon,
};
- let parameters = self.parse_comma_separated0(Parser::parse_expr,
end_token)?;
+ let parameters = if name_had_parentheses {
+ vec![]
+ } else {
+ self.parse_comma_separated0(Parser::parse_expr, end_token)?
+ };
if has_parentheses {
self.expect_token(&Token::RParen)?;
}
+ let name = name.map(|(n, _)| n);
+
let into = if self.parse_keyword(Keyword::INTO) {
self.parse_comma_separated(Self::parse_identifier)?
} else {
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 6c8412a4..8bdb1c20 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -2783,3 +2783,26 @@ fn test_tsql_statement_keywords_not_implicit_aliases() {
);
}
}
+
+#[test]
+fn test_exec_dynamic_sql() {
+ // EXEC (@sql) executes a dynamic SQL string held in a variable.
+ // It must parse as a single Execute statement and not attempt to parse
+ // parameters after the closing paren.
+ let stmts = tsql()
+ .parse_sql_statements("EXEC (@sql)")
+ .expect("EXEC (@sql) should parse");
+ assert_eq!(stmts.len(), 1);
+ assert!(
+ matches!(&stmts[0], Statement::Execute { .. }),
+ "expected Execute, got: {:?}",
+ stmts[0]
+ );
+
+ // Verify that a statement following EXEC (@sql) on the next line is parsed
+ // as a separate statement and not consumed as a parameter.
+ let stmts = tsql()
+ .parse_sql_statements("EXEC (@sql)\nDROP TABLE #tmp")
+ .expect("EXEC (@sql) followed by DROP TABLE should parse");
+ assert_eq!(stmts.len(), 2);
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]