Skip to content

Commit

Permalink
support TRUE and FALSE in predicates
Browse files Browse the repository at this point in the history
  • Loading branch information
jussisaurio committed Dec 13, 2024
1 parent 138b3a0 commit 5e9e2df
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 23 deletions.
36 changes: 21 additions & 15 deletions core/translate/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
59 changes: 53 additions & 6 deletions core/translate/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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
{
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down
2 changes: 2 additions & 0 deletions core/translate/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 11 additions & 2 deletions core/translate/planner.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions testing/agg-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
12 changes: 12 additions & 0 deletions testing/join.test
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
8 changes: 8 additions & 0 deletions testing/where.test
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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';
} {}
Expand Down

0 comments on commit 5e9e2df

Please sign in to comment.