Skip to content

Commit c644708

Browse files
committed
Fix never evaluating constant conditions at all
1 parent 692bb28 commit c644708

File tree

5 files changed

+167
-42
lines changed

5 files changed

+167
-42
lines changed

core/translate/emitter.rs

+16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::vdbe::{insn::Insn, BranchOffset};
1111
use crate::{Result, SymbolTable};
1212

1313
use super::aggregation::emit_ungrouped_aggregation;
14+
use super::expr::{translate_condition_expr, ConditionMetadata};
1415
use super::group_by::{emit_group_by, init_group_by, GroupByMetadata};
1516
use super::main_loop::{close_loop, emit_loop, init_loop, open_loop, LeftJoinMetadata, LoopLabels};
1617
use super::order_by::{emit_order_by, init_order_by, SortMetadata};
@@ -241,6 +242,21 @@ pub fn emit_query<'a>(
241242
&OperationMode::SELECT,
242243
)?;
243244

245+
for where_term in plan.where_clause.iter().filter(|wt| wt.is_constant()) {
246+
let condition_metadata = ConditionMetadata {
247+
jump_if_condition_is_true: false,
248+
jump_target_when_false: after_main_loop_label,
249+
jump_target_when_true: BranchOffset::Placeholder,
250+
};
251+
translate_condition_expr(
252+
program,
253+
&plan.table_references,
254+
&where_term.expr,
255+
condition_metadata,
256+
&mut t_ctx.resolver,
257+
)?;
258+
}
259+
244260
// Set up main query execution loop
245261
open_loop(program, t_ctx, &plan.table_references, &plan.where_clause)?;
246262

core/translate/main_loop.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ pub fn open_loop(
237237
// E.g. SELECT foo FROM (SELECT bar as foo FROM t1) sub WHERE sub.foo > 10
238238
for cond in predicates
239239
.iter()
240-
.filter(|cond| cond.eval_at_loop == table_index)
240+
.filter(|cond| cond.should_eval_at_loop(table_index))
241241
{
242242
let jump_target_when_true = program.allocate_label();
243243
let condition_metadata = ConditionMetadata {
@@ -311,7 +311,7 @@ pub fn open_loop(
311311

312312
for cond in predicates
313313
.iter()
314-
.filter(|cond| cond.eval_at_loop == table_index)
314+
.filter(|cond| cond.should_eval_at_loop(table_index))
315315
{
316316
let jump_target_when_true = program.allocate_label();
317317
let condition_metadata = ConditionMetadata {
@@ -483,7 +483,7 @@ pub fn open_loop(
483483
}
484484
for cond in predicates
485485
.iter()
486-
.filter(|cond| cond.eval_at_loop == table_index)
486+
.filter(|cond| cond.should_eval_at_loop(table_index))
487487
{
488488
let jump_target_when_true = program.allocate_label();
489489
let condition_metadata = ConditionMetadata {

core/translate/optimizer.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use crate::{
88
};
99

1010
use super::plan::{
11-
DeletePlan, Direction, IterationDirection, Operation, Plan, Search, SelectPlan, TableReference,
12-
WhereTerm,
11+
DeletePlan, Direction, EvalAt, IterationDirection, Operation, Plan, Search, SelectPlan,
12+
TableReference, WhereTerm,
1313
};
1414

1515
pub fn optimize_plan(plan: &mut Plan, schema: &Schema) -> Result<()> {
@@ -499,7 +499,7 @@ pub fn try_extract_index_search_expression(
499499
table_reference: &TableReference,
500500
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
501501
) -> Result<Option<Search>> {
502-
if cond.eval_at_loop != table_index {
502+
if !cond.should_eval_at_loop(table_index) {
503503
return Ok(None);
504504
}
505505
match &mut cond.expr {
@@ -512,7 +512,7 @@ pub fn try_extract_index_search_expression(
512512
cmp_expr: WhereTerm {
513513
expr: rhs_owned,
514514
from_outer_join: cond.from_outer_join,
515-
eval_at_loop: cond.eval_at_loop,
515+
eval_at: EvalAt::Loop(table_index),
516516
},
517517
}));
518518
}
@@ -526,7 +526,7 @@ pub fn try_extract_index_search_expression(
526526
cmp_expr: WhereTerm {
527527
expr: rhs_owned,
528528
from_outer_join: cond.from_outer_join,
529-
eval_at_loop: cond.eval_at_loop,
529+
eval_at: EvalAt::Loop(table_index),
530530
},
531531
}));
532532
}
@@ -542,7 +542,7 @@ pub fn try_extract_index_search_expression(
542542
cmp_expr: WhereTerm {
543543
expr: lhs_owned,
544544
from_outer_join: cond.from_outer_join,
545-
eval_at_loop: cond.eval_at_loop,
545+
eval_at: cond.eval_at,
546546
},
547547
}));
548548
}
@@ -556,7 +556,7 @@ pub fn try_extract_index_search_expression(
556556
cmp_expr: WhereTerm {
557557
expr: lhs_owned,
558558
from_outer_join: cond.from_outer_join,
559-
eval_at_loop: cond.eval_at_loop,
559+
eval_at: cond.eval_at,
560560
},
561561
}));
562562
}
@@ -580,7 +580,7 @@ pub fn try_extract_index_search_expression(
580580
cmp_expr: WhereTerm {
581581
expr: rhs_owned,
582582
from_outer_join: cond.from_outer_join,
583-
eval_at_loop: cond.eval_at_loop,
583+
eval_at: cond.eval_at,
584584
},
585585
}));
586586
}
@@ -604,7 +604,7 @@ pub fn try_extract_index_search_expression(
604604
cmp_expr: WhereTerm {
605605
expr: lhs_owned,
606606
from_outer_join: cond.from_outer_join,
607-
eval_at_loop: cond.eval_at_loop,
607+
eval_at: cond.eval_at,
608608
},
609609
}));
610610
}

