From 5e9e2dffe90d5b54ec8152464e5419ccd234496e Mon Sep 17 00:00:00 2001 From: jussisaurio Date: Fri, 13 Dec 2024 22:58:29 +0200 Subject: [PATCH] support TRUE and FALSE in predicates --- core/translate/emitter.rs | 36 ++++++++++++---------- core/translate/optimizer.rs | 59 +++++++++++++++++++++++++++++++++---- core/translate/plan.rs | 2 ++ core/translate/planner.rs | 13 ++++++-- testing/agg-functions.test | 8 +++++ testing/join.test | 12 ++++++++ testing/where.test | 8 +++++ 7 files changed, 115 insertions(+), 23 deletions(-) diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index bb11bc5f0..eccbbb1d6 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -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( diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 604f0f9e0..3e3946996 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -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 { + 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>, ) -> Result { + 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> { 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) => { diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 7db65b713..7a13d6b88 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -46,6 +46,8 @@ pub struct Plan { pub referenced_tables: Vec, /// all the indexes available pub available_indexes: Vec>, + /// query contains a constant condition that is always false + pub contains_constant_false_condition: bool, } impl Display for Plan { diff --git a/core/translate/planner.rs b/core/translate/planner.rs index b67630c3f..af6add833 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -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