mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-29 21:04:23 +01:00
Merge 'Add notion of join ordering to plan' from Jussi Saurio
This PR is an enabler for our (Coming Soon ™️ ) join reordering optimizer -- simply adds the notion of a join order to the current query execution. This PR does not do any join ordering -- the join order is always the same as expressed in the SQL query. Reviewed-by: Preston Thorpe (@PThorpe92) Closes #1439
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
// 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<Index>],
|
||||
join_order: &[JoinOrderMember],
|
||||
) -> Result<Option<Search>> {
|
||||
// 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<WhereTerm>,
|
||||
table_reference: &TableReference,
|
||||
table_index: usize,
|
||||
join_order: &[JoinOrderMember],
|
||||
) -> (f64, Vec<(usize, IndexConstraint)>, Vec<IndexConstraint>) {
|
||||
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<WhereTerm>,
|
||||
table_index: usize,
|
||||
index: &Arc<Index>,
|
||||
join_order: &[JoinOrderMember],
|
||||
out_constraints: &mut Vec<IndexConstraint>,
|
||||
) -> 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<Option<Search>> {
|
||||
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,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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<usize>,
|
||||
}
|
||||
|
||||
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<EvalAt> {
|
||||
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<ConstraintInfo> {
|
||||
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<TableReference>,
|
||||
/// The order in which the tables are joined. Tables have usize Ids (their index in table_references)
|
||||
pub join_order: Vec<JoinOrderMember>,
|
||||
/// the columns inside SELECT ... FROM
|
||||
pub result_columns: Vec<ResultSetColumn>,
|
||||
/// where clause split into a vec at 'AND' boundaries. all join conditions also get shoved in here,
|
||||
|
||||
@@ -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<EvalAt> {
|
||||
pub fn determine_where_to_eval_term(
|
||||
term: &WhereTerm,
|
||||
join_order: &[JoinOrderMember],
|
||||
) -> Result<EvalAt> {
|
||||
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<EvalAt> {
|
||||
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<Eval
|
||||
}
|
||||
ast::Expr::Literal(_) => {}
|
||||
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<Eval
|
||||
else_expr,
|
||||
} => {
|
||||
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<Eval
|
||||
}
|
||||
Expr::FunctionCall { args, .. } => {
|
||||
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
|
||||
todo!("in table not supported yet")
|
||||
}
|
||||
Expr::IsNull(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::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
|
||||
todo!("subquery not supported yet")
|
||||
}
|
||||
Expr::Unary(_, 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::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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user