diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 995370f5b..bc559a660 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,12 +3307,16 @@ impl fmt::Display for Statement { describe_alias, verbose, analyze, + query_plan, statement, format, options, } => { write!(f, "{describe_alias} ")?; + if *query_plan { + write!(f, "QUERY PLAN ")?; + } if *analyze { write!(f, "ANALYZE ")?; } 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 34574a6b3..cc60bbbd7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8662,6 +8662,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; @@ -8672,6 +8673,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); @@ -8688,6 +8691,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";