LucaCappelletti94 commented on code in PR #2618:
URL:
https://github.com/apache/datafusion-sqlparser-rs/pull/2618#discussion_r4284898258
##########
src/ast/mod.rs:
##########
@@ -11589,6 +11596,33 @@ impl fmt::Display for WaitForStatement {
}
}
+/// PostgreSQL `DO` statement, executing an anonymous code block.
+///
+/// ```sql
+/// DO [ LANGUAGE lang_name ] code
+/// ```
+///
+/// See <https://www.postgresql.org/docs/current/sql-do.html>
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct DoStatement {
+ /// Optional procedural language name.
+ pub language: Option<Ident>,
+ /// The anonymous code block, a string literal.
+ pub code: ValueWithSpan,
+}
+
+impl fmt::Display for DoStatement {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "DO ")?;
+ if let Some(language) = &self.language {
+ write!(f, "LANGUAGE {language} ")?;
+ }
+ write!(f, "{}", self.code)
+ }
+}
Review Comment:
You should store the items in source order, so every accepted form prints
back as written.
```suggestion
pub struct DoStatement {
/// The code blocks and `LANGUAGE` options, in source order.
pub options: Vec<DoOption>,
}
impl fmt::Display for DoStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DO {}", display_separated(&self.options, " "))
}
}
/// An item of a [`DoStatement`].
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum DoOption {
/// `LANGUAGE lang_name`
Language(Ident),
/// `LANGUAGE` followed by the name as a string literal.
LanguageLiteral(ValueWithSpan),
/// The anonymous code block, a string literal.
Code(ValueWithSpan),
}
impl fmt::Display for DoOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DoOption::Language(language) => write!(f, "LANGUAGE {language}"),
DoOption::LanguageLiteral(language) => write!(f, "LANGUAGE
{language}"),
DoOption::Code(code) => write!(f, "{code}"),
}
}
}
```
##########
src/parser/mod.rs:
##########
@@ -1512,6 +1513,31 @@ impl<'a> Parser<'a> {
Ok(Statement::UNLISTEN { channel })
}
+ /// Parse `DO` statement.
+ pub fn parse_do(&mut self) -> Result<Statement, ParserError> {
+ let mut language = self.parse_do_language()?;
+ match &self.peek_token_ref().token {
+ Token::SingleQuotedString(_)
+ | Token::DollarQuotedString(_)
+ | Token::EscapedStringLiteral(_)
+ | Token::UnicodeStringLiteral(_) => {}
+ _ => return self.expected_ref("string literal",
self.peek_token_ref()),
+ }
+ let code = self.parse_value()?;
+ if language.is_none() {
+ language = self.parse_do_language()?;
+ }
+ Ok(Statement::Do(DoStatement { language, code }))
+ }
+
+ fn parse_do_language(&mut self) -> Result<Option<Ident>, ParserError> {
+ if self.parse_keyword(Keyword::LANGUAGE) {
+ Ok(Some(self.parse_identifier()?))
+ } else {
+ Ok(None)
+ }
+ }
Review Comment:
You should parse `DO` the way PostgreSQL's `dostmt_opt_list` does. That
means any run of code blocks and `LANGUAGE` items, with the name either an
identifier or any string literal, and the missing-code and duplicate checks
left to the server. The repo's `fuzz_postgres_accepts` target panics on `DO
LANGUAGE plpgsql`, `DO $$a$$ $$b$$`, a repeated `LANGUAGE` and `DO LANGUAGE
$$plpgsql$$ $$a$$`, all of which PostgreSQL's grammar accepts, and fuzzing from
this PR's seeds reaches one within seconds.
Please use the fuzzer locally before the next review round to catch these
things.
```suggestion
pub fn parse_do(&mut self) -> Result<Statement, ParserError> {
let mut options = Vec::new();
loop {
let language = self.parse_keyword(Keyword::LANGUAGE);
let string_literal = matches!(
self.peek_token_ref().token,
Token::SingleQuotedString(_)
| Token::DollarQuotedString(_)
| Token::EscapedStringLiteral(_)
| Token::UnicodeStringLiteral(_)
);
let option = match (language, string_literal) {
(true, true) =>
DoOption::LanguageLiteral(self.parse_value()?),
(true, false) =>
DoOption::Language(self.parse_identifier()?),
(false, true) => DoOption::Code(self.parse_value()?),
(false, false) if options.is_empty() => {
return self.expected_ref("string literal",
self.peek_token_ref());
}
(false, false) => break,
};
options.push(option);
}
Ok(Statement::Do(DoStatement { options }))
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]