Skip to content
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
26 changes: 26 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,15 @@ impl fmt::Display for WindowFrameBound {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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
Query(Box<Query>),
/// INSERT
Expand Down Expand Up @@ -591,6 +600,23 @@ impl fmt::Display for Statement {
#[allow(clippy::cognitive_complexity)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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::Insert {
table_name,
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ define_keywords!(
ALL,
ALLOCATE,
ALTER,
ANALYZE,
AND,
ANY,
APPLY,
Expand Down Expand Up @@ -190,6 +191,7 @@ define_keywords!(
EXECUTE,
EXISTS,
EXP,
EXPLAIN,
EXTENDED,
EXTERNAL,
EXTRACT,
Expand Down Expand Up @@ -443,6 +445,7 @@ define_keywords!(
VARYING,
VAR_POP,
VAR_SAMP,
VERBOSE,
VERSIONING,
VIEW,
VIRTUAL,
Expand All @@ -465,6 +468,8 @@ define_keywords!(
pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
// Reserved as both a table and a column alias:
Keyword::WITH,
Keyword::EXPLAIN,
Keyword::ANALYZE,
Keyword::SELECT,
Keyword::WHERE,
Keyword::GROUP,
Expand Down Expand Up @@ -496,6 +501,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
// Reserved as both a table and a column alias:
Keyword::WITH,
Keyword::EXPLAIN,
Keyword::ANALYZE,
Keyword::SELECT,
Keyword::WHERE,
Keyword::GROUP,
Expand Down
14 changes: 14 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl<'a> Parser<'a> {
pub fn parse_statement(&mut self) -> Result<Statement, ParserError> {
match self.next_token() {
Token::Word(w) => match w.keyword {
Keyword::EXPLAIN => Ok(self.parse_explain()?),
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
self.prev_token();
Ok(Statement::Query(Box::new(self.parse_query()?)))
Expand Down Expand Up @@ -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
/// preceeded with some `WITH` CTE declarations and optionally followed
/// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't
Expand Down
62 changes: 62 additions & 0 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,68 @@ mod tests {
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]
fn tokenize_string_predicate() {
let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");
Expand Down
58 changes: 47 additions & 11 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,17 +543,23 @@ fn parse_is_not_null() {
fn parse_not_precedence() {
// NOT has higher precedence than OR/AND, so the following must parse as (NOT true) OR true
let sql = "NOT true OR true";
assert_matches!(verified_expr(sql), Expr::BinaryOp {
op: BinaryOperator::Or,
..
});
assert_matches!(
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)
let sql = "NOT a IS NULL";
assert_matches!(verified_expr(sql), Expr::UnaryOp {
op: UnaryOperator::Not,
..
});
assert_matches!(
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)
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
Expand Down Expand Up @@ -1463,7 +1469,7 @@ fn parse_create_external_table_lowercase() {
lng DOUBLE) \
STORED AS PARQUET LOCATION '/tmp/example.csv'",
);
assert_matches!(ast, Statement::CreateTable{..});
assert_matches!(ast, Statement::CreateTable { .. });
}

#[test]
Expand Down Expand Up @@ -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]
fn parse_named_argument_function() {
let sql = "SELECT FUN(a => '1', b => '2') FROM foo";
Expand Down Expand Up @@ -2554,11 +2587,14 @@ fn parse_multiple_statements() {
#[test]
fn parse_scalar_subqueries() {
let sql = "(SELECT 1) + (SELECT 2)";
assert_matches!(verified_expr(sql), Expr::BinaryOp {
assert_matches!(
verified_expr(sql),
Expr::BinaryOp {
op: BinaryOperator::Plus, ..
//left: box Subquery { .. },
//right: box Subquery { .. },
});
}
);
}

#[test]
Expand Down