diff --git a/.gitignore b/.gitignore index 84cdd03cf0..842b7967d6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ debug/ # JetBrains IDE config directory .idea/ +*.iml # VSCode IDE config directory .vscode/ diff --git a/src/datanode/src/instance.rs b/src/datanode/src/instance.rs index b5c0b028e2..4646b525e5 100644 --- a/src/datanode/src/instance.rs +++ b/src/datanode/src/instance.rs @@ -139,7 +139,11 @@ impl Instance { }; Ok(Self { query_engine: query_engine.clone(), - sql_handler: SqlHandler::new(table_engine, catalog_manager.clone()), + sql_handler: SqlHandler::new( + table_engine, + catalog_manager.clone(), + query_engine.clone(), + ), catalog_manager, physical_planner: PhysicalPlanner::new(query_engine), script_executor, diff --git a/src/datanode/src/instance/sql.rs b/src/datanode/src/instance/sql.rs index ba1793e8f8..8f8b263a31 100644 --- a/src/datanode/src/instance/sql.rs +++ b/src/datanode/src/instance/sql.rs @@ -115,6 +115,11 @@ impl Instance { Statement::ShowTables(stmt) => { self.sql_handler.execute(SqlRequest::ShowTables(stmt)).await } + Statement::Explain(stmt) => { + self.sql_handler + .execute(SqlRequest::Explain(Box::new(stmt))) + .await + } Statement::DescribeTable(stmt) => { self.sql_handler .execute(SqlRequest::DescribeTable(stmt)) diff --git a/src/datanode/src/mock.rs b/src/datanode/src/mock.rs index 240b68e04d..73b758cc13 100644 --- a/src/datanode/src/mock.rs +++ b/src/datanode/src/mock.rs @@ -58,7 +58,11 @@ impl Instance { let factory = QueryEngineFactory::new(catalog_manager.clone()); let query_engine = factory.query_engine(); - let sql_handler = SqlHandler::new(mock_engine.clone(), catalog_manager.clone()); + let sql_handler = SqlHandler::new( + mock_engine.clone(), + catalog_manager.clone(), + query_engine.clone(), + ); let physical_planner = PhysicalPlanner::new(query_engine.clone()); let script_executor = ScriptExecutor::new(catalog_manager.clone(), query_engine.clone()) .await @@ -123,7 +127,11 @@ impl Instance { ); Ok(Self { query_engine: query_engine.clone(), - sql_handler: SqlHandler::new(table_engine, catalog_manager.clone()), + sql_handler: SqlHandler::new( + table_engine, + catalog_manager.clone(), + query_engine.clone(), + ), catalog_manager, physical_planner: PhysicalPlanner::new(query_engine), script_executor, diff --git a/src/datanode/src/sql.rs b/src/datanode/src/sql.rs index 8f989badef..2817fda387 100644 --- a/src/datanode/src/sql.rs +++ b/src/datanode/src/sql.rs @@ -16,9 +16,11 @@ use catalog::CatalogManagerRef; use common_query::Output; -use query::sql::{describe_table, show_databases, show_tables}; +use query::query_engine::QueryEngineRef; +use query::sql::{describe_table, explain, show_databases, show_tables}; use snafu::{OptionExt, ResultExt}; use sql::statements::describe::DescribeTable; +use sql::statements::explain::Explain; use sql::statements::show::{ShowDatabases, ShowTables}; use table::engine::{EngineContext, TableEngineRef, TableReference}; use table::requests::*; @@ -39,19 +41,26 @@ pub enum SqlRequest { ShowDatabases(ShowDatabases), ShowTables(ShowTables), DescribeTable(DescribeTable), + Explain(Box), } // Handler to execute SQL except query pub struct SqlHandler { table_engine: TableEngineRef, catalog_manager: CatalogManagerRef, + query_engine: QueryEngineRef, } impl SqlHandler { - pub fn new(table_engine: TableEngineRef, catalog_manager: CatalogManagerRef) -> Self { + pub fn new( + table_engine: TableEngineRef, + catalog_manager: CatalogManagerRef, + query_engine: QueryEngineRef, + ) -> Self { Self { table_engine, catalog_manager, + query_engine, } } @@ -70,6 +79,9 @@ impl SqlHandler { SqlRequest::DescribeTable(stmt) => { describe_table(stmt, self.catalog_manager.clone()).context(error::ExecuteSqlSnafu) } + SqlRequest::Explain(stmt) => explain(stmt, self.query_engine.clone()) + .await + .context(error::ExecuteSqlSnafu), } } @@ -216,7 +228,7 @@ mod tests { ); let factory = QueryEngineFactory::new(catalog_list.clone()); let query_engine = factory.query_engine(); - let sql_handler = SqlHandler::new(table_engine, catalog_list); + let sql_handler = SqlHandler::new(table_engine, catalog_list, query_engine.clone()); let stmt = match query_engine.sql_to_statement(sql).unwrap() { Statement::Insert(i) => i, diff --git a/src/datanode/src/sql/create.rs b/src/datanode/src/sql/create.rs index d02de70211..c134832063 100644 --- a/src/datanode/src/sql/create.rs +++ b/src/datanode/src/sql/create.rs @@ -172,7 +172,7 @@ impl SqlHandler { return ConstraintNotSupportedSnafu { constraint: format!("{:?}", c), } - .fail() + .fail(); } } } diff --git a/src/datanode/src/tests/test_util.rs b/src/datanode/src/tests/test_util.rs index 4c9a390d58..07a49f35ba 100644 --- a/src/datanode/src/tests/test_util.rs +++ b/src/datanode/src/tests/test_util.rs @@ -21,6 +21,7 @@ use datatypes::data_type::ConcreteDataType; use datatypes::schema::{ColumnSchema, SchemaBuilder}; use mito::config::EngineConfig; use mito::table::test_util::{new_test_object_store, MockEngine, MockMitoEngine}; +use query::QueryEngineFactory; use servers::Mode; use snafu::ResultExt; use table::engine::{EngineContext, TableEngineRef}; @@ -121,5 +122,9 @@ pub async fn create_mock_sql_handler() -> SqlHandler { .await .unwrap(), ); - SqlHandler::new(mock_engine, catalog_manager) + + let catalog_list = catalog::local::new_memory_catalog_list().unwrap(); + let factory = QueryEngineFactory::new(catalog_list); + + SqlHandler::new(mock_engine, catalog_manager, factory.query_engine()) } diff --git a/src/frontend/src/instance.rs b/src/frontend/src/instance.rs index 8d49fc32a3..26f1dd72fe 100644 --- a/src/frontend/src/instance.rs +++ b/src/frontend/src/instance.rs @@ -52,6 +52,7 @@ use snafu::prelude::*; use sql::dialect::GenericDialect; use sql::parser::ParserContext; use sql::statements::create::Partitions; +use sql::statements::explain::Explain; use sql::statements::insert::Insert; use sql::statements::statement::Statement; @@ -279,6 +280,17 @@ impl Instance { } } + /// Handle explain expr + pub async fn handle_explain(&self, sql: &str, explain_stmt: Explain) -> Result { + if let Some(dist_instance) = &self.dist_instance { + dist_instance + .handle_sql(sql, Statement::Explain(explain_stmt)) + .await + } else { + Ok(Output::AffectedRows(0)) + } + } + /// Handle batch inserts pub async fn handle_inserts(&self, insert_expr: &[InsertExpr]) -> Result { let mut success = 0; @@ -639,8 +651,13 @@ impl SqlQueryHandler for Instance { .await .map_err(BoxedError::new) .context(server_error::ExecuteQuerySnafu { query }), + Statement::Explain(explain_stmt) => self + .handle_explain(query, explain_stmt) + .await + .map_err(BoxedError::new) + .context(server_error::ExecuteQuerySnafu { query }), Statement::ShowCreateTable(_) => { - return server_error::NotSupportedSnafu { feat: query }.fail() + return server_error::NotSupportedSnafu { feat: query }.fail(); } } .map_err(BoxedError::new) diff --git a/src/frontend/src/instance/distributed.rs b/src/frontend/src/instance/distributed.rs index b5b4a6a9af..0611f2bb4a 100644 --- a/src/frontend/src/instance/distributed.rs +++ b/src/frontend/src/instance/distributed.rs @@ -31,7 +31,7 @@ use meta_client::rpc::{ CreateRequest as MetaCreateRequest, Partition as MetaPartition, PutRequest, RouteResponse, TableName, TableRoute, }; -use query::sql::{describe_table, show_databases, show_tables}; +use query::sql::{describe_table, explain, show_databases, show_tables}; use query::{QueryEngineFactory, QueryEngineRef}; use snafu::{ensure, OptionExt, ResultExt}; use sql::statements::create::Partitions; @@ -146,6 +146,9 @@ impl DistInstance { .context(error::ExecuteSqlSnafu { sql }), Statement::DescribeTable(stmt) => describe_table(stmt, self.catalog_manager.clone()) .context(error::ExecuteSqlSnafu { sql }), + Statement::Explain(stmt) => explain(Box::new(stmt), self.query_engine.clone()) + .await + .context(error::ExecuteSqlSnafu { sql }), _ => unreachable!(), } } diff --git a/src/query/src/datafusion/planner.rs b/src/query/src/datafusion/planner.rs index 6d5dcae527..b3ae490fd4 100644 --- a/src/query/src/datafusion/planner.rs +++ b/src/query/src/datafusion/planner.rs @@ -22,6 +22,7 @@ use datafusion::physical_plan::udf::ScalarUDF; use datafusion::sql::planner::{ContextProvider, SqlToRel}; use datatypes::arrow::datatypes::DataType; use snafu::ResultExt; +use sql::statements::explain::Explain; use sql::statements::query::Query; use sql::statements::statement::Statement; @@ -53,6 +54,18 @@ impl<'a, S: ContextProvider + Send + Sync> DfPlanner<'a, S> { Ok(LogicalPlan::DfPlan(result)) } + + /// Converts EXPLAIN statement to logical plan. + pub fn explain_to_plan(&self, explain: Explain) -> Result { + let result = self + .sql_to_rel + .sql_statement_to_plan(explain.inner.clone()) + .context(error::PlanSqlSnafu { + sql: explain.to_string(), + })?; + + Ok(LogicalPlan::DfPlan(result)) + } } impl<'a, S> Planner for DfPlanner<'a, S> @@ -63,6 +76,7 @@ where fn statement_to_plan(&self, statement: Statement) -> Result { match statement { Statement::Query(qb) => self.query_to_plan(qb), + Statement::Explain(explain) => self.explain_to_plan(explain), Statement::ShowTables(_) | Statement::ShowDatabases(_) | Statement::ShowCreateTable(_) diff --git a/src/query/src/sql.rs b/src/query/src/sql.rs index 51b81d01af..cfc8a39fd1 100644 --- a/src/query/src/sql.rs +++ b/src/query/src/sql.rs @@ -24,9 +24,12 @@ use datatypes::vectors::{Helper, StringVector}; use once_cell::sync::Lazy; use snafu::{ensure, OptionExt, ResultExt}; use sql::statements::describe::DescribeTable; +use sql::statements::explain::Explain; use sql::statements::show::{ShowDatabases, ShowKind, ShowTables}; +use sql::statements::statement::Statement; use crate::error::{self, Result}; +use crate::QueryEngineRef; const SCHEMAS_COLUMN: &str = "Schemas"; const TABLES_COLUMN: &str = "Tables"; @@ -138,6 +141,11 @@ pub fn show_tables(stmt: ShowTables, catalog_manager: CatalogManagerRef) -> Resu Ok(Output::RecordBatches(records)) } +pub async fn explain(stmt: Box, query_engine: QueryEngineRef) -> Result { + let plan = query_engine.statement_to_plan(Statement::Explain(*stmt))?; + query_engine.execute(&plan).await +} + pub fn describe_table(stmt: DescribeTable, catalog_manager: CatalogManagerRef) -> Result { let catalog = stmt.catalog_name.as_str(); let schema = stmt.schema_name.as_str(); diff --git a/src/sql/src/parser.rs b/src/sql/src/parser.rs index 6744cb824b..11e29b26a7 100644 --- a/src/sql/src/parser.rs +++ b/src/sql/src/parser.rs @@ -22,6 +22,7 @@ use crate::error::{ self, InvalidDatabaseNameSnafu, InvalidTableNameSnafu, Result, SyntaxSnafu, TokenizerSnafu, }; use crate::statements::describe::DescribeTable; +use crate::statements::explain::Explain; use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowKind, ShowTables}; use crate::statements::statement::Statement; use crate::statements::table_idents_to_full_name; @@ -258,7 +259,16 @@ impl<'a> ParserContext<'a> { } fn parse_explain(&mut self) -> Result { - todo!() + let explain_statement = + self.parser + .parse_explain(false) + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a query statement", + actual: self.peek_token_as_string(), + })?; + + Ok(Statement::Explain(Explain::try_from(explain_statement)?)) } // Report unexpected token @@ -328,6 +338,7 @@ impl<'a> ParserContext<'a> { mod tests { use std::assert_matches::assert_matches; + use sqlparser::ast::{Query as SpQuery, Statement as SpStatement}; use sqlparser::dialect::GenericDialect; use super::*; @@ -471,4 +482,54 @@ mod tests { }) ); } + + #[test] + pub fn test_explain() { + let sql = "EXPLAIN select * from foo"; + let result = ParserContext::create_with_dialect(sql, &GenericDialect {}); + let stmts = result.unwrap(); + assert_eq!(1, stmts.len()); + + let select = sqlparser::ast::Select { + distinct: false, + top: None, + projection: vec![sqlparser::ast::SelectItem::Wildcard], + from: vec![sqlparser::ast::TableWithJoins { + relation: sqlparser::ast::TableFactor::Table { + name: sqlparser::ast::ObjectName(vec![sqlparser::ast::Ident::new("foo")]), + alias: None, + args: vec![], + with_hints: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + selection: None, + group_by: vec![], + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + }; + + let sp_statement = SpStatement::Query(Box::new(SpQuery { + with: None, + body: sqlparser::ast::SetExpr::Select(Box::new(select)), + order_by: vec![], + limit: None, + offset: None, + fetch: None, + lock: None, + })); + + let explain = Explain::try_from(SpStatement::Explain { + describe_alias: false, + analyze: false, + verbose: false, + statement: Box::new(sp_statement), + }) + .unwrap(); + + assert_eq!(stmts[0], Statement::Explain(explain)) + } } diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index 4e15d15ea4..48cbbe1531 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -15,6 +15,7 @@ pub mod alter; pub mod create; pub mod describe; +pub mod explain; pub mod insert; pub mod query; pub mod show; diff --git a/src/sql/src/statements/explain.rs b/src/sql/src/statements/explain.rs new file mode 100644 index 0000000000..01f9330ef3 --- /dev/null +++ b/src/sql/src/statements/explain.rs @@ -0,0 +1,37 @@ +// Copyright 2022 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sqlparser::ast::Statement as SpStatement; + +use crate::error::Error; + +/// Explain statement. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Explain { + pub inner: SpStatement, +} + +impl TryFrom for Explain { + type Error = Error; + + fn try_from(value: SpStatement) -> Result { + Ok(Explain { inner: value }) + } +} + +impl ToString for Explain { + fn to_string(&self) -> String { + self.inner.to_string() + } +} diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 6e91424cc8..c3e734dc9d 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -18,11 +18,13 @@ use sqlparser::parser::ParserError; use crate::statements::alter::AlterTable; use crate::statements::create::{CreateDatabase, CreateTable}; use crate::statements::describe::DescribeTable; +use crate::statements::explain::Explain; use crate::statements::insert::Insert; use crate::statements::query::Query; use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowTables}; /// Tokens parsed by `DFParser` are converted into these values. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Statement { // Query @@ -43,6 +45,8 @@ pub enum Statement { ShowCreateTable(ShowCreateTable), // DESCRIBE TABLE DescribeTable(DescribeTable), + // EXPLAIN QUERY + Explain(Explain), } /// Converts Statement to sqlparser statement @@ -68,6 +72,7 @@ impl TryFrom for SpStatement { Statement::CreateDatabase(_) | Statement::CreateTable(_) | Statement::Alter(_) => { unimplemented!() } + Statement::Explain(e) => Ok(e.inner), } } }