Skip to content

Commit 17f2930

Browse files
authored
Introduce support for EXPLAIN [ANALYZE] [VERBOSE] <STATEMENT> syntax
Introduce support for EXPLAIN [ANALYZE] [VERBOSE] <STATEMENT> syntax
1 parent cbd3c6b commit 17f2930

5 files changed

Lines changed: 156 additions & 11 deletions

File tree

src/ast/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,15 @@ impl fmt::Display for WindowFrameBound {
431431
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
432432
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
433433
pub enum Statement {
434+
// EXPLAIN
435+
Explain {
436+
// Carry out the command and show actual run times and other statistics.
437+
analyze: bool,
438+
// Display additional information regarding the plan.
439+
verbose: bool,
440+
/// A SQL query that specifies what to explain
441+
statement: Box<Statement>,
442+
},
434443
/// SELECT
435444
Query(Box<Query>),
436445
/// INSERT
@@ -591,6 +600,23 @@ impl fmt::Display for Statement {
591600
#[allow(clippy::cognitive_complexity)]
592601
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
593602
match self {
603+
Statement::Explain {
604+
verbose,
605+
analyze,
606+
statement,
607+
} => {
608+
write!(f, "EXPLAIN ")?;
609+
610+
if *analyze {
611+
write!(f, "ANALYZE ")?;
612+
}
613+
614+
if *verbose {
615+
write!(f, "VERBOSE ")?;
616+
}
617+
618+
write!(f, "{}", statement)
619+
}
594620
Statement::Query(s) => write!(f, "{}", s),
595621
Statement::Insert {
596622
table_name,

src/dialect/keywords.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ define_keywords!(
7272
ALL,
7373
ALLOCATE,
7474
ALTER,
75+
ANALYZE,
7576
AND,
7677
ANY,
7778
APPLY,
@@ -190,6 +191,7 @@ define_keywords!(
190191
EXECUTE,
191192
EXISTS,
192193
EXP,
194+
EXPLAIN,
193195
EXTENDED,
194196
EXTERNAL,
195197
EXTRACT,
@@ -443,6 +445,7 @@ define_keywords!(
443445
VARYING,
444446
VAR_POP,
445447
VAR_SAMP,
448+
VERBOSE,
446449
VERSIONING,
447450
VIEW,
448451
VIRTUAL,
@@ -465,6 +468,8 @@ define_keywords!(
465468
pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
466469
// Reserved as both a table and a column alias:
467470
Keyword::WITH,
471+
Keyword::EXPLAIN,
472+
Keyword::ANALYZE,
468473
Keyword::SELECT,
469474
Keyword::WHERE,
470475
Keyword::GROUP,
@@ -496,6 +501,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
496501
pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
497502
// Reserved as both a table and a column alias:
498503
Keyword::WITH,
504+
Keyword::EXPLAIN,
505+
Keyword::ANALYZE,
499506
Keyword::SELECT,
500507
Keyword::WHERE,
501508
Keyword::GROUP,

src/parser.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ impl<'a> Parser<'a> {
131131
pub fn parse_statement(&mut self) -> Result<Statement, ParserError> {
132132
match self.next_token() {
133133
Token::Word(w) => match w.keyword {
134+
Keyword::EXPLAIN => Ok(self.parse_explain()?),
134135
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
135136
self.prev_token();
136137
Ok(Statement::Query(Box::new(self.parse_query()?)))
@@ -1790,6 +1791,19 @@ impl<'a> Parser<'a> {
17901791
})
17911792
}
17921793

1794+
pub fn parse_explain(&mut self) -> Result<Statement, ParserError> {
1795+
let analyze = self.parse_keyword(Keyword::ANALYZE);
1796+
let verbose = self.parse_keyword(Keyword::VERBOSE);
1797+
1798+
let statement = Box::new(self.parse_statement()?);
1799+
1800+
Ok(Statement::Explain {
1801+
analyze,
1802+
verbose,
1803+
statement,
1804+
})
1805+
}
1806+
17931807
/// Parse a query expression, i.e. a `SELECT` statement optionally
17941808
/// preceeded with some `WITH` CTE declarations and optionally followed
17951809
/// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't

src/tokenizer.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,68 @@ mod tests {
734734
compare(expected, tokens);
735735
}
736736

737+
#[test]
738+
fn tokenize_explain_select() {
739+
let sql = String::from("EXPLAIN SELECT * FROM customer WHERE id = 1");
740+
let dialect = GenericDialect {};
741+
let mut tokenizer = Tokenizer::new(&dialect, &sql);
742+
let tokens = tokenizer.tokenize().unwrap();
743+
744+
let expected = vec![
745+
Token::make_keyword("EXPLAIN"),
746+
Token::Whitespace(Whitespace::Space),
747+
Token::make_keyword("SELECT"),
748+
Token::Whitespace(Whitespace::Space),
749+
Token::Mult,
750+
Token::Whitespace(Whitespace::Space),
751+
Token::make_keyword("FROM"),
752+
Token::Whitespace(Whitespace::Space),
753+
Token::make_word("customer", None),
754+
Token::Whitespace(Whitespace::Space),
755+
Token::make_keyword("WHERE"),
756+
Token::Whitespace(Whitespace::Space),
757+
Token::make_word("id", None),
758+
Token::Whitespace(Whitespace::Space),
759+
Token::Eq,
760+
Token::Whitespace(Whitespace::Space),
761+
Token::Number(String::from("1")),
762+
];
763+
764+
compare(expected, tokens);
765+
}
766+
767+
#[test]
768+
fn tokenize_explain_analyze_select() {
769+
let sql = String::from("EXPLAIN ANALYZE SELECT * FROM customer WHERE id = 1");
770+
let dialect = GenericDialect {};
771+
let mut tokenizer = Tokenizer::new(&dialect, &sql);
772+
let tokens = tokenizer.tokenize().unwrap();
773+
774+
let expected = vec![
775+
Token::make_keyword("EXPLAIN"),
776+
Token::Whitespace(Whitespace::Space),
777+
Token::make_keyword("ANALYZE"),
778+
Token::Whitespace(Whitespace::Space),
779+
Token::make_keyword("SELECT"),
780+
Token::Whitespace(Whitespace::Space),
781+
Token::Mult,
782+
Token::Whitespace(Whitespace::Space),
783+
Token::make_keyword("FROM"),
784+
Token::Whitespace(Whitespace::Space),
785+
Token::make_word("customer", None),
786+
Token::Whitespace(Whitespace::Space),
787+
Token::make_keyword("WHERE"),
788+
Token::Whitespace(Whitespace::Space),
789+
Token::make_word("id", None),
790+
Token::Whitespace(Whitespace::Space),
791+
Token::Eq,
792+
Token::Whitespace(Whitespace::Space),
793+
Token::Number(String::from("1")),
794+
];
795+
796+
compare(expected, tokens);
797+
}
798+
737799
#[test]
738800
fn tokenize_string_predicate() {
739801
let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");

tests/sqlparser_common.rs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -543,17 +543,23 @@ fn parse_is_not_null() {
543543
fn parse_not_precedence() {
544544
// NOT has higher precedence than OR/AND, so the following must parse as (NOT true) OR true
545545
let sql = "NOT true OR true";
546-
assert_matches!(verified_expr(sql), Expr::BinaryOp {
547-
op: BinaryOperator::Or,
548-
..
549-
});
546+
assert_matches!(
547+
verified_expr(sql),
548+
Expr::BinaryOp {
549+
op: BinaryOperator::Or,
550+
..
551+
}
552+
);
550553

551554
// But NOT has lower precedence than comparison operators, so the following parses as NOT (a IS NULL)
552555
let sql = "NOT a IS NULL";
553-
assert_matches!(verified_expr(sql), Expr::UnaryOp {
554-
op: UnaryOperator::Not,
555-
..
556-
});
556+
assert_matches!(
557+
verified_expr(sql),
558+
Expr::UnaryOp {
559+
op: UnaryOperator::Not,
560+
..
561+
}
562+
);
557563

558564
// NOT has lower precedence than BETWEEN, so the following parses as NOT (1 NOT BETWEEN 1 AND 2)
559565
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
@@ -1463,7 +1469,7 @@ fn parse_create_external_table_lowercase() {
14631469
lng DOUBLE) \
14641470
STORED AS PARQUET LOCATION '/tmp/example.csv'",
14651471
);
1466-
assert_matches!(ast, Statement::CreateTable{..});
1472+
assert_matches!(ast, Statement::CreateTable { .. });
14671473
}
14681474

14691475
#[test]
@@ -1606,6 +1612,33 @@ fn parse_scalar_function_in_projection() {
16061612
);
16071613
}
16081614

1615+
fn run_explain_analyze(query: &str, expected_verbose: bool, expected_analyze: bool) {
1616+
match verified_stmt(query) {
1617+
Statement::Explain {
1618+
analyze,
1619+
verbose,
1620+
statement,
1621+
} => {
1622+
assert_eq!(verbose, expected_verbose);
1623+
assert_eq!(analyze, expected_analyze);
1624+
assert_eq!("SELECT sqrt(id) FROM foo", statement.to_string());
1625+
}
1626+
_ => panic!("Unexpected Statement, must be Explain"),
1627+
}
1628+
}
1629+
1630+
#[test]
1631+
fn parse_explain_analyze_with_simple_select() {
1632+
run_explain_analyze("EXPLAIN SELECT sqrt(id) FROM foo", false, false);
1633+
run_explain_analyze("EXPLAIN VERBOSE SELECT sqrt(id) FROM foo", true, false);
1634+
run_explain_analyze("EXPLAIN ANALYZE SELECT sqrt(id) FROM foo", false, true);
1635+
run_explain_analyze(
1636+
"EXPLAIN ANALYZE VERBOSE SELECT sqrt(id) FROM foo",
1637+
true,
1638+
true,
1639+
);
1640+
}
1641+
16091642
#[test]
16101643
fn parse_named_argument_function() {
16111644
let sql = "SELECT FUN(a => '1', b => '2') FROM foo";
@@ -2554,11 +2587,14 @@ fn parse_multiple_statements() {
25542587
#[test]
25552588
fn parse_scalar_subqueries() {
25562589
let sql = "(SELECT 1) + (SELECT 2)";
2557-
assert_matches!(verified_expr(sql), Expr::BinaryOp {
2590+
assert_matches!(
2591+
verified_expr(sql),
2592+
Expr::BinaryOp {
25582593
op: BinaryOperator::Plus, ..
25592594
//left: box Subquery { .. },
25602595
//right: box Subquery { .. },
2561-
});
2596+
}
2597+
);
25622598
}
25632599

25642600
#[test]

0 commit comments

Comments
 (0)