core/translate/plan.rs

+43-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use core::fmt;
22
use limbo_sqlite3_parser::ast;
33
use std::{
4+
cmp::Ordering,
45
fmt::{Display, Formatter},
56
rc::Rc,
67
};
@@ -60,10 +61,48 @@ pub struct WhereTerm {
6061
/// regardless of which tables it references.
6162
/// We also cannot e.g. short circuit the entire query in the optimizer if the condition is statically false.
6263
pub from_outer_join: bool,
63-
/// The loop index where to evaluate the condition.
64-
/// For example, in `SELECT * FROM u JOIN p WHERE u.id = 5`, the condition can already be evaluated at the first loop (idx 0),
65-
/// because that is the rightmost table that it references.
66-
pub eval_at_loop: usize,
64+
pub eval_at: EvalAt,
65+
}
66+
67+
impl WhereTerm {
68+
pub fn is_constant(&self) -> bool {
69+
self.eval_at == EvalAt::BeforeLoop
70+
}
71+
72+
pub fn should_eval_at_loop(&self, loop_idx: usize) -> bool {
73+
self.eval_at == EvalAt::Loop(loop_idx)
74+
}
75+
}
76+
77+
/// The loop index where to evaluate the condition.
78+
/// For example, in `SELECT * FROM u JOIN p WHERE u.id = 5`, the condition can already be evaluated at the first loop (idx 0),
79+
/// because that is the rightmost table that it references.
80+
///
81+
/// Conditions like 1=2 can be evaluated before the main loop is opened, because they are constant.
82+
/// In theory we should be able to statically analyze them all and reduce them to a single boolean value,
83+
/// but that is not implemented yet.
84+
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
85+
pub enum EvalAt {
86+
Loop(usize),
87+
BeforeLoop,
88+
}
89+
90+
impl PartialOrd for EvalAt {
91+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92+
match (self, other) {
93+
(EvalAt::Loop(a), EvalAt::Loop(b)) => a.partial_cmp(b),
94+
(EvalAt::BeforeLoop, EvalAt::BeforeLoop) => Some(Ordering::Equal),
95+
(EvalAt::BeforeLoop, _) => Some(Ordering::Less),
96+
(_, EvalAt::BeforeLoop) => Some(Ordering::Greater),
97+
}
98+
}
99+
}
100+
101+
impl Ord for EvalAt {
102+
fn cmp(&self, other: &Self) -> Ordering {
103+
self.partial_cmp(other)
104+
.expect("total ordering not implemented for EvalAt")
105+
}
67106
}
68107

69108
/// A query plan is either a SELECT or a DELETE (for now)

core/translate/planner.rs

+96-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
plan::{
3-
Aggregate, JoinInfo, Operation, Plan, ResultSetColumn, SelectPlan, SelectQueryType,
3+
Aggregate, EvalAt, JoinInfo, Operation, Plan, ResultSetColumn, SelectPlan, SelectQueryType,
44
TableReference, WhereTerm,
55
},
66
select::prepare_select_plan,
@@ -534,11 +534,11 @@ pub fn parse_where(
534534
bind_column_references(expr, table_references, result_columns)?;
535535
}
536536
for expr in predicates {
537-
let eval_at_loop = get_rightmost_table_referenced_in_expr(&expr)?;
537+
let eval_at = determine_where_to_eval_expr(&expr)?;
538538
out_where_clause.push(WhereTerm {
539539
expr,
540540
from_outer_join: false,
541-
eval_at_loop,
541+
eval_at,
542542
});
543543
}
544544
Ok(())
@@ -554,48 +554,118 @@ pub fn parse_where(
554554
and it needs to be the rightmost table referenced in the expression, because otherwise
555555
the condition would be evaluated before a row is read from that table.
556556
*/
557-
fn get_rightmost_table_referenced_in_expr<'a>(predicate: &'a ast::Expr) -> Result<usize> {
558-
let mut max_table_idx = 0;
557+
fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<EvalAt> {
558+
let mut eval_at: EvalAt = EvalAt::BeforeLoop;
559559
match predicate {
560560
ast::Expr::Binary(e1, _, e2) => {
561-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(e1)?);
562-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(e2)?);
561+
eval_at = eval_at.max(determine_where_to_eval_expr(e1)?);
562+
eval_at = eval_at.max(determine_where_to_eval_expr(e2)?);
563563
}
564-
ast::Expr::Column { table, .. } => {
565-
max_table_idx = max_table_idx.max(*table);
564+
ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => {
565+
eval_at = eval_at.max(EvalAt::Loop(*table));
566566
}
567567
ast::Expr::Id(_) => {
568568
/* Id referring to column will already have been rewritten as an Expr::Column */
569569
/* we only get here with literal 'true' or 'false' etc */
570570
}
571571
ast::Expr::Qualified(_, _) => {
572-
unreachable!("Qualified should be resolved to a Column before optimizer")
572+
unreachable!("Qualified should be resolved to a Column before resolving eval_at")
573573
}
574574
ast::Expr::Literal(_) => {}
575575
ast::Expr::Like { lhs, rhs, .. } => {
576-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(lhs)?);
577-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(rhs)?);
576+
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
577+
eval_at = eval_at.max(determine_where_to_eval_expr(rhs)?);
578578
}
579579
ast::Expr::FunctionCall {
580580
args: Some(args), ..
581581
} => {
582582
for arg in args {
583-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(arg)?);
583+
eval_at = eval_at.max(determine_where_to_eval_expr(arg)?);
584584
}
585585
}
586586
ast::Expr::InList { lhs, rhs, .. } => {
587-
max_table_idx = max_table_idx.max(get_rightmost_table_referenced_in_expr(lhs)?);
587+
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
588588
if let Some(rhs_list) = rhs {
589589
for rhs_expr in rhs_list {
590-
max_table_idx =
591-
max_table_idx.max(get_rightmost_table_referenced_in_expr(rhs_expr)?);
590+
eval_at = eval_at.max(determine_where_to_eval_expr(rhs_expr)?);
592591
}
593592
}
594593
}
595-
_ => {}
594+
Expr::Between {
595+
lhs,
596+
not,
597+
start,
598+
end,
599+
} => {
600+
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
601+
eval_at = eval_at.max(determine_where_to_eval_expr(start)?);
602+
eval_at = eval_at.max(determine_where_to_eval_expr(end)?);
603+
}
604+
Expr::Case {
605+
base,
606+
when_then_pairs,
607+
else_expr,
608+
} => {
609+
if let Some(base) = base {
610+
eval_at = eval_at.max(determine_where_to_eval_expr(base)?);
611+
}
612+
for (when, then) in when_then_pairs {
613+
eval_at = eval_at.max(determine_where_to_eval_expr(when)?);
614+
eval_at = eval_at.max(determine_where_to_eval_expr(then)?);
615+
}
616+
if let Some(else_expr) = else_expr {
617+
eval_at = eval_at.max(determine_where_to_eval_expr(else_expr)?);
618+
}
619+
}
620+
Expr::Cast { expr, .. } => {
621+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
622+
}
623+
Expr::Collate(expr, _) => {
624+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
625+
}
626+
Expr::DoublyQualified(_, _, _) => {
627+
unreachable!("DoublyQualified should be resolved to a Column before resolving eval_at")
628+
}
629+
Expr::Exists(_) => {
630+
todo!("exists not supported yet")
631+
}
632+
Expr::FunctionCall { args, .. } => {
633+
for arg in args.as_ref().unwrap_or(&vec![]).iter() {
634+
eval_at = eval_at.max(determine_where_to_eval_expr(arg)?);
635+
}
636+
}
637+
Expr::FunctionCallStar { .. } => {}
638+
Expr::InSelect { .. } => {
639+
todo!("in select not supported yet")
640+
}
641+
Expr::InTable { .. } => {
642+
todo!("in table not supported yet")
643+
}
644+
Expr::IsNull(expr) => {
645+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
646+
}
647+
Expr::Name(_) => {}
648+
Expr::NotNull(expr) => {
649+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
650+
}
651+
Expr::Parenthesized(exprs) => {
652+
for expr in exprs.iter() {
653+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
654+
}
655+
}
656+
Expr::Raise(_, _) => {
657+
todo!("raise not supported yet")
658+
}
659+
Expr::Subquery(_) => {
660+
todo!("subquery not supported yet")
661+
}
662+
Expr::Unary(_, expr) => {
663+
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
664+
}
665+
Expr::Variable(_) => {}
596666
}
597667

598-
Ok(max_table_idx)
668+
Ok(eval_at)
599669
}
600670

601671
fn parse_join<'a>(
@@ -679,15 +749,15 @@ fn parse_join<'a>(
679749
}
680750
for pred in preds {
681751
let cur_table_idx = scope.tables.len() - 1;
682-
let eval_at_loop = if outer {
683-
cur_table_idx
752+
let eval_at = if outer {
753+
EvalAt::Loop(cur_table_idx)
684754
} else {
685-
get_rightmost_table_referenced_in_expr(&pred)?
755+
determine_where_to_eval_expr(&pred)?
686756
};
687757
out_where_clause.push(WhereTerm {
688758
expr: pred,
689759
from_outer_join: outer,
690-
eval_at_loop,
760+
eval_at,
691761
});
692762
}
693763
}
@@ -749,15 +819,15 @@ fn parse_join<'a>(
749819
is_rowid_alias: right_col.is_rowid_alias,
750820
}),
751821
);
752-
let eval_at_loop = if outer {
753-
cur_table_idx
822+
let eval_at = if outer {
823+
EvalAt::Loop(cur_table_idx)
754824
} else {
755-
get_rightmost_table_referenced_in_expr(&expr)?
825+
determine_where_to_eval_expr(&expr)?
756826
};
757827
out_where_clause.push(WhereTerm {
758828
expr,
759829
from_outer_join: outer,
760-
eval_at_loop,
830+
eval_at,
761831
});
762832
}
763833
using = Some(distinct_names);

0 commit comments

Comments
 (0)