mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-01 15:34:19 +01:00
Merge 'support TRUE and FALSE in predicates' from Jussi Saurio
closes #466 Closes #469
This commit is contained in:
@@ -19,7 +19,6 @@ use super::expr::{
|
||||
translate_aggregation, translate_aggregation_groupby, translate_condition_expr, translate_expr,
|
||||
ConditionMetadata,
|
||||
};
|
||||
use super::optimizer::Optimizable;
|
||||
use super::plan::{Aggregate, BTreeTableReference, Direction, GroupBy, Plan};
|
||||
use super::plan::{ResultSetColumn, SourceOperator};
|
||||
|
||||
@@ -177,6 +176,22 @@ pub fn emit_program(
|
||||
}
|
||||
}
|
||||
|
||||
// No rows will be read from source table loops if there is a constant false condition eg. WHERE 0
|
||||
// however an aggregation might still happen,
|
||||
// e.g. SELECT COUNT(*) WHERE 0 returns a row with 0, not an empty result set
|
||||
let skip_loops_label = if plan.contains_constant_false_condition {
|
||||
let skip_loops_label = program.allocate_label();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: skip_loops_label,
|
||||
},
|
||||
skip_loops_label,
|
||||
);
|
||||
Some(skip_loops_label)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Initialize cursors and other resources needed for query execution
|
||||
if let Some(ref mut order_by) = plan.order_by {
|
||||
init_order_by(&mut program, order_by, &mut metadata)?;
|
||||
@@ -207,7 +222,11 @@ pub fn emit_program(
|
||||
&plan.referenced_tables,
|
||||
)?;
|
||||
|
||||
let mut order_by_necessary = plan.order_by.is_some();
|
||||
if let Some(skip_loops_label) = skip_loops_label {
|
||||
program.resolve_label(skip_loops_label, program.offset());
|
||||
}
|
||||
|
||||
let mut order_by_necessary = plan.order_by.is_some() && !plan.contains_constant_false_condition;
|
||||
|
||||
// Handle GROUP BY and aggregation processing
|
||||
if let Some(ref mut group_by) = plan.group_by {
|
||||
@@ -797,19 +816,6 @@ fn inner_loop_emit(
|
||||
plan: &mut Plan,
|
||||
metadata: &mut Metadata,
|
||||
) -> Result<()> {
|
||||
if let Some(wc) = &plan.where_clause {
|
||||
for predicate in wc.iter() {
|
||||
if predicate.is_always_false()? {
|
||||
return Ok(());
|
||||
} else if predicate.is_always_true()? {
|
||||
// do nothing
|
||||
} else {
|
||||
unreachable!(
|
||||
"all WHERE clause terms that are not trivially true or false should have been pushed down to the source"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we have a group by, we emit a record into the group by sorter.
|
||||
if let Some(group_by) = &plan.group_by {
|
||||
return inner_loop_source_emit(
|
||||
|
||||
@@ -15,12 +15,17 @@ use super::plan::{
|
||||
* but having them separate makes them easier to understand
|
||||
*/
|
||||
pub fn optimize_plan(mut select_plan: Plan) -> Result<Plan> {
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constants(&mut select_plan.source, &mut select_plan.where_clause)?
|
||||
{
|
||||
select_plan.contains_constant_false_condition = true;
|
||||
return Ok(select_plan);
|
||||
}
|
||||
push_predicates(
|
||||
&mut select_plan.source,
|
||||
&mut select_plan.where_clause,
|
||||
&select_plan.referenced_tables,
|
||||
)?;
|
||||
eliminate_constants(&mut select_plan.source)?;
|
||||
use_indexes(
|
||||
&mut select_plan.source,
|
||||
&select_plan.referenced_tables,
|
||||
@@ -177,7 +182,24 @@ enum ConstantConditionEliminationResult {
|
||||
// returns a ConstantEliminationResult indicating whether any predicates are always false
|
||||
fn eliminate_constants(
|
||||
operator: &mut SourceOperator,
|
||||
where_clause: &mut Option<Vec<ast::Expr>>,
|
||||
) -> Result<ConstantConditionEliminationResult> {
|
||||
if let Some(predicates) = where_clause {
|
||||
let mut i = 0;
|
||||
while i < predicates.len() {
|
||||
let predicate = &predicates[i];
|
||||
if predicate.is_always_true()? {
|
||||
// true predicates can be removed since they don't affect the result
|
||||
predicates.remove(i);
|
||||
} else if predicate.is_always_false()? {
|
||||
// any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false
|
||||
predicates.truncate(0);
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
match operator {
|
||||
SourceOperator::Join {
|
||||
left,
|
||||
@@ -186,11 +208,12 @@ fn eliminate_constants(
|
||||
outer,
|
||||
..
|
||||
} => {
|
||||
if eliminate_constants(left)? == ConstantConditionEliminationResult::ImpossibleCondition
|
||||
if eliminate_constants(left, where_clause)?
|
||||
== ConstantConditionEliminationResult::ImpossibleCondition
|
||||
{
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
}
|
||||
if eliminate_constants(right)?
|
||||
if eliminate_constants(right, where_clause)?
|
||||
== ConstantConditionEliminationResult::ImpossibleCondition
|
||||
&& !*outer
|
||||
{
|
||||
@@ -205,11 +228,19 @@ fn eliminate_constants(
|
||||
|
||||
let mut i = 0;
|
||||
while i < predicates.len() {
|
||||
let predicate = &predicates[i];
|
||||
let predicate = &mut predicates[i];
|
||||
if predicate.is_always_true()? {
|
||||
predicates.remove(i);
|
||||
} else if predicate.is_always_false()? && !*outer {
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
} else if predicate.is_always_false()? {
|
||||
if !*outer {
|
||||
predicates.truncate(0);
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
}
|
||||
// in an outer join, we can't skip rows, so just replace all constant false predicates with 0
|
||||
// so we don't later have to evaluate anything more complex or special-case the identifiers true and false
|
||||
// which are just aliases for 1 and 0
|
||||
*predicate = ast::Expr::Literal(ast::Literal::Numeric("0".to_string()));
|
||||
i += 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
@@ -223,8 +254,11 @@ fn eliminate_constants(
|
||||
while i < ps.len() {
|
||||
let predicate = &ps[i];
|
||||
if predicate.is_always_true()? {
|
||||
// true predicates can be removed since they don't affect the result
|
||||
ps.remove(i);
|
||||
} else if predicate.is_always_false()? {
|
||||
// any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false
|
||||
ps.truncate(0);
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
} else {
|
||||
i += 1;
|
||||
@@ -243,8 +277,11 @@ fn eliminate_constants(
|
||||
while i < predicates.len() {
|
||||
let predicate = &predicates[i];
|
||||
if predicate.is_always_true()? {
|
||||
// true predicates can be removed since they don't affect the result
|
||||
predicates.remove(i);
|
||||
} else if predicate.is_always_false()? {
|
||||
// any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false
|
||||
predicates.truncate(0);
|
||||
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
|
||||
} else {
|
||||
i += 1;
|
||||
@@ -550,6 +587,16 @@ impl Optimizable for ast::Expr {
|
||||
}
|
||||
fn check_constant(&self) -> Result<Option<ConstantPredicate>> {
|
||||
match self {
|
||||
ast::Expr::Id(id) => {
|
||||
// true and false are special constants that are effectively aliases for 1 and 0
|
||||
if id.0.eq_ignore_ascii_case("true") {
|
||||
return Ok(Some(ConstantPredicate::AlwaysTrue));
|
||||
}
|
||||
if id.0.eq_ignore_ascii_case("false") {
|
||||
return Ok(Some(ConstantPredicate::AlwaysFalse));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
ast::Expr::Literal(lit) => match lit {
|
||||
ast::Literal::Null => Ok(Some(ConstantPredicate::AlwaysFalse)),
|
||||
ast::Literal::Numeric(b) => {
|
||||
|
||||
@@ -46,6 +46,8 @@ pub struct Plan {
|
||||
pub referenced_tables: Vec<BTreeTableReference>,
|
||||
/// all the indexes available
|
||||
pub available_indexes: Vec<Rc<Index>>,
|
||||
/// query contains a constant condition that is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
}
|
||||
|
||||
impl Display for Plan {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::plan::{
|
||||
Aggregate, BTreeTableReference, Direction, GroupBy, Plan, ResultSetColumn, SourceOperator,
|
||||
use super::{
|
||||
optimizer::Optimizable,
|
||||
plan::{
|
||||
Aggregate, BTreeTableReference, Direction, GroupBy, Plan, ResultSetColumn, SourceOperator,
|
||||
},
|
||||
};
|
||||
use crate::{function::Func, schema::Schema, util::normalize_ident, Result};
|
||||
use sqlite3_parser::ast::{self, FromClause, JoinType, ResultColumn};
|
||||
@@ -88,6 +91,11 @@ fn bind_column_references(
|
||||
) -> Result<()> {
|
||||
match expr {
|
||||
ast::Expr::Id(id) => {
|
||||
// true and false are special constants that are effectively aliases for 1 and 0
|
||||
// and not identifiers of columns
|
||||
if id.0.eq_ignore_ascii_case("true") || id.0.eq_ignore_ascii_case("false") {
|
||||
return Ok(());
|
||||
}
|
||||
let mut match_result = None;
|
||||
for (tbl_idx, table) in referenced_tables.iter().enumerate() {
|
||||
let col_idx = table
|
||||
@@ -270,6 +278,7 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result<P
|
||||
limit: None,
|
||||
referenced_tables,
|
||||
available_indexes: schema.indexes.clone().into_values().flatten().collect(),
|
||||
contains_constant_false_condition: false,
|
||||
};
|
||||
|
||||
// Parse the WHERE clause
|
||||
|
||||
@@ -39,6 +39,14 @@ do_execsql_test select-count {
|
||||
SELECT count(*) FROM users;
|
||||
} {10000}
|
||||
|
||||
do_execsql_test select-count-constant-true {
|
||||
SELECT count(*) FROM users WHERE true;
|
||||
} {10000}
|
||||
|
||||
do_execsql_test select-count-constant-false {
|
||||
SELECT count(*) FROM users WHERE false;
|
||||
} {0}
|
||||
|
||||
do_execsql_test select-max {
|
||||
SELECT max(age) FROM users;
|
||||
} {100}
|
||||
|
||||
@@ -106,6 +106,18 @@ Jamie|coat
|
||||
Jamie|accessories
|
||||
Cindy|}
|
||||
|
||||
do_execsql_test left-join-constant-condition-true {
|
||||
select u.first_name, p.name from users u left join products as p on true limit 1;
|
||||
} {Jamie|hat}
|
||||
|
||||
do_execsql_test left-join-constant-condition-false {
|
||||
select u.first_name, p.name from users u left join products as p on false limit 1;
|
||||
} {Jamie|}
|
||||
|
||||
do_execsql_test left-join-constant-condition-where-false {
|
||||
select u.first_name, p.name from users u left join products as p where false limit 1;
|
||||
} {}
|
||||
|
||||
do_execsql_test left-join-non-pk {
|
||||
select users.first_name as user_name, products.name as product_name from users left join products on users.first_name = products.name limit 3;
|
||||
} {Jamie|
|
||||
|
||||
@@ -44,6 +44,10 @@ do_execsql_test where-clause-no-table-constant-condition-true {
|
||||
select 1 where 1;
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-identifier-true {
|
||||
select 1 where true;
|
||||
} {1}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-true-2 {
|
||||
select 1 where '1';
|
||||
} {1}
|
||||
@@ -68,6 +72,10 @@ do_execsql_test where-clause-no-table-constant-condition-false {
|
||||
select 1 where 0;
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-identifier-false {
|
||||
select 1 where false;
|
||||
} {}
|
||||
|
||||
do_execsql_test where-clause-no-table-constant-condition-false-2 {
|
||||
select 1 where '0';
|
||||
} {}
|
||||
|
||||
Reference in New Issue
Block a user