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.
Deallocate { name: Ident, prepare: bool },
/// ```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>
/// 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 {
name: ObjectName,
name: Option<ObjectName>,
parameters: Vec<Expr>,
has_parentheses: bool,
using: Vec<Expr>,
/// Is this an `EXECUTE IMMEDIATE`
immediate: bool,
into: Vec<Ident>,
using: Vec<ExprWithAlias>,
},
/// ```sql
/// PREPARE name [ ( data_type [, ...] ) ] AS statement
@ -4889,6 +4892,8 @@ impl fmt::Display for Statement {
name,
parameters,
has_parentheses,
immediate,
into,
using,
} => {
let (open, close) = if *has_parentheses {
@ -4896,11 +4901,17 @@ impl fmt::Display for Statement {
} else {
(if parameters.is_empty() { "" } else { " " }, "")
};
write!(
f,
"EXECUTE {name}{open}{}{close}",
display_comma_separated(parameters),
)?;
write!(f, "EXECUTE")?;
if *immediate {
write!(f, " IMMEDIATE")?;
}
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() {
write!(f, " USING {}", display_comma_separated(using))?;
};

View file

@ -110,6 +110,11 @@ impl Dialect for BigQueryDialect {
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>
fn supports_timestamp_versioning(&self) -> bool {
true

View file

@ -261,6 +261,11 @@ pub trait Dialect: Debug + Any {
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.
fn supports_match_recognize(&self) -> bool {
false

View file

@ -96,6 +96,11 @@ impl Dialect for SnowflakeDialect {
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 {
true
}

View file

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

View file

@ -10745,7 +10745,7 @@ fn parse_call() {
#[test]
fn parse_execute_stored_procedure() {
let expected = Statement::Execute {
name: ObjectName::from(vec![
name: Some(ObjectName::from(vec![
Ident {
value: "my_schema".to_string(),
quote_style: None,
@ -10756,13 +10756,15 @@ fn parse_execute_stored_procedure() {
quote_style: None,
span: Span::empty(),
},
]),
])),
parameters: vec![
Expr::Value(Value::NationalStringLiteral("param1".to_string())),
Expr::Value(Value::NationalStringLiteral("param2".to_string())),
],
has_parentheses: false,
immediate: false,
using: vec![],
into: vec![],
};
assert_eq!(
// 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]
fn parse_create_table_collate() {
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!(
stmt,
Statement::Execute {
name: ObjectName::from(vec!["a".into()]),
name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![],
has_parentheses: false,
using: vec![]
using: vec![],
immediate: false,
into: vec![]
}
);
@ -1670,13 +1672,15 @@ fn parse_execute() {
assert_eq!(
stmt,
Statement::Execute {
name: ObjectName::from(vec!["a".into()]),
name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![
Expr::Value(number("1")),
Expr::Value(Value::SingleQuotedString("t".to_string()))
],
has_parentheses: true,
using: vec![]
using: vec![],
immediate: false,
into: vec![]
}
);
@ -1685,23 +1689,31 @@ fn parse_execute() {
assert_eq!(
stmt,
Statement::Execute {
name: ObjectName::from(vec!["a".into()]),
name: Some(ObjectName::from(vec!["a".into()])),
parameters: vec![],
has_parentheses: false,
using: vec![
Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))),
data_type: DataType::SmallInt(None),
format: None
ExprWithAlias {
expr: Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))),
data_type: DataType::SmallInt(None),
format: None
},
alias: None
},
Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))),
data_type: DataType::SmallInt(None),
format: None
ExprWithAlias {
expr: Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))),
data_type: DataType::SmallInt(None),
format: None
},
alias: None
},
]
],
immediate: false,
into: vec![]
}
);
}