use glaredb_error::{DbError, Result};
use super::plan_query::QueryPlanner;
use super::plan_subquery::SubqueryPlanner;
use crate::arrays::scalar::BorrowedScalarValue;
use crate::expr::column_expr::{ColumnExpr, ColumnReference};
use crate::expr::literal_expr::LiteralExpr;
use crate::expr::{self, Expression};
use crate::functions::table::TableFunctionType;
use crate::logical::binder::bind_context::BindContext;
use crate::logical::binder::bind_query::bind_from::{BoundFrom, BoundFromItem, BoundJoin};
use crate::logical::logical_filter::LogicalFilter;
use crate::logical::logical_inout::LogicalTableExecute;
use crate::logical::logical_join::{
JoinCondition,
JoinType,
LogicalArbitraryJoin,
LogicalComparisonJoin,
LogicalCrossJoin,
};
use crate::logical::logical_materialization::LogicalMaterializationScan;
use crate::logical::logical_project::LogicalProject;
use crate::logical::logical_scan::{
LogicalScan,
ScanSource,
TableFunctionScanSource,
TableScan,
TableScanSource,
};
use crate::logical::logical_single_row::LogicalSingleRow;
use crate::logical::operator::{LocationRequirement, LogicalNode, LogicalOperator, Node};
use crate::optimizer::filter_pushdown::condition_extractor::JoinConditionExtractor;
use crate::statistics::value::StatisticsValue;
#[derive(Debug)]
pub struct FromPlanner;
impl FromPlanner {
pub fn plan(&self, bind_context: &mut BindContext, from: BoundFrom) -> Result<LogicalOperator> {
match from.item {
BoundFromItem::BaseTable(table) => {
let data_table = bind_context.get_table(table.data_table_ref)?;
let data_scan = TableScan {
table_ref: table.data_table_ref,
projection: (0..data_table.num_columns()).collect(),
scan_filters: Vec::new(),
};
let meta_scan = match table.meta_table_ref {
Some(meta_table_ref) => {
let meta_table = bind_context.get_table(meta_table_ref)?;
let meta_scan = TableScan {
table_ref: meta_table_ref,
projection: (0..meta_table.num_columns()).collect(),
scan_filters: Vec::new(),
};
Some(meta_scan)
}
None => None,
};
let source = ScanSource::Table(TableScanSource {
catalog: table.catalog,
schema: table.schema,
source: table.entry,
function: table.scan_function,
});
let estimated_cardinality = source.cardinality();
Ok(LogicalOperator::Scan(Node {
node: LogicalScan {
data_scan,
meta_scan,
source: Box::new(source),
},
location: table.location,
children: Vec::new(),
estimated_cardinality,
}))
}
BoundFromItem::Join(join) => self.plan_join(bind_context, join),
BoundFromItem::TableFunction(func) => {
match func.function.raw.function_type() {
TableFunctionType::Execute => {
let cardinality = func.function.bind_state.cardinality;
Ok(LogicalOperator::TableExecute(Node {
node: LogicalTableExecute {
function_table_ref: func.data_table_ref,
function: func.function,
projected_table_ref: None,
projected_outputs: Vec::new(),
},
location: func.location,
children: vec![LogicalOperator::SINGLE_ROW],
estimated_cardinality: cardinality,
}))
}
TableFunctionType::Scan => {
let source = ScanSource::Function(TableFunctionScanSource {
function: func.function,
});
let estimated_cardinality = source.cardinality();
let data_table = bind_context.get_table(func.data_table_ref)?;
let data_scan = TableScan {
table_ref: func.data_table_ref,
projection: (0..data_table.num_columns()).collect(),
scan_filters: Vec::new(),
};
let meta_scan = match func.meta_table_ref {
Some(meta_table_ref) => {
let meta_table = bind_context.get_table(meta_table_ref)?;
let meta_scan = TableScan {
table_ref: meta_table_ref,
projection: (0..meta_table.num_columns()).collect(),
scan_filters: Vec::new(),
};
Some(meta_scan)
}
None => None,
};
Ok(LogicalOperator::Scan(Node {
node: LogicalScan {
data_scan,
meta_scan,
source: Box::new(source),
},
location: func.location,
children: Vec::new(),
estimated_cardinality,
}))
}
}
}
BoundFromItem::Subquery(subquery) => {
let plan = QueryPlanner.plan(bind_context, *subquery.subquery)?;
let mut projections = Vec::new();
for table_ref in plan.get_output_table_refs(bind_context) {
let table = bind_context.get_table(table_ref)?;
for col_idx in 0..table.num_columns() {
projections.push(Expression::Column(ColumnExpr {
reference: ColumnReference {
table_scope: table_ref,
column: col_idx,
},
datatype: table.column_types[col_idx].clone(),
}));
}
}
Ok(LogicalOperator::Project(Node {
node: LogicalProject {
projections,
projection_table: subquery.table_ref,
},
location: LocationRequirement::Any,
children: vec![plan],
estimated_cardinality: StatisticsValue::Unknown,
}))
}
BoundFromItem::MaterializedCte(mat_cte) => {
let cte = bind_context.get_cte(mat_cte.cte_ref)?;
let mat_ref = match cte.mat_ref {
Some(mat_ref) => {
bind_context.inc_materialization_scan_count(mat_ref, 1)?;
mat_ref
}
None => {
let plan = QueryPlanner.plan(bind_context, *cte.bound.clone())?;
let mat_ref = bind_context.new_materialization(plan)?;
bind_context.inc_materialization_scan_count(mat_ref, 1)?;
let cte = bind_context.get_cte_mut(mat_cte.cte_ref)?;
cte.mat_ref = Some(mat_ref);
mat_ref
}
};
let mat = bind_context.get_materialization(mat_ref)?;
let mut projections = Vec::new();
for table_ref in mat.plan.get_output_table_refs(bind_context) {
let table = bind_context.get_table(table_ref)?;
for col_idx in 0..table.num_columns() {
projections.push(Expression::Column(ColumnExpr {
reference: ColumnReference {
table_scope: table_ref,
column: col_idx,
},
datatype: table.column_types[col_idx].clone(),
}));
}
}
Ok(LogicalOperator::Project(Node {
node: LogicalProject {
projections,
projection_table: mat_cte.table_ref,
},
location: LocationRequirement::Any,
children: vec![LogicalOperator::MaterializationScan(Node {
node: LogicalMaterializationScan { mat: mat.mat_ref },
location: LocationRequirement::Any,
children: Vec::new(),
estimated_cardinality: StatisticsValue::Unknown,
})],
estimated_cardinality: StatisticsValue::Unknown,
}))
}
BoundFromItem::Empty => Ok(LogicalOperator::SingleRow(Node {
node: LogicalSingleRow,
location: LocationRequirement::Any,
children: Vec::new(),
estimated_cardinality: StatisticsValue::Unknown,
})),
}
}
fn plan_join(
&self,
bind_context: &mut BindContext,
join: BoundJoin,
) -> Result<LogicalOperator> {
let mut left = self.plan(bind_context, *join.left)?;
let mut right = self.plan(bind_context, *join.right)?;
let is_lateral = !join.lateral_columns.is_empty();
if !is_lateral && join.conditions.is_empty() {
if !join.conditions.is_empty() {
return Err(DbError::new("CROSS JOIN should not have conditions"));
}
return Ok(LogicalOperator::CrossJoin(Node {
node: LogicalCrossJoin,
location: LocationRequirement::Any,
children: vec![left, right],
estimated_cardinality: StatisticsValue::Unknown,
}));
}
let left_tables = left.get_output_table_refs(bind_context);
let right_tables = right.get_output_table_refs(bind_context);
let extractor = JoinConditionExtractor::new(&left_tables, &right_tables, join.join_type);
let extracted = extractor.extract(join.conditions)?;
if !extracted.left_filter.is_empty() {
left = LogicalOperator::Filter(Node {
node: LogicalFilter {
filter: expr::and(extracted.left_filter)?.into(),
},
location: LocationRequirement::Any,
children: vec![left],
estimated_cardinality: StatisticsValue::Unknown,
})
}
if !extracted.right_filter.is_empty() {
right = LogicalOperator::Filter(Node {
node: LogicalFilter {
filter: expr::and(extracted.right_filter)?.into(),
},
location: LocationRequirement::Any,
children: vec![right],
estimated_cardinality: StatisticsValue::Unknown,
})
}
if is_lateral {
if !extracted.arbitrary.is_empty() {
return Err(DbError::new(
"Arbitrary expressions not yet supported for LATERAL joins",
));
}
let planned = SubqueryPlanner.plan_lateral_join(
bind_context,
left,
right,
join.join_type,
extracted.comparisons,
join.lateral_columns,
)?;
return Ok(planned);
}
self.plan_join_from_conditions(
join.join_type,
extracted.comparisons,
extracted.arbitrary,
left,
right,
)
}
pub fn plan_join_from_conditions(
&self,
join_type: JoinType,
comparisons: Vec<JoinCondition>,
arbitrary: Vec<Expression>,
left: LogicalOperator,
right: LogicalOperator,
) -> Result<LogicalOperator> {
let use_arbitrary_join =
comparisons.is_empty() || (join_type != JoinType::Inner && !arbitrary.is_empty());
if use_arbitrary_join {
let mut expressions = arbitrary;
for condition in comparisons {
expressions.push(Expression::Comparison(condition.into()));
}
if expressions.is_empty() {
expressions.push(Expression::Literal(LiteralExpr(
BorrowedScalarValue::Boolean(true),
)));
}
return Ok(LogicalOperator::ArbitraryJoin(Node {
node: LogicalArbitraryJoin {
join_type,
condition: expr::and(expressions)?.into(),
},
location: LocationRequirement::Any,
children: vec![left, right],
estimated_cardinality: StatisticsValue::Unknown,
}));
}
let mut plan = LogicalOperator::ComparisonJoin(Node {
node: LogicalComparisonJoin {
join_type,
conditions: comparisons,
},
location: LocationRequirement::Any,
children: vec![left, right],
estimated_cardinality: StatisticsValue::Unknown,
});
if !arbitrary.is_empty() {
plan = LogicalOperator::Filter(Node {
node: LogicalFilter {
filter: expr::and(arbitrary)?.into(),
},
location: LocationRequirement::Any,
children: vec![plan],
estimated_cardinality: StatisticsValue::Unknown,
})
}
Ok(plan)
}
}