improve support for T-SQL EXECUTE statements (#1490)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index a24739a..31d7af1 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -3113,10 +3113,14 @@
/// EXECUTE name [ ( parameter [, ...] ) ] [USING <expr>]
/// ```
///
- /// Note: this is a PostgreSQL-specific statement.
+ /// Note: this statement is supported by Postgres and MSSQL, with slight differences in syntax.
+ ///
+ /// Postgres: <https://www.postgresql.org/docs/current/sql-execute.html>
+ /// MSSQL: <https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/execute-a-stored-procedure>
Execute {
- name: Ident,
+ name: ObjectName,
parameters: Vec<Expr>,
+ has_parentheses: bool,
using: Vec<Expr>,
},
/// ```sql
@@ -4585,12 +4589,19 @@
Statement::Execute {
name,
parameters,
+ has_parentheses,
using,
} => {
- write!(f, "EXECUTE {name}")?;
- if !parameters.is_empty() {
- write!(f, "({})", display_comma_separated(parameters))?;
- }
+ let (open, close) = if *has_parentheses {
+ ("(", ")")
+ } else {
+ (if parameters.is_empty() { "" } else { " " }, "")
+ };
+ write!(
+ f,
+ "EXECUTE {name}{open}{}{close}",
+ display_comma_separated(parameters),
+ )?;
if !using.is_empty() {
write!(f, " USING {}", display_comma_separated(using))?;
};
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 2bd4543..942ff19 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -529,7 +529,7 @@
// `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific
// syntaxes. They are used for Postgres prepared statement.
Keyword::DEALLOCATE => self.parse_deallocate(),
- Keyword::EXECUTE => self.parse_execute(),
+ Keyword::EXECUTE | Keyword::EXEC => self.parse_execute(),
Keyword::PREPARE => self.parse_prepare(),
Keyword::MERGE => self.parse_merge(),
// `LISTEN` and `NOTIFY` are Postgres-specific
@@ -11807,11 +11807,20 @@
}
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
- let name = self.parse_identifier(false)?;
+ let name = self.parse_object_name(false)?;
- let mut parameters = vec![];
- if self.consume_token(&Token::LParen) {
- parameters = self.parse_comma_separated(Parser::parse_expr)?;
+ let has_parentheses = self.consume_token(&Token::LParen);
+
+ let end_token = match (has_parentheses, self.peek_token().token) {
+ (true, _) => Token::RParen,
+ (false, Token::EOF) => Token::EOF,
+ (false, Token::Word(w)) if w.keyword == Keyword::USING => Token::Word(w),
+ (false, _) => Token::SemiColon,
+ };
+
+ let parameters = self.parse_comma_separated0(Parser::parse_expr, end_token)?;
+
+ if has_parentheses {
self.expect_token(&Token::RParen)?;
}
@@ -11827,6 +11836,7 @@
Ok(Statement::Execute {
name,
parameters,
+ has_parentheses,
using,
})
}
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 49753a1..e372806 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -1396,6 +1396,10 @@
])
}
+fn ms_and_generic() -> TestedDialects {
+ TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})])
+}
+
#[test]
fn parse_json_ops_without_colon() {
use self::BinaryOperator::*;
@@ -9736,6 +9740,41 @@
}
#[test]
+fn parse_execute_stored_procedure() {
+ let expected = Statement::Execute {
+ name: ObjectName(vec![
+ Ident {
+ value: "my_schema".to_string(),
+ quote_style: None,
+ },
+ Ident {
+ value: "my_stored_procedure".to_string(),
+ quote_style: None,
+ },
+ ]),
+ parameters: vec![
+ Expr::Value(Value::NationalStringLiteral("param1".to_string())),
+ Expr::Value(Value::NationalStringLiteral("param2".to_string())),
+ ],
+ has_parentheses: false,
+ using: vec![],
+ };
+ assert_eq!(
+ // Microsoft SQL Server does not use parentheses around arguments for EXECUTE
+ ms_and_generic()
+ .verified_stmt("EXECUTE my_schema.my_stored_procedure N'param1', N'param2'"),
+ expected
+ );
+ assert_eq!(
+ ms_and_generic().one_statement_parses_to(
+ "EXEC my_schema.my_stored_procedure N'param1', N'param2';",
+ "EXECUTE my_schema.my_stored_procedure N'param1', N'param2'",
+ ),
+ expected
+ );
+}
+
+#[test]
fn parse_create_table_collate() {
pg_and_generic().verified_stmt("CREATE TABLE tbl (foo INT, bar TEXT COLLATE \"de_DE\")");
}
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index c30603b..100c8ee 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -1539,8 +1539,9 @@
assert_eq!(
stmt,
Statement::Execute {
- name: "a".into(),
+ name: ObjectName(vec!["a".into()]),
parameters: vec![],
+ has_parentheses: false,
using: vec![]
}
);
@@ -1549,11 +1550,12 @@
assert_eq!(
stmt,
Statement::Execute {
- name: "a".into(),
+ name: ObjectName(vec!["a".into()]),
parameters: vec![
Expr::Value(number("1")),
Expr::Value(Value::SingleQuotedString("t".to_string()))
],
+ has_parentheses: true,
using: vec![]
}
);
@@ -1563,8 +1565,9 @@
assert_eq!(
stmt,
Statement::Execute {
- name: "a".into(),
+ name: ObjectName(vec!["a".into()]),
parameters: vec![],
+ has_parentheses: false,
using: vec![
Expr::Cast {
kind: CastKind::Cast,