From b0a36eda6ef3ac22595bef355ca6e75882f87479 Mon Sep 17 00:00:00 2001 From: git-hulk Date: Sat, 5 Oct 2024 22:51:27 +0800 Subject: [PATCH 1/2] Add support of `EXPLAIN QUERY PLAN` syntax for SQLite dialect This closes #1455. For the syntax documentation, please refer to: https://sqlite.org/lang_explain.html --- src/ast/mod.rs | 26 ++++++++++++++++++-------- src/keywords.rs | 1 + src/parser/mod.rs | 4 ++++ tests/sqlparser_common.rs | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8c4bc25af..2d7a5a882 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3111,6 +3111,11 @@ pub enum Statement { analyze: bool, // Display additional information regarding the plan. verbose: bool, + /// `EXPLAIN QUERY PLAN` + /// Display the query plan without running the query. + /// + /// [SQLite](https://sqlite.org/lang_explain.html) + query_plan: bool, /// A SQL query that specifies what to explain statement: Box, /// Optional output format of explain @@ -3302,22 +3307,27 @@ impl fmt::Display for Statement { describe_alias, verbose, analyze, + query_plan, statement, format, options, } => { write!(f, "{describe_alias} ")?; - if *analyze { - write!(f, "ANALYZE ")?; - } + if *query_plan { + write!(f, "QUERY PLAN ")?; + } else { + if *analyze { + write!(f, "ANALYZE ")?; + } - if *verbose { - write!(f, "VERBOSE ")?; - } + if *verbose { + write!(f, "VERBOSE ")?; + } - if let Some(format) = format { - write!(f, "FORMAT {format} ")?; + if let Some(format) = format { + write!(f, "FORMAT {format} ")?; + } } if let Some(options) = options { diff --git a/src/keywords.rs b/src/keywords.rs index 49c6ce20f..ecf4bd474 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -572,6 +572,7 @@ define_keywords!( PERSISTENT, PIVOT, PLACING, + PLAN, PLANS, POLICY, PORTION, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 88c3bd19c..e01195161 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8632,6 +8632,7 @@ impl<'a> Parser<'a> { ) -> Result { let mut analyze = false; let mut verbose = false; + let mut query_plan = false; let mut format = None; let mut options = None; @@ -8642,6 +8643,8 @@ impl<'a> Parser<'a> { && self.peek_token().token == Token::LParen { options = Some(self.parse_utility_options()?) + } else if self.parse_keywords(&[Keyword::QUERY, Keyword::PLAN]) { + query_plan = true; } else { analyze = self.parse_keyword(Keyword::ANALYZE); verbose = self.parse_keyword(Keyword::VERBOSE); @@ -8658,6 +8661,7 @@ impl<'a> Parser<'a> { describe_alias, analyze, verbose, + query_plan, statement: Box::new(statement), format, options, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 5d5a17ca5..7068ffc30 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4295,6 +4295,7 @@ fn run_explain_analyze( describe_alias: _, analyze, verbose, + query_plan, statement, format, options, @@ -4303,6 +4304,7 @@ fn run_explain_analyze( assert_eq!(analyze, expected_analyze); assert_eq!(format, expected_format); assert_eq!(options, exepcted_options); + assert!(!query_plan); assert_eq!("SELECT sqrt(id) FROM foo", statement.to_string()); } _ => panic!("Unexpected Statement, must be Explain"), @@ -4417,6 +4419,36 @@ fn parse_explain_analyze_with_simple_select() { ); } +#[test] +fn parse_explain_query_plan() { + match all_dialects().verified_stmt("EXPLAIN QUERY PLAN SELECT sqrt(id) FROM foo") { + Statement::Explain { + query_plan, + analyze, + verbose, + statement, + .. + } => { + assert!(query_plan); + assert!(!analyze); + assert!(!verbose); + assert_eq!("SELECT sqrt(id) FROM foo", statement.to_string()); + } + _ => unreachable!(), + } + + // omit QUERY PLAN should be good + all_dialects().verified_stmt("EXPLAIN SELECT sqrt(id) FROM foo"); + + // missing PLAN keyword should return error + assert_eq!( + ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()), + all_dialects() + .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo") + .unwrap_err() + ); +} + #[test] fn parse_named_argument_function() { let sql = "SELECT FUN(a => '1', b => '2') FROM foo"; From 8986b338548aecfa170628a15a79be49c7c970f9 Mon Sep 17 00:00:00 2001 From: git-hulk Date: Tue, 8 Oct 2024 10:14:43 +0800 Subject: [PATCH 2/2] Remove the else condition --- src/ast/mod.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2d7a5a882..4acc545b0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3316,18 +3316,17 @@ impl fmt::Display for Statement { if *query_plan { write!(f, "QUERY PLAN ")?; - } else { - if *analyze { - write!(f, "ANALYZE ")?; - } + } + if *analyze { + write!(f, "ANALYZE ")?; + } - if *verbose { - write!(f, "VERBOSE ")?; - } + if *verbose { + write!(f, "VERBOSE ")?; + } - if let Some(format) = format { - write!(f, "FORMAT {format} ")?; - } + if let Some(format) = format { + write!(f, "FORMAT {format} ")?; } if let Some(options) = options {