diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index e549c61..58a871b 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use crate::db::table::{Table, Value, DataType}; use crate::interpreter::ast::{SelectStatementColumns, WhereStackElement, OrderByClause, LimitClause}; -use crate::db::table::helpers::where_stack::matches_where_stack; +use crate::db::table::helpers::where_clause::row_matches_where_stack; use crate::db::table::helpers::{order_by_clause::get_ordered_row_indicies, limit_clause::get_limited_rows}; pub struct DistinctOn<'a> { @@ -42,7 +42,7 @@ pub fn get_row_indicies_matching_where_clause(table: &Table, where_clause: &Opti if let Some(where_clause) = where_clause { let mut row_indicies: Vec = vec![]; for (i, row) in table.rows.iter().enumerate() { - if matches_where_stack(table, &row, &where_clause)? { + if row_matches_where_stack(table, &row, &where_clause)? { row_indicies.push(i); } } diff --git a/src/db/table/helpers/mod.rs b/src/db/table/helpers/mod.rs index a1901b7..e87f052 100644 --- a/src/db/table/helpers/mod.rs +++ b/src/db/table/helpers/mod.rs @@ -1,5 +1,4 @@ pub mod limit_clause; pub mod order_by_clause; pub mod common; -pub mod where_stack; -pub mod where_condition; \ No newline at end of file +pub mod where_clause; \ No newline at end of file diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/helpers/where_clause/mod.rs new file mode 100644 index 0000000..36288a9 --- /dev/null +++ b/src/db/table/helpers/where_clause/mod.rs @@ -0,0 +1,89 @@ +mod where_condition; +mod where_stack; +use crate::interpreter::ast::{WhereStackElement, WhereCondition}; +use crate::db::table::{Table, Value}; + + +// We create an interface here to allow us to create a spy for testing short circuiting. +trait MatchesWhereClause { + fn matches_where_clause(&mut self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result; +} + +struct WhereConditionEvaluator; + +impl MatchesWhereClause for WhereConditionEvaluator { + fn matches_where_clause(&mut self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + where_condition::matches_where_clause(table, row, where_clause) + } +} + +// This is the public function that is used to check if a row matches a where stack. +pub fn row_matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec) -> Result { + where_stack::matches_where_stack(table, row, where_stack, &mut WhereConditionEvaluator{}) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::{Table, Value, ColumnDefinition, DataType}; + use crate::interpreter::ast::{WhereStackElement, Operand, Operator, LogicalOperator}; + + struct SpyWhereConditionEvaluator { + conditions_evaluated: Vec, + } + + impl MatchesWhereClause for SpyWhereConditionEvaluator { + fn matches_where_clause(&mut self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + self.conditions_evaluated.push(where_clause.clone()); + where_condition::matches_where_clause(table, row, where_clause) + } + } + + + #[test] + fn matches_where_stack_works_short_circuits() { + // WHERE (id = 1 OR id = 2); + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ColumnDefinition {name:"name".to_string(),data_type:DataType::Text, constraints: vec![] }, + ]); + let mut spy_where_condition_evaluator = SpyWhereConditionEvaluator{conditions_evaluated: vec![]}; + let row = vec![ + Value::Integer(1), + Value::Text("John".to_string()), + ]; + let condition_1 = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; + let condition_2 = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(2))}; + let where_stack = vec![ + WhereStackElement::Condition(condition_1.clone()), + WhereStackElement::Condition(condition_2.clone()), + WhereStackElement::LogicalOperator(LogicalOperator::Or), + ]; + let result = where_stack::matches_where_stack(&table, &row, &where_stack, &mut spy_where_condition_evaluator); + assert!(result.is_ok() && result.unwrap()); + assert_eq!(spy_where_condition_evaluator.conditions_evaluated, vec![condition_1]); + } + + #[test] + fn matches_where_stack_does_not_short_circuit_if_condition_does_not_match() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ColumnDefinition {name:"name".to_string(),data_type:DataType::Text, constraints: vec![] }, + ]); + let mut spy_where_condition_evaluator = SpyWhereConditionEvaluator{conditions_evaluated: vec![]}; + let row = vec![ + Value::Integer(1), + Value::Text("John".to_string()), + ]; + let condition_1 = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(2))}; + let condition_2 = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; + let where_stack = vec![ + WhereStackElement::Condition(condition_1.clone()), + WhereStackElement::Condition(condition_2.clone()), + WhereStackElement::LogicalOperator(LogicalOperator::Or), + ]; + let result = where_stack::matches_where_stack(&table, &row, &where_stack, &mut spy_where_condition_evaluator); + assert!(result.is_ok() && result.unwrap()); + assert_eq!(spy_where_condition_evaluator.conditions_evaluated, vec![condition_1, condition_2]); + } +} \ No newline at end of file diff --git a/src/db/table/helpers/where_condition.rs b/src/db/table/helpers/where_clause/where_condition.rs similarity index 100% rename from src/db/table/helpers/where_condition.rs rename to src/db/table/helpers/where_clause/where_condition.rs diff --git a/src/db/table/helpers/where_stack.rs b/src/db/table/helpers/where_clause/where_stack.rs similarity index 73% rename from src/db/table/helpers/where_stack.rs rename to src/db/table/helpers/where_clause/where_stack.rs index 25a3114..92a0b01 100644 --- a/src/db/table/helpers/where_stack.rs +++ b/src/db/table/helpers/where_clause/where_stack.rs @@ -1,15 +1,31 @@ -use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; use crate::db::table::{Table, Value}; -use crate::db::table::helpers::where_condition::matches_where_clause; +use crate::interpreter::ast::{WhereStackElement, LogicalOperator, WhereCondition}; +use crate::db::table::helpers::where_clause::MatchesWhereClause; + // This file holds the logic for whether a row matches a where stack which is a vec of WhereConditions // and logical operators stored in Reverse Polish Notation. -pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec) -> Result { - let mut result_stack = vec![]; + +enum Condition<'a> { + Boolean(bool), + WhereCondition(&'a WhereCondition), +} + +impl<'a> Condition<'a> { + fn evaluate(&self, table: &Table, row: &Vec, where_clause_evaluator: &mut dyn MatchesWhereClause) -> Result { + match self { + Condition::Boolean(boolean) => Ok(*boolean), + Condition::WhereCondition(where_condition) => where_clause_evaluator.matches_where_clause(table, row, where_condition), + } + } +} + +pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec, where_clause_evaluator: &mut dyn MatchesWhereClause) -> Result { + let mut result_stack: Vec = vec![]; for where_stack_element in where_stack { match where_stack_element { WhereStackElement::Condition(where_condition) => { - result_stack.push(matches_where_clause(table, row, where_condition)?); + result_stack.push(Condition::WhereCondition(where_condition)); }, WhereStackElement::LogicalOperator(logical_operator) => { let pop1 = match result_stack.pop() { @@ -17,31 +33,31 @@ pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec return Err(format!("Error evaluating where clause with table: {:?}", table)), }; match logical_operator { - LogicalOperator::Not => { - result_stack.push(!pop1); + LogicalOperator::Not => { + result_stack.push(Condition::Boolean(!pop1.evaluate(table, row, where_clause_evaluator)?)); } LogicalOperator::And => { let pop2 = match result_stack.pop() { Some(pop2) => pop2, None => return Err(format!("Error evaluating where clause with table: {:?}", table)), }; - result_stack.push(pop1 && pop2); + result_stack.push(Condition::Boolean(pop2.evaluate(table, row, where_clause_evaluator)? && pop1.evaluate(table, row, where_clause_evaluator)?)); } LogicalOperator::Or => { let pop2 = match result_stack.pop() { Some(pop2) => pop2, None => return Err(format!("Error evaluating where clause with table: {:?}", table)), }; - result_stack.push(pop1 || pop2); + result_stack.push(Condition::Boolean(pop2.evaluate(table, row, where_clause_evaluator)? || pop1.evaluate(table, row, where_clause_evaluator)?)); } } }, - _ => unreachable!(), + _ => unreachable!(), // There should be no Parentheses in the final where stack. } } if let Some(result) = result_stack.pop() { - return Ok(result); + return Ok(result.evaluate(table, row, where_clause_evaluator)?); } else { return Err(format!("Error evaluating where clause with table: {:?}", table)); @@ -54,12 +70,12 @@ mod tests { use crate::db::table::{Table, Value, ColumnDefinition, DataType}; use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; use crate::interpreter::ast::{Operator, Operand, WhereCondition}; + use crate::db::table::helpers::where_clause::WhereConditionEvaluator; fn simple_condition(l_side: &str, operator: Operator, r_side: Value) -> WhereStackElement { WhereStackElement::Condition(WhereCondition {l_side: Operand::Identifier(l_side.to_string()), operator, r_side: Operand::Value(r_side)}) } - #[test] fn matches_where_stack_returns_true_if_row_matches_where_stack_with_single_condition() { let table = Table::new("users".to_string(), vec![ @@ -67,7 +83,7 @@ mod tests { ]); let row = vec![Value::Integer(1)]; let where_stack = vec![WhereStackElement::Condition(WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))})]; - let result = matches_where_stack(&table, &row, &where_stack); + let result = matches_where_stack(&table, &row, &where_stack, &mut WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); } @@ -92,7 +108,7 @@ mod tests { WhereStackElement::LogicalOperator(LogicalOperator::Not), WhereStackElement::LogicalOperator(LogicalOperator::Or), ]; - let result = matches_where_stack(&table, &row, &where_stack); + let result = matches_where_stack(&table, &row, &where_stack, &mut WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); let row = vec![ @@ -100,7 +116,7 @@ mod tests { Value::Text("Fletcher".to_string()), Value::Integer(15), ]; - let result = matches_where_stack(&table, &row, &where_stack); + let result = matches_where_stack(&table, &row, &where_stack, &mut WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); let row = vec![ @@ -108,7 +124,7 @@ mod tests { Value::Text("John".to_string()), Value::Integer(25), ]; - let result = matches_where_stack(&table, &row, &where_stack); + let result = matches_where_stack(&table, &row, &where_stack, &mut WhereConditionEvaluator{}); assert!(result.is_ok() && !result.unwrap()); } } \ No newline at end of file diff --git a/src/interpreter/ast/mod.rs b/src/interpreter/ast/mod.rs index 27693fc..7567e15 100644 --- a/src/interpreter/ast/mod.rs +++ b/src/interpreter/ast/mod.rs @@ -164,6 +164,7 @@ impl SelectStatementColumns { } #[derive(Debug, PartialEq)] +#[cfg_attr(test, derive(Clone))] pub enum Operator { Equals, NotEquals, @@ -178,6 +179,7 @@ pub enum Operator { } #[derive(Debug, PartialEq)] +#[cfg_attr(test, derive(Clone))] pub struct WhereCondition { pub l_side: Operand, pub operator: Operator, @@ -186,6 +188,7 @@ pub struct WhereCondition { #[derive(Debug, PartialEq)] +#[cfg_attr(test, derive(Clone))] pub enum Operand { Value(Value), ValueList(Vec),