diff --git a/src/binder/expression/mod.rs b/src/binder/expression/mod.rs index ff34cf6..585acc6 100644 --- a/src/binder/expression/mod.rs +++ b/src/binder/expression/mod.rs @@ -6,10 +6,12 @@ pub use agg_func::*; use arrow::datatypes::DataType; pub use binary_op::*; use itertools::Itertools; +use paste::paste; use sqlparser::ast::{Expr, Ident}; -use super::{BindError, Binder}; +use super::{BindError, Binder, BoundSubqueryExpr}; use crate::catalog::{ColumnCatalog, ColumnId, TableId}; +use crate::optimizer::ExprVisitor; use crate::types::ScalarValue; #[derive(Clone, PartialEq, Eq, Hash)] @@ -21,6 +23,7 @@ pub enum BoundExpr { TypeCast(BoundTypeCast), AggFunc(BoundAggFunc), Alias(BoundAlias), + Subquery(BoundSubqueryExpr), } impl BoundExpr { @@ -33,6 +36,7 @@ impl BoundExpr { BoundExpr::TypeCast(e) => e.expr.nullable(), BoundExpr::AggFunc(e) => e.exprs[0].nullable(), BoundExpr::Alias(e) => e.expr.nullable(), + BoundExpr::Subquery(e) => e.query_ref.query.select_list[0].nullable(), } } @@ -47,6 +51,7 @@ impl BoundExpr { BoundExpr::TypeCast(tc) => Some(tc.cast_type.clone()), BoundExpr::AggFunc(agg) => Some(agg.return_type.clone()), BoundExpr::Alias(alias) => alias.expr.return_type(), + BoundExpr::Subquery(e) => e.query_ref.query.select_list[0].return_type(), } } @@ -68,6 +73,7 @@ impl BoundExpr { .flat_map(|arg| arg.get_referenced_column_catalog()) .collect::>(), BoundExpr::Alias(alias) => alias.expr.get_referenced_column_catalog(), + BoundExpr::Subquery(_) => unreachable!(), } } @@ -110,6 +116,7 @@ impl BoundExpr { let data_type = e.expr.return_type().unwrap(); (table_id, column_id, data_type) } + BoundExpr::Subquery(_) => unreachable!(), }; ColumnCatalog::new(table_id, column_id, self.nullable(), data_type) } @@ -154,6 +161,7 @@ impl Binder { Expr::Value(v) => Ok(BoundExpr::Constant(v.into())), Expr::Function(func) => self.bind_agg_func(func), Expr::Nested(expr) => self.bind_expr(expr), + Expr::Subquery(query) => self.bind_scalar_subquery(query), _ => todo!("unsupported expr {:?}", expr), } } @@ -239,6 +247,9 @@ impl fmt::Debug for BoundExpr { alias.expr, alias.table_id, alias.column_id ) } + BoundExpr::Subquery(subquery) => { + write!(f, "ScalarSubquery {{{:?}}}", subquery.query_ref) + } } } } @@ -260,3 +271,32 @@ impl fmt::Debug for BoundTypeCast { write!(f, "Cast({:?} as {})", self.expr, self.cast_type) } } + +macro_rules! impl_contains_variant { + ( $($variant:ty),* ) => { + paste! { + impl BoundExpr { + $( + pub fn [](&self) -> bool { + struct Contains(bool); + + impl ExprVisitor for Contains { + fn pre_visit(&mut self, expr: &BoundExpr) { + if let BoundExpr::$variant(_) = expr { + self.0 = true; + } + } + + } + + let mut visitor = Contains(false); + visitor.visit_expr(self); + visitor.0 + } + )* + } + } + }; +} + +impl_contains_variant! {Subquery} diff --git a/src/binder/statement/mod.rs b/src/binder/statement/mod.rs index 66fcc1c..c9a3789 100644 --- a/src/binder/statement/mod.rs +++ b/src/binder/statement/mod.rs @@ -10,7 +10,7 @@ pub enum BoundStatement { Select(BoundSelect), } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BoundSelect { pub select_list: Vec, pub from_table: Option, @@ -22,7 +22,7 @@ pub struct BoundSelect { pub select_distinct: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BoundOrderBy { pub expr: BoundExpr, pub asc: bool, @@ -36,7 +36,7 @@ impl Binder { }; // currently, only support select one table - let from_table = if select.from.is_empty() { + let mut from_table = if select.from.is_empty() { None } else if select.from.len() > 1 { // merge select from multiple tables into one cross join @@ -67,11 +67,13 @@ impl Binder { for item in &select.projection { match item { SelectItem::UnnamedExpr(expr) => { - let expr = self.bind_expr(expr)?; + let mut expr = self.bind_expr(expr)?; + self.rewrite_scalar_subquery(&mut expr, &mut from_table); select_list.push(expr); } SelectItem::ExprWithAlias { expr, alias } => { - let expr = self.bind_expr(expr)?; + let mut expr = self.bind_expr(expr)?; + self.rewrite_scalar_subquery(&mut expr, &mut from_table); self.context.aliases.insert(alias.to_string(), expr.clone()); select_list.push(BoundExpr::Alias(BoundAlias { expr: Box::new(expr), diff --git a/src/binder/table/join.rs b/src/binder/table/join.rs index c1e426c..60c4985 100644 --- a/src/binder/table/join.rs +++ b/src/binder/table/join.rs @@ -6,7 +6,7 @@ use sqlparser::ast::{BinaryOperator, Expr, JoinConstraint, JoinOperator}; use super::*; use crate::binder::{BoundBinaryOp, BoundExpr}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Join { pub left: Box, pub right: Box, @@ -14,7 +14,7 @@ pub struct Join { pub join_condition: JoinCondition, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum JoinType { Inner, Left, @@ -36,7 +36,7 @@ impl fmt::Display for JoinType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum JoinCondition { On { /// Equijoin clause expressed as pairs of (left, right) join columns diff --git a/src/binder/table/mod.rs b/src/binder/table/mod.rs index d2bf014..c12a3b2 100644 --- a/src/binder/table/mod.rs +++ b/src/binder/table/mod.rs @@ -14,14 +14,14 @@ pub static DEFAULT_DATABASE_NAME: &str = "postgres"; pub static DEFAULT_SCHEMA_NAME: &str = "postgres"; pub static EMPTY_DATABASE_ID: &str = "empty-database-id"; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum BoundTableRef { Table(BoundSimpleTable), Join(Join), - Subquery(BoundSubquery), + Subquery(BoundSubqueryRef), } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct BoundSimpleTable { pub catalog: TableCatalog, pub alias: Option, @@ -199,7 +199,7 @@ impl Binder { .clone() .map(|a| a.to_string().to_lowercase()) .ok_or(BindError::SubqueryMustHaveAlias)?; - let mut subquery = BoundSubquery::new(Box::new(query), alias.clone()); + let mut subquery = BoundSubqueryRef::new(Box::new(query), alias.clone()); // add subquery output columns into context let subquery_catalog = subquery.gen_table_catalog_for_outside_reference(); diff --git a/src/binder/table/subquery.rs b/src/binder/table/subquery.rs index 09d2dae..6ecf23d 100644 --- a/src/binder/table/subquery.rs +++ b/src/binder/table/subquery.rs @@ -1,14 +1,19 @@ -use crate::binder::{Binder, BoundAlias, BoundExpr, BoundSelect, TableSchema}; +use sqlparser::ast::Query; + +use crate::binder::{ + BindError, Binder, BoundAggFunc, BoundAlias, BoundBinaryOp, BoundColumnRef, BoundExpr, + BoundSelect, BoundTableRef, BoundTypeCast, Join, JoinCondition, JoinType, TableSchema, +}; use crate::catalog::{ColumnCatalog, TableCatalog, TableId}; -#[derive(Clone, Debug, PartialEq)] -pub struct BoundSubquery { +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoundSubqueryRef { pub query: Box, /// subquery always has a alias, if not, we will generate a alias number pub alias: TableId, } -impl BoundSubquery { +impl BoundSubqueryRef { pub fn new(query: Box, alias: TableId) -> Self { Self { query, alias } } @@ -54,10 +59,205 @@ impl BoundSubquery { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoundSubqueryExpr { + pub query_ref: BoundSubqueryRef, + pub kind: SubqueryKind, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SubqueryKind { + /// Returns a scalar value + Scalar, +} + +impl BoundSubqueryExpr { + pub fn new(query: Box, alias: TableId, kind: SubqueryKind) -> Self { + Self { + query_ref: BoundSubqueryRef::new(query, alias), + kind, + } + } +} + impl Binder { - pub fn gen_subquery_table_id(&mut self) -> String { + pub fn bind_scalar_subquery(&mut self, subquery: &Query) -> Result { + let bound_select = self.bind_select(subquery)?; + Ok(BoundExpr::Subquery(BoundSubqueryExpr::new( + Box::new(bound_select), + self.gen_subquery_table_id(), + SubqueryKind::Scalar, + ))) + } + + fn gen_subquery_table_id(&mut self) -> String { let id = format!("subquery_{}", self.subquery_base_index); self.subquery_base_index += 1; id } + + /// Rewrite scalar subquery to join that includes: + /// 1. replace scalar subquery with ColumnRef + /// 2. add join to from_table + pub fn rewrite_scalar_subquery( + &mut self, + expr: &mut BoundExpr, + from_table: &mut Option, + ) { + if expr.contains_subquery() { + let bound_table_ref = from_table + .clone() + .unwrap_or_else(|| todo!("need logical values")); + if let Some((new_expr, new_table_ref)) = + self.rewrite_scalar_subquery_to_join(&*expr, &bound_table_ref) + { + *expr = new_expr; + *from_table = Some(new_table_ref) + } + } + } + + fn rewrite_scalar_subquery_to_join( + &mut self, + scalar_subquery_expr: &BoundExpr, + base_table_ref: &BoundTableRef, + ) -> Option<(BoundExpr, BoundTableRef)> { + // rewrite uncorrelated scalar subquery to cross join + let subqueries = scalar_subquery_expr.get_scalar_subquery(); + if subqueries.is_empty() { + return None; + } + + let mut new_scalar_subquery_expr = scalar_subquery_expr.clone(); + let mut new_base_table_ref = base_table_ref.clone(); + for mut subquery in subqueries { + let subquery_table_id = subquery.query_ref.alias.clone(); + let column_id = format!("{}_{}", subquery_table_id, "scalar_v0"); + let replaced_col_ref = BoundExpr::ColumnRef(BoundColumnRef { + column_catalog: ColumnCatalog::new( + subquery_table_id.clone(), + column_id.clone(), + true, + subquery.query_ref.query.select_list[0] + .return_type() + .unwrap(), + ), + }); + + new_scalar_subquery_expr = new_scalar_subquery_expr + .replace_subquery_with_new_expr(&subquery, &replaced_col_ref); + let new_subquery_select_expr = BoundExpr::Alias(BoundAlias { + expr: Box::new(subquery.query_ref.query.select_list[0].clone()), + column_id, + table_id: subquery_table_id.clone(), + }); + subquery.query_ref.query.select_list = vec![new_subquery_select_expr]; + new_base_table_ref = BoundTableRef::Join(Join { + left: Box::new(new_base_table_ref), + right: Box::new(BoundTableRef::Subquery(BoundSubqueryRef::new( + subquery.query_ref.query, + subquery_table_id, + ))), + join_type: JoinType::Cross, + join_condition: JoinCondition::None, + }); + } + + Some((new_scalar_subquery_expr, new_base_table_ref)) + } +} + +impl BoundExpr { + // pub fn contains_scalar_subquery(&self) -> bool { + // match self { + // BoundExpr::Constant(_) | BoundExpr::ColumnRef(_) | BoundExpr::InputRef(_) => false, + // BoundExpr::BinaryOp(binary_op) => { + // binary_op.left.contains_scalar_subquery() + // || binary_op.right.contains_scalar_subquery() + // } + // BoundExpr::TypeCast(tc) => tc.expr.contains_scalar_subquery(), + // BoundExpr::AggFunc(agg) => agg.exprs.iter().any(|arg| + // arg.contains_scalar_subquery()), BoundExpr::Alias(alias) => + // alias.expr.contains_scalar_subquery(), BoundExpr::Subquery(_) => true, + // } + // } + + pub fn get_scalar_subquery(&self) -> Vec { + match self { + BoundExpr::Constant(_) | BoundExpr::InputRef(_) | BoundExpr::ColumnRef(_) => vec![], + BoundExpr::BinaryOp(binary_op) => binary_op + .left + .get_scalar_subquery() + .into_iter() + .chain(binary_op.right.get_scalar_subquery().into_iter()) + .collect(), + BoundExpr::TypeCast(tc) => tc.expr.get_scalar_subquery(), + BoundExpr::AggFunc(agg) => agg + .exprs + .iter() + .flat_map(|arg| arg.get_scalar_subquery()) + .collect(), + BoundExpr::Alias(alias) => alias.expr.get_scalar_subquery(), + BoundExpr::Subquery(query) => vec![query.clone()], + } + } + + pub fn replace_subquery_with_new_expr( + &self, + replaced_subquery: &BoundSubqueryExpr, + new_expr: &BoundExpr, + ) -> BoundExpr { + match self { + BoundExpr::Constant(_) | BoundExpr::InputRef(_) | BoundExpr::ColumnRef(_) => { + self.clone() + } + BoundExpr::BinaryOp(binary_op) => BoundExpr::BinaryOp(BoundBinaryOp { + left: Box::new( + binary_op + .left + .replace_subquery_with_new_expr(replaced_subquery, new_expr), + ), + op: binary_op.op.clone(), + right: Box::new( + binary_op + .right + .replace_subquery_with_new_expr(replaced_subquery, new_expr), + ), + return_type: binary_op.return_type.clone(), + }), + BoundExpr::TypeCast(tc) => BoundExpr::TypeCast(BoundTypeCast { + expr: Box::new( + tc.expr + .replace_subquery_with_new_expr(replaced_subquery, new_expr), + ), + cast_type: tc.cast_type.clone(), + }), + BoundExpr::AggFunc(agg) => BoundExpr::AggFunc(BoundAggFunc { + distinct: agg.distinct, + exprs: agg + .exprs + .iter() + .map(|arg| arg.replace_subquery_with_new_expr(replaced_subquery, new_expr)) + .collect(), + func: agg.func.clone(), + return_type: agg.return_type.clone(), + }), + BoundExpr::Alias(alias) => BoundExpr::Alias(BoundAlias { + expr: Box::new( + alias + .expr + .replace_subquery_with_new_expr(replaced_subquery, new_expr), + ), + column_id: alias.column_id.clone(), + table_id: alias.table_id.clone(), + }), + BoundExpr::Subquery(query) => { + if query == replaced_subquery { + new_expr.clone() + } else { + self.clone() + } + } + } + } } diff --git a/src/catalog/mod.rs b/src/catalog/mod.rs index 08f72dd..2a9707a 100644 --- a/src/catalog/mod.rs +++ b/src/catalog/mod.rs @@ -33,7 +33,7 @@ impl RootCatalog { /// use table name as id for simplicity pub type TableId = String; -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct TableCatalog { pub id: TableId, pub name: String, @@ -87,7 +87,7 @@ impl TableCatalog { /// use column name as id for simplicity pub type ColumnId = String; -#[derive(Clone, Eq)] +#[derive(Clone)] pub struct ColumnCatalog { pub table_id: TableId, pub column_id: ColumnId, @@ -144,6 +144,8 @@ impl PartialEq for ColumnCatalog { } } +impl Eq for ColumnCatalog {} + impl Hash for ColumnCatalog { fn hash(&self, state: &mut H) { self.table_id.hash(state); diff --git a/src/executor/evaluator.rs b/src/executor/evaluator.rs index 30f5dc0..650570c 100644 --- a/src/executor/evaluator.rs +++ b/src/executor/evaluator.rs @@ -23,6 +23,7 @@ impl BoundExpr { BoundExpr::TypeCast(tc) => Ok(cast(&tc.expr.eval_column(batch)?, &tc.cast_type)?), BoundExpr::AggFunc(_) => todo!(), BoundExpr::Alias(alias) => alias.expr.eval_column(batch), + BoundExpr::Subquery(_) => unreachable!("scalar subquery should rewrite to join"), } } @@ -58,6 +59,7 @@ impl BoundExpr { let data_type = alias.expr.return_type().unwrap(); Field::new(new_name.as_str(), data_type, true) } + BoundExpr::Subquery(_) => unreachable!("scalar subquery should rewrite to join"), } } } diff --git a/src/optimizer/expr_rewriter.rs b/src/optimizer/expr_rewriter.rs index 1892eb1..999c033 100644 --- a/src/optimizer/expr_rewriter.rs +++ b/src/optimizer/expr_rewriter.rs @@ -10,6 +10,7 @@ pub trait ExprRewriter { BoundExpr::TypeCast(_) => self.rewrite_type_cast(expr), BoundExpr::AggFunc(_) => self.rewrite_agg_func(expr), BoundExpr::Alias(_) => self.rewrite_alias(expr), + BoundExpr::Subquery(_) => self.rewrite_subquery(expr), } } @@ -50,4 +51,8 @@ pub trait ExprRewriter { _ => unreachable!(), } } + + fn rewrite_subquery(&self, _: &mut BoundExpr) { + // Do nothing due to BoundSubqueryExpr should be rewritten + } } diff --git a/src/optimizer/expr_visitor.rs b/src/optimizer/expr_visitor.rs index 531d68e..0205205 100644 --- a/src/optimizer/expr_visitor.rs +++ b/src/optimizer/expr_visitor.rs @@ -1,6 +1,6 @@ use crate::binder::{ BoundAggFunc, BoundAlias, BoundBinaryOp, BoundColumnRef, BoundExpr, BoundInputRef, - BoundTypeCast, + BoundSubqueryExpr, BoundTypeCast, }; use crate::types::ScalarValue; @@ -17,6 +17,7 @@ pub trait ExprVisitor { BoundExpr::TypeCast(expr) => self.visit_type_cast(expr), BoundExpr::AggFunc(expr) => self.visit_agg_func(expr), BoundExpr::Alias(expr) => self.visit_alias(expr), + BoundExpr::Subquery(expr) => self.visit_subquery(expr), } } @@ -44,4 +45,8 @@ pub trait ExprVisitor { fn visit_alias(&mut self, expr: &BoundAlias) { self.visit_expr(&expr.expr); } + + fn visit_subquery(&mut self, _: &BoundSubqueryExpr) { + // Do nothing due to BoundSubqueryExpr should be rewritten + } } diff --git a/src/optimizer/rules/util.rs b/src/optimizer/rules/util.rs index bce56ea..9b6fbe5 100644 --- a/src/optimizer/rules/util.rs +++ b/src/optimizer/rules/util.rs @@ -33,3 +33,47 @@ pub fn reduce_conjunctive_predicate(exprs: Vec) -> Option }) }) } + +#[cfg(test)] +mod test { + use arrow::datatypes::DataType; + + use super::*; + use crate::catalog::ColumnCatalog; + + #[test] + fn is_subset_cols_return_true_when_right_contains_all_left_items() { + let table_id = "t1".to_string(); + let data_type = DataType::Int8; + + let left = vec![ + ColumnCatalog::new(table_id.clone(), "c1".to_string(), true, data_type.clone()), + ColumnCatalog::new(table_id.clone(), "c2".to_string(), true, data_type.clone()), + ColumnCatalog::new(table_id.clone(), "c2".to_string(), false, data_type.clone()), + ]; + let right = vec![ + ColumnCatalog::new(table_id.clone(), "c1".to_string(), false, data_type.clone()), + ColumnCatalog::new(table_id.clone(), "c2".to_string(), true, data_type.clone()), + ColumnCatalog::new(table_id, "c3".to_string(), true, data_type), + ]; + + assert!(is_subset_cols(&left, &right)); + } + + #[test] + fn is_superset_cols_return_true_when_right_contains_all_left_items_and_others() { + let table_id = "t1".to_string(); + let data_type = DataType::Int8; + + let left = vec![ + ColumnCatalog::new(table_id.clone(), "c1".to_string(), false, data_type.clone()), + ColumnCatalog::new(table_id.clone(), "c2".to_string(), true, data_type.clone()), + ColumnCatalog::new(table_id.clone(), "c3".to_string(), true, data_type.clone()), + ]; + let right = vec![ + ColumnCatalog::new(table_id.clone(), "c1".to_string(), true, data_type.clone()), + ColumnCatalog::new(table_id, "c2".to_string(), true, data_type), + ]; + assert!(is_superset_cols(&left, &right)); + } +} diff --git a/tests/planner/column-pruning.planner.sql b/tests/planner/column-pruning.planner.sql index c537aa7..b1a5b7b 100644 --- a/tests/planner/column-pruning.planner.sql +++ b/tests/planner/column-pruning.planner.sql @@ -147,3 +147,56 @@ PhysicalProject: exprs [t1.a:Int64, sub0.v0:Int64, sub1.v0:Int64] PhysicalTableScan: table: #t1, columns: [b] */ +-- PushProjectThroughChild: column pruning across scalar subquery + +select a, (select max(b) from t1) from t1; + +/* +original plan: +LogicalProject: exprs [t1.a:Int64, subquery_0.subquery_0_scalar_v0:Nullable(Int64)] + LogicalJoin: type Cross, cond None + LogicalTableScan: table: #t1, columns: [a, b, c] + LogicalProject: exprs [(Max(t1.b:Int64):Int64) as subquery_0.subquery_0_scalar_v0] + LogicalAgg: agg_funcs [Max(t1.b:Int64):Int64] group_by [] + LogicalTableScan: table: #t1, columns: [a, b, c] + +optimized plan: +PhysicalProject: exprs [t1.a:Int64, subquery_0.subquery_0_scalar_v0:Nullable(Int64)] + PhysicalCrossJoin: type Cross + PhysicalTableScan: table: #t1, columns: [a] + PhysicalProject: exprs [(Max(t1.b:Int64):Int64) as subquery_0.subquery_0_scalar_v0] + PhysicalSimpleAgg: agg_funcs [Max(t1.b:Int64):Int64] group_by [] + PhysicalTableScan: table: #t1, columns: [b] +*/ + +-- PushProjectThroughChild: column pruning across multiple scalar subquery + +select a, (select max(b) from t1) + (select min(b) from t1) as mix_b from t1; + +/* +original plan: +LogicalProject: exprs [t1.a:Int64, (subquery_0.subquery_0_scalar_v0:Nullable(Int64) + subquery_1.subquery_1_scalar_v0:Nullable(Int64)) as t1.mix_b] + LogicalJoin: type Cross, cond None + LogicalJoin: type Cross, cond None + LogicalTableScan: table: #t1, columns: [a, b, c] + LogicalProject: exprs [(Max(t1.b:Int64):Int64) as subquery_0.subquery_0_scalar_v0] + LogicalAgg: agg_funcs [Max(t1.b:Int64):Int64] group_by [] + LogicalTableScan: table: #t1, columns: [a, b, c] + LogicalProject: exprs [(Min(t1.b:Int64):Int64) as subquery_1.subquery_1_scalar_v0] + LogicalAgg: agg_funcs [Min(t1.b:Int64):Int64] group_by [] + LogicalTableScan: table: #t1, columns: [a, b, c] + +optimized plan: +PhysicalProject: exprs [t1.a:Int64, (subquery_0.subquery_0_scalar_v0:Nullable(Int64) + subquery_1.subquery_1_scalar_v0:Nullable(Int64)) as t1.mix_b] + PhysicalCrossJoin: type Cross + PhysicalProject: exprs [t1.a:Nullable(Int64), subquery_0.subquery_0_scalar_v0:Nullable(Int64)] + PhysicalCrossJoin: type Cross + PhysicalTableScan: table: #t1, columns: [a] + PhysicalProject: exprs [(Max(t1.b:Int64):Int64) as subquery_0.subquery_0_scalar_v0] + PhysicalSimpleAgg: agg_funcs [Max(t1.b:Int64):Int64] group_by [] + PhysicalTableScan: table: #t1, columns: [b] + PhysicalProject: exprs [(Min(t1.b:Int64):Int64) as subquery_1.subquery_1_scalar_v0] + PhysicalSimpleAgg: agg_funcs [Min(t1.b:Int64):Int64] group_by [] + PhysicalTableScan: table: #t1, columns: [b] +*/ + diff --git a/tests/planner/column-pruning.yml b/tests/planner/column-pruning.yml index 930aae9..2c684bb 100644 --- a/tests/planner/column-pruning.yml +++ b/tests/planner/column-pruning.yml @@ -34,3 +34,13 @@ select t1.a, sub0.v0, sub1.v0 from t1 cross join (select max(b) as v0 from t1) sub0 cross join (select min(b) as v0 from t1) sub1; desc: | PushProjectThroughChild: column pruning across multiple subquery + +- sql: | + select a, (select max(b) from t1) from t1; + desc: | + PushProjectThroughChild: column pruning across scalar subquery + +- sql: | + select a, (select max(b) from t1) + (select min(b) from t1) as mix_b from t1; + desc: | + PushProjectThroughChild: column pruning across multiple scalar subquery diff --git a/tests/slt/subquery.slt b/tests/slt/subquery.slt index a3073fe..eff5588 100644 --- a/tests/slt/subquery.slt +++ b/tests/slt/subquery.slt @@ -40,10 +40,34 @@ select a, t2.v1 as max_b from t1 cross join (select max(b) as v1 from t1) t2; 2 8 2 8 -# query II -# select a, (select max(b) from t1) max_b from t1; -# ---- -# 0 8 -# 1 8 -# 2 8 -# 2 8 +query II +select a, (select max(b) from t1) max_b from t1; +---- +0 8 +1 8 +2 8 +2 8 + +query II +select a, (select max(b) from t1) from t1; +---- +0 8 +1 8 +2 8 +2 8 + +query II +select a, (select max(b) from t1) + 2 as max_b from t1; +---- +0 10 +1 10 +2 10 +2 10 + +query II +select a, (select max(b) from t1) + (select min(b) from t1) as mix_b from t1; +---- +0 12 +1 12 +2 12 +2 12