mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-09 02:04:22 +01:00
Optimize constant conditions
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::function::{AggFunc, Func};
|
||||
use crate::schema::{Column, PseudoTable, Schema, Table};
|
||||
use crate::translate::expr::{analyze_columns, maybe_apply_affinity, translate_expr};
|
||||
use crate::translate::where_clause::{
|
||||
process_where, translate_processed_where, translate_where, ProcessedWhereClause,
|
||||
process_where, translate_processed_where, translate_tableless_where, ProcessedWhereClause,
|
||||
};
|
||||
use crate::translate::{normalize_ident, Insn};
|
||||
use crate::types::{OwnedRecord, OwnedValue};
|
||||
@@ -93,15 +93,19 @@ impl<'a> ColumnInfo<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LeftJoinBookkeeping {
|
||||
// integer register that holds a flag that is set to true if the current row has a match for the left join
|
||||
pub match_flag_register: usize,
|
||||
// label for the instruction that sets the match flag to true
|
||||
pub set_match_flag_true_label: BranchOffset,
|
||||
// label for the instruction that checks if the match flag is true
|
||||
pub check_match_flag_label: BranchOffset,
|
||||
// label for the instruction where the program jumps to if the current row has a match for the left join
|
||||
pub on_match_jump_to_label: BranchOffset,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoopInfo {
|
||||
// The table or table alias that we are looping over
|
||||
pub identifier: String,
|
||||
@@ -239,6 +243,7 @@ pub fn prepare_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result<Se
|
||||
pub fn translate_select(mut select: Select) -> Result<Program> {
|
||||
let mut program = ProgramBuilder::new();
|
||||
let init_label = program.allocate_label();
|
||||
let early_terminate_label = program.allocate_label();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Init {
|
||||
target_pc: init_label,
|
||||
@@ -299,7 +304,7 @@ pub fn translate_select(mut select: Select) -> Result<Program> {
|
||||
};
|
||||
|
||||
if !select.src_tables.is_empty() {
|
||||
translate_tables_begin(&mut program, &mut select)?;
|
||||
translate_tables_begin(&mut program, &mut select, early_terminate_label)?;
|
||||
|
||||
let (register_start, column_count) = if let Some(sort_columns) = select.order_by {
|
||||
let start = program.next_free_register();
|
||||
@@ -359,6 +364,7 @@ pub fn translate_select(mut select: Select) -> Result<Program> {
|
||||
translate_tables_end(&mut program, &select);
|
||||
|
||||
if select.exist_aggregation {
|
||||
program.resolve_label(early_terminate_label, program.offset());
|
||||
let mut target = register_start;
|
||||
for info in &select.column_info {
|
||||
if let Some(Func::Agg(func)) = &info.func {
|
||||
@@ -379,7 +385,7 @@ pub fn translate_select(mut select: Select) -> Result<Program> {
|
||||
} else {
|
||||
assert!(!select.exist_aggregation);
|
||||
assert!(sort_info.is_none());
|
||||
let where_maybe = translate_where(&select, &mut program)?;
|
||||
let where_maybe = translate_tableless_where(&select, &mut program, early_terminate_label)?;
|
||||
let (register_start, count) = translate_columns(&mut program, &select, None)?;
|
||||
if let Some(where_clause_label) = where_maybe {
|
||||
program.resolve_label(where_clause_label, program.offset() + 1);
|
||||
@@ -396,6 +402,9 @@ pub fn translate_select(mut select: Select) -> Result<Program> {
|
||||
let _ = translate_sorter(&select, &mut program, &sort_info.unwrap(), &limit_info);
|
||||
}
|
||||
|
||||
if !select.exist_aggregation {
|
||||
program.resolve_label(early_terminate_label, program.offset());
|
||||
}
|
||||
program.emit_insn(Insn::Halt);
|
||||
let halt_offset = program.offset() - 1;
|
||||
if let Some(limit_info) = limit_info {
|
||||
@@ -502,7 +511,11 @@ fn translate_sorter(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn translate_tables_begin(program: &mut ProgramBuilder, select: &mut Select) -> Result<()> {
|
||||
fn translate_tables_begin(
|
||||
program: &mut ProgramBuilder,
|
||||
select: &mut Select,
|
||||
early_terminate_label: BranchOffset,
|
||||
) -> Result<()> {
|
||||
for join in &select.src_tables {
|
||||
let loop_info = translate_table_open_cursor(program, join);
|
||||
select.loops.push(loop_info);
|
||||
@@ -511,7 +524,22 @@ fn translate_tables_begin(program: &mut ProgramBuilder, select: &mut Select) ->
|
||||
let processed_where = process_where(program, select)?;
|
||||
|
||||
for loop_info in &select.loops {
|
||||
translate_table_open_loop(program, select, loop_info, &processed_where)?;
|
||||
// if there is a left join and there is a condition on the join that is always false,
|
||||
// every row in the outer table will be emitted with nulls for the right table
|
||||
let early_terminate_label = if let Some(ljbk) = &loop_info.left_join_bookkeeping {
|
||||
ljbk.check_match_flag_label
|
||||
} else {
|
||||
// if there is an inner join or a where (same thing) and the condition is always false,
|
||||
// no rows will be emitted
|
||||
early_terminate_label
|
||||
};
|
||||
translate_table_open_loop(
|
||||
program,
|
||||
select,
|
||||
loop_info,
|
||||
&processed_where,
|
||||
early_terminate_label,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -555,6 +583,7 @@ fn translate_table_open_cursor(program: &mut ProgramBuilder, table: &SrcTable) -
|
||||
Some(LeftJoinBookkeeping {
|
||||
match_flag_register: program.alloc_register(),
|
||||
on_match_jump_to_label: program.allocate_label(),
|
||||
check_match_flag_label: program.allocate_label(),
|
||||
set_match_flag_true_label: program.allocate_label(),
|
||||
})
|
||||
} else {
|
||||
@@ -602,6 +631,7 @@ fn left_join_match_flag_check(
|
||||
cursor_id: usize,
|
||||
) {
|
||||
// If the left join match flag has been set to 1, we jump to the next row on the outer table (result row has been emitted already)
|
||||
program.resolve_label(ljbk.check_match_flag_label, program.offset());
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::IfPos {
|
||||
reg: ljbk.match_flag_register,
|
||||
@@ -629,6 +659,7 @@ fn translate_table_open_loop(
|
||||
select: &Select,
|
||||
loop_info: &LoopInfo,
|
||||
w: &ProcessedWhereClause,
|
||||
early_terminate_label: BranchOffset,
|
||||
) -> Result<()> {
|
||||
if let Some(ljbk) = loop_info.left_join_bookkeeping.as_ref() {
|
||||
left_join_match_flag_initialize(program, ljbk);
|
||||
@@ -646,7 +677,7 @@ fn translate_table_open_loop(
|
||||
loop_info.rewind_on_empty_label,
|
||||
);
|
||||
|
||||
translate_processed_where(program, select, loop_info, w, None)?;
|
||||
translate_processed_where(program, select, loop_info, w, early_terminate_label, None)?;
|
||||
|
||||
if let Some(ljbk) = loop_info.left_join_bookkeeping.as_ref() {
|
||||
left_join_match_flag_set_true(program, ljbk);
|
||||
|
||||
@@ -46,6 +46,9 @@ pub fn split_constraint_to_terms<'a>(
|
||||
queue.push(right);
|
||||
}
|
||||
expr => {
|
||||
if expr.is_always_true()? {
|
||||
continue;
|
||||
}
|
||||
let term = WhereTerm {
|
||||
expr: expr.clone(),
|
||||
evaluate_at_cursor: match outer_join_table_name {
|
||||
@@ -143,11 +146,29 @@ pub fn process_where<'a>(
|
||||
Ok(wc)
|
||||
}
|
||||
|
||||
pub fn translate_where(
|
||||
/**
|
||||
* Translate the WHERE clause of a SELECT statement that doesn't have any tables.
|
||||
* TODO: refactor this to use the same code path as the other WHERE clause translation functions.
|
||||
*/
|
||||
pub fn translate_tableless_where(
|
||||
select: &Select,
|
||||
program: &mut ProgramBuilder,
|
||||
early_terminate_label: BranchOffset,
|
||||
) -> Result<Option<BranchOffset>> {
|
||||
if let Some(w) = &select.where_clause {
|
||||
if w.is_always_false()? {
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: early_terminate_label,
|
||||
},
|
||||
early_terminate_label,
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
if w.is_always_true()? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let jump_target_when_false = program.allocate_label();
|
||||
let jump_target_when_true = program.allocate_label();
|
||||
translate_condition_expr(
|
||||
@@ -180,12 +201,28 @@ pub fn translate_processed_where<'a>(
|
||||
select: &'a Select,
|
||||
current_loop: &'a LoopInfo,
|
||||
where_c: &'a ProcessedWhereClause,
|
||||
skip_entire_table_label: BranchOffset,
|
||||
cursor_hint: Option<usize>,
|
||||
) -> Result<()> {
|
||||
for term in where_c.terms.iter() {
|
||||
if term.evaluate_at_cursor != current_loop.open_cursor {
|
||||
continue;
|
||||
}
|
||||
if where_c
|
||||
.terms
|
||||
.iter()
|
||||
.filter(|t| t.evaluate_at_cursor == current_loop.open_cursor)
|
||||
.any(|t| t.expr.is_always_false().unwrap_or(false))
|
||||
{
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: skip_entire_table_label,
|
||||
},
|
||||
skip_entire_table_label,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
for term in where_c
|
||||
.terms
|
||||
.iter()
|
||||
.filter(|t| t.evaluate_at_cursor == current_loop.open_cursor)
|
||||
{
|
||||
let jump_target_when_false = current_loop.next_row_label;
|
||||
let jump_target_when_true = program.allocate_label();
|
||||
translate_condition_expr(
|
||||
@@ -749,3 +786,143 @@ fn introspect_expression_for_cursors(
|
||||
|
||||
Ok(cursors)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConstantCondition {
|
||||
AlwaysTrue,
|
||||
AlwaysFalse,
|
||||
}
|
||||
|
||||
pub trait Evaluatable {
|
||||
fn check_constant(&self) -> Result<Option<ConstantCondition>>;
|
||||
fn is_always_true(&self) -> Result<bool> {
|
||||
Ok(self
|
||||
.check_constant()?
|
||||
.map_or(false, |c| c == ConstantCondition::AlwaysTrue))
|
||||
}
|
||||
fn is_always_false(&self) -> Result<bool> {
|
||||
Ok(self
|
||||
.check_constant()?
|
||||
.map_or(false, |c| c == ConstantCondition::AlwaysFalse))
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluatable for ast::Expr {
|
||||
fn check_constant(&self) -> Result<Option<ConstantCondition>> {
|
||||
match self {
|
||||
ast::Expr::Literal(lit) => match lit {
|
||||
ast::Literal::Null => Ok(Some(ConstantCondition::AlwaysFalse)),
|
||||
ast::Literal::Numeric(b) => {
|
||||
if let Ok(int_value) = b.parse::<i64>() {
|
||||
return Ok(Some(if int_value == 0 {
|
||||
ConstantCondition::AlwaysFalse
|
||||
} else {
|
||||
ConstantCondition::AlwaysTrue
|
||||
}));
|
||||
}
|
||||
if let Ok(float_value) = b.parse::<f64>() {
|
||||
return Ok(Some(if float_value == 0.0 {
|
||||
ConstantCondition::AlwaysFalse
|
||||
} else {
|
||||
ConstantCondition::AlwaysTrue
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
ast::Literal::String(s) => {
|
||||
let without_quotes = s.trim_matches('\'');
|
||||
if let Ok(int_value) = without_quotes.parse::<i64>() {
|
||||
return Ok(Some(if int_value == 0 {
|
||||
ConstantCondition::AlwaysFalse
|
||||
} else {
|
||||
ConstantCondition::AlwaysTrue
|
||||
}));
|
||||
}
|
||||
|
||||
if let Ok(float_value) = without_quotes.parse::<f64>() {
|
||||
return Ok(Some(if float_value == 0.0 {
|
||||
ConstantCondition::AlwaysFalse
|
||||
} else {
|
||||
ConstantCondition::AlwaysTrue
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(Some(ConstantCondition::AlwaysFalse))
|
||||
}
|
||||
_ => Ok(None),
|
||||
},
|
||||
ast::Expr::Unary(op, expr) => {
|
||||
if *op == ast::UnaryOperator::Not {
|
||||
let trivial = expr.check_constant()?;
|
||||
return Ok(trivial.map(|t| match t {
|
||||
ConstantCondition::AlwaysTrue => ConstantCondition::AlwaysFalse,
|
||||
ConstantCondition::AlwaysFalse => ConstantCondition::AlwaysTrue,
|
||||
}));
|
||||
}
|
||||
|
||||
if *op == ast::UnaryOperator::Negative {
|
||||
let trivial = expr.check_constant()?;
|
||||
return Ok(trivial);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
ast::Expr::InList { lhs: _, not, rhs } => {
|
||||
if rhs.is_none() {
|
||||
return Ok(Some(if *not {
|
||||
ConstantCondition::AlwaysTrue
|
||||
} else {
|
||||
ConstantCondition::AlwaysFalse
|
||||
}));
|
||||
}
|
||||
let rhs = rhs.as_ref().unwrap();
|
||||
if rhs.is_empty() {
|
||||
return Ok(Some(if *not {
|
||||
ConstantCondition::AlwaysTrue
|
||||
} else {
|
||||
ConstantCondition::AlwaysFalse
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
ast::Expr::Binary(lhs, op, rhs) => {
|
||||
let lhs_trivial = lhs.check_constant()?;
|
||||
let rhs_trivial = rhs.check_constant()?;
|
||||
match op {
|
||||
ast::Operator::And => {
|
||||
if lhs_trivial == Some(ConstantCondition::AlwaysFalse)
|
||||
|| rhs_trivial == Some(ConstantCondition::AlwaysFalse)
|
||||
{
|
||||
return Ok(Some(ConstantCondition::AlwaysFalse));
|
||||
}
|
||||
if lhs_trivial == Some(ConstantCondition::AlwaysTrue)
|
||||
&& rhs_trivial == Some(ConstantCondition::AlwaysTrue)
|
||||
{
|
||||
return Ok(Some(ConstantCondition::AlwaysTrue));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
ast::Operator::Or => {
|
||||
if lhs_trivial == Some(ConstantCondition::AlwaysTrue)
|
||||
|| rhs_trivial == Some(ConstantCondition::AlwaysTrue)
|
||||
{
|
||||
return Ok(Some(ConstantCondition::AlwaysTrue));
|
||||
}
|
||||
if lhs_trivial == Some(ConstantCondition::AlwaysFalse)
|
||||
&& rhs_trivial == Some(ConstantCondition::AlwaysFalse)
|
||||
{
|
||||
return Ok(Some(ConstantCondition::AlwaysFalse));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,18 @@ do_execsql_test inner-join-self-with-where {
|
||||
# select u.first_name from users u join products as p on u.first_name != p.name where u.last_name = 'Williams' limit 1;
|
||||
#} {Laura} <-- sqlite3 returns 'Aaron'
|
||||
|
||||
do_execsql_test inner-join-constant-condition-true {
|
||||
select u.first_name, p.name from users u join products as p where 1 limit 5;
|
||||
} {Jamie|hat
|
||||
Jamie|cap
|
||||
Jamie|shirt
|
||||
Jamie|sweater
|
||||
Jamie|sweatshirt}
|
||||
|
||||
do_execsql_test inner-join-constant-condition-false {
|
||||
select u.first_name from users u join products as p where 0 limit 5;
|
||||
} {}
|
||||
|
||||
do_execsql_test left-join-pk {
|
||||
select users.first_name as user_name, products.name as product_name from users left join products on users.id = products.id limit 12;
|
||||
} {Jamie|hat
|
||||
@@ -133,6 +145,22 @@ do_execsql_test left-join-order-by-qualified-nullable-sorting-col {
|
||||
select users.first_name, products.name from users left join products on users.id = products.id order by products.name limit 1;
|
||||
} {Alan|}
|
||||
|
||||
do_execsql_test left-join-constant-condition-true {
|
||||
select u.first_name, p.name from users u left join products as p on 1 limit 5;
|
||||
} {Jamie|hat
|
||||
Jamie|cap
|
||||
Jamie|shirt
|
||||
Jamie|sweater
|
||||
Jamie|sweatshirt}
|
||||
|
||||
do_execsql_test left-join-constant-condition-false {
|
||||
select u.first_name, p.name from users u left join products as p on 0 limit 5;
|
||||
} {Jamie|
|
||||
Cindy|
|
||||
Tommy|
|
||||
Jennifer|
|
||||
Edward|}
|
||||
|
||||
do_execsql_test four-way-inner-join {
|
||||
select u1.first_name, u2.first_name, u3.first_name, u4.first_name from users u1 join users u2 on u1.id = u2.id join users u3 on u2.id = u3.id + 1 join users u4 on u3.id = u4.id + 1 limit 1;
|
||||
} {Tommy|Tommy|Cindy|Jamie}
|
||||
@@ -155,3 +183,15 @@ do_execsql_test innerjoin-leftjoin-with-or-terms {
|
||||
select u.first_name, u2.first_name, p.name from users u join users u2 on u.id = u2.id + 1 left join products p on p.name = u.first_name or p.name like 'sweat%' where u.first_name = 'Franklin';
|
||||
} {Franklin|Cynthia|sweater
|
||||
Franklin|Cynthia|sweatshirt}
|
||||
|
||||
do_execsql_test left-join-constant-condition-false-inner-join-constant-condition-true {
|
||||
select u.first_name, p.name, u2.first_name from users u left join products as p on 0 join users u2 on 1 limit 5;
|
||||
} {Jamie||Jamie
|
||||
Jamie||Cindy
|
||||
Jamie||Tommy
|
||||
Jamie||Jennifer
|
||||
Jamie||Edward}
|
||||
|
||||
do_execsql_test left-join-constant-condition-true-inner-join-constant-condition-false {
|
||||
select u.first_name, p.name, u2.first_name from users u left join products as p on 1 join users u2 on 0 limit 5;
|
||||
} {}
|
||||
@@ -40,14 +40,58 @@ do_execsql_test where-clause-unary-false {
|
||||
select count(1) from users where 0;
|
||||
} {0}
|
||||
|
||||
do_execsql_test where-clause-no-table-unary-true {
|
||||
do_execsql_test where-clause-no-table-constant-condition-true {
|
||||
select 1 where 1;
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-unary-false {
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-2 {
|
||||
select 1 where '1';
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-3 {
|
||||
select 1 where 6.66;
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-4 {
|
||||
select 1 where '6.66';
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-5 {
|
||||
select 1 where -1;
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-6 {
|
||||
select 1 where '-1';
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false {
|
||||
select 1 where 0;
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-2 {
|
||||
select 1 where '0';
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-3 {
|
||||
select 1 where 0.0;
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-4 {
|
||||
select 1 where '0.0';
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-5 {
|
||||
select 1 where -0.0;
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-6 {
|
||||
select 1 where '-0.0';
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-7 {
|
||||
select 1 where 'hamburger';
|
||||
} {}
|
||||
|
||||
do_execsql_test select-where-and {
|
||||
select first_name, age from users where first_name = 'Jamie' and age > 80
|
||||
} {Jamie|94
|
||||
|
||||
Reference in New Issue
Block a user