Skip to content

Commit 04a53e5

Browse files
authored
feat: support explain options (#1426)
1 parent 1c505ce commit 04a53e5

File tree

7 files changed

+268
-7
lines changed

7 files changed

+268
-7
lines changed

src/ast/mod.rs

+48
Original file line numberDiff line numberDiff line change
@@ -3032,6 +3032,8 @@ pub enum Statement {
30323032
statement: Box<Statement>,
30333033
/// Optional output format of explain
30343034
format: Option<AnalyzeFormat>,
3035+
/// Postgres style utility options, `(analyze, verbose true)`
3036+
options: Option<Vec<UtilityOption>>,
30353037
},
30363038
/// ```sql
30373039
/// SAVEPOINT
@@ -3219,6 +3221,7 @@ impl fmt::Display for Statement {
32193221
analyze,
32203222
statement,
32213223
format,
3224+
options,
32223225
} => {
32233226
write!(f, "{describe_alias} ")?;
32243227

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

3240+
if let Some(options) = options {
3241+
write!(f, "({}) ", display_comma_separated(options))?;
3242+
}
3243+
32373244
write!(f, "{statement}")
32383245
}
32393246
Statement::Query(s) => write!(f, "{s}"),
@@ -7125,6 +7132,47 @@ where
71257132
}
71267133
}
71277134

7135+
/// Represents a single PostgreSQL utility option.
7136+
///
7137+
/// A utility option is a key-value pair where the key is an identifier (IDENT) and the value
7138+
/// can be one of the following:
7139+
/// - A number with an optional sign (`+` or `-`). Example: `+10`, `-10.2`, `3`
7140+
/// - A non-keyword string. Example: `option1`, `'option2'`, `"option3"`
7141+
/// - keyword: `TRUE`, `FALSE`, `ON` (`off` is also accept).
7142+
/// - Empty. Example: `ANALYZE` (identifier only)
7143+
///
7144+
/// Utility options are used in various PostgreSQL DDL statements, including statements such as
7145+
/// `CLUSTER`, `EXPLAIN`, `VACUUM`, and `REINDEX`. These statements format options as `( option [, ...] )`.
7146+
///
7147+
/// [CLUSTER](https://www.postgresql.org/docs/current/sql-cluster.html)
7148+
/// [EXPLAIN](https://www.postgresql.org/docs/current/sql-explain.html)
7149+
/// [VACUUM](https://www.postgresql.org/docs/current/sql-vacuum.html)
7150+
/// [REINDEX](https://www.postgresql.org/docs/current/sql-reindex.html)
7151+
///
7152+
/// For example, the `EXPLAIN` AND `VACUUM` statements with options might look like this:
7153+
/// ```sql
7154+
/// EXPLAIN (ANALYZE, VERBOSE TRUE, FORMAT TEXT) SELECT * FROM my_table;
7155+
///
7156+
/// VACCUM (VERBOSE, ANALYZE ON, PARALLEL 10) my_table;
7157+
/// ```
7158+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7159+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7160+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7161+
pub struct UtilityOption {
7162+
pub name: Ident,
7163+
pub arg: Option<Expr>,
7164+
}
7165+
7166+
impl Display for UtilityOption {
7167+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7168+
if let Some(ref arg) = self.arg {
7169+
write!(f, "{} {}", self.name, arg)
7170+
} else {
7171+
write!(f, "{}", self.name)
7172+
}
7173+
}
7174+
}
7175+
71287176
#[cfg(test)]
71297177
mod tests {
71307178
use super::*;

src/dialect/duckdb.rs

+6
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ impl Dialect for DuckDbDialect {
5555
fn support_map_literal_syntax(&self) -> bool {
5656
true
5757
}
58+
59+
// DuckDB is compatible with PostgreSQL syntax for this statement,
60+
// although not all features may be implemented.
61+
fn supports_explain_with_utility_options(&self) -> bool {
62+
true
63+
}
5864
}

src/dialect/generic.rs

+4
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,8 @@ impl Dialect for GenericDialect {
9090
fn supports_create_index_with_clause(&self) -> bool {
9191
true
9292
}
93+
94+
fn supports_explain_with_utility_options(&self) -> bool {
95+
true
96+
}
9397
}

src/dialect/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@ pub trait Dialect: Debug + Any {
536536
fn require_interval_qualifier(&self) -> bool {
537537
false
538538
}
539+
540+
fn supports_explain_with_utility_options(&self) -> bool {
541+
false
542+
}
539543
}
540544

541545
/// This represents the operators for which precedence must be defined

src/dialect/postgresql.rs

+5
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ impl Dialect for PostgreSqlDialect {
166166
fn supports_create_index_with_clause(&self) -> bool {
167167
true
168168
}
169+
170+
/// see <https://www.postgresql.org/docs/current/sql-explain.html>
171+
fn supports_explain_with_utility_options(&self) -> bool {
172+
true
173+
}
169174
}
170175

171176
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

src/parser/mod.rs

+41-4
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,29 @@ impl<'a> Parser<'a> {
12771277
}
12781278
}
12791279

1280+
pub fn parse_utility_options(&mut self) -> Result<Vec<UtilityOption>, ParserError> {
1281+
self.expect_token(&Token::LParen)?;
1282+
let options = self.parse_comma_separated(Self::parse_utility_option)?;
1283+
self.expect_token(&Token::RParen)?;
1284+
1285+
Ok(options)
1286+
}
1287+
1288+
fn parse_utility_option(&mut self) -> Result<UtilityOption, ParserError> {
1289+
let name = self.parse_identifier(false)?;
1290+
1291+
let next_token = self.peek_token();
1292+
if next_token == Token::Comma || next_token == Token::RParen {
1293+
return Ok(UtilityOption { name, arg: None });
1294+
}
1295+
let arg = self.parse_expr()?;
1296+
1297+
Ok(UtilityOption {
1298+
name,
1299+
arg: Some(arg),
1300+
})
1301+
}
1302+
12801303
fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> {
12811304
if self
12821305
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
@@ -8464,11 +8487,24 @@ impl<'a> Parser<'a> {
84648487
&mut self,
84658488
describe_alias: DescribeAlias,
84668489
) -> Result<Statement, ParserError> {
8467-
let analyze = self.parse_keyword(Keyword::ANALYZE);
8468-
let verbose = self.parse_keyword(Keyword::VERBOSE);
8490+
let mut analyze = false;
8491+
let mut verbose = false;
84698492
let mut format = None;
8470-
if self.parse_keyword(Keyword::FORMAT) {
8471-
format = Some(self.parse_analyze_format()?);
8493+
let mut options = None;
8494+
8495+
// Note: DuckDB is compatible with PostgreSQL syntax for this statement,
8496+
// although not all features may be implemented.
8497+
if describe_alias == DescribeAlias::Explain
8498+
&& self.dialect.supports_explain_with_utility_options()
8499+
&& self.peek_token().token == Token::LParen
8500+
{
8501+
options = Some(self.parse_utility_options()?)
8502+
} else {
8503+
analyze = self.parse_keyword(Keyword::ANALYZE);
8504+
verbose = self.parse_keyword(Keyword::VERBOSE);
8505+
if self.parse_keyword(Keyword::FORMAT) {
8506+
format = Some(self.parse_analyze_format()?);
8507+
}
84728508
}
84738509

84748510
match self.maybe_parse(|parser| parser.parse_statement()) {
@@ -8481,6 +8517,7 @@ impl<'a> Parser<'a> {
84818517
verbose,
84828518
statement: Box::new(statement),
84838519
format,
8520+
options,
84848521
}),
84858522
_ => {
84868523
let hive_format =

0 commit comments

Comments
 (0)