mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-27 07:59:11 +00:00
Introduce support for EXPLAIN [ANALYZE] [VERBOSE] <STATEMENT> syntax
Introduce support for EXPLAIN [ANALYZE] [VERBOSE] <STATEMENT> syntax
This commit is contained in:
parent
cbd3c6b1a1
commit
17f2930885
5 changed files with 156 additions and 11 deletions
|
@ -431,6 +431,15 @@ impl fmt::Display for WindowFrameBound {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
|
// EXPLAIN
|
||||||
|
Explain {
|
||||||
|
// Carry out the command and show actual run times and other statistics.
|
||||||
|
analyze: bool,
|
||||||
|
// Display additional information regarding the plan.
|
||||||
|
verbose: bool,
|
||||||
|
/// A SQL query that specifies what to explain
|
||||||
|
statement: Box<Statement>,
|
||||||
|
},
|
||||||
/// SELECT
|
/// SELECT
|
||||||
Query(Box<Query>),
|
Query(Box<Query>),
|
||||||
/// INSERT
|
/// INSERT
|
||||||
|
@ -591,6 +600,23 @@ impl fmt::Display for Statement {
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Statement::Explain {
|
||||||
|
verbose,
|
||||||
|
analyze,
|
||||||
|
statement,
|
||||||
|
} => {
|
||||||
|
write!(f, "EXPLAIN ")?;
|
||||||
|
|
||||||
|
if *analyze {
|
||||||
|
write!(f, "ANALYZE ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if *verbose {
|
||||||
|
write!(f, "VERBOSE ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{}", statement)
|
||||||
|
}
|
||||||
Statement::Query(s) => write!(f, "{}", s),
|
Statement::Query(s) => write!(f, "{}", s),
|
||||||
Statement::Insert {
|
Statement::Insert {
|
||||||
table_name,
|
table_name,
|
||||||
|
|
|
@ -72,6 +72,7 @@ define_keywords!(
|
||||||
ALL,
|
ALL,
|
||||||
ALLOCATE,
|
ALLOCATE,
|
||||||
ALTER,
|
ALTER,
|
||||||
|
ANALYZE,
|
||||||
AND,
|
AND,
|
||||||
ANY,
|
ANY,
|
||||||
APPLY,
|
APPLY,
|
||||||
|
@ -190,6 +191,7 @@ define_keywords!(
|
||||||
EXECUTE,
|
EXECUTE,
|
||||||
EXISTS,
|
EXISTS,
|
||||||
EXP,
|
EXP,
|
||||||
|
EXPLAIN,
|
||||||
EXTENDED,
|
EXTENDED,
|
||||||
EXTERNAL,
|
EXTERNAL,
|
||||||
EXTRACT,
|
EXTRACT,
|
||||||
|
@ -443,6 +445,7 @@ define_keywords!(
|
||||||
VARYING,
|
VARYING,
|
||||||
VAR_POP,
|
VAR_POP,
|
||||||
VAR_SAMP,
|
VAR_SAMP,
|
||||||
|
VERBOSE,
|
||||||
VERSIONING,
|
VERSIONING,
|
||||||
VIEW,
|
VIEW,
|
||||||
VIRTUAL,
|
VIRTUAL,
|
||||||
|
@ -465,6 +468,8 @@ define_keywords!(
|
||||||
pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
|
pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
|
||||||
// Reserved as both a table and a column alias:
|
// Reserved as both a table and a column alias:
|
||||||
Keyword::WITH,
|
Keyword::WITH,
|
||||||
|
Keyword::EXPLAIN,
|
||||||
|
Keyword::ANALYZE,
|
||||||
Keyword::SELECT,
|
Keyword::SELECT,
|
||||||
Keyword::WHERE,
|
Keyword::WHERE,
|
||||||
Keyword::GROUP,
|
Keyword::GROUP,
|
||||||
|
@ -496,6 +501,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
|
||||||
pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
|
pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
|
||||||
// Reserved as both a table and a column alias:
|
// Reserved as both a table and a column alias:
|
||||||
Keyword::WITH,
|
Keyword::WITH,
|
||||||
|
Keyword::EXPLAIN,
|
||||||
|
Keyword::ANALYZE,
|
||||||
Keyword::SELECT,
|
Keyword::SELECT,
|
||||||
Keyword::WHERE,
|
Keyword::WHERE,
|
||||||
Keyword::GROUP,
|
Keyword::GROUP,
|
||||||
|
|
|
@ -131,6 +131,7 @@ impl<'a> Parser<'a> {
|
||||||
pub fn parse_statement(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_statement(&mut self) -> Result<Statement, ParserError> {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Token::Word(w) => match w.keyword {
|
Token::Word(w) => match w.keyword {
|
||||||
|
Keyword::EXPLAIN => Ok(self.parse_explain()?),
|
||||||
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
|
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
Ok(Statement::Query(Box::new(self.parse_query()?)))
|
Ok(Statement::Query(Box::new(self.parse_query()?)))
|
||||||
|
@ -1790,6 +1791,19 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_explain(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let analyze = self.parse_keyword(Keyword::ANALYZE);
|
||||||
|
let verbose = self.parse_keyword(Keyword::VERBOSE);
|
||||||
|
|
||||||
|
let statement = Box::new(self.parse_statement()?);
|
||||||
|
|
||||||
|
Ok(Statement::Explain {
|
||||||
|
analyze,
|
||||||
|
verbose,
|
||||||
|
statement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a query expression, i.e. a `SELECT` statement optionally
|
/// Parse a query expression, i.e. a `SELECT` statement optionally
|
||||||
/// preceeded with some `WITH` CTE declarations and optionally followed
|
/// preceeded with some `WITH` CTE declarations and optionally followed
|
||||||
/// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't
|
/// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't
|
||||||
|
|
|
@ -734,6 +734,68 @@ mod tests {
|
||||||
compare(expected, tokens);
|
compare(expected, tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_explain_select() {
|
||||||
|
let sql = String::from("EXPLAIN SELECT * FROM customer WHERE id = 1");
|
||||||
|
let dialect = GenericDialect {};
|
||||||
|
let mut tokenizer = Tokenizer::new(&dialect, &sql);
|
||||||
|
let tokens = tokenizer.tokenize().unwrap();
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
Token::make_keyword("EXPLAIN"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("SELECT"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Mult,
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("FROM"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("customer", None),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("WHERE"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("id", None),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Eq,
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Number(String::from("1")),
|
||||||
|
];
|
||||||
|
|
||||||
|
compare(expected, tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_explain_analyze_select() {
|
||||||
|
let sql = String::from("EXPLAIN ANALYZE SELECT * FROM customer WHERE id = 1");
|
||||||
|
let dialect = GenericDialect {};
|
||||||
|
let mut tokenizer = Tokenizer::new(&dialect, &sql);
|
||||||
|
let tokens = tokenizer.tokenize().unwrap();
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
Token::make_keyword("EXPLAIN"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("ANALYZE"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("SELECT"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Mult,
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("FROM"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("customer", None),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("WHERE"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("id", None),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Eq,
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::Number(String::from("1")),
|
||||||
|
];
|
||||||
|
|
||||||
|
compare(expected, tokens);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_string_predicate() {
|
fn tokenize_string_predicate() {
|
||||||
let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");
|
let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");
|
||||||
|
|
|
@ -543,17 +543,23 @@ fn parse_is_not_null() {
|
||||||
fn parse_not_precedence() {
|
fn parse_not_precedence() {
|
||||||
// NOT has higher precedence than OR/AND, so the following must parse as (NOT true) OR true
|
// NOT has higher precedence than OR/AND, so the following must parse as (NOT true) OR true
|
||||||
let sql = "NOT true OR true";
|
let sql = "NOT true OR true";
|
||||||
assert_matches!(verified_expr(sql), Expr::BinaryOp {
|
assert_matches!(
|
||||||
op: BinaryOperator::Or,
|
verified_expr(sql),
|
||||||
..
|
Expr::BinaryOp {
|
||||||
});
|
op: BinaryOperator::Or,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// But NOT has lower precedence than comparison operators, so the following parses as NOT (a IS NULL)
|
// But NOT has lower precedence than comparison operators, so the following parses as NOT (a IS NULL)
|
||||||
let sql = "NOT a IS NULL";
|
let sql = "NOT a IS NULL";
|
||||||
assert_matches!(verified_expr(sql), Expr::UnaryOp {
|
assert_matches!(
|
||||||
op: UnaryOperator::Not,
|
verified_expr(sql),
|
||||||
..
|
Expr::UnaryOp {
|
||||||
});
|
op: UnaryOperator::Not,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// NOT has lower precedence than BETWEEN, so the following parses as NOT (1 NOT BETWEEN 1 AND 2)
|
// NOT has lower precedence than BETWEEN, so the following parses as NOT (1 NOT BETWEEN 1 AND 2)
|
||||||
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
|
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
|
||||||
|
@ -1463,7 +1469,7 @@ fn parse_create_external_table_lowercase() {
|
||||||
lng DOUBLE) \
|
lng DOUBLE) \
|
||||||
STORED AS PARQUET LOCATION '/tmp/example.csv'",
|
STORED AS PARQUET LOCATION '/tmp/example.csv'",
|
||||||
);
|
);
|
||||||
assert_matches!(ast, Statement::CreateTable{..});
|
assert_matches!(ast, Statement::CreateTable { .. });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1606,6 +1612,33 @@ fn parse_scalar_function_in_projection() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_explain_analyze(query: &str, expected_verbose: bool, expected_analyze: bool) {
|
||||||
|
match verified_stmt(query) {
|
||||||
|
Statement::Explain {
|
||||||
|
analyze,
|
||||||
|
verbose,
|
||||||
|
statement,
|
||||||
|
} => {
|
||||||
|
assert_eq!(verbose, expected_verbose);
|
||||||
|
assert_eq!(analyze, expected_analyze);
|
||||||
|
assert_eq!("SELECT sqrt(id) FROM foo", statement.to_string());
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected Statement, must be Explain"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_explain_analyze_with_simple_select() {
|
||||||
|
run_explain_analyze("EXPLAIN SELECT sqrt(id) FROM foo", false, false);
|
||||||
|
run_explain_analyze("EXPLAIN VERBOSE SELECT sqrt(id) FROM foo", true, false);
|
||||||
|
run_explain_analyze("EXPLAIN ANALYZE SELECT sqrt(id) FROM foo", false, true);
|
||||||
|
run_explain_analyze(
|
||||||
|
"EXPLAIN ANALYZE VERBOSE SELECT sqrt(id) FROM foo",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_named_argument_function() {
|
fn parse_named_argument_function() {
|
||||||
let sql = "SELECT FUN(a => '1', b => '2') FROM foo";
|
let sql = "SELECT FUN(a => '1', b => '2') FROM foo";
|
||||||
|
@ -2554,11 +2587,14 @@ fn parse_multiple_statements() {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_scalar_subqueries() {
|
fn parse_scalar_subqueries() {
|
||||||
let sql = "(SELECT 1) + (SELECT 2)";
|
let sql = "(SELECT 1) + (SELECT 2)";
|
||||||
assert_matches!(verified_expr(sql), Expr::BinaryOp {
|
assert_matches!(
|
||||||
|
verified_expr(sql),
|
||||||
|
Expr::BinaryOp {
|
||||||
op: BinaryOperator::Plus, ..
|
op: BinaryOperator::Plus, ..
|
||||||
//left: box Subquery { .. },
|
//left: box Subquery { .. },
|
||||||
//right: box Subquery { .. },
|
//right: box Subquery { .. },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue