From 55d7ef30cb33cc7328a57b108f0a0772020c95b2 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 15:30:13 -0400 Subject: [PATCH 1/5] Refactor files into two seperate conditions --- src/db/table/select/mod.rs | 1 + src/db/table/select/where_condition.rs | 62 ++++++++++++++++++++++++++ src/db/table/select/where_stack.rs | 62 ++------------------------ 3 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 src/db/table/select/where_condition.rs diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 50015eb..e4dd3cf 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,4 +1,5 @@ pub mod where_stack; +pub mod where_condition; pub mod limit_clause; pub mod order_by_clause; use crate::db::table::{Table, Value}; diff --git a/src/db/table/select/where_condition.rs b/src/db/table/select/where_condition.rs new file mode 100644 index 0000000..e37aa4d --- /dev/null +++ b/src/db/table/select/where_condition.rs @@ -0,0 +1,62 @@ +use crate::db::table::{Table, Value}; +use crate::cli::ast::{Operator, Operand, WhereCondition}; +use crate::db::table::DataType; + + +// This file holds the logic for whether a row matches a where condition + +pub fn matches_where_clause(table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + let l_side = match &where_clause.l_side { + Operand::Identifier(column) => column, + _ => return Err(format!("Found invalid left side of condition: {:?}", where_clause.l_side)), + }; + let r_side = match &where_clause.r_side { + Operand::Value(value) => value, + _ => return Err(format!("Found invalid right side of condition: {:?}", where_clause.r_side)), + }; + let column_value = table.get_column_from_row(row, &l_side); + if column_value.get_type() == DataType::Null && r_side.get_type() == DataType::Null { + return Ok(true); + } + else if column_value.get_type() == DataType::Null || r_side.get_type() == DataType::Null { + return Ok(false); + } + if column_value.get_type() != r_side.get_type() { + return Err(format!("Found different data types for column and value: {:?} and {:?}", column_value.get_type(), r_side.get_type())); + } + + match where_clause.operator { + Operator::Equals => { + return Ok(*column_value == *r_side); + }, + Operator::NotEquals => { + return Ok(*column_value != *r_side); + }, + _ => { + match column_value.get_type() { + DataType::Integer | DataType::Real | DataType::Text => { + match where_clause.operator { + Operator::LessThan => { + return Ok(*column_value < *r_side); + }, + Operator::GreaterThan => { + return Ok(*column_value > *r_side); + }, + Operator::LessEquals => { + return Ok(*column_value <= *r_side); + }, + Operator::GreaterEquals => { + return Ok(*column_value >= *r_side); + }, + _ => { + return Err(format!("Found invalid operator: {:?}", where_clause.operator)); + }, + } + }, + _ => { + return Err(format!("Found invalid operator: {:?} for data type: {:?}", where_clause.operator, column_value.get_type())); + }, + } + } + } +} \ No newline at end of file diff --git a/src/db/table/select/where_stack.rs b/src/db/table/select/where_stack.rs index 060d4a0..35a5303 100644 --- a/src/db/table/select/where_stack.rs +++ b/src/db/table/select/where_stack.rs @@ -1,5 +1,6 @@ -use crate::cli::ast::{Operator, WhereCondition, Operand, WhereStackElement}; -use crate::db::table::{Table, Value, DataType}; +use crate::cli::ast::{Operand, WhereStackElement}; +use crate::db::table::{Table, Value}; +use crate::db::table::select::where_condition::matches_where_clause; // For now this function only supports one column = value where clause @@ -18,66 +19,11 @@ pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec, where_clause: &WhereCondition) -> Result { - let l_side = match &where_clause.l_side { - Operand::Identifier(column) => column, - _ => return Err(format!("Found invalid left side of condition: {:?}", where_clause.l_side)), - }; - let r_side = match &where_clause.r_side { - Operand::Value(value) => value, - _ => return Err(format!("Found invalid right side of condition: {:?}", where_clause.r_side)), - }; - let column_value = table.get_column_from_row(row, &l_side); - if column_value.get_type() == DataType::Null && r_side.get_type() == DataType::Null { - return Ok(true); - } - else if column_value.get_type() == DataType::Null || r_side.get_type() == DataType::Null { - return Ok(false); - } - if column_value.get_type() != r_side.get_type() { - return Err(format!("Found different data types for column and value: {:?} and {:?}", column_value.get_type(), r_side.get_type())); - } - - match where_clause.operator { - Operator::Equals => { - return Ok(*column_value == *r_side); - }, - Operator::NotEquals => { - return Ok(*column_value != *r_side); - }, - _ => { - match column_value.get_type() { - DataType::Integer | DataType::Real | DataType::Text => { - match where_clause.operator { - Operator::LessThan => { - return Ok(*column_value < *r_side); - }, - Operator::GreaterThan => { - return Ok(*column_value > *r_side); - }, - Operator::LessEquals => { - return Ok(*column_value <= *r_side); - }, - Operator::GreaterEquals => { - return Ok(*column_value >= *r_side); - }, - _ => { - return Err(format!("Found invalid operator: {:?}", where_clause.operator)); - }, - } - }, - _ => { - return Err(format!("Found invalid operator: {:?} for data type: {:?}", where_clause.operator, column_value.get_type())); - }, - } - } - } -} - #[cfg(test)] mod tests { use super::*; use crate::db::table::{Table, Value, DataType, ColumnDefinition}; + use crate::cli::ast::{Operator, Operand, WhereCondition}; #[test] fn matches_where_clause_returns_true_if_row_matches_where_clause() { From 61c1bef5c376cc81aafad01d542664a491ec7de9 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 15:50:35 -0400 Subject: [PATCH 2/5] Improve abstraction of where condition evaluation --- src/db/table/select/where_condition.rs | 180 +++++++++++++++++++++---- src/db/table/select/where_stack.rs | 135 +------------------ 2 files changed, 160 insertions(+), 155 deletions(-) diff --git a/src/db/table/select/where_condition.rs b/src/db/table/select/where_condition.rs index e37aa4d..39e7da4 100644 --- a/src/db/table/select/where_condition.rs +++ b/src/db/table/select/where_condition.rs @@ -3,50 +3,48 @@ use crate::cli::ast::{Operator, Operand, WhereCondition}; use crate::db::table::DataType; -// This file holds the logic for whether a row matches a where condition - +// This file holds the logic for whether a row matches a where condition. pub fn matches_where_clause(table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { - let l_side = match &where_clause.l_side { - Operand::Identifier(column) => column, - _ => return Err(format!("Found invalid left side of condition: {:?}", where_clause.l_side)), - }; - let r_side = match &where_clause.r_side { - Operand::Value(value) => value, - _ => return Err(format!("Found invalid right side of condition: {:?}", where_clause.r_side)), - }; - let column_value = table.get_column_from_row(row, &l_side); - if column_value.get_type() == DataType::Null && r_side.get_type() == DataType::Null { + match where_clause.operator { + Operator::In | Operator::NotIn => { + todo!(); // Todo handle IN and NOT IN. + }, + _ => {}, + } + let l_side = operand_to_value(table, row, &where_clause.l_side)?; + let r_side = operand_to_value(table, row, &where_clause.r_side)?; + if l_side.get_type() == DataType::Null && r_side.get_type() == DataType::Null { return Ok(true); } - else if column_value.get_type() == DataType::Null || r_side.get_type() == DataType::Null { + else if l_side.get_type() == DataType::Null || r_side.get_type() == DataType::Null { return Ok(false); } - if column_value.get_type() != r_side.get_type() { - return Err(format!("Found different data types for column and value: {:?} and {:?}", column_value.get_type(), r_side.get_type())); + if l_side.get_type() != r_side.get_type() { + return Err(format!("Found different data types for column and value: {:?} and {:?}", l_side.get_type(), r_side.get_type())); } match where_clause.operator { Operator::Equals => { - return Ok(*column_value == *r_side); + return Ok(*l_side == *r_side); }, Operator::NotEquals => { - return Ok(*column_value != *r_side); + return Ok(*l_side != *r_side); }, _ => { - match column_value.get_type() { + match l_side.get_type() { DataType::Integer | DataType::Real | DataType::Text => { match where_clause.operator { Operator::LessThan => { - return Ok(*column_value < *r_side); + return Ok(*l_side < *r_side); }, Operator::GreaterThan => { - return Ok(*column_value > *r_side); + return Ok(*l_side > *r_side); }, Operator::LessEquals => { - return Ok(*column_value <= *r_side); + return Ok(*l_side <= *r_side); }, Operator::GreaterEquals => { - return Ok(*column_value >= *r_side); + return Ok(*l_side >= *r_side); }, _ => { return Err(format!("Found invalid operator: {:?}", where_clause.operator)); @@ -54,9 +52,145 @@ pub fn matches_where_clause(table: &Table, row: &Vec, where_clause: &Wher } }, _ => { - return Err(format!("Found invalid operator: {:?} for data type: {:?}", where_clause.operator, column_value.get_type())); + return Err(format!("Found invalid operator: {:?} for data type: {:?}", where_clause.operator, l_side.get_type())); }, } } } +} + +fn operand_to_value<'a>(table: &'a Table, row: &'a Vec, operand: &'a Operand) -> Result<&'a Value, String> { + match operand { + Operand::Value(value) => Ok(value), + Operand::Identifier(column) => { + if !table.has_column(column) { + return Err(format!("Column {} does not exist in table {}", column, table.name)); + } + Ok(table.get_column_from_row(row, column)) + }, + _ => Err(format!("Found invalid operand: {:?}", operand)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::{Table, Value, DataType, ColumnDefinition}; + use crate::cli::ast::{Operator, Operand, WhereCondition}; + + #[test] + fn matches_where_clause_returns_true_if_row_matches_where_clause() { + 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + } + + #[test] + fn matches_where_clause_returns_false_if_row_does_not_match_where_clause() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Integer(2)]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_different_data_types() { + 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Text("Fletcher".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_err()); + let expected_error = "Found different data types for column and value: Integer and Text"; + assert_eq!(expected_error, result.err().unwrap()); + } + + #[test] + fn matches_where_clause_handles_different_operators() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Integer(10)]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterThan,r_side: Operand::Value(Value::Integer(0))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Integer(0))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::LessThan,r_side: Operand::Value(Value::Integer(20))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::LessEquals,r_side: Operand::Value(Value::Integer(20))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::NotEquals,r_side: Operand::Value(Value::Integer(10))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_string_comparison() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"name".to_string(),data_type:DataType::Text, constraints: vec![] }, + ]); + let row = vec![Value::Text("lop".to_string())]; + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Text("abc".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::LessEquals,r_side: Operand::Value(Value::Text("lop".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::GreaterThan,r_side: Operand::Value(Value::Text("xyz".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::LessThan,r_side: Operand::Value(Value::Text("abc".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::NotEquals,r_side: Operand::Value(Value::Text("abc".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Text("lop".to_string()))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_null() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Null]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_invalid_operator_for_data_type() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Blob, constraints: vec![] }, + ]); + let row = vec![Value::Blob(vec![1, 2, 3])]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Blob(vec![1, 2, 3]))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_err()); + let expected_error = "Found invalid operator: GreaterEquals for data type: Blob"; + assert_eq!(expected_error, result.err().unwrap()); + } } \ No newline at end of file diff --git a/src/db/table/select/where_stack.rs b/src/db/table/select/where_stack.rs index 35a5303..70acc36 100644 --- a/src/db/table/select/where_stack.rs +++ b/src/db/table/select/where_stack.rs @@ -1,143 +1,14 @@ -use crate::cli::ast::{Operand, WhereStackElement}; +use crate::cli::ast::{WhereStackElement}; use crate::db::table::{Table, Value}; use crate::db::table::select::where_condition::matches_where_clause; -// For now this function only supports one column = value 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 { let where_condition = match where_stack.first() { Some(WhereStackElement::Condition(where_condition)) => where_condition, _ => return Err(format!("Found nothing when expected edge")), }; - if let Operand::Identifier(column_name) = &where_condition.l_side { - if !table.has_column(column_name) { - return Err(format!("Column {} does not exist in table {}", column_name, table.name)); - } - } - matches_where_clause(table, row, where_condition) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::table::{Table, Value, DataType, ColumnDefinition}; - use crate::cli::ast::{Operator, Operand, WhereCondition}; - - #[test] - fn matches_where_clause_returns_true_if_row_matches_where_clause() { - 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - } - - #[test] - fn matches_where_clause_returns_false_if_row_does_not_match_where_clause() { - let table = Table::new("users".to_string(), vec![ - ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, - ]); - let row = vec![Value::Integer(2)]; - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && !result.unwrap()); - } - - #[test] - fn matches_where_clause_handles_different_data_types() { - 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Text("Fletcher".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_err()); - let expected_error = "Found different data types for column and value: Integer and Text"; - assert_eq!(expected_error, result.err().unwrap()); - } - - #[test] - fn matches_where_clause_handles_different_operators() { - let table = Table::new("users".to_string(), vec![ - ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, - ]); - let row = vec![Value::Integer(10)]; - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterThan,r_side: Operand::Value(Value::Integer(0))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Integer(0))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::LessThan,r_side: Operand::Value(Value::Integer(20))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::LessEquals,r_side: Operand::Value(Value::Integer(20))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::NotEquals,r_side: Operand::Value(Value::Integer(10))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && !result.unwrap()); - } - - #[test] - fn matches_where_clause_handles_string_comparison() { - let table = Table::new("users".to_string(), vec![ - ColumnDefinition {name:"name".to_string(),data_type:DataType::Text, constraints: vec![] }, - ]); - let row = vec![Value::Text("lop".to_string())]; - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Text("abc".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::LessEquals,r_side: Operand::Value(Value::Text("lop".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::GreaterThan,r_side: Operand::Value(Value::Text("xyz".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && !result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::LessThan,r_side: Operand::Value(Value::Text("abc".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && !result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::NotEquals,r_side: Operand::Value(Value::Text("abc".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - let where_clause = WhereCondition {l_side: Operand::Identifier("name".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Text("lop".to_string()))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && result.unwrap()); - } - - #[test] - fn matches_where_clause_handles_null() { - let table = Table::new("users".to_string(), vec![ - ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, - ]); - let row = vec![Value::Null]; - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Integer(1))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_ok() && !result.unwrap()); - } - - #[test] - fn matches_where_clause_handles_invalid_operator_for_data_type() { - let table = Table::new("users".to_string(), vec![ - ColumnDefinition {name:"id".to_string(),data_type:DataType::Blob, constraints: vec![] }, - ]); - let row = vec![Value::Blob(vec![1, 2, 3])]; - let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::GreaterEquals,r_side: Operand::Value(Value::Blob(vec![1, 2, 3]))}; - let result = matches_where_clause(&table, &row, &where_clause); - assert!(result.is_err()); - let expected_error = "Found invalid operator: GreaterEquals for data type: Blob"; - assert_eq!(expected_error, result.err().unwrap()); - } } \ No newline at end of file From e380ad9a71cbb167b29f663090a61f525585af8a Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 15:58:02 -0400 Subject: [PATCH 3/5] Add support for IS within Where clauses --- src/cli/ast/helpers/where_condition.rs | 48 +++++++++++++++++++++++++- src/cli/ast/mod.rs | 2 ++ src/cli/tokenizer/scanner.rs | 1 + src/cli/tokenizer/token.rs | 2 +- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/cli/ast/helpers/where_condition.rs b/src/cli/ast/helpers/where_condition.rs index 3b68622..e03e3e8 100644 --- a/src/cli/ast/helpers/where_condition.rs +++ b/src/cli/ast/helpers/where_condition.rs @@ -8,7 +8,7 @@ pub fn get_condition(parser: &mut Parser) -> Result { parser.advance()?; let token = parser.current_token()?; - let operator = match token.token_type { + let mut operator = match token.token_type { TokenTypes::Equals => Operator::Equals, TokenTypes::NotEquals => Operator::NotEquals, TokenTypes::LessThan => Operator::LessThan, @@ -17,6 +17,7 @@ pub fn get_condition(parser: &mut Parser) -> Result { TokenTypes::GreaterEquals => Operator::GreaterEquals, TokenTypes::In => Operator::In, TokenTypes::Not => Operator::NotIn, + TokenTypes::Is => Operator::Is, _ => return Err(parser.format_error()), }; parser.advance()?; @@ -35,6 +36,12 @@ pub fn get_condition(parser: &mut Parser) -> Result { r_side: r_side, }); } + + if operator == Operator::Is && parser.current_token()?.token_type == TokenTypes::Not { + operator = Operator::IsNot; + parser.advance()?; + } + let r_side = get_operand(parser)?; parser.advance()?; @@ -219,4 +226,43 @@ mod tests { assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Error at line 1, column 0: Unexpected value: ("); } + + #[test] + fn where_condition_handles_is_operator() { + // id IS NULL;... + let tokens = vec![ + token(TokenTypes::Identifier, "id"), + token(TokenTypes::Is, "IS"), + token(TokenTypes::Null, "NULL"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_condition(&mut parser); + let expected = WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Is, + r_side: Operand::Value(Value::Null), + }; + assert_where_condition(result, expected, &mut parser); + } + + #[test] + fn where_condition_handles_is_not_operator() { + // id IS NOT name;... + let tokens = vec![ + token(TokenTypes::Identifier, "id"), + token(TokenTypes::Is, "IS"), + token(TokenTypes::Not, "NOT"), + token(TokenTypes::Null, "NULL"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_condition(&mut parser); + let expected = WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::IsNot, + r_side: Operand::Value(Value::Null), + }; + assert_where_condition(result, expected, &mut parser); + } } \ No newline at end of file diff --git a/src/cli/ast/mod.rs b/src/cli/ast/mod.rs index ee87e89..168a845 100644 --- a/src/cli/ast/mod.rs +++ b/src/cli/ast/mod.rs @@ -88,6 +88,8 @@ pub enum Operator { GreaterEquals, In, NotIn, + Is, + IsNot, } #[derive(Debug, PartialEq)] diff --git a/src/cli/tokenizer/scanner.rs b/src/cli/tokenizer/scanner.rs index 55a6e74..1e3b22a 100644 --- a/src/cli/tokenizer/scanner.rs +++ b/src/cli/tokenizer/scanner.rs @@ -168,6 +168,7 @@ impl<'a> Scanner<'a> { slice if slice.eq_ignore_ascii_case("THEN") => TokenTypes::Then, slice if slice.eq_ignore_ascii_case("ELSE") => TokenTypes::Else, slice if slice.eq_ignore_ascii_case("END") => TokenTypes::End, + slice if slice.eq_ignore_ascii_case("IS") => TokenTypes::Is, slice if slice.eq_ignore_ascii_case("COUNT") => TokenTypes::Count, slice if slice.eq_ignore_ascii_case("SUM") => TokenTypes::Sum, slice if slice.eq_ignore_ascii_case("AVG") => TokenTypes::Avg, diff --git a/src/cli/tokenizer/token.rs b/src/cli/tokenizer/token.rs index 42b0bb5..517b11f 100644 --- a/src/cli/tokenizer/token.rs +++ b/src/cli/tokenizer/token.rs @@ -13,7 +13,7 @@ pub enum TokenTypes { Limit, Offset, // Logical Operators And, Or, In, Exists, - Case, When, Then, Else, End, + Case, When, Then, Else, End, Is, Equals, NotEquals, LessThan, LessEquals, GreaterThan, GreaterEquals, // Aggregate Functions Count, Sum, Avg, Min, Max, From 6d08393d882118f7feb6cad4d0ecd4292cfb7597 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 16:01:43 -0400 Subject: [PATCH 4/5] Update tests in where clause to support proper null equality --- src/db/table/select/where_condition.rs | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/db/table/select/where_condition.rs b/src/db/table/select/where_condition.rs index 39e7da4..4867fbd 100644 --- a/src/db/table/select/where_condition.rs +++ b/src/db/table/select/where_condition.rs @@ -9,14 +9,14 @@ pub fn matches_where_clause(table: &Table, row: &Vec, where_clause: &Wher Operator::In | Operator::NotIn => { todo!(); // Todo handle IN and NOT IN. }, + Operator::Is | Operator::IsNot => { + todo!(); // Todo handle IS and IS NOT. + }, _ => {}, } let l_side = operand_to_value(table, row, &where_clause.l_side)?; let r_side = operand_to_value(table, row, &where_clause.r_side)?; - if l_side.get_type() == DataType::Null && r_side.get_type() == DataType::Null { - return Ok(true); - } - else if l_side.get_type() == DataType::Null || r_side.get_type() == DataType::Null { + if l_side.get_type() == DataType::Null || r_side.get_type() == DataType::Null { return Ok(false); } if l_side.get_type() != r_side.get_type() { @@ -193,4 +193,26 @@ mod tests { let expected_error = "Found invalid operator: GreaterEquals for data type: Blob"; assert_eq!(expected_error, result.err().unwrap()); } + + #[test] + fn matches_where_clause_handles_null_equality() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Null]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Null)}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_single_null_equality() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Null]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } } \ No newline at end of file From 7d1f53028807c6b4ae66bc41530939c12a3e9c9e Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 16:19:00 -0400 Subject: [PATCH 5/5] Add support for evaluating IN clauses with WHERE operator --- src/db/table/select/where_condition.rs | 97 ++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/src/db/table/select/where_condition.rs b/src/db/table/select/where_condition.rs index 4867fbd..9ce0302 100644 --- a/src/db/table/select/where_condition.rs +++ b/src/db/table/select/where_condition.rs @@ -5,23 +5,45 @@ use crate::db::table::DataType; // This file holds the logic for whether a row matches a where condition. pub fn matches_where_clause(table: &Table, row: &Vec, where_clause: &WhereCondition) -> Result { + let l_side = operand_to_value(table, row, &where_clause.l_side)?; match where_clause.operator { Operator::In | Operator::NotIn => { - todo!(); // Todo handle IN and NOT IN. + let r_side = match &where_clause.r_side { + Operand::ValueList(value_list) => value_list, + _ => return Err(format!("Found invalid r_side operand: {:?}", where_clause.r_side)), + }; + if r_side.is_empty() { + return Ok(false); + } + let result = r_side.contains(l_side); + if where_clause.operator == Operator::NotIn { + return Ok(!result); + } + return Ok(result); }, Operator::Is | Operator::IsNot => { - todo!(); // Todo handle IS and IS NOT. + let r_side = operand_to_value(table, row, &where_clause.r_side)?; + expect_same_type(l_side, r_side)?; + + match (l_side, r_side, &where_clause.operator) { + (Value::Null, Value::Null, Operator::Is) => return Ok(true), + (Value::Null, Value::Null, Operator::IsNot) => return Ok(false), + (Value::Null, _, Operator::Is) | (_, Value::Null, Operator::Is) => return Ok(false), + (Value::Null, _, Operator::IsNot) | (_, Value::Null, Operator::IsNot) => return Ok(true), + (_, _, Operator::Is) => return Ok(l_side == r_side), + (_, _, Operator::IsNot) => return Ok(l_side != r_side), + _ => unreachable!(), + } }, _ => {}, } - let l_side = operand_to_value(table, row, &where_clause.l_side)?; + let r_side = operand_to_value(table, row, &where_clause.r_side)?; if l_side.get_type() == DataType::Null || r_side.get_type() == DataType::Null { return Ok(false); } - if l_side.get_type() != r_side.get_type() { - return Err(format!("Found different data types for column and value: {:?} and {:?}", l_side.get_type(), r_side.get_type())); - } + + expect_same_type(l_side, r_side)?; match where_clause.operator { Operator::Equals => { @@ -72,6 +94,13 @@ fn operand_to_value<'a>(table: &'a Table, row: &'a Vec, operand: &'a Oper } } +fn expect_same_type(l_side: &Value, r_side: &Value) -> Result<(), String> { + if l_side.get_type() != r_side.get_type() && l_side.get_type() != DataType::Null && r_side.get_type() != DataType::Null { + return Err(format!("Found different data types for l_side and r_side: {:?} and {:?}", l_side.get_type(), r_side.get_type())); + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -117,7 +146,7 @@ mod tests { let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Equals,r_side: Operand::Value(Value::Text("Fletcher".to_string()))}; let result = matches_where_clause(&table, &row, &where_clause); assert!(result.is_err()); - let expected_error = "Found different data types for column and value: Integer and Text"; + let expected_error = "Found different data types for l_side and r_side: Integer and Text"; assert_eq!(expected_error, result.err().unwrap()); } @@ -215,4 +244,58 @@ mod tests { let result = matches_where_clause(&table, &row, &where_clause); assert!(result.is_ok() && !result.unwrap()); } + + #[test] + fn matches_where_clause_handles_is_and_is_not_operators() { + 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Is,r_side: Operand::Value(Value::Null)}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::IsNot,r_side: Operand::Value(Value::Null)}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::Is,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::IsNot,r_side: Operand::Value(Value::Integer(1))}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_in_and_not_in_operators() { + 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_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::In,r_side: Operand::ValueList(vec![Value::Integer(1)])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::NotIn,r_side: Operand::ValueList(vec![Value::Integer(1)])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::In,r_side: Operand::ValueList(vec![Value::Integer(2), Value::Integer(3)])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::NotIn,r_side: Operand::ValueList(vec![Value::Integer(2), Value::Integer(3)])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + } + + #[test] + fn matches_where_clause_handles_in_with_diff_data_types() { + let table = Table::new("users".to_string(), vec![ + ColumnDefinition {name:"id".to_string(),data_type:DataType::Integer, constraints: vec![] }, + ]); + let row = vec![Value::Text("hello".to_string())]; + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::In,r_side: Operand::ValueList(vec![Value::Integer(2), Value::Text("hello".to_string())])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && result.unwrap()); + let where_clause = WhereCondition {l_side: Operand::Identifier("id".to_string()),operator:Operator::NotIn,r_side: Operand::ValueList(vec![Value::Integer(2), Value::Text("hello".to_string())])}; + let result = matches_where_clause(&table, &row, &where_clause); + assert!(result.is_ok() && !result.unwrap()); + } } \ No newline at end of file