Skip to content

feat: support explain options #1426

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

Merged
merged 10 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3032,6 +3032,8 @@ pub enum Statement {
statement: Box<Statement>,
/// Optional output format of explain
format: Option<AnalyzeFormat>,
/// Postgres style utility options, `(analyze, verbose true)`
options: Option<Vec<UtilityOption>>,
},
/// ```sql
/// SAVEPOINT
Expand Down Expand Up @@ -3219,6 +3221,7 @@ impl fmt::Display for Statement {
analyze,
statement,
format,
options,
} => {
write!(f, "{describe_alias} ")?;

Expand All @@ -3234,6 +3237,10 @@ impl fmt::Display for Statement {
write!(f, "FORMAT {format} ")?;
}

if let Some(options) = options {
write!(f, "({}) ", display_comma_separated(options))?;
}

write!(f, "{statement}")
}
Statement::Query(s) => write!(f, "{s}"),
Expand Down Expand Up @@ -7125,6 +7132,47 @@ where
}
}

/// Represents a single PostgreSQL utility option.
///
/// A utility option is a key-value pair where the key is an identifier (IDENT) and the value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

/// can be one of the following:
/// - A number with an optional sign (`+` or `-`). Example: `+10`, `-10.2`, `3`
/// - A non-keyword string. Example: `option1`, `'option2'`, `"option3"`
/// - keyword: `TRUE`, `FALSE`, `ON` (`off` is also accept).
/// - Empty. Example: `ANALYZE` (identifier only)
///
/// Utility options are used in various PostgreSQL DDL statements, including statements such as
/// `CLUSTER`, `EXPLAIN`, `VACUUM`, and `REINDEX`. These statements format options as `( option [, ...] )`.
///
/// [CLUSTER](https://www.postgresql.org/docs/current/sql-cluster.html)
/// [EXPLAIN](https://www.postgresql.org/docs/current/sql-explain.html)
/// [VACUUM](https://www.postgresql.org/docs/current/sql-vacuum.html)
/// [REINDEX](https://www.postgresql.org/docs/current/sql-reindex.html)
///
/// For example, the `EXPLAIN` AND `VACUUM` statements with options might look like this:
/// ```sql
/// EXPLAIN (ANALYZE, VERBOSE TRUE, FORMAT TEXT) SELECT * FROM my_table;
///
/// VACCUM (VERBOSE, ANALYZE ON, PARALLEL 10) my_table;
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct UtilityOption {
pub name: Ident,
pub arg: Option<Expr>,
}

impl Display for UtilityOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref arg) = self.arg {
write!(f, "{} {}", self.name, arg)
} else {
write!(f, "{}", self.name)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
6 changes: 6 additions & 0 deletions src/dialect/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ impl Dialect for DuckDbDialect {
fn support_map_literal_syntax(&self) -> bool {
true
}

// DuckDB is compatible with PostgreSQL syntax for this statement,
// although not all features may be implemented.
fn supports_explain_with_utility_options(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,8 @@ impl Dialect for GenericDialect {
fn supports_create_index_with_clause(&self) -> bool {
true
}

fn supports_explain_with_utility_options(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ pub trait Dialect: Debug + Any {
fn require_interval_qualifier(&self) -> bool {
false
}

fn supports_explain_with_utility_options(&self) -> bool {
false
}
}

/// This represents the operators for which precedence must be defined
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ impl Dialect for PostgreSqlDialect {
fn supports_create_index_with_clause(&self) -> bool {
true
}

/// see <https://www.postgresql.org/docs/current/sql-explain.html>
fn supports_explain_with_utility_options(&self) -> bool {
true
}
}

pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {
Expand Down
45 changes: 41 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,29 @@ impl<'a> Parser<'a> {
}
}

pub fn parse_utility_options(&mut self) -> Result<Vec<UtilityOption>, ParserError> {
self.expect_token(&Token::LParen)?;
let options = self.parse_comma_separated(Self::parse_utility_option)?;
self.expect_token(&Token::RParen)?;

Ok(options)
}

fn parse_utility_option(&mut self) -> Result<UtilityOption, ParserError> {
let name = self.parse_identifier(false)?;

let next_token = self.peek_token();
if next_token == Token::Comma || next_token == Token::RParen {
return Ok(UtilityOption { name, arg: None });
}
let arg = self.parse_expr()?;

Ok(UtilityOption {
name,
arg: Some(arg),
})
}

fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> {
if self
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
Expand Down Expand Up @@ -8464,11 +8487,24 @@ impl<'a> Parser<'a> {
&mut self,
describe_alias: DescribeAlias,
) -> Result<Statement, ParserError> {
let analyze = self.parse_keyword(Keyword::ANALYZE);
let verbose = self.parse_keyword(Keyword::VERBOSE);
let mut analyze = false;
let mut verbose = false;
let mut format = None;
if self.parse_keyword(Keyword::FORMAT) {
format = Some(self.parse_analyze_format()?);
let mut options = None;

// Note: DuckDB is compatible with PostgreSQL syntax for this statement,
// although not all features may be implemented.
if describe_alias == DescribeAlias::Explain
&& self.dialect.supports_explain_with_utility_options()
&& self.peek_token().token == Token::LParen
{
options = Some(self.parse_utility_options()?)
} else {
analyze = self.parse_keyword(Keyword::ANALYZE);
verbose = self.parse_keyword(Keyword::VERBOSE);
if self.parse_keyword(Keyword::FORMAT) {
format = Some(self.parse_analyze_format()?);
}
}

match self.maybe_parse(|parser| parser.parse_statement()) {
Expand All @@ -8481,6 +8517,7 @@ impl<'a> Parser<'a> {
verbose,
statement: Box::new(statement),
format,
options,
}),
_ => {
let hive_format =
Expand Down
Loading
Loading