From 330fedbc2f1e638eb911fb582418b04cd09bc983 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 17:02:36 +0300 Subject: [PATCH] Add notion of join ordering to plan + make determining where to eval expr dynamic always --- core/translate/emitter.rs | 46 ++++++++++++++--- core/translate/main_loop.rs | 24 +++++---- core/translate/optimizer.rs | 56 ++++++++++++++------ core/translate/plan.rs | 46 +++++++++++++---- core/translate/planner.rs | 100 ++++++++++++++++++++---------------- core/translate/select.rs | 10 +++- 6 files changed, 195 insertions(+), 87 deletions(-) diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index e2d3f78c4..fe6567fe9 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -20,7 +20,7 @@ use super::expr::{translate_condition_expr, translate_expr, ConditionMetadata}; use super::group_by::{emit_group_by, init_group_by, GroupByMetadata}; use super::main_loop::{close_loop, emit_loop, init_loop, open_loop, LeftJoinMetadata, LoopLabels}; use super::order_by::{emit_order_by, init_order_by, SortMetadata}; -use super::plan::{Operation, SelectPlan, TableReference, UpdatePlan}; +use super::plan::{JoinOrderMember, Operation, SelectPlan, TableReference, UpdatePlan}; use super::subquery::emit_subqueries; #[derive(Debug)] @@ -285,7 +285,11 @@ pub fn emit_query<'a>( OperationMode::SELECT, )?; - for where_term in plan.where_clause.iter().filter(|wt| wt.is_constant()) { + for where_term in plan + .where_clause + .iter() + .filter(|wt| wt.is_constant(&plan.join_order)) + { let jump_target_when_true = program.allocate_label(); let condition_metadata = ConditionMetadata { jump_if_condition_is_true: false, @@ -303,13 +307,19 @@ pub fn emit_query<'a>( } // Set up main query execution loop - open_loop(program, t_ctx, &plan.table_references, &plan.where_clause)?; + open_loop( + program, + t_ctx, + &plan.table_references, + &plan.join_order, + &plan.where_clause, + )?; // Process result columns and expressions in the inner loop emit_loop(program, t_ctx, plan)?; // Clean up and close the main execution loop - close_loop(program, t_ctx, &plan.table_references)?; + close_loop(program, t_ctx, &plan.table_references, &plan.join_order)?; program.preassign_label_to_next_insn(after_main_loop_label); let mut order_by_necessary = plan.order_by.is_some() && !plan.contains_constant_false_condition; @@ -374,6 +384,7 @@ fn emit_program_for_delete( program, &mut t_ctx, &plan.table_references, + &[JoinOrderMember::default()], &plan.where_clause, )?; emit_delete_insns( @@ -385,7 +396,12 @@ fn emit_program_for_delete( )?; // Clean up and close the main execution loop - close_loop(program, &mut t_ctx, &plan.table_references)?; + close_loop( + program, + &mut t_ctx, + &plan.table_references, + &[JoinOrderMember::default()], + )?; program.preassign_label_to_next_insn(after_main_loop_label); // Finalize program @@ -571,10 +587,16 @@ fn emit_program_for_update( program, &mut t_ctx, &plan.table_references, + &[JoinOrderMember::default()], &plan.where_clause, )?; emit_update_insns(&plan, &t_ctx, program, index_cursors)?; - close_loop(program, &mut t_ctx, &plan.table_references)?; + close_loop( + program, + &mut t_ctx, + &plan.table_references, + &[JoinOrderMember::default()], + )?; program.preassign_label_to_next_insn(after_main_loop_label); // Finalize program @@ -615,7 +637,11 @@ fn emit_update_insns( _ => return Ok(()), }; - for cond in plan.where_clause.iter().filter(|c| c.is_constant()) { + for cond in plan + .where_clause + .iter() + .filter(|c| c.is_constant(&[JoinOrderMember::default()])) + { let jump_target = program.allocate_label(); let meta = ConditionMetadata { jump_if_condition_is_true: false, @@ -664,7 +690,11 @@ fn emit_update_insns( }); } - for cond in plan.where_clause.iter().filter(|c| c.is_constant()) { + for cond in plan + .where_clause + .iter() + .filter(|c| c.is_constant(&[JoinOrderMember::default()])) + { let meta = ConditionMetadata { jump_if_condition_is_true: false, jump_target_when_true: BranchOffset::Placeholder, diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index a1cabc511..a5732b0a1 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -26,8 +26,8 @@ use super::{ optimizer::Optimizable, order_by::{order_by_sorter_insert, sorter_insert}, plan::{ - convert_where_to_vtab_constraint, IterationDirection, Operation, Search, SeekDef, - SelectPlan, SelectQueryType, TableReference, WhereTerm, + convert_where_to_vtab_constraint, IterationDirection, JoinOrderMember, Operation, Search, + SeekDef, SelectPlan, SelectQueryType, TableReference, WhereTerm, }, }; @@ -199,9 +199,12 @@ pub fn open_loop( program: &mut ProgramBuilder, t_ctx: &mut TranslateCtx, tables: &[TableReference], + join_order: &[JoinOrderMember], predicates: &[WhereTerm], ) -> Result<()> { - for (table_index, table) in tables.iter().enumerate() { + for (join_index, join) in join_order.iter().enumerate() { + let table_index = join.table_no; + let table = &tables[table_index]; let LoopLabels { loop_start, loop_end, @@ -253,7 +256,7 @@ pub fn open_loop( for cond in predicates .iter() - .filter(|cond| cond.should_eval_at_loop(table_index)) + .filter(|cond| cond.should_eval_at_loop(join_index, join_order)) { let jump_target_when_true = program.allocate_label(); let condition_metadata = ConditionMetadata { @@ -306,7 +309,7 @@ pub fn open_loop( // We then materialise the RHS/LHS into registers before issuing VFilter. let converted_constraints = predicates .iter() - .filter(|p| p.should_eval_at_loop(table_index)) + .filter(|p| p.should_eval_at_loop(join_index, join_order)) .enumerate() .filter_map(|(i, p)| { // Build ConstraintInfo from the predicates @@ -408,7 +411,8 @@ pub fn open_loop( } for (_, cond) in predicates.iter().enumerate().filter(|(i, cond)| { - cond.should_eval_at_loop(table_index) && !t_ctx.omit_predicates.contains(i) + cond.should_eval_at_loop(join_index, join_order) + && !t_ctx.omit_predicates.contains(i) }) { let jump_target_when_true = program.allocate_label(); let condition_metadata = ConditionMetadata { @@ -516,7 +520,7 @@ pub fn open_loop( for cond in predicates .iter() - .filter(|cond| cond.should_eval_at_loop(table_index)) + .filter(|cond| cond.should_eval_at_loop(join_index, join_order)) { let jump_target_when_true = program.allocate_label(); let condition_metadata = ConditionMetadata { @@ -792,6 +796,7 @@ pub fn close_loop( program: &mut ProgramBuilder, t_ctx: &mut TranslateCtx, tables: &[TableReference], + join_order: &[JoinOrderMember], ) -> Result<()> { // We close the loops for all tables in reverse order, i.e. innermost first. // OPEN t1 @@ -801,8 +806,9 @@ pub fn close_loop( // CLOSE t3 // CLOSE t2 // CLOSE t1 - for (idx, table) in tables.iter().rev().enumerate() { - let table_index = tables.len() - idx - 1; + for join in join_order.iter().rev() { + let table_index = join.table_no; + let table = &tables[table_index]; let loop_labels = *t_ctx .labels_main_loop .get(table_index) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 8520f3e9d..1409bd9af 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -13,8 +13,8 @@ use crate::{ use super::{ emitter::Resolver, plan::{ - DeletePlan, EvalAt, GroupBy, IterationDirection, Operation, Plan, Search, SeekDef, SeekKey, - SelectPlan, TableReference, UpdatePlan, WhereTerm, + DeletePlan, EvalAt, GroupBy, IterationDirection, JoinOrderMember, Operation, Plan, Search, + SeekDef, SeekKey, SelectPlan, TableReference, UpdatePlan, WhereTerm, }, planner::determine_where_to_eval_expr, }; @@ -281,6 +281,15 @@ fn use_indexes( let did_eliminate_orderby = eliminate_unnecessary_orderby(table_references, available_indexes, order_by, group_by)?; + let join_order = table_references + .iter() + .enumerate() + .map(|(i, t)| JoinOrderMember { + table_no: i, + is_outer: t.join_info.as_ref().map_or(false, |j| j.outer), + }) + .collect::>(); + // Try to use indexes for WHERE conditions for (table_index, table_reference) in table_references.iter_mut().enumerate() { if matches!(table_reference.op, Operation::Scan { .. }) { @@ -303,6 +312,7 @@ fn use_indexes( table_index, table_reference, &available_indexes, + &join_order, )? { table_reference.op = Operation::Search(search); } @@ -317,6 +327,7 @@ fn use_indexes( &mut where_clause[i], table_index, table_reference, + &join_order, )? { where_clause.remove(i); table_reference.op = Operation::Search(search); @@ -341,6 +352,7 @@ fn use_indexes( table_index, table_reference, usable_indexes_ref, + &join_order, )? { table_reference.op = Operation::Search(search); } @@ -389,7 +401,7 @@ fn eliminate_constant_conditions( } else if predicate.expr.is_always_false()? { // any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false, // except an outer join condition, because that just results in NULLs, not skipping the whole loop - if predicate.from_outer_join { + if predicate.from_outer_join.is_some() { i += 1; continue; } @@ -872,6 +884,7 @@ pub fn try_extract_index_search_from_where_clause( table_index: usize, table_reference: &TableReference, table_indexes: &[Arc], + join_order: &[JoinOrderMember], ) -> Result> { // If there are no WHERE terms, we can't extract a search if where_clause.is_empty() { @@ -901,7 +914,13 @@ pub fn try_extract_index_search_from_where_clause( for index in table_indexes { // Check how many terms in the where clause constrain the index in column order - find_index_constraints(where_clause, table_index, index, &mut constraints_cur)?; + find_index_constraints( + where_clause, + table_index, + index, + join_order, + &mut constraints_cur, + )?; // naive scoring since we don't have statistics: prefer the index where we can use the most columns // e.g. if we can use all columns of an index on (a,b), it's better than an index of (c,d,e) where we can only use c. let cost = dumb_cost_estimator( @@ -925,7 +944,7 @@ pub fn try_extract_index_search_from_where_clause( // let's see if building an ephemeral index would be better. if best_index.index.is_none() { let (ephemeral_cost, constraints_with_col_idx, mut constraints_without_col_idx) = - ephemeral_index_estimate_cost(where_clause, table_reference, table_index); + ephemeral_index_estimate_cost(where_clause, table_reference, table_index, join_order); if ephemeral_cost < best_index.cost { // ephemeral index makes sense, so let's build it now. // ephemeral columns are: columns from the table_reference, constraints first, then the rest @@ -970,11 +989,12 @@ fn ephemeral_index_estimate_cost( where_clause: &mut Vec, table_reference: &TableReference, table_index: usize, + join_order: &[JoinOrderMember], ) -> (f64, Vec<(usize, IndexConstraint)>, Vec) { let mut constraints_with_col_idx: Vec<(usize, IndexConstraint)> = where_clause .iter() .enumerate() - .filter(|(_, term)| is_potential_index_constraint(term, table_index)) + .filter(|(_, term)| is_potential_index_constraint(term, table_index, join_order)) .filter_map(|(i, term)| { let Ok(ast::Expr::Binary(lhs, operator, rhs)) = unwrap_parens(&term.expr) else { panic!("expected binary expression"); @@ -1179,9 +1199,13 @@ fn get_column_position_in_index( Ok(index.column_table_pos_to_index_pos(*column)) } -fn is_potential_index_constraint(term: &WhereTerm, table_index: usize) -> bool { +fn is_potential_index_constraint( + term: &WhereTerm, + table_index: usize, + join_order: &[JoinOrderMember], +) -> bool { // Skip terms that cannot be evaluated at this table's loop level - if !term.should_eval_at_loop(table_index) { + if !term.should_eval_at_loop(table_index, join_order) { return false; } // Skip terms that are not binary comparisons @@ -1206,10 +1230,10 @@ fn is_potential_index_constraint(term: &WhereTerm, table_index: usize) -> bool { // - WHERE t.x > t.y // - WHERE t.x + 1 > t.y - 5 // - WHERE t.x = (t.x) - let Ok(eval_at_left) = determine_where_to_eval_expr(&lhs) else { + let Ok(eval_at_left) = determine_where_to_eval_expr(&lhs, join_order) else { return false; }; - let Ok(eval_at_right) = determine_where_to_eval_expr(&rhs) else { + let Ok(eval_at_right) = determine_where_to_eval_expr(&rhs, join_order) else { return false; }; if eval_at_left == EvalAt::Loop(table_index) && eval_at_right == EvalAt::Loop(table_index) { @@ -1226,12 +1250,13 @@ fn find_index_constraints( where_clause: &mut Vec, table_index: usize, index: &Arc, + join_order: &[JoinOrderMember], out_constraints: &mut Vec, ) -> Result<()> { for position_in_index in 0..index.columns.len() { let mut found = false; for (position_in_where_clause, term) in where_clause.iter().enumerate() { - if !is_potential_index_constraint(term, table_index) { + if !is_potential_index_constraint(term, table_index, join_order) { continue; } @@ -1748,13 +1773,14 @@ pub fn try_extract_rowid_search_expression( cond: &mut WhereTerm, table_index: usize, table_reference: &TableReference, + join_order: &[JoinOrderMember], ) -> Result> { let iter_dir = if let Operation::Scan { iter_dir, .. } = &table_reference.op { *iter_dir } else { return Ok(None); }; - if !cond.should_eval_at_loop(table_index) { + if !cond.should_eval_at_loop(table_index, join_order) { return Ok(None); } match &mut cond.expr { @@ -1764,8 +1790,8 @@ pub fn try_extract_rowid_search_expression( // - WHERE t.x > t.y // - WHERE t.x + 1 > t.y - 5 // - WHERE t.x = (t.x) - if determine_where_to_eval_expr(lhs)? == EvalAt::Loop(table_index) - && determine_where_to_eval_expr(rhs)? == EvalAt::Loop(table_index) + if determine_where_to_eval_expr(lhs, join_order)? == EvalAt::Loop(table_index) + && determine_where_to_eval_expr(rhs, join_order)? == EvalAt::Loop(table_index) { return Ok(None); } @@ -1777,7 +1803,6 @@ pub fn try_extract_rowid_search_expression( cmp_expr: WhereTerm { expr: rhs_owned, from_outer_join: cond.from_outer_join, - eval_at: cond.eval_at, }, })); } @@ -1805,7 +1830,6 @@ pub fn try_extract_rowid_search_expression( cmp_expr: WhereTerm { expr: lhs_owned, from_outer_join: cond.from_outer_join, - eval_at: cond.eval_at, }, })); } diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 079e99908..7106a0e23 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -23,7 +23,7 @@ use crate::{ util::can_pushdown_predicate, }; -use super::emitter::OperationMode; +use super::{emitter::OperationMode, planner::determine_where_to_eval_term}; #[derive(Debug, Clone)] pub struct ResultSetColumn { @@ -77,21 +77,30 @@ pub struct GroupBy { pub struct WhereTerm { /// The original condition expression. pub expr: ast::Expr, - /// Is this condition originally from an OUTER JOIN? + /// Is this condition originally from an OUTER JOIN, and which table number in the plan's [TableReference] vector? /// If so, we need to evaluate it at the loop of the right table in that JOIN, /// regardless of which tables it references. /// We also cannot e.g. short circuit the entire query in the optimizer if the condition is statically false. - pub from_outer_join: bool, - pub eval_at: EvalAt, + pub from_outer_join: Option, } impl WhereTerm { - pub fn is_constant(&self) -> bool { - self.eval_at == EvalAt::BeforeLoop + pub fn is_constant(&self, join_order: &[JoinOrderMember]) -> bool { + let Ok(eval_at) = self.eval_at(join_order) else { + return false; + }; + eval_at == EvalAt::BeforeLoop } - pub fn should_eval_at_loop(&self, loop_idx: usize) -> bool { - self.eval_at == EvalAt::Loop(loop_idx) + pub fn should_eval_at_loop(&self, loop_idx: usize, join_order: &[JoinOrderMember]) -> bool { + let Ok(eval_at) = self.eval_at(join_order) else { + return false; + }; + eval_at == EvalAt::Loop(loop_idx) + } + + fn eval_at(&self, join_order: &[JoinOrderMember]) -> Result { + determine_where_to_eval_term(&self, join_order) } } @@ -141,7 +150,7 @@ pub fn convert_where_to_vtab_constraint( table_index: usize, pred_idx: usize, ) -> Option { - if term.from_outer_join { + if term.from_outer_join.is_some() { return None; } let Expr::Binary(lhs, op, rhs) = &term.expr else { @@ -255,10 +264,29 @@ pub enum SelectQueryType { }, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct JoinOrderMember { + /// The index of the table in the plan's vector of [TableReference] + pub table_no: usize, + /// Whether this member is the right side of an OUTER JOIN + pub is_outer: bool, +} + +impl Default for JoinOrderMember { + fn default() -> Self { + Self { + table_no: 0, + is_outer: false, + } + } +} + #[derive(Debug, Clone)] pub struct SelectPlan { /// List of table references in loop order, outermost first. pub table_references: Vec, + /// The order in which the tables are joined. Tables have usize Ids (their index in table_references) + pub join_order: Vec, /// the columns inside SELECT ... FROM pub result_columns: Vec, /// where clause split into a vec at 'AND' boundaries. all join conditions also get shoved in here, diff --git a/core/translate/planner.rs b/core/translate/planner.rs index f1d7aaeea..1d4cd568e 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -1,7 +1,7 @@ use super::{ plan::{ - Aggregate, ColumnUsedMask, EvalAt, IterationDirection, JoinInfo, Operation, Plan, - ResultSetColumn, SelectPlan, SelectQueryType, TableReference, WhereTerm, + Aggregate, ColumnUsedMask, EvalAt, IterationDirection, JoinInfo, JoinOrderMember, + Operation, Plan, ResultSetColumn, SelectPlan, SelectQueryType, TableReference, WhereTerm, }, select::prepare_select_plan, SymbolTable, @@ -554,11 +554,9 @@ pub fn parse_where( bind_column_references(expr, table_references, result_columns)?; } for expr in predicates { - let eval_at = determine_where_to_eval_expr(&expr)?; out_where_clause.push(WhereTerm { expr, - from_outer_join: false, - eval_at, + from_outer_join: None, }); } Ok(()) @@ -574,15 +572,38 @@ pub fn parse_where( For expressions not referencing any tables (e.g. constants), this is before the main loop is opened, because they do not need any table data. */ -pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result { +pub fn determine_where_to_eval_term( + term: &WhereTerm, + join_order: &[JoinOrderMember], +) -> Result { + if let Some(table_no) = term.from_outer_join { + return Ok(EvalAt::Loop( + join_order + .iter() + .position(|t| t.table_no == table_no) + .unwrap(), + )); + } + + return determine_where_to_eval_expr(&term.expr, join_order); +} + +pub fn determine_where_to_eval_expr<'a>( + expr: &'a Expr, + join_order: &[JoinOrderMember], +) -> Result { let mut eval_at: EvalAt = EvalAt::BeforeLoop; - match predicate { + match expr { ast::Expr::Binary(e1, _, e2) => { - eval_at = eval_at.max(determine_where_to_eval_expr(e1)?); - eval_at = eval_at.max(determine_where_to_eval_expr(e2)?); + eval_at = eval_at.max(determine_where_to_eval_expr(e1, join_order)?); + eval_at = eval_at.max(determine_where_to_eval_expr(e2, join_order)?); } ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => { - eval_at = eval_at.max(EvalAt::Loop(*table)); + let join_idx = join_order + .iter() + .position(|t| t.table_no == *table) + .unwrap(); + eval_at = eval_at.max(EvalAt::Loop(join_idx)); } ast::Expr::Id(_) => { /* Id referring to column will already have been rewritten as an Expr::Column */ @@ -593,30 +614,30 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result {} ast::Expr::Like { lhs, rhs, .. } => { - eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?); - eval_at = eval_at.max(determine_where_to_eval_expr(rhs)?); + eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?); + eval_at = eval_at.max(determine_where_to_eval_expr(rhs, join_order)?); } ast::Expr::FunctionCall { args: Some(args), .. } => { for arg in args { - eval_at = eval_at.max(determine_where_to_eval_expr(arg)?); + eval_at = eval_at.max(determine_where_to_eval_expr(arg, join_order)?); } } ast::Expr::InList { lhs, rhs, .. } => { - eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?); + eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?); if let Some(rhs_list) = rhs { for rhs_expr in rhs_list { - eval_at = eval_at.max(determine_where_to_eval_expr(rhs_expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(rhs_expr, join_order)?); } } } Expr::Between { lhs, start, end, .. } => { - eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?); - eval_at = eval_at.max(determine_where_to_eval_expr(start)?); - eval_at = eval_at.max(determine_where_to_eval_expr(end)?); + eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?); + eval_at = eval_at.max(determine_where_to_eval_expr(start, join_order)?); + eval_at = eval_at.max(determine_where_to_eval_expr(end, join_order)?); } Expr::Case { base, @@ -624,21 +645,21 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result { if let Some(base) = base { - eval_at = eval_at.max(determine_where_to_eval_expr(base)?); + eval_at = eval_at.max(determine_where_to_eval_expr(base, join_order)?); } for (when, then) in when_then_pairs { - eval_at = eval_at.max(determine_where_to_eval_expr(when)?); - eval_at = eval_at.max(determine_where_to_eval_expr(then)?); + eval_at = eval_at.max(determine_where_to_eval_expr(when, join_order)?); + eval_at = eval_at.max(determine_where_to_eval_expr(then, join_order)?); } if let Some(else_expr) = else_expr { - eval_at = eval_at.max(determine_where_to_eval_expr(else_expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(else_expr, join_order)?); } } Expr::Cast { expr, .. } => { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } Expr::Collate(expr, _) => { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } Expr::DoublyQualified(_, _, _) => { unreachable!("DoublyQualified should be resolved to a Column before resolving eval_at") @@ -648,7 +669,7 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result { for arg in args.as_ref().unwrap_or(&vec![]).iter() { - eval_at = eval_at.max(determine_where_to_eval_expr(arg)?); + eval_at = eval_at.max(determine_where_to_eval_expr(arg, join_order)?); } } Expr::FunctionCallStar { .. } => {} @@ -659,15 +680,15 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } Expr::Name(_) => {} Expr::NotNull(expr) => { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } Expr::Parenthesized(exprs) => { for expr in exprs.iter() { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } } Expr::Raise(_, _) => { @@ -677,7 +698,7 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result { - eval_at = eval_at.max(determine_where_to_eval_expr(expr)?); + eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?); } Expr::Variable(_) => {} } @@ -765,16 +786,13 @@ fn parse_join<'a>( bind_column_references(predicate, &mut scope.tables, None)?; } for pred in preds { - let cur_table_idx = scope.tables.len() - 1; - let eval_at = if outer { - EvalAt::Loop(cur_table_idx) - } else { - determine_where_to_eval_expr(&pred)? - }; out_where_clause.push(WhereTerm { expr: pred, - from_outer_join: outer, - eval_at, + from_outer_join: if outer { + Some(scope.tables.len() - 1) + } else { + None + }, }); } } @@ -841,15 +859,9 @@ fn parse_join<'a>( left_table.mark_column_used(left_col_idx); let right_table = scope.tables.get_mut(cur_table_idx).unwrap(); right_table.mark_column_used(right_col_idx); - let eval_at = if outer { - EvalAt::Loop(cur_table_idx) - } else { - determine_where_to_eval_expr(&expr)? - }; out_where_clause.push(WhereTerm { expr, - from_outer_join: outer, - eval_at, + from_outer_join: if outer { Some(cur_table_idx) } else { None }, }); } using = Some(distinct_names); diff --git a/core/translate/select.rs b/core/translate/select.rs index b1eb613bb..f9529e92a 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -1,5 +1,5 @@ use super::emitter::emit_program; -use super::plan::{select_star, Operation, Search, SelectQueryType}; +use super::plan::{select_star, JoinOrderMember, Operation, Search, SelectQueryType}; use super::planner::Scope; use crate::function::{AggFunc, ExtFunc, Func}; use crate::translate::optimizer::optimize_plan; @@ -87,6 +87,14 @@ pub fn prepare_select_plan<'a>( ); let mut plan = SelectPlan { + join_order: table_references + .iter() + .enumerate() + .map(|(i, t)| JoinOrderMember { + table_no: i, + is_outer: t.join_info.as_ref().map_or(false, |j| j.outer), + }) + .collect(), table_references, result_columns, where_clause: where_predicates,