diff --git a/src/execution/physical_plan/mod.rs b/src/execution/physical_plan/mod.rs index f65940d..cfd207d 100644 --- a/src/execution/physical_plan/mod.rs +++ b/src/execution/physical_plan/mod.rs @@ -1,13 +1,17 @@ +mod physical_column_data_scan; mod physical_create_table; mod physical_dummy_scan; +mod physical_explain; mod physical_expression_scan; mod physical_insert; mod physical_projection; mod physical_table_scan; use derive_new::new; +pub use physical_column_data_scan::*; pub use physical_create_table::*; pub use physical_dummy_scan::*; +pub use physical_explain::*; pub use physical_expression_scan::*; pub use physical_insert::*; pub use physical_projection::*; @@ -30,4 +34,19 @@ pub enum PhysicalOperator { PhysicalInsert(PhysicalInsert), PhysicalTableScan(PhysicalTableScan), PhysicalProjection(PhysicalProjection), + PhysicalColumnDataScan(PhysicalColumnDataScan), +} + +impl PhysicalOperator { + pub fn children(&self) -> &[PhysicalOperator] { + match self { + PhysicalOperator::PhysicalCreateTable(op) => &op.base.children, + PhysicalOperator::PhysicalExpressionScan(op) => &op.base.children, + PhysicalOperator::PhysicalInsert(op) => &op.base.children, + PhysicalOperator::PhysicalTableScan(op) => &op.base.children, + PhysicalOperator::PhysicalProjection(op) => &op.base.children, + PhysicalOperator::PhysicalDummyScan(op) => &op.base.children, + PhysicalOperator::PhysicalColumnDataScan(op) => &op.base.children, + } + } } diff --git a/src/execution/physical_plan/physical_column_data_scan.rs b/src/execution/physical_plan/physical_column_data_scan.rs new file mode 100644 index 0000000..a834f05 --- /dev/null +++ b/src/execution/physical_plan/physical_column_data_scan.rs @@ -0,0 +1,11 @@ +use arrow::record_batch::RecordBatch; +use derive_new::new; + +use super::PhysicalOperatorBase; + +/// The PhysicalColumnDataScan scans a Arrow RecordBatch +#[derive(new, Clone)] +pub struct PhysicalColumnDataScan { + pub(crate) base: PhysicalOperatorBase, + pub(crate) collection: Vec, +} diff --git a/src/execution/physical_plan/physical_create_table.rs b/src/execution/physical_plan/physical_create_table.rs index 68a6699..0f882a1 100644 --- a/src/execution/physical_plan/physical_create_table.rs +++ b/src/execution/physical_plan/physical_create_table.rs @@ -7,7 +7,7 @@ use crate::planner_v2::{BoundCreateTableInfo, LogicalCreateTable}; #[derive(new, Clone)] pub struct PhysicalCreateTable { #[new(default)] - _base: PhysicalOperatorBase, + pub(crate) base: PhysicalOperatorBase, pub(crate) info: BoundCreateTableInfo, } diff --git a/src/execution/physical_plan/physical_explain.rs b/src/execution/physical_plan/physical_explain.rs new file mode 100644 index 0000000..797b48f --- /dev/null +++ b/src/execution/physical_plan/physical_explain.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; + +use arrow::array::StringArray; +use arrow::record_batch::RecordBatch; + +use super::{PhysicalColumnDataScan, PhysicalOperator, PhysicalOperatorBase}; +use crate::execution::{PhysicalPlanGenerator, SchemaUtil}; +use crate::planner_v2::LogicalExplain; +use crate::util::tree_render::TreeRender; + +impl PhysicalPlanGenerator { + pub(crate) fn create_physical_explain(&self, op: LogicalExplain) -> PhysicalOperator { + let types = op.base.types.clone(); + let logical_child = op.base.children[0].clone(); + // optimized logical plan explain string + let logical_plan_opt_string = TreeRender::logical_plan_tree(&logical_child); + + let physical_child = self.create_plan_internal(logical_child); + // physical plan explain string + let physical_plan_string = TreeRender::physical_plan_tree(&physical_child); + + let base = PhysicalOperatorBase::new(vec![], types.clone()); + + let schema = SchemaUtil::new_schema_ref(&["type".to_string(), "plan".to_string()], &types); + let types_column = Arc::new(StringArray::from(vec![ + "logical_plan".to_string(), + "logical_plan_opt".to_string(), + "physical_plan".to_string(), + ])); + let plans_column = Arc::new(StringArray::from(vec![ + op.logical_plan, + logical_plan_opt_string, + physical_plan_string, + ])); + let collection = RecordBatch::try_new(schema, vec![types_column, plans_column]).unwrap(); + PhysicalOperator::PhysicalColumnDataScan(PhysicalColumnDataScan::new( + base, + vec![collection], + )) + } +} diff --git a/src/execution/physical_plan/physical_expression_scan.rs b/src/execution/physical_plan/physical_expression_scan.rs index 625389c..906f635 100644 --- a/src/execution/physical_plan/physical_expression_scan.rs +++ b/src/execution/physical_plan/physical_expression_scan.rs @@ -9,7 +9,7 @@ use crate::types_v2::LogicalType; #[derive(new, Clone)] pub struct PhysicalExpressionScan { #[new(default)] - pub(crate) _base: PhysicalOperatorBase, + pub(crate) base: PhysicalOperatorBase, /// The types of the expressions pub(crate) expr_types: Vec, /// The set of expressions to scan diff --git a/src/execution/physical_plan/physical_table_scan.rs b/src/execution/physical_plan/physical_table_scan.rs index ee0112b..6117b63 100644 --- a/src/execution/physical_plan/physical_table_scan.rs +++ b/src/execution/physical_plan/physical_table_scan.rs @@ -8,7 +8,7 @@ use crate::types_v2::LogicalType; #[derive(new, Clone)] pub struct PhysicalTableScan { - pub(crate) _base: PhysicalOperatorBase, + pub(crate) base: PhysicalOperatorBase, pub(crate) bind_table: TableCatalogEntry, /// The types of ALL columns that can be returned by the table function pub(crate) returned_types: Vec, diff --git a/src/execution/physical_plan_generator.rs b/src/execution/physical_plan_generator.rs index 944b7b2..b627e04 100644 --- a/src/execution/physical_plan_generator.rs +++ b/src/execution/physical_plan_generator.rs @@ -32,6 +32,7 @@ impl PhysicalPlanGenerator { LogicalOperator::LogicalGet(op) => self.create_physical_table_scan(op), LogicalOperator::LogicalProjection(op) => self.create_physical_projection(op), LogicalOperator::LogicalDummyScan(op) => self.create_physical_dummy_scan(op), + LogicalOperator::LogicalExplain(op) => self.create_physical_explain(op), } } } diff --git a/src/execution/volcano_executor/column_data_scan.rs b/src/execution/volcano_executor/column_data_scan.rs new file mode 100644 index 0000000..e8b08e0 --- /dev/null +++ b/src/execution/volcano_executor/column_data_scan.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use arrow::record_batch::RecordBatch; +use derive_new::new; +use futures_async_stream::try_stream; + +use crate::execution::{ExecutionContext, ExecutorError, PhysicalColumnDataScan}; + +#[derive(new)] +pub struct ColumnDataScan { + pub(crate) plan: PhysicalColumnDataScan, +} + +impl ColumnDataScan { + #[try_stream(boxed, ok = RecordBatch, error = ExecutorError)] + pub async fn execute(self, _context: Arc) { + for batch in self.plan.collection.into_iter() { + yield batch; + } + } +} diff --git a/src/execution/volcano_executor/mod.rs b/src/execution/volcano_executor/mod.rs index 75b5854..621bfd4 100644 --- a/src/execution/volcano_executor/mod.rs +++ b/src/execution/volcano_executor/mod.rs @@ -1,3 +1,4 @@ +mod column_data_scan; mod create_table; mod dummy_scan; mod expression_scan; @@ -7,6 +8,7 @@ mod table_scan; use std::sync::Arc; use arrow::record_batch::RecordBatch; +pub use column_data_scan::*; pub use create_table::*; pub use dummy_scan::*; pub use expression_scan::*; @@ -46,6 +48,9 @@ impl VolcanoExecutor { Projection::new(op, child_executor).execute(context) } PhysicalOperator::PhysicalDummyScan(op) => DummyScan::new(op).execute(context), + PhysicalOperator::PhysicalColumnDataScan(op) => { + ColumnDataScan::new(op).execute(context) + } } } diff --git a/src/planner_v2/binder/statement/bind_explain.rs b/src/planner_v2/binder/statement/bind_explain.rs new file mode 100644 index 0000000..d145e46 --- /dev/null +++ b/src/planner_v2/binder/statement/bind_explain.rs @@ -0,0 +1,34 @@ +use sqlparser::ast::Statement; + +use super::BoundStatement; +use crate::planner_v2::{ + BindError, Binder, ExplainType, LogicalExplain, LogicalOperator, LogicalOperatorBase, +}; +use crate::types_v2::LogicalType; +use crate::util::tree_render::TreeRender; + +impl Binder { + pub fn bind_explain(&mut self, stmt: &Statement) -> Result { + match stmt { + Statement::Explain { + statement, analyze, .. + } => { + let bound_stmt = self.bind(statement)?; + let explain_type = if *analyze { + ExplainType::ANALYZE + } else { + ExplainType::STANDARD + }; + + let types = vec![LogicalType::Varchar, LogicalType::Varchar]; + let names = vec!["explain_type".to_string(), "explain_value".to_string()]; + let logical_plan_string = TreeRender::logical_plan_tree(&bound_stmt.plan); + let base = LogicalOperatorBase::new(vec![bound_stmt.plan], vec![], vec![]); + let logical_explain = LogicalExplain::new(base, explain_type, logical_plan_string); + let new_plan = LogicalOperator::LogicalExplain(logical_explain); + Ok(BoundStatement::new(new_plan, types, names)) + } + _ => Err(BindError::UnsupportedStmt(format!("{:?}", stmt))), + } + } +} diff --git a/src/planner_v2/binder/statement/mod.rs b/src/planner_v2/binder/statement/mod.rs index 9d759dc..25a0905 100644 --- a/src/planner_v2/binder/statement/mod.rs +++ b/src/planner_v2/binder/statement/mod.rs @@ -1,4 +1,5 @@ mod bind_create; +mod bind_explain; mod bind_insert; mod bind_select; mod create_info; @@ -26,6 +27,7 @@ impl Binder { Statement::CreateTable { .. } => self.bind_create_table(statement), Statement::Insert { .. } => self.bind_insert(statement), Statement::Query { .. } => self.bind_select(statement), + Statement::Explain { .. } => self.bind_explain(statement), _ => Err(BindError::UnsupportedStmt(format!("{:?}", statement))), } } diff --git a/src/planner_v2/logical_operator_visitor.rs b/src/planner_v2/logical_operator_visitor.rs index 813b694..0d1a4fc 100644 --- a/src/planner_v2/logical_operator_visitor.rs +++ b/src/planner_v2/logical_operator_visitor.rs @@ -11,7 +11,7 @@ pub trait LogicalOperatorVisitor { } fn visit_operator_children(&mut self, op: &mut LogicalOperator) { - for child in op.children() { + for child in op.children_mut() { self.visit_operator(child); } } diff --git a/src/planner_v2/operator/logical_create_table.rs b/src/planner_v2/operator/logical_create_table.rs index 83eba6a..45d3634 100644 --- a/src/planner_v2/operator/logical_create_table.rs +++ b/src/planner_v2/operator/logical_create_table.rs @@ -3,7 +3,7 @@ use derive_new::new; use super::LogicalOperatorBase; use crate::planner_v2::BoundCreateTableInfo; -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalCreateTable { #[new(default)] pub(crate) base: LogicalOperatorBase, diff --git a/src/planner_v2/operator/logical_dummy_scan.rs b/src/planner_v2/operator/logical_dummy_scan.rs index 2bc81b0..1c293b1 100644 --- a/src/planner_v2/operator/logical_dummy_scan.rs +++ b/src/planner_v2/operator/logical_dummy_scan.rs @@ -3,7 +3,7 @@ use derive_new::new; use super::LogicalOperatorBase; /// LogicalDummyScan represents a dummy scan returning nothing. -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalDummyScan { #[new(default)] pub(crate) base: LogicalOperatorBase, diff --git a/src/planner_v2/operator/logical_explain.rs b/src/planner_v2/operator/logical_explain.rs new file mode 100644 index 0000000..d5837af --- /dev/null +++ b/src/planner_v2/operator/logical_explain.rs @@ -0,0 +1,18 @@ +use derive_new::new; + +use super::LogicalOperatorBase; + +#[derive(new, Debug, Clone)] +pub struct LogicalExplain { + pub(crate) base: LogicalOperatorBase, + #[allow(dead_code)] + pub(crate) explain_type: ExplainType, + /// un-optimized logical plan explain string + pub(crate) logical_plan: String, +} + +#[derive(Debug, Clone)] +pub enum ExplainType { + STANDARD, + ANALYZE, +} diff --git a/src/planner_v2/operator/logical_expression_get.rs b/src/planner_v2/operator/logical_expression_get.rs index ad4a5b3..2ade1d8 100644 --- a/src/planner_v2/operator/logical_expression_get.rs +++ b/src/planner_v2/operator/logical_expression_get.rs @@ -5,7 +5,7 @@ use crate::planner_v2::BoundExpression; use crate::types_v2::LogicalType; /// LogicalExpressionGet represents a scan operation over a set of to-be-executed expressions -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalExpressionGet { pub(crate) base: LogicalOperatorBase, pub(crate) table_idx: usize, diff --git a/src/planner_v2/operator/logical_get.rs b/src/planner_v2/operator/logical_get.rs index 3aea6f0..ede1175 100644 --- a/src/planner_v2/operator/logical_get.rs +++ b/src/planner_v2/operator/logical_get.rs @@ -5,7 +5,7 @@ use crate::catalog_v2::TableCatalogEntry; use crate::types_v2::LogicalType; /// LogicalGet represents a scan operation from a data source -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalGet { pub(crate) base: LogicalOperatorBase, pub(crate) table_idx: usize, diff --git a/src/planner_v2/operator/logical_insert.rs b/src/planner_v2/operator/logical_insert.rs index cc1990d..f209c1c 100644 --- a/src/planner_v2/operator/logical_insert.rs +++ b/src/planner_v2/operator/logical_insert.rs @@ -4,7 +4,7 @@ use super::LogicalOperatorBase; use crate::catalog_v2::TableCatalogEntry; use crate::types_v2::LogicalType; -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalInsert { pub(crate) base: LogicalOperatorBase, /// The insertion map ([table_index -> index in result, or INVALID_INDEX if not specified]) diff --git a/src/planner_v2/operator/logical_projection.rs b/src/planner_v2/operator/logical_projection.rs index 482e097..cd2d85c 100644 --- a/src/planner_v2/operator/logical_projection.rs +++ b/src/planner_v2/operator/logical_projection.rs @@ -2,7 +2,7 @@ use derive_new::new; use super::LogicalOperatorBase; -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct LogicalProjection { pub(crate) base: LogicalOperatorBase, pub(crate) table_idx: usize, diff --git a/src/planner_v2/operator/mod.rs b/src/planner_v2/operator/mod.rs index 34c9cec..1c1b062 100644 --- a/src/planner_v2/operator/mod.rs +++ b/src/planner_v2/operator/mod.rs @@ -2,6 +2,7 @@ use crate::types_v2::LogicalType; mod logical_create_table; mod logical_dummy_scan; +mod logical_explain; mod logical_expression_get; mod logical_get; mod logical_insert; @@ -9,6 +10,7 @@ mod logical_projection; use derive_new::new; pub use logical_create_table::*; pub use logical_dummy_scan::*; +pub use logical_explain::*; pub use logical_expression_get::*; pub use logical_get::*; pub use logical_insert::*; @@ -16,7 +18,7 @@ pub use logical_projection::*; use super::{BoundExpression, ColumnBinding}; -#[derive(new, Default, Debug)] +#[derive(new, Default, Debug, Clone)] pub struct LogicalOperatorBase { pub(crate) children: Vec, // The set of expressions contained within the operator, if any @@ -25,7 +27,7 @@ pub struct LogicalOperatorBase { pub(crate) types: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum LogicalOperator { LogicalCreateTable(LogicalCreateTable), LogicalDummyScan(LogicalDummyScan), @@ -33,10 +35,11 @@ pub enum LogicalOperator { LogicalInsert(LogicalInsert), LogicalGet(LogicalGet), LogicalProjection(LogicalProjection), + LogicalExplain(LogicalExplain), } impl LogicalOperator { - pub fn children(&mut self) -> &mut [LogicalOperator] { + pub fn children_mut(&mut self) -> &mut [LogicalOperator] { match self { LogicalOperator::LogicalCreateTable(op) => &mut op.base.children, LogicalOperator::LogicalExpressionGet(op) => &mut op.base.children, @@ -44,6 +47,19 @@ impl LogicalOperator { LogicalOperator::LogicalGet(op) => &mut op.base.children, LogicalOperator::LogicalProjection(op) => &mut op.base.children, LogicalOperator::LogicalDummyScan(op) => &mut op.base.children, + LogicalOperator::LogicalExplain(op) => &mut op.base.children, + } + } + + pub fn children(&self) -> &[LogicalOperator] { + match self { + LogicalOperator::LogicalCreateTable(op) => &op.base.children, + LogicalOperator::LogicalExpressionGet(op) => &op.base.children, + LogicalOperator::LogicalInsert(op) => &op.base.children, + LogicalOperator::LogicalGet(op) => &op.base.children, + LogicalOperator::LogicalProjection(op) => &op.base.children, + LogicalOperator::LogicalDummyScan(op) => &op.base.children, + LogicalOperator::LogicalExplain(op) => &op.base.children, } } @@ -55,6 +71,7 @@ impl LogicalOperator { LogicalOperator::LogicalGet(op) => &mut op.base.expressioins, LogicalOperator::LogicalProjection(op) => &mut op.base.expressioins, LogicalOperator::LogicalDummyScan(op) => &mut op.base.expressioins, + LogicalOperator::LogicalExplain(op) => &mut op.base.expressioins, } } @@ -66,6 +83,7 @@ impl LogicalOperator { LogicalOperator::LogicalGet(op) => &op.base.types, LogicalOperator::LogicalProjection(op) => &op.base.types, LogicalOperator::LogicalDummyScan(op) => &op.base.types, + LogicalOperator::LogicalExplain(op) => &op.base.types, } } @@ -84,11 +102,14 @@ impl LogicalOperator { self.generate_column_bindings(op.table_idx, op.base.expressioins.len()) } LogicalOperator::LogicalDummyScan(op) => vec![ColumnBinding::new(op.table_idx, 0)], + LogicalOperator::LogicalExplain(_) => { + vec![ColumnBinding::new(0, 0), ColumnBinding::new(0, 1)] + } } } pub fn resolve_operator_types(&mut self) { - for child in self.children() { + for child in self.children_mut() { child.resolve_operator_types(); } match self { @@ -110,6 +131,9 @@ impl LogicalOperator { op.base.types.extend(types); } LogicalOperator::LogicalDummyScan(op) => op.base.types.push(LogicalType::Integer), + LogicalOperator::LogicalExplain(op) => { + op.base.types = vec![LogicalType::Varchar, LogicalType::Varchar]; + } } } diff --git a/src/util/mod.rs b/src/util/mod.rs index be3adb0..7bef94c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,5 @@ +pub mod tree_render; + use std::collections::HashMap; use arrow::datatypes::{DataType, Field, Schema, SchemaRef}; diff --git a/src/util/tree_render.rs b/src/util/tree_render.rs new file mode 100644 index 0000000..c765cad --- /dev/null +++ b/src/util/tree_render.rs @@ -0,0 +1,130 @@ +use std::fmt::Write; + +use derive_new::new; + +use crate::catalog_v2::ColumnDefinition; +use crate::execution::PhysicalOperator; +use crate::planner_v2::{BoundExpression, LogicalOperator}; + +#[derive(new)] +pub struct TreeRender; + +impl TreeRender { + fn column_definition_to_string(column: &ColumnDefinition) -> String { + format!("{}({:?})", column.name, column.ty) + } + + fn bound_expression_to_string(expr: &BoundExpression) -> String { + match expr { + BoundExpression::BoundColumnRefExpression(e) => { + format!( + "ColumnRef({}[{}.{}]))", + e.base.alias, e.binding.table_idx, e.binding.column_idx + ) + } + BoundExpression::BoundConstantExpression(e) => { + format!("Constant({})", e.value) + } + BoundExpression::BoundReferenceExpression(e) => { + format!("Reference({}[{}])", e.base.alias, e.index) + } + BoundExpression::BoundCastExpression(e) => { + format!( + "Cast({}[{}])", + e.base.alias, + Self::bound_expression_to_string(&e.child) + ) + } + } + } + + fn logical_plan_to_string(plan: &LogicalOperator) -> String { + match plan { + LogicalOperator::LogicalCreateTable(op) => { + let table = format!("{}.{}", op.info.base.base.schema, op.info.base.table); + let columns = op + .info + .base + .columns + .iter() + .map(Self::column_definition_to_string) + .collect::>() + .join(", "); + format!("LogicalCreateTable: {}[{}]", table, columns) + } + LogicalOperator::LogicalDummyScan(_) => "LogicalDummyScan".to_string(), + LogicalOperator::LogicalExpressionGet(_) => "LogicalExpressionGet".to_string(), + LogicalOperator::LogicalInsert(op) => { + format!( + "LogicalInsert: {}.{}", + op.table.storage.info.schema, op.table.storage.info.table + ) + } + LogicalOperator::LogicalGet(op) => { + format!( + "LogicalGet: {}.{}", + op.bind_table.storage.info.schema, op.bind_table.storage.info.table + ) + } + LogicalOperator::LogicalProjection(op) => { + let exprs = op + .base + .expressioins + .iter() + .map(Self::bound_expression_to_string) + .collect::>() + .join(", "); + format!("LogicalProjection: {}", exprs) + } + LogicalOperator::LogicalExplain(_) => "LogicalExplain".to_string(), + } + } + + fn logical_plan_tree_internal( + plan: &LogicalOperator, + level: usize, + explain_result: &mut dyn Write, + ) { + let plan_string = Self::logical_plan_to_string(plan); + writeln!(explain_result, "{}{}", " ".repeat(level * 2), plan_string).unwrap(); + for child in plan.children() { + Self::logical_plan_tree_internal(child, level + 1, explain_result); + } + } + + pub fn logical_plan_tree(plan: &LogicalOperator) -> String { + let mut result = String::new(); + Self::logical_plan_tree_internal(plan, 0, &mut result); + result.trim_end().to_string() + } + + fn physical_plan_to_string(plan: &PhysicalOperator) -> String { + match plan { + PhysicalOperator::PhysicalCreateTable(_) => "PhysicalCreateTable".to_string(), + PhysicalOperator::PhysicalDummyScan(_) => "PhysicalDummyScan".to_string(), + PhysicalOperator::PhysicalInsert(_) => "PhysicalInsert".to_string(), + PhysicalOperator::PhysicalExpressionScan(_) => "PhysicalExpressionScan".to_string(), + PhysicalOperator::PhysicalTableScan(_) => "PhysicalTableScan".to_string(), + PhysicalOperator::PhysicalProjection(_) => "PhysicalProjection".to_string(), + PhysicalOperator::PhysicalColumnDataScan(_) => "PhysicalColumnDataScan".to_string(), + } + } + + fn physical_plan_tree_internal( + plan: &PhysicalOperator, + level: usize, + explain_result: &mut dyn Write, + ) { + let plan_string = Self::physical_plan_to_string(plan); + writeln!(explain_result, "{}{}", " ".repeat(level * 2), plan_string).unwrap(); + for child in plan.children() { + Self::physical_plan_tree_internal(child, level + 1, explain_result); + } + } + + pub fn physical_plan_tree(plan: &PhysicalOperator) -> String { + let mut result = String::new(); + Self::physical_plan_tree_internal(plan, 0, &mut result); + result.trim_end().to_string() + } +} diff --git a/tests/slt/explain.slt b/tests/slt/explain.slt new file mode 100644 index 0000000..df185ce --- /dev/null +++ b/tests/slt/explain.slt @@ -0,0 +1,15 @@ +onlyif sqlrs_v2 +statement ok +explain select 1, 2.3, '😇', true, null; + +onlyif sqlrs_v2 +statement ok +CREATE TABLE integers(i INTEGER, j INTEGER) + +onlyif sqlrs_v2 +statement ok +INSERT INTO integers VALUES (1, 1), (2, 2), (3, 3), (NULL, NULL) + +onlyif sqlrs_v2 +statement ok +EXPLAIN SELECT * FROM integers