-
Notifications
You must be signed in to change notification settings - Fork 606
Add Postgres-specific statements PREPARE, EXECUTE and DEALLOCATE #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -551,12 +551,34 @@ pub enum Statement { | |
Rollback { chain: bool }, | ||
/// CREATE SCHEMA | ||
CreateSchema { schema_name: ObjectName }, | ||
|
||
/// ASSERT <condition> [AS <message>] | ||
Assert { | ||
condition: Expr, | ||
message: Option<Expr>, | ||
}, | ||
/// DEALLOCATE [ PREPARE ] { name | ALL } | ||
/// | ||
/// Note: this is a PostgreSQL-specific statement. | ||
Deallocate { | ||
name: Option<ObjectName>, | ||
all: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't need to handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry. I tried to check and test again. It is not schema-qualified. I will remove |
||
prepare: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be meaningless syntax. It's fine to keep it, but maybe note that PostgreSQL docs say it's ignored? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. The syntax is meaningless. But I think if we should output (format) the useless keyword |
||
}, | ||
/// EXECUTE name [ ( parameter [, ...] ) ] | ||
/// | ||
/// Note: this is a PostgreSQL-specific statement. | ||
Execute { | ||
name: ObjectName, | ||
parameters: Vec<Expr>, | ||
}, | ||
/// PREPARE name [ ( data_type [, ...] ) ] AS statement | ||
/// | ||
/// Note: this is a PostgreSQL-specific statement. | ||
Prepare { | ||
name: ObjectName, | ||
data_types: Vec<DataType>, | ||
statement: Box<Statement>, | ||
}, | ||
} | ||
|
||
impl fmt::Display for Statement { | ||
|
@@ -824,6 +846,36 @@ impl fmt::Display for Statement { | |
} | ||
Ok(()) | ||
} | ||
Statement::Deallocate { name, all, prepare } => { | ||
f.write_str("DEALLOCATE ")?; | ||
if *prepare { | ||
f.write_str("PREPARE ")?; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see we're inconsistent about that, but I believe using the same style as in the Statement::CreateTable branch will yield smaller code (especially we figure out the ALL vs object name question above). |
||
if *all { | ||
f.write_str("ALL") | ||
} else { | ||
write!(f, "{}", name.as_ref().unwrap()) | ||
} | ||
} | ||
Statement::Execute { name, parameters } => { | ||
write!(f, "EXECUTE {}", name)?; | ||
if !parameters.is_empty() { | ||
write!(f, "({})", display_comma_separated(parameters))?; | ||
} | ||
Ok(()) | ||
} | ||
Statement::Prepare { | ||
name, | ||
data_types, | ||
statement, | ||
} => { | ||
write!(f, "PREPARE {} ", name)?; | ||
if !data_types.is_empty() { | ||
write!(f, "({}) ", display_comma_separated(data_types))?; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I know we're inconsistent about this, but it seems that we usually don't add blank lines in situations like this. |
||
write!(f, "AS {}", statement) | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ impl Dialect for PostgreSqlDialect { | |
// See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS | ||
// We don't yet support identifiers beginning with "letters with | ||
// diacritical marks and non-Latin letters" | ||
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' | ||
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '$' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't match the documentation linked just above this line (Postgres seems to have |
||
} | ||
|
||
fn is_identifier_part(&self, ch: char) -> bool { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -149,6 +149,11 @@ impl Parser { | |
Keyword::COMMIT => Ok(self.parse_commit()?), | ||
Keyword::ROLLBACK => Ok(self.parse_rollback()?), | ||
Keyword::ASSERT => Ok(self.parse_assert()?), | ||
// `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific | ||
// syntaxes. They are used for Postgres prepared statement. | ||
Keyword::DEALLOCATE => Ok(self.parse_deallocate()?), | ||
Keyword::EXECUTE => Ok(self.parse_execute()?), | ||
Keyword::PREPARE => Ok(self.parse_prepare()?), | ||
_ => self.expected("an SQL statement", Token::Word(w)), | ||
}, | ||
Token::LParen => { | ||
|
@@ -2386,6 +2391,49 @@ impl Parser { | |
Ok(false) | ||
} | ||
} | ||
|
||
fn parse_deallocate(&mut self) -> Result<Statement, ParserError> { | ||
let prepare = self.parse_keyword(Keyword::PREPARE); | ||
let all = self.parse_keyword(Keyword::ALL); | ||
|
||
let name = if all { | ||
None | ||
} else { | ||
Some(self.parse_object_name()?) | ||
}; | ||
|
||
Ok(Statement::Deallocate { name, all, prepare }) | ||
} | ||
|
||
fn parse_execute(&mut self) -> Result<Statement, ParserError> { | ||
let name = self.parse_object_name()?; | ||
|
||
let mut parameters = vec![]; | ||
if self.expect_token(&Token::LParen).is_ok() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have confusingly named |
||
parameters = self.parse_comma_separated(Parser::parse_expr)?; | ||
self.expect_token(&Token::RParen)?; | ||
} | ||
|
||
Ok(Statement::Execute { name, parameters }) | ||
} | ||
|
||
fn parse_prepare(&mut self) -> Result<Statement, ParserError> { | ||
let name = self.parse_object_name()?; | ||
|
||
let mut data_types = vec![]; | ||
if self.expect_token(&Token::LParen).is_ok() { | ||
data_types = self.parse_comma_separated(Parser::parse_data_type)?; | ||
self.expect_token(&Token::RParen)?; | ||
} | ||
|
||
self.expect_keyword(Keyword::AS)?; | ||
let statement = Box::new(self.parse_statement()?); | ||
Ok(Statement::Prepare { | ||
name, | ||
data_types, | ||
statement, | ||
}) | ||
} | ||
} | ||
|
||
impl Word { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -423,6 +423,139 @@ fn parse_show() { | |
) | ||
} | ||
|
||
#[test] | ||
fn parse_deallocate() { | ||
let stmt = pg().verified_stmt("DEALLOCATE a"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
assert_eq!( | ||
stmt, | ||
Statement::Deallocate { | ||
name: Some(ObjectName(vec!["a".into()])), | ||
all: false, | ||
prepare: false, | ||
} | ||
); | ||
|
||
let stmt = pg().verified_stmt("DEALLOCATE ALL"); | ||
assert_eq!( | ||
stmt, | ||
Statement::Deallocate { | ||
name: None, | ||
all: true, | ||
prepare: false, | ||
} | ||
); | ||
|
||
let stmt = pg().verified_stmt("DEALLOCATE PREPARE a"); | ||
assert_eq!( | ||
stmt, | ||
Statement::Deallocate { | ||
name: Some(ObjectName(vec!["a".into()])), | ||
all: false, | ||
prepare: true, | ||
} | ||
); | ||
|
||
let stmt = pg().verified_stmt("DEALLOCATE PREPARE ALL"); | ||
assert_eq!( | ||
stmt, | ||
Statement::Deallocate { | ||
name: None, | ||
all: true, | ||
prepare: true, | ||
} | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_execute() { | ||
let stmt = pg().verified_stmt("EXECUTE a"); | ||
assert_eq!( | ||
stmt, | ||
Statement::Execute { | ||
name: ObjectName(vec!["a".into()]), | ||
parameters: vec![], | ||
} | ||
); | ||
|
||
let stmt = pg().verified_stmt("EXECUTE a(1, 't')"); | ||
|
||
#[cfg(feature = "bigdecimal")] | ||
assert_eq!( | ||
stmt, | ||
Statement::Execute { | ||
name: ObjectName(vec!["a".into()]), | ||
parameters: vec![ | ||
Expr::Value(Value::Number(bigdecimal::BigDecimal::from(1))), | ||
Expr::Value(Value::SingleQuotedString("t".to_string())) | ||
], | ||
} | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_prepare() { | ||
let stmt = pg().verified_stmt("PREPARE a AS INSERT INTO customers VALUES ($1, $2, $3)"); | ||
let sub_stmt = match stmt { | ||
Statement::Prepare { | ||
name, | ||
data_types, | ||
statement, | ||
.. | ||
} => { | ||
assert_eq!(name, ObjectName(vec!["a".into()])); | ||
assert!(data_types.is_empty()); | ||
|
||
statement | ||
} | ||
_ => unreachable!(), | ||
}; | ||
match sub_stmt.as_ref() { | ||
Statement::Insert { | ||
table_name, | ||
columns, | ||
source, | ||
.. | ||
} => { | ||
assert_eq!(table_name.to_string(), "customers"); | ||
assert!(columns.is_empty()); | ||
|
||
let expected_values = [vec![ | ||
Expr::Identifier("$1".into()), | ||
Expr::Identifier("$2".into()), | ||
Expr::Identifier("$3".into()), | ||
]]; | ||
match &source.body { | ||
SetExpr::Values(Values(values)) => assert_eq!(values.as_slice(), &expected_values), | ||
_ => unreachable!(), | ||
} | ||
} | ||
_ => unreachable!(), | ||
}; | ||
|
||
let stmt = pg() | ||
.verified_stmt("PREPARE a (INT, TEXT) AS SELECT * FROM customers WHERE customers.id = $1"); | ||
let sub_stmt = match stmt { | ||
Statement::Prepare { | ||
name, | ||
data_types, | ||
statement, | ||
.. | ||
} => { | ||
assert_eq!(name, ObjectName(vec!["a".into()])); | ||
assert_eq!(data_types, vec![DataType::Int, DataType::Text]); | ||
|
||
statement | ||
} | ||
_ => unreachable!(), | ||
}; | ||
assert_eq!( | ||
sub_stmt, | ||
Box::new(Statement::Query(Box::new(pg().verified_query( | ||
"SELECT * FROM customers WHERE customers.id = $1" | ||
)))) | ||
); | ||
} | ||
|
||
fn pg() -> TestedDialects { | ||
TestedDialects { | ||
dialects: vec![Box::new(PostgreSqlDialect {})], | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be enclosed in backticks (as well as the
ASSERT
comment above -- sorry about missing that!), so thatcargo doc
output is properly formatted. Same below.