feat: `EXPLAIN` statement (#5763)
parent
82d5c7f336
commit
b862ae6476
|
@ -0,0 +1,140 @@
|
|||
#![allow(dead_code)] // Temporary
|
||||
|
||||
use crate::internal::{expect, ParseResult};
|
||||
use crate::select::{select_statement, SelectStatement};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::multispace1;
|
||||
use nom::combinator::{map, opt, value};
|
||||
use nom::sequence::{preceded, tuple};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Represents various options for an `EXPLAIN` statement.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExplainOption {
|
||||
/// `EXPLAIN VERBOSE statement`
|
||||
Verbose,
|
||||
/// `EXPLAIN ANALYZE statement`
|
||||
Analyze,
|
||||
/// `EXPLAIN ANALYZE VERBOSE statement`
|
||||
AnalyzeVerbose,
|
||||
}
|
||||
|
||||
impl Display for ExplainOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Verbose => f.write_str("VERBOSE"),
|
||||
Self::Analyze => f.write_str("ANALYZE"),
|
||||
Self::AnalyzeVerbose => f.write_str("ANALYZE VERBOSE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an `EXPLAIN` statement.
|
||||
///
|
||||
/// ```text
|
||||
/// explain ::= "EXPLAIN" explain_options? select_statement
|
||||
/// explain_options ::= "VERBOSE" | ( "ANALYZE" "VERBOSE"? )
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExplainStatement {
|
||||
options: Option<ExplainOption>,
|
||||
select: Box<SelectStatement>,
|
||||
}
|
||||
|
||||
impl Display for ExplainStatement {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("EXPLAIN ")?;
|
||||
if let Some(options) = &self.options {
|
||||
write!(f, "{} ", options)?;
|
||||
}
|
||||
Display::fmt(&self.select, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an `EXPLAIN` statement.
|
||||
pub fn explain_statement(i: &str) -> ParseResult<&str, ExplainStatement> {
|
||||
map(
|
||||
tuple((
|
||||
tag_no_case("EXPLAIN"),
|
||||
opt(preceded(
|
||||
multispace1,
|
||||
alt((
|
||||
map(
|
||||
preceded(
|
||||
tag_no_case("ANALYZE"),
|
||||
opt(preceded(multispace1, tag_no_case("VERBOSE"))),
|
||||
),
|
||||
|v| match v {
|
||||
// If the optional combinator is Some, then it matched VERBOSE
|
||||
Some(_) => ExplainOption::AnalyzeVerbose,
|
||||
_ => ExplainOption::Analyze,
|
||||
},
|
||||
),
|
||||
value(ExplainOption::Verbose, tag_no_case("VERBOSE")),
|
||||
)),
|
||||
)),
|
||||
multispace1,
|
||||
expect(
|
||||
"invalid EXPLAIN statement, expected SELECT statement",
|
||||
select_statement,
|
||||
),
|
||||
)),
|
||||
|(_, options, _, select)| ExplainStatement {
|
||||
options,
|
||||
select: Box::new(select),
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::assert_expect_error;
|
||||
use crate::explain::{explain_statement, ExplainOption};
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn test_explain_statement() {
|
||||
let (remain, got) = explain_statement("EXPLAIN SELECT val from temp").unwrap();
|
||||
assert_eq!(remain, ""); // assert that all input was consumed
|
||||
assert_matches!(got.options, None);
|
||||
assert_eq!(format!("{}", got), "EXPLAIN SELECT val FROM temp");
|
||||
|
||||
let (remain, got) = explain_statement("EXPLAIN VERBOSE SELECT val from temp").unwrap();
|
||||
assert_eq!(remain, "");
|
||||
assert_matches!(&got.options, Some(o) if *o == ExplainOption::Verbose);
|
||||
assert_eq!(format!("{}", got), "EXPLAIN VERBOSE SELECT val FROM temp");
|
||||
|
||||
let (remain, got) = explain_statement("EXPLAIN ANALYZE SELECT val from temp").unwrap();
|
||||
assert_eq!(remain, "");
|
||||
assert_matches!(&got.options, Some(o) if *o == ExplainOption::Analyze);
|
||||
assert_eq!(format!("{}", got), "EXPLAIN ANALYZE SELECT val FROM temp");
|
||||
|
||||
let (remain, got) =
|
||||
explain_statement("EXPLAIN ANALYZE VERBOSE SELECT val from temp").unwrap();
|
||||
assert_eq!(remain, "");
|
||||
assert_matches!(&got.options, Some(o) if *o == ExplainOption::AnalyzeVerbose);
|
||||
assert_eq!(
|
||||
format!("{}", got),
|
||||
"EXPLAIN ANALYZE VERBOSE SELECT val FROM temp"
|
||||
);
|
||||
|
||||
// Fallible cases
|
||||
|
||||
assert_expect_error!(
|
||||
explain_statement("EXPLAIN ANALYZE SHOW DATABASES"),
|
||||
"invalid EXPLAIN statement, expected SELECT statement"
|
||||
);
|
||||
|
||||
assert_expect_error!(
|
||||
explain_statement("EXPLAIN ANALYZE EXPLAIN SELECT val from temp"),
|
||||
"invalid EXPLAIN statement, expected SELECT statement"
|
||||
);
|
||||
|
||||
// surfaces statement-specific errors
|
||||
assert_expect_error!(
|
||||
explain_statement("EXPLAIN ANALYZE SELECT cpu FROM 'foo'"),
|
||||
"invalid FROM clause, expected identifier, regular expression or subquery"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ mod test_util;
|
|||
mod common;
|
||||
mod delete;
|
||||
mod drop;
|
||||
mod explain;
|
||||
mod expression;
|
||||
mod identifier;
|
||||
mod internal;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::delete::{delete_statement, DeleteStatement};
|
||||
use crate::drop::{drop_statement, DropMeasurementStatement};
|
||||
use crate::explain::{explain_statement, ExplainStatement};
|
||||
use crate::internal::ParseResult;
|
||||
use crate::select::{select_statement, SelectStatement};
|
||||
use crate::show::{show_statement, ShowDatabasesStatement};
|
||||
|
@ -19,6 +20,8 @@ pub enum Statement {
|
|||
Delete(Box<DeleteStatement>),
|
||||
/// Represents a `DROP MEASUREMENT` statement.
|
||||
DropMeasurement(Box<DropMeasurementStatement>),
|
||||
/// Represents an `EXPLAIN` statement.
|
||||
Explain(Box<ExplainStatement>),
|
||||
/// Represents a `SELECT` statement.
|
||||
Select(Box<SelectStatement>),
|
||||
/// Represents a `SHOW DATABASES` statement.
|
||||
|
@ -40,6 +43,7 @@ impl Display for Statement {
|
|||
match self {
|
||||
Self::Delete(s) => Display::fmt(s, f),
|
||||
Self::DropMeasurement(s) => Display::fmt(s, f),
|
||||
Self::Explain(s) => Display::fmt(s, f),
|
||||
Self::Select(s) => Display::fmt(s, f),
|
||||
Self::ShowDatabases(s) => Display::fmt(s, f),
|
||||
Self::ShowMeasurements(s) => Display::fmt(s, f),
|
||||
|
@ -56,6 +60,7 @@ pub fn statement(i: &str) -> ParseResult<&str, Statement> {
|
|||
alt((
|
||||
map(delete_statement, |s| Statement::Delete(Box::new(s))),
|
||||
map(drop_statement, |s| Statement::DropMeasurement(Box::new(s))),
|
||||
map(explain_statement, |s| Statement::Explain(Box::new(s))),
|
||||
map(select_statement, |s| Statement::Select(Box::new(s))),
|
||||
show_statement,
|
||||
))(i)
|
||||
|
@ -77,6 +82,10 @@ mod test {
|
|||
let (got, _) = statement("DROP MEASUREMENT foo").unwrap();
|
||||
assert_eq!(got, "");
|
||||
|
||||
// explain_statement combinator
|
||||
let (got, _) = statement("EXPLAIN SELECT * FROM cpu").unwrap();
|
||||
assert_eq!(got, "");
|
||||
|
||||
let (got, _) = statement("SELECT * FROM foo WHERE time > now() - 5m AND host = 'bar' GROUP BY TIME(5m) FILL(previous) ORDER BY time DESC").unwrap();
|
||||
assert_eq!(got, "");
|
||||
|
||||
|
|
Loading…
Reference in New Issue