Add support for EXECUTE IMMEDIATE (#1717)

This commit is contained in:
Ifeanyi Ubah 2025-02-19 18:54:14 +01:00 committed by GitHub
parent 3e90a18f6d
commit b482562618
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 122 additions and 35 deletions

View file

@ -3269,18 +3269,21 @@ pub enum Statement {
/// Note: this is a PostgreSQL-specific statement. /// Note: this is a PostgreSQL-specific statement.
Deallocate { name: Ident, prepare: bool }, Deallocate { name: Ident, prepare: bool },
/// ```sql /// ```sql
/// EXECUTE name [ ( parameter [, ...] ) ] [USING <expr>] /// An `EXECUTE` 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> /// 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> /// MSSQL: <https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/execute-a-stored-procedure>
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
Execute { Execute {
name: ObjectName, name: Option<ObjectName>,
parameters: Vec<Expr>, parameters: Vec<Expr>,
has_parentheses: bool, has_parentheses: bool,
using: Vec<Expr>, /// Is this an `EXECUTE IMMEDIATE`
immediate: bool,
into: Vec<Ident>,
using: Vec<ExprWithAlias>,
}, },
/// ```sql /// ```sql
/// PREPARE name [ ( data_type [, ...] ) ] AS statement /// PREPARE name [ ( data_type [, ...] ) ] AS statement
@ -4889,6 +4892,8 @@ impl fmt::Display for Statement {
name, name,
parameters, parameters,
has_parentheses, has_parentheses,
immediate,
into,
using, using,
} => { } => {
let (open, close) = if *has_parentheses { let (open, close) = if *has_parentheses {
@ -4896,11 +4901,17 @@ impl fmt::Display for Statement {
} else { } else {
(if parameters.is_empty() { "" } else { " " }, "") (if parameters.is_empty() { "" } else { " " }, "")
}; };
write!( write!(f, "EXECUTE")?;
f, if *immediate {
"EXECUTE {name}{open}{}{close}", write!(f, " IMMEDIATE")?;
display_comma_separated(parameters), }
)?; if let Some(name) = name {
write!(f, " {name}")?;
}
write!(f, "{open}{}{close}", display_comma_separated(parameters),)?;
if !into.is_empty() {
write!(f, " INTO {}", display_comma_separated(into))?;
}
if !using.is_empty() { if !using.is_empty() {
write!(f, " USING {}", display_comma_separated(using))?; write!(f, " USING {}", display_comma_separated(using))?;
}; };

View file

@ -110,6 +110,11 @@ impl Dialect for BigQueryDialect {
true true
} }
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
fn supports_execute_immediate(&self) -> bool {
true
}
// See <https://cloud.google.com/bigquery/docs/access-historical-data> // See <https://cloud.google.com/bigquery/docs/access-historical-data>
fn supports_timestamp_versioning(&self) -> bool { fn supports_timestamp_versioning(&self) -> bool {
true true

View file

@ -261,6 +261,11 @@ pub trait Dialect: Debug + Any {
false false
} }
/// Returns true if the dialect supports `EXECUTE IMMEDIATE` statements.
fn supports_execute_immediate(&self) -> bool {
false
}
/// Returns true if the dialect supports the MATCH_RECOGNIZE operation. /// Returns true if the dialect supports the MATCH_RECOGNIZE operation.
fn supports_match_recognize(&self) -> bool { fn supports_match_recognize(&self) -> bool {
false false

View file

@ -96,6 +96,11 @@ impl Dialect for SnowflakeDialect {
true true
} }
/// See <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
fn supports_execute_immediate(&self) -> bool {
true
}
fn supports_match_recognize(&self) -> bool { fn supports_match_recognize(&self) -> bool {
true true
} }

View file

@ -13849,7 +13849,14 @@ impl<'a> Parser<'a> {
} }
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> { pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
let name = if self.dialect.supports_execute_immediate()
&& self.parse_keyword(Keyword::IMMEDIATE)
{
None
} else {
let name = self.parse_object_name(false)?; let name = self.parse_object_name(false)?;
Some(name)
};
let has_parentheses = self.consume_token(&Token::LParen); let has_parentheses = self.consume_token(&Token::LParen);
@ -13866,19 +13873,24 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?; self.expect_token(&Token::RParen)?;
} }
let mut using = vec![]; let into = if self.parse_keyword(Keyword::INTO) {
if self.parse_keyword(Keyword::USING) { self.parse_comma_separated(Self::parse_identifier)?
using.push(self.parse_expr()?); } else {
vec![]
};
while self.consume_token(&Token::Comma) { let using = if self.parse_keyword(Keyword::USING) {
using.push(self.parse_expr()?); self.parse_comma_separated(Self::parse_expr_with_alias)?
} } else {
vec![]
}; };
Ok(Statement::Execute { Ok(Statement::Execute {
immediate: name.is_none(),
name, name,
parameters, parameters,
has_parentheses, has_parentheses,
into,
using, using,
}) })
} }

View file

@ -10745,7 +10745,7 @@ fn parse_call() {
#[test] #[test]
fn parse_execute_stored_procedure() { fn parse_execute_stored_procedure() {
let expected = Statement::Execute { let expected = Statement::Execute {
name: ObjectName::from(vec![ name: Some(ObjectName::from(vec![
Ident { Ident {
value: "my_schema".to_string(), value: "my_schema".to_string(),
quote_style: None, quote_style: None,
@ -10756,13 +10756,15 @@ fn parse_execute_stored_procedure() {
quote_style: None, quote_style: None,
span: Span::empty(), span: Span::empty(),
}, },
]), ])),
parameters: vec![ parameters: vec![
Expr::Value(Value::NationalStringLiteral("param1".to_string())), Expr::Value(Value::NationalStringLiteral("param1".to_string())),
Expr::Value(Value::NationalStringLiteral("param2".to_string())), Expr::Value(Value::NationalStringLiteral("param2".to_string())),
], ],
has_parentheses: false, has_parentheses: false,
immediate: false,
using: vec![], using: vec![],
into: vec![],
}; };
assert_eq!( assert_eq!(
// Microsoft SQL Server does not use parentheses around arguments for EXECUTE // Microsoft SQL Server does not use parentheses around arguments for EXECUTE
@ -10779,6 +10781,41 @@ fn parse_execute_stored_procedure() {
); );
} }
#[test]
fn parse_execute_immediate() {
let dialects = all_dialects_where(|d| d.supports_execute_immediate());
let expected = Statement::Execute {
parameters: vec![Expr::Value(Value::SingleQuotedString(
"SELECT 1".to_string(),
))],
immediate: true,
using: vec![ExprWithAlias {
expr: Expr::Value(number("1")),
alias: Some(Ident::new("b")),
}],
into: vec![Ident::new("a")],
name: None,
has_parentheses: false,
};
let stmt = dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a USING 1 AS b");
assert_eq!(expected, stmt);
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a, b USING 1 AS x, y");
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' USING 1 AS x, y");
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1' INTO a, b");
dialects.verified_stmt("EXECUTE IMMEDIATE 'SELECT 1'");
dialects.verified_stmt("EXECUTE 'SELECT 1'");
assert_eq!(
ParserError::ParserError("Expected: identifier, found: ,".to_string()),
dialects
.parse_sql_statements("EXECUTE IMMEDIATE 'SELECT 1' USING 1 AS, y")
.unwrap_err()
);
}
#[test] #[test]
fn parse_create_table_collate() { fn parse_create_table_collate() {
pg_and_generic().verified_stmt("CREATE TABLE tbl (foo INT, bar TEXT COLLATE \"de_DE\")"); pg_and_generic().verified_stmt("CREATE TABLE tbl (foo INT, bar TEXT COLLATE \"de_DE\")");

View file

@ -1659,10 +1659,12 @@ fn parse_execute() {
assert_eq!( assert_eq!(
stmt, stmt,
Statement::Execute { Statement::Execute {
name: ObjectName::from(vec!["a".into()]), name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![], parameters: vec![],
has_parentheses: false, has_parentheses: false,
using: vec![] using: vec![],
immediate: false,
into: vec![]
} }
); );
@ -1670,13 +1672,15 @@ fn parse_execute() {
assert_eq!( assert_eq!(
stmt, stmt,
Statement::Execute { Statement::Execute {
name: ObjectName::from(vec!["a".into()]), name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![ parameters: vec![
Expr::Value(number("1")), Expr::Value(number("1")),
Expr::Value(Value::SingleQuotedString("t".to_string())) Expr::Value(Value::SingleQuotedString("t".to_string()))
], ],
has_parentheses: true, has_parentheses: true,
using: vec![] using: vec![],
immediate: false,
into: vec![]
} }
); );
@ -1685,23 +1689,31 @@ fn parse_execute() {
assert_eq!( assert_eq!(
stmt, stmt,
Statement::Execute { Statement::Execute {
name: ObjectName::from(vec!["a".into()]), name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![], parameters: vec![],
has_parentheses: false, has_parentheses: false,
using: vec![ using: vec![
Expr::Cast { ExprWithAlias {
expr: Expr::Cast {
kind: CastKind::Cast, kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))), expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))),
data_type: DataType::SmallInt(None), data_type: DataType::SmallInt(None),
format: None format: None
}, },
Expr::Cast { alias: None
},
ExprWithAlias {
expr: Expr::Cast {
kind: CastKind::Cast, kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))), expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))),
data_type: DataType::SmallInt(None), data_type: DataType::SmallInt(None),
format: None format: None
}, },
] alias: None
},
],
immediate: false,
into: vec![]
} }
); );
} }