From b03810f34653c584a43d16b0008f764c99c810d9 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 9 Sep 2025 19:05:26 -0400 Subject: [PATCH 1/5] Move where clause in DB to a new folder --- src/db/table/helpers/common.rs | 2 +- src/db/table/helpers/mod.rs | 3 +-- src/db/table/helpers/{where_stack.rs => where_clause/mod.rs} | 5 +++-- src/db/table/helpers/{ => where_clause}/where_condition.rs | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename src/db/table/helpers/{where_stack.rs => where_clause/mod.rs} (96%) rename src/db/table/helpers/{ => where_clause}/where_condition.rs (100%) diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index e549c61..a01e60d 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::matches_where_stack; use crate::db::table::helpers::{order_by_clause::get_ordered_row_indicies, limit_clause::get_limited_rows}; pub struct DistinctOn<'a> { 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_stack.rs b/src/db/table/helpers/where_clause/mod.rs similarity index 96% rename from src/db/table/helpers/where_stack.rs rename to src/db/table/helpers/where_clause/mod.rs index 25a3114..ef76a29 100644 --- a/src/db/table/helpers/where_stack.rs +++ b/src/db/table/helpers/where_clause/mod.rs @@ -1,6 +1,7 @@ +mod where_condition; + use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; use crate::db::table::{Table, Value}; -use crate::db::table::helpers::where_condition::matches_where_clause; // 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. @@ -9,7 +10,7 @@ pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec { - result_stack.push(matches_where_clause(table, row, where_condition)?); + result_stack.push(where_condition::matches_where_clause(table, row, where_condition)?); }, WhereStackElement::LogicalOperator(logical_operator) => { let pop1 = match result_stack.pop() { 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 From c526c566739060a0de39144922fedd16fd0275fe Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 9 Sep 2025 19:13:05 -0400 Subject: [PATCH 2/5] Add interface to allow me to create a spy for testing short circuits --- src/db/table/helpers/common.rs | 4 ++-- src/db/table/helpers/where_clause/mod.rs | 27 +++++++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index a01e60d..3df9f92 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_clause::matches_where_stack; +use crate::db::table::helpers::where_clause::{matches_where_stack, WhereConditionEvaluator}; 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 matches_where_stack(table, &row, &where_clause, &WhereConditionEvaluator{})? { row_indicies.push(i); } } diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/helpers/where_clause/mod.rs index ef76a29..a344984 100644 --- a/src/db/table/helpers/where_clause/mod.rs +++ b/src/db/table/helpers/where_clause/mod.rs @@ -1,16 +1,28 @@ mod where_condition; -use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; +use crate::interpreter::ast::{WhereStackElement, LogicalOperator, WhereCondition}; use crate::db::table::{Table, Value}; +pub trait MatchesWhereClause { + fn matches_where_clause(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result; +} + +pub struct WhereConditionEvaluator; + +impl MatchesWhereClause for WhereConditionEvaluator { + fn matches_where_clause(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + where_condition::matches_where_clause(table, row, where_clause) + } +} + // 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 { +pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec, where_clause_evaluator: &dyn MatchesWhereClause) -> Result { let mut result_stack = vec![]; for where_stack_element in where_stack { match where_stack_element { WhereStackElement::Condition(where_condition) => { - result_stack.push(where_condition::matches_where_clause(table, row, where_condition)?); + result_stack.push(where_clause_evaluator.matches_where_clause(table, row, where_condition)?); }, WhereStackElement::LogicalOperator(logical_operator) => { let pop1 = match result_stack.pop() { @@ -60,7 +72,6 @@ mod tests { 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![ @@ -68,7 +79,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, &WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); } @@ -93,7 +104,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, &WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); let row = vec![ @@ -101,7 +112,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, &WhereConditionEvaluator{}); assert!(result.is_ok() && result.unwrap()); let row = vec![ @@ -109,7 +120,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, &WhereConditionEvaluator{}); assert!(result.is_ok() && !result.unwrap()); } } \ No newline at end of file From 0c888b5b858ff040aa754e301caad6767231d47b Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 9 Sep 2025 19:16:54 -0400 Subject: [PATCH 3/5] Improve interface to narrow it down to the single module --- src/db/table/helpers/common.rs | 4 +- src/db/table/helpers/where_clause/mod.rs | 121 ++---------------- .../table/helpers/where_clause/where_stack.rs | 115 +++++++++++++++++ 3 files changed, 126 insertions(+), 114 deletions(-) create mode 100644 src/db/table/helpers/where_clause/where_stack.rs diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index 3df9f92..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_clause::{matches_where_stack, WhereConditionEvaluator}; +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, &WhereConditionEvaluator{})? { + if row_matches_where_stack(table, &row, &where_clause)? { row_indicies.push(i); } } diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/helpers/where_clause/mod.rs index a344984..77d75ca 100644 --- a/src/db/table/helpers/where_clause/mod.rs +++ b/src/db/table/helpers/where_clause/mod.rs @@ -1,13 +1,15 @@ mod where_condition; - -use crate::interpreter::ast::{WhereStackElement, LogicalOperator, WhereCondition}; +mod where_stack; +use crate::interpreter::ast::{WhereStackElement, WhereCondition}; use crate::db::table::{Table, Value}; -pub trait MatchesWhereClause { + +// We create an interface here to allow us to create a spy for testing short circuiting. +trait MatchesWhereClause { fn matches_where_clause(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result; } -pub struct WhereConditionEvaluator; +struct WhereConditionEvaluator; impl MatchesWhereClause for WhereConditionEvaluator { fn matches_where_clause(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { @@ -15,112 +17,7 @@ impl MatchesWhereClause for WhereConditionEvaluator { } } -// 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, where_clause_evaluator: &dyn MatchesWhereClause) -> Result { - let mut result_stack = vec![]; - for where_stack_element in where_stack { - match where_stack_element { - WhereStackElement::Condition(where_condition) => { - result_stack.push(where_clause_evaluator.matches_where_clause(table, row, where_condition)?); - }, - WhereStackElement::LogicalOperator(logical_operator) => { - let pop1 = match result_stack.pop() { - Some(pop1) => pop1, - None => return Err(format!("Error evaluating where clause with table: {:?}", table)), - }; - match logical_operator { - LogicalOperator::Not => { - result_stack.push(!pop1); - } - 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); - } - 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); - } - } - }, - _ => unreachable!(), - } - } - - if let Some(result) = result_stack.pop() { - return Ok(result); - } - else { - return Err(format!("Error evaluating where clause with table: {:?}", table)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::table::{Table, Value, ColumnDefinition, DataType}; - use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; - use crate::interpreter::ast::{Operator, Operand, WhereCondition}; - - 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![ - ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, - ]); - 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, &WhereConditionEvaluator{}); - assert!(result.is_ok() && result.unwrap()); - } - - #[test] - fn matches_where_stack_works_with_complex_conditions() { - // WHERE (id = 1 OR NOT (name = "John" AND age > 20)); - 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![] }, - ColumnDefinition {name:"age".to_string(),data_type:DataType::Integer, constraints: vec![] }, - ]); - let row = vec![ - Value::Integer(1), - Value::Text("John".to_string()), - Value::Integer(20), - ]; - let where_stack = vec![ - simple_condition("id", Operator::Equals, Value::Integer(1)), - simple_condition("name", Operator::Equals, Value::Text("John".to_string())), - simple_condition("age", Operator::GreaterThan, Value::Integer(20)), - WhereStackElement::LogicalOperator(LogicalOperator::And), - WhereStackElement::LogicalOperator(LogicalOperator::Not), - WhereStackElement::LogicalOperator(LogicalOperator::Or), - ]; - let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); - assert!(result.is_ok() && result.unwrap()); - - let row = vec![ - Value::Integer(2), - Value::Text("Fletcher".to_string()), - Value::Integer(15), - ]; - let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); - assert!(result.is_ok() && result.unwrap()); - - let row = vec![ - Value::Integer(2), - Value::Text("John".to_string()), - Value::Integer(25), - ]; - let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); - assert!(result.is_ok() && !result.unwrap()); - } +// 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, &WhereConditionEvaluator{}) } \ No newline at end of file diff --git a/src/db/table/helpers/where_clause/where_stack.rs b/src/db/table/helpers/where_clause/where_stack.rs new file mode 100644 index 0000000..0356871 --- /dev/null +++ b/src/db/table/helpers/where_clause/where_stack.rs @@ -0,0 +1,115 @@ +use crate::db::table::{Table, Value}; +use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; +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, where_clause_evaluator: &dyn MatchesWhereClause) -> Result { + let mut result_stack = vec![]; + for where_stack_element in where_stack { + match where_stack_element { + WhereStackElement::Condition(where_condition) => { + result_stack.push(where_clause_evaluator.matches_where_clause(table, row, where_condition)?); + }, + WhereStackElement::LogicalOperator(logical_operator) => { + let pop1 = match result_stack.pop() { + Some(pop1) => pop1, + None => return Err(format!("Error evaluating where clause with table: {:?}", table)), + }; + match logical_operator { + LogicalOperator::Not => { + result_stack.push(!pop1); + } + 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); + } + 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); + } + } + }, + _ => unreachable!(), + } + } + + if let Some(result) = result_stack.pop() { + return Ok(result); + } + else { + return Err(format!("Error evaluating where clause with table: {:?}", table)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + 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![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + 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, &WhereConditionEvaluator{}); + assert!(result.is_ok() && result.unwrap()); + } + + #[test] + fn matches_where_stack_works_with_complex_conditions() { + // WHERE (id = 1 OR NOT (name = "John" AND age > 20)); + 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![] }, + ColumnDefinition {name:"age".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![ + Value::Integer(1), + Value::Text("John".to_string()), + Value::Integer(20), + ]; + let where_stack = vec![ + simple_condition("id", Operator::Equals, Value::Integer(1)), + simple_condition("name", Operator::Equals, Value::Text("John".to_string())), + simple_condition("age", Operator::GreaterThan, Value::Integer(20)), + WhereStackElement::LogicalOperator(LogicalOperator::And), + WhereStackElement::LogicalOperator(LogicalOperator::Not), + WhereStackElement::LogicalOperator(LogicalOperator::Or), + ]; + let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); + assert!(result.is_ok() && result.unwrap()); + + let row = vec![ + Value::Integer(2), + Value::Text("Fletcher".to_string()), + Value::Integer(15), + ]; + let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); + assert!(result.is_ok() && result.unwrap()); + + let row = vec![ + Value::Integer(2), + Value::Text("John".to_string()), + Value::Integer(25), + ]; + let result = matches_where_stack(&table, &row, &where_stack, &WhereConditionEvaluator{}); + assert!(result.is_ok() && !result.unwrap()); + } +} \ No newline at end of file From fe61ffd4f25affe985630c839ea4b91f78572168 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 9 Sep 2025 19:45:57 -0400 Subject: [PATCH 4/5] Implement Short-circuiting and add tests to spy to ensure that it is working correctly --- src/db/table/helpers/where_clause/mod.rs | 109 +++++++++++++++++- .../table/helpers/where_clause/where_stack.rs | 41 ++++--- 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/helpers/where_clause/mod.rs index 77d75ca..0b8bc5d 100644 --- a/src/db/table/helpers/where_clause/mod.rs +++ b/src/db/table/helpers/where_clause/mod.rs @@ -6,18 +6,121 @@ 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(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result; + fn matches_where_clause(&mut self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result; } struct WhereConditionEvaluator; impl MatchesWhereClause for WhereConditionEvaluator { - fn matches_where_clause(&self, table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + 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, &WhereConditionEvaluator{}) + 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 WhereCondition { + fn clone(&self) -> WhereCondition { + WhereCondition { + l_side: self.l_side.clone(), + operator: self.operator.clone(), + r_side: self.r_side.clone(), + } + } + } + + impl Operand { + fn clone(&self) -> Operand { + match self { + Operand::Identifier(id) => Operand::Identifier(id.clone()), + Operand::Value(value) => Operand::Value(value.clone()), + Operand::ValueList(value_list) => Operand::ValueList(value_list.clone()), + } + } + } + + impl Operator { + fn clone(&self) -> Operator { + match self { + Operator::Equals => Operator::Equals, + Operator::NotEquals => Operator::NotEquals, + Operator::LessThan => Operator::LessThan, + Operator::LessEquals => Operator::LessEquals, + Operator::GreaterThan => Operator::GreaterThan, + Operator::GreaterEquals => Operator::GreaterEquals, + Operator::In => Operator::In, + Operator::NotIn => Operator::NotIn, + Operator::Is => Operator::Is, + Operator::IsNot => Operator::IsNot, + } + } + } + + 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_clause/where_stack.rs b/src/db/table/helpers/where_clause/where_stack.rs index 0356871..5383e78 100644 --- a/src/db/table/helpers/where_clause/where_stack.rs +++ b/src/db/table/helpers/where_clause/where_stack.rs @@ -1,16 +1,31 @@ use crate::db::table::{Table, Value}; -use crate::interpreter::ast::{WhereStackElement, LogicalOperator}; +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, where_clause_evaluator: &dyn MatchesWhereClause) -> 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(where_clause_evaluator.matches_where_clause(table, row, where_condition)?); + result_stack.push(Condition::WhereCondition(where_condition)); }, WhereStackElement::LogicalOperator(logical_operator) => { let pop1 = match result_stack.pop() { @@ -18,22 +33,22 @@ 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)?)); } } }, @@ -42,7 +57,7 @@ pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec Date: Tue, 9 Sep 2025 19:51:11 -0400 Subject: [PATCH 5/5] Add condition derives rather than manual implementations of clone --- src/db/table/helpers/where_clause/mod.rs | 37 ------------------- .../table/helpers/where_clause/where_stack.rs | 2 +- src/interpreter/ast/mod.rs | 3 ++ 3 files changed, 4 insertions(+), 38 deletions(-) diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/helpers/where_clause/mod.rs index 0b8bc5d..36288a9 100644 --- a/src/db/table/helpers/where_clause/mod.rs +++ b/src/db/table/helpers/where_clause/mod.rs @@ -32,43 +32,6 @@ mod tests { conditions_evaluated: Vec, } - impl WhereCondition { - fn clone(&self) -> WhereCondition { - WhereCondition { - l_side: self.l_side.clone(), - operator: self.operator.clone(), - r_side: self.r_side.clone(), - } - } - } - - impl Operand { - fn clone(&self) -> Operand { - match self { - Operand::Identifier(id) => Operand::Identifier(id.clone()), - Operand::Value(value) => Operand::Value(value.clone()), - Operand::ValueList(value_list) => Operand::ValueList(value_list.clone()), - } - } - } - - impl Operator { - fn clone(&self) -> Operator { - match self { - Operator::Equals => Operator::Equals, - Operator::NotEquals => Operator::NotEquals, - Operator::LessThan => Operator::LessThan, - Operator::LessEquals => Operator::LessEquals, - Operator::GreaterThan => Operator::GreaterThan, - Operator::GreaterEquals => Operator::GreaterEquals, - Operator::In => Operator::In, - Operator::NotIn => Operator::NotIn, - Operator::Is => Operator::Is, - Operator::IsNot => Operator::IsNot, - } - } - } - 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()); diff --git a/src/db/table/helpers/where_clause/where_stack.rs b/src/db/table/helpers/where_clause/where_stack.rs index 5383e78..92a0b01 100644 --- a/src/db/table/helpers/where_clause/where_stack.rs +++ b/src/db/table/helpers/where_clause/where_stack.rs @@ -52,7 +52,7 @@ pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec unreachable!(), + _ => unreachable!(), // There should be no Parentheses in the final where stack. } } 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),