From da92c7b03cb10c5d4c1441fd8aa5824e521d9d52 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 14:11:23 -0400 Subject: [PATCH 1/6] Set up file structure to support select statement stack evaluation --- src/db/database.rs | 25 +-- src/db/table/delete/mod.rs | 2 +- src/db/table/helpers/common.rs | 4 +- src/db/table/select/mod.rs | 192 +++--------------------- src/db/table/select/select_statement.rs | 175 +++++++++++++++++++++ src/db/table/update/mod.rs | 2 +- 6 files changed, 205 insertions(+), 195 deletions(-) create mode 100644 src/db/table/select/select_statement.rs diff --git a/src/db/database.rs b/src/db/database.rs index 430af8e..1a6a142 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -1,5 +1,5 @@ use crate::db::table::{Table, Value}; -use crate::interpreter::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatement, DeleteStatement, UpdateStatement, SelectStatementStackElement}; +use crate::interpreter::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatementStack, DeleteStatement, UpdateStatement}; use crate::db::table::select; use crate::db::table::insert; use crate::db::table::delete; @@ -27,19 +27,9 @@ impl Database { self.insert_into_table(statement)?; Ok(None) }, - SqlStatement::Select(mut statement) => { - let select_statement = statement.elements.pop(); - if let Some(select_statement) = select_statement { - match select_statement { - SelectStatementStackElement::SelectStatement(select_statement) => { - let rows = self.select_from_table(select_statement)?; - Ok(Some(rows)) - } - _ => Err(format!("Expected select statement, got {:?}", select_statement)), - } - } else { - Ok(None) - } + SqlStatement::Select(statement) => { + let result = self.select_statement_stack(statement)?; + Ok(Some(result)) }, SqlStatement::UpdateStatement(statement) => { self.update_table(statement)?; @@ -67,9 +57,8 @@ impl Database { Ok(()) } - fn select_from_table(&mut self, statement: SelectStatement) -> Result>, String> { - let table = self.get_table(&statement.table_name)?; - let rows = select::select(table, statement)?; + fn select_statement_stack(&mut self, statement: SelectStatementStack) -> Result>, String> { + let rows = select::select_statement_stack(self, statement)?; Ok(rows) } @@ -89,7 +78,7 @@ impl Database { self.tables.contains_key(table_name) } - fn get_table(&self, table_name: &str) -> Result<&Table, String> { + pub fn get_table(&self, table_name: &str) -> Result<&Table, String> { if !self.has_table(table_name) { return Err(format!("Table not found: {}", table_name)); } diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs index b49da25..ecfa914 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/delete/mod.rs @@ -6,7 +6,7 @@ use crate::db::table::helpers::common::get_row_indicies_matching_clauses; pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result<(), String> { - let row_indicies_to_delete = get_row_indicies_matching_clauses(table, statement.where_clause, statement.order_by_clause, statement.limit_clause)?; + let row_indicies_to_delete = get_row_indicies_matching_clauses(table, &statement.where_clause, &statement.order_by_clause, &statement.limit_clause)?; swap_remove_bulk(table, row_indicies_to_delete)?; Ok(()) } diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index 88f78dc..d96006a 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -32,7 +32,7 @@ pub fn get_row_columns_from_indicies(table: &Table, row_indicies: Vec, co Ok(rows) } -pub fn get_row_indicies_matching_where_clause(table: &Table, where_clause: Option>) -> Result, String> { +pub fn get_row_indicies_matching_where_clause(table: &Table, where_clause: &Option>) -> Result, String> { if let Some(where_clause) = where_clause { let mut row_indicies: Vec = vec![]; for (i, row) in table.rows.iter().enumerate() { @@ -62,7 +62,7 @@ pub fn get_columns_from_row(table: &Table, row: &Vec, selected_columns: & return Ok(row_values); } -pub fn get_row_indicies_matching_clauses(table: &Table, where_clause: Option>, order_by_clause: Option>, limit_clause: Option) -> Result, String> { +pub fn get_row_indicies_matching_clauses(table: &Table, where_clause: &Option>, order_by_clause: &Option>, limit_clause: &Option) -> Result, String> { let mut row_indicies = get_row_indicies_matching_where_clause(table, where_clause)?; if let Some(order_by_clause) = order_by_clause { diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index bce7199..530947b 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,175 +1,21 @@ -use crate::db::table::{Table, Value}; -use crate::interpreter::ast::{SelectStatement}; -use crate::db::table::helpers::common::{get_row_indicies_matching_clauses, get_row_columns_from_indicies}; - - - -pub fn select(table: &Table, statement: SelectStatement) -> Result>, String> { - let row_indicies = get_row_indicies_matching_clauses(table, statement.where_clause, statement.order_by_clause, statement.limit_clause)?; - - return Ok(get_row_columns_from_indicies(table, row_indicies, Some(&statement.columns))?); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::table::Value; - use crate::interpreter::ast::{SelectStatementColumns, LimitClause, OrderByClause, OrderByDirection, Operator}; - use crate::interpreter::ast::WhereStackElement; - use crate::interpreter::ast::WhereCondition; - use crate::interpreter::ast::Operand; - use crate::db::table::test_utils::default_table; - - #[test] - fn select_with_all_tokens_is_generated_correctly() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::All, - where_clause: None, - order_by_clause: None, - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], - vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], - vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], - vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], - ]; - assert_eq!(expected, result.unwrap()); - } - - #[test] - fn select_specific_columns_is_generated_correctly() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::Specific(vec!["name".to_string(), "age".to_string()]), - where_clause: None, - order_by_clause: None, - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Text("John".to_string()), Value::Integer(25)], - vec![Value::Text("Jane".to_string()), Value::Integer(30)], - vec![Value::Text("Jim".to_string()), Value::Integer(35)], - vec![Value::Null, Value::Integer(40)], - ]; - assert_eq!(expected, result.unwrap()); - } - - #[test] - fn select_with_where_clause_is_generated_correctly() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::All, - where_clause: Some(vec![ - WhereStackElement::Condition(WhereCondition { - l_side: Operand::Identifier("name".to_string()), - operator: Operator::Equals, - r_side: Operand::Value(Value::Text("John".to_string())), - }), - ]), - order_by_clause: None, - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], - ]; - assert_eq!(expected, result.unwrap()); - } - - #[test] - fn select_with_where_clause_using_column_not_included_in_selected_columns() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::Specific(vec!["name".to_string(), "age".to_string()]), - where_clause: Some(vec![ - WhereStackElement::Condition(WhereCondition { - l_side: Operand::Identifier("money".to_string()), - operator: Operator::Equals, - r_side: Operand::Value(Value::Real(1000.0)), - }), - ]), - order_by_clause: None, - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Text("John".to_string()), Value::Integer(25)], - ]; - assert_eq!(expected, result.unwrap()); - } - - #[test] - fn select_with_limit_clause_is_generated_correctly() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::All, - where_clause: None, - order_by_clause: None, - limit_clause: Some(LimitClause { - limit: Value::Integer(1), - offset: Some(Value::Integer(1)), - }), - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], - ]; - assert_eq!(expected, result.unwrap()); - } - - #[test] - fn select_with_where_clause_using_column_not_included_in_table_returns_error() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::All, - where_clause: Some(vec![ - WhereStackElement::Condition(WhereCondition { - l_side: Operand::Identifier("column_not_included".to_string()), - operator: Operator::Equals, - r_side: Operand::Value(Value::Text("John".to_string())), - }), - ]), - order_by_clause: None, - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "Column column_not_included does not exist in table users"); - } - - #[test] - fn select_with_order_by_clause_is_generated_correctly() { - let table = default_table(); - let statement = SelectStatement { - table_name: "users".to_string(), - columns: SelectStatementColumns::All, - where_clause: None, - order_by_clause: Some(vec![OrderByClause {column: "money".to_string(), direction: OrderByDirection::Desc}]), - limit_clause: None, - }; - let result = select(&table, statement); - assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], - vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], - vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], - vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], - ]; - assert_eq!(expected, result.unwrap()); +pub mod select_statement; +use crate::db::database::Database; +use crate::db::table::Value; +use crate::interpreter::ast::SelectStatementStack; +use crate::interpreter::ast::SelectStatementStackElement; + + +pub fn select_statement_stack(database: &Database, statement: SelectStatementStack) -> Result>, String> { + let select_statement = statement.elements.first(); + if let Some(select_statement) = select_statement { + match select_statement { + SelectStatementStackElement::SelectStatement(select_statement) => { + let rows = select_statement::select_statement(database.get_table(&select_statement.table_name)?, select_statement); + return rows; + } + _ => Err(format!("Expected select statement, got {:?}", select_statement)), + } + } else { + Ok(vec![]) } } \ No newline at end of file diff --git a/src/db/table/select/select_statement.rs b/src/db/table/select/select_statement.rs new file mode 100644 index 0000000..9612992 --- /dev/null +++ b/src/db/table/select/select_statement.rs @@ -0,0 +1,175 @@ +use crate::db::table::{Table, Value}; +use crate::interpreter::ast::{SelectStatement}; +use crate::db::table::helpers::common::{get_row_indicies_matching_clauses, get_row_columns_from_indicies}; + + + +pub fn select_statement(table: &Table, statement: &SelectStatement) -> Result>, String> { + let row_indicies = get_row_indicies_matching_clauses(table, &statement.where_clause, &statement.order_by_clause, &statement.limit_clause)?; + + return Ok(get_row_columns_from_indicies(table, row_indicies, Some(&statement.columns))?); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::Value; + use crate::interpreter::ast::{SelectStatementColumns, LimitClause, OrderByClause, OrderByDirection, Operator}; + use crate::interpreter::ast::WhereStackElement; + use crate::interpreter::ast::WhereCondition; + use crate::interpreter::ast::Operand; + use crate::db::table::test_utils::default_table; + + #[test] + fn select_with_all_tokens_is_generated_correctly() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_specific_columns_is_generated_correctly() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::Specific(vec!["name".to_string(), "age".to_string()]), + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Text("John".to_string()), Value::Integer(25)], + vec![Value::Text("Jane".to_string()), Value::Integer(30)], + vec![Value::Text("Jim".to_string()), Value::Integer(35)], + vec![Value::Null, Value::Integer(40)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_with_where_clause_is_generated_correctly() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: Some(vec![ + WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("name".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Text("John".to_string())), + }), + ]), + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_with_where_clause_using_column_not_included_in_selected_columns() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::Specific(vec!["name".to_string(), "age".to_string()]), + where_clause: Some(vec![ + WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("money".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Real(1000.0)), + }), + ]), + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Text("John".to_string()), Value::Integer(25)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_with_limit_clause_is_generated_correctly() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: None, + limit_clause: Some(LimitClause { + limit: Value::Integer(1), + offset: Some(Value::Integer(1)), + }), + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_with_where_clause_using_column_not_included_in_table_returns_error() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: Some(vec![ + WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("column_not_included".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Text("John".to_string())), + }), + ]), + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Column column_not_included does not exist in table users"); + } + + #[test] + fn select_with_order_by_clause_is_generated_correctly() { + let table = default_table(); + let statement = SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: Some(vec![OrderByClause {column: "money".to_string(), direction: OrderByDirection::Desc}]), + limit_clause: None, + }; + let result = select_statement(&table, &statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } +} \ No newline at end of file diff --git a/src/db/table/update/mod.rs b/src/db/table/update/mod.rs index 2efcbc2..c3428df 100644 --- a/src/db/table/update/mod.rs +++ b/src/db/table/update/mod.rs @@ -4,7 +4,7 @@ use crate::db::table::helpers::common::get_row_indicies_matching_clauses; use crate::db::table::DataType; pub fn update(table: &mut Table, statement: UpdateStatement) -> Result<(), String> { - let row_indicies = get_row_indicies_matching_clauses(table, statement.where_clause, statement.order_by_clause, statement.limit_clause)?; + let row_indicies = get_row_indicies_matching_clauses(table, &statement.where_clause, &statement.order_by_clause, &statement.limit_clause)?; update_rows_from_indicies(table, row_indicies, statement.update_values)?; Ok(()) } From 81d149e1c4149a93565d78344757ee0faaf22056 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 14:35:46 -0400 Subject: [PATCH 2/6] Add Set operator evaluator object --- src/db/table/select/mod.rs | 47 +++++++++++++------ src/db/table/select/set_operator_evaluator.rs | 28 +++++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/db/table/select/set_operator_evaluator.rs diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 530947b..cf3e45f 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,21 +1,38 @@ -pub mod select_statement; -use crate::db::database::Database; -use crate::db::table::Value; -use crate::interpreter::ast::SelectStatementStack; -use crate::interpreter::ast::SelectStatementStackElement; - +mod select_statement; +mod set_operator_evaluator; +use crate::db::{database::Database, table::Value}; +use crate::interpreter::ast::{SelectStatementStack, SetOperator, SelectStatementStackElement}; pub fn select_statement_stack(database: &Database, statement: SelectStatementStack) -> Result>, String> { - let select_statement = statement.elements.first(); - if let Some(select_statement) = select_statement { - match select_statement { + let mut evaluator = set_operator_evaluator::SetOperatorEvaluator { + stack: vec![], + }; + for element in statement.elements { + match element { SelectStatementStackElement::SelectStatement(select_statement) => { - let rows = select_statement::select_statement(database.get_table(&select_statement.table_name)?, select_statement); - return rows; + let table = database.get_table(&select_statement.table_name)?; + let rows = select_statement::select_statement(table, &select_statement)?; + evaluator.push(rows); + } + SelectStatementStackElement::SetOperator(set_operator) => { + match set_operator { + SetOperator::UnionAll => { + evaluator.union_all(); + } + SetOperator::Union => { + evaluator.union(); + } + SetOperator::Intersect => { + evaluator.intersect(); + } + SetOperator::Except => { + evaluator.except(); + } + } } - _ => Err(format!("Expected select statement, got {:?}", select_statement)), } - } else { - Ok(vec![]) } -} \ No newline at end of file + let result = evaluator.result()?; + Ok(result) +} + diff --git a/src/db/table/select/set_operator_evaluator.rs b/src/db/table/select/set_operator_evaluator.rs new file mode 100644 index 0000000..86a20da --- /dev/null +++ b/src/db/table/select/set_operator_evaluator.rs @@ -0,0 +1,28 @@ +use crate::db::table::Value; + +pub struct SetOperatorEvaluator { + pub stack: Vec>>, +} + +impl SetOperatorEvaluator { + pub fn result(&mut self) -> Result>, String> { + self.stack.pop().ok_or() + } + + pub fn union_all(&mut self) { + todo!() + } + + pub fn union(&mut self) { + todo!() + } + pub fn intersect(&mut self) { + todo!() + } + pub fn except(&mut self) { + todo!() + } + pub fn push(&mut self, rows: Vec>) { + self.stack.push(rows); + } +} \ No newline at end of file From 0007baf2cd4258d6426ed10f60cde90900e52e36 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 16:01:33 -0400 Subject: [PATCH 3/6] Initial set up of set evaluator --- src/db/table/select/mod.rs | 12 +++-- src/db/table/select/set_operator_evaluator.rs | 44 ++++++++++++++----- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index cf3e45f..7463a40 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -4,9 +4,7 @@ use crate::db::{database::Database, table::Value}; use crate::interpreter::ast::{SelectStatementStack, SetOperator, SelectStatementStackElement}; pub fn select_statement_stack(database: &Database, statement: SelectStatementStack) -> Result>, String> { - let mut evaluator = set_operator_evaluator::SetOperatorEvaluator { - stack: vec![], - }; + let mut evaluator = set_operator_evaluator::SetOperatorEvaluator::new(); for element in statement.elements { match element { SelectStatementStackElement::SelectStatement(select_statement) => { @@ -17,16 +15,16 @@ pub fn select_statement_stack(database: &Database, statement: SelectStatementSta SelectStatementStackElement::SetOperator(set_operator) => { match set_operator { SetOperator::UnionAll => { - evaluator.union_all(); + evaluator.union_all()?; } SetOperator::Union => { - evaluator.union(); + evaluator.union()?; } SetOperator::Intersect => { - evaluator.intersect(); + evaluator.intersect()?; } SetOperator::Except => { - evaluator.except(); + evaluator.except()?; } } } diff --git a/src/db/table/select/set_operator_evaluator.rs b/src/db/table/select/set_operator_evaluator.rs index 86a20da..24e3923 100644 --- a/src/db/table/select/set_operator_evaluator.rs +++ b/src/db/table/select/set_operator_evaluator.rs @@ -5,24 +5,48 @@ pub struct SetOperatorEvaluator { } impl SetOperatorEvaluator { + pub fn new() -> Self { + Self { + stack: vec![], + } + } + pub fn result(&mut self) -> Result>, String> { - self.stack.pop().ok_or() + if self.stack.len() != 1 { + return Err("Error processing SELECT statement. Stack length is not 1".to_string()); + } + self.pop() } - pub fn union_all(&mut self) { - todo!() + pub fn push(&mut self, rows: Vec>) { + self.stack.push(rows); } - pub fn union(&mut self) { - todo!() + fn pop(&mut self) -> Result>, String> { + self.stack.pop().ok_or("Error processing SELECT statement. Stack is empty".to_string()) } - pub fn intersect(&mut self) { - todo!() + + // Keeps duplicates + pub fn union_all(&mut self) -> Result<(), String> { + let mut first = self.pop()?; + let second = self.pop()?; + first.extend(second); + self.push(first); + Ok(()) + } + + pub fn union(&mut self) -> Result<(), String> { + let mut first = self.pop()?; + let second = self.pop()?; + first.extend(second); + self.push(first); + Ok(()) } - pub fn except(&mut self) { + pub fn intersect(&mut self) -> Result<(), String> { todo!() } - pub fn push(&mut self, rows: Vec>) { - self.stack.push(rows); + pub fn except(&mut self) -> Result<(), String> { + todo!() } + } \ No newline at end of file From e509c0d62d0a0b353f9d530749e63f08805b3019 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 16:41:34 -0400 Subject: [PATCH 4/6] Modify Parser to handle statement modifiers on the results of set operations --- src/interpreter/ast/mod.rs | 6 + src/interpreter/ast/parser.rs | 4 + src/interpreter/ast/select_statement_stack.rs | 200 +++++++++++++++++- 3 files changed, 200 insertions(+), 10 deletions(-) diff --git a/src/interpreter/ast/mod.rs b/src/interpreter/ast/mod.rs index d916488..25fb204 100644 --- a/src/interpreter/ast/mod.rs +++ b/src/interpreter/ast/mod.rs @@ -43,6 +43,8 @@ pub struct InsertIntoStatement { #[derive(Debug, PartialEq)] pub struct SelectStatementStack { pub elements: Vec, + pub order_by_clause: Option>, + pub limit_clause: Option, } #[derive(Debug, PartialEq)] @@ -360,6 +362,8 @@ mod tests { order_by_clause: None, limit_clause: None, })], + order_by_clause: None, + limit_clause: None, }), line_num: 1, statement_text: "".to_string(), @@ -451,6 +455,8 @@ mod tests { order_by_clause: None, limit_clause: None, })], + order_by_clause: None, + limit_clause: None, }), line_num: 1, statement_text: "".to_string(), diff --git a/src/interpreter/ast/parser.rs b/src/interpreter/ast/parser.rs index d88fabd..4196181 100644 --- a/src/interpreter/ast/parser.rs +++ b/src/interpreter/ast/parser.rs @@ -155,6 +155,8 @@ mod tests { order_by_clause: None, limit_clause: None, })], + order_by_clause: None, + limit_clause: None, })); } @@ -207,6 +209,8 @@ mod tests { order_by_clause: None, limit_clause: None, })], + order_by_clause: None, + limit_clause: None, }))); assert_eq!(result, expected); diff --git a/src/interpreter/ast/select_statement_stack.rs b/src/interpreter/ast/select_statement_stack.rs index 63c36fa..e47231f 100644 --- a/src/interpreter/ast/select_statement_stack.rs +++ b/src/interpreter/ast/select_statement_stack.rs @@ -1,3 +1,5 @@ +use crate::interpreter::ast::helpers::order_by_clause::get_order_by; +use crate::interpreter::ast::helpers::limit_clause::get_limit; use crate::interpreter::ast::{parser::Parser, SqlStatement, SelectStatementStack, SelectStatementStackElement, SetOperator, SelectStackOperators}; use crate::interpreter::ast::helpers::select_statement; use crate::interpreter::ast::Parentheses; @@ -5,15 +7,29 @@ use crate::interpreter::tokenizer::token::TokenTypes; // Returns a SelectStatementStack which is an RPN representation of the SELECT statements and set operators. pub fn build(parser: &mut Parser) -> Result { - let mut select_statement_stack: Vec = vec![]; + let mut statement_stack = SelectStatementStack { + elements: vec![], + order_by_clause: None, + limit_clause: None, + }; let mut set_operator_stack: Vec = vec![]; loop { let token = parser.current_token()?; match token.token_type { TokenTypes::Select => { - let statement = select_statement::get_statement(parser)?; - select_statement_stack.push(SelectStatementStackElement::SelectStatement(statement)); + let mut statement = select_statement::get_statement(parser)?; + if parser.current_token()?.token_type != TokenTypes::SemiColon { + if statement.order_by_clause.is_some() || statement.limit_clause.is_some() { + return Err("ORDER BY, or LIMIT clause not allowed with UNION SELECT statements".to_string()); + } + } + else if statement_stack.elements.len() > 0 && parser.current_token()?.token_type == TokenTypes::SemiColon { + statement_stack.order_by_clause = statement.order_by_clause.take(); + statement_stack.limit_clause = statement.limit_clause.take(); + } + statement_stack.elements.push(SelectStatementStackElement::SelectStatement(statement)); + } TokenTypes::LeftParen => { set_operator_stack.push(SelectStackOperators::Parentheses(Parentheses::Left)); @@ -25,13 +41,23 @@ pub fn build(parser: &mut Parser) -> Result { break; } else if let SelectStackOperators::SetOperator(set_operator) = current_set_operator { - select_statement_stack.push(SelectStatementStackElement::SetOperator(set_operator)); + statement_stack.elements.push(SelectStatementStackElement::SetOperator(set_operator)); } else { return Err("Mismatched parentheses found.".to_string()); } } parser.advance()?; + match parser.current_token()?.token_type { + TokenTypes::Order => { + statement_stack.order_by_clause = get_order_by(parser)?; + statement_stack.limit_clause = get_limit(parser)?; + } + TokenTypes::Limit => { + statement_stack.limit_clause = get_limit(parser)?; + } + _ => {}, + } } TokenTypes::Union | TokenTypes::Except => { let set_operator = get_set_operator(parser)?; @@ -41,7 +67,7 @@ pub fn build(parser: &mut Parser) -> Result { break; } else if let SelectStackOperators::SetOperator(current_set_operator) = current_set_operator { - select_statement_stack.push(SelectStatementStackElement::SetOperator(current_set_operator)); + statement_stack.elements.push(SelectStatementStackElement::SetOperator(current_set_operator)); } } set_operator_stack.push(SelectStackOperators::SetOperator(set_operator)); @@ -55,7 +81,7 @@ pub fn build(parser: &mut Parser) -> Result { break; } else { - select_statement_stack.push(SelectStatementStackElement::SetOperator(current_set_operator)); + statement_stack.elements.push(SelectStatementStackElement::SetOperator(current_set_operator)); } } else { @@ -72,16 +98,14 @@ pub fn build(parser: &mut Parser) -> Result { while let Some(current_set_operator) = set_operator_stack.pop() { if let SelectStackOperators::SetOperator(set_operator) = current_set_operator { - select_statement_stack.push(SelectStatementStackElement::SetOperator(set_operator)); + statement_stack.elements.push(SelectStatementStackElement::SetOperator(set_operator)); } else { return Err("Mismatched parentheses found.".to_string()); } } - return Ok(SqlStatement::Select(SelectStatementStack { - elements: select_statement_stack, - })); + return Ok(SqlStatement::Select(statement_stack)); } fn get_set_operator(parser: &mut Parser) -> Result { @@ -121,6 +145,9 @@ mod tests { use crate::db::table::Value; use crate::interpreter::tokenizer::token::TokenTypes; use crate::interpreter::tokenizer::scanner::Token; + use crate::interpreter::ast::OrderByClause; + use crate::interpreter::ast::OrderByDirection; + use crate::interpreter::ast::LimitClause; fn simple_select_statement_tokens(id: &'static str) -> Vec> { vec![ @@ -161,6 +188,8 @@ mod tests { let statement = result.unwrap(); let expected = SqlStatement::Select(SelectStatementStack { elements: vec![expected_simple_select_statement(1)], + order_by_clause: None, + limit_clause: None, }); assert_eq!(expected, statement); } @@ -183,6 +212,8 @@ mod tests { expected_simple_select_statement(2), SelectStatementStackElement::SetOperator(SetOperator::UnionAll), ], + order_by_clause: None, + limit_clause: None, }); assert_eq!(expected, statement); } @@ -213,6 +244,8 @@ mod tests { expected_simple_select_statement(4), SelectStatementStackElement::SetOperator(SetOperator::Except), ], + order_by_clause: None, + limit_clause: None, }); assert_eq!(expected, statement); } @@ -248,6 +281,153 @@ mod tests { SelectStatementStackElement::SetOperator(SetOperator::Except), SelectStatementStackElement::SetOperator(SetOperator::Intersect), ], + order_by_clause: None, + limit_clause: None, + }); + assert_eq!(expected, statement); + } + + #[test] + fn select_statement_stack_with_all_clauses_is_generated_correctly() { + // SELECT name FROM employees WHERE name = 'Henry' UNION ALL SELECT name FROM employees WHERE name = 'John' ORDER BY name LIMIT 10 OFFSET 15; + let tokens = vec![ + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::From, "FROM"), + token(TokenTypes::Identifier, "employees"), + token(TokenTypes::Where, "WHERE"), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::Equals, "="), + token(TokenTypes::String, "Henry"), + token(TokenTypes::Union, "UNION"), + token(TokenTypes::All, "ALL"), + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::From, "FROM"), + token(TokenTypes::Identifier, "employees"), + token(TokenTypes::Where, "WHERE"), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::Equals, "="), + token(TokenTypes::String, "John"), + token(TokenTypes::Order, "ORDER"), + token(TokenTypes::By, "BY"), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::Limit, "LIMIT"), + token(TokenTypes::IntLiteral, "10"), + token(TokenTypes::Offset, "OFFSET"), + token(TokenTypes::IntLiteral, "15"), + token(TokenTypes::SemiColon, ";") + ]; + let mut parser = Parser::new(tokens); + let result = build(&mut parser); + println!("{:?}", result); + assert!(result.is_ok()); + let statement = result.unwrap(); + let expected = SqlStatement::Select(SelectStatementStack { + elements: vec![ + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "employees".to_string(), + columns: SelectStatementColumns::Specific(vec!["name".to_string()]), + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("name".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Text("Henry".to_string())), + })]), + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "employees".to_string(), + columns: SelectStatementColumns::Specific(vec!["name".to_string()]), + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("name".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Text("John".to_string())), + })]), + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SetOperator(SetOperator::UnionAll), + ], + order_by_clause: Some(vec![OrderByClause { + column: "name".to_string(), + direction: OrderByDirection::Asc, + }]), + limit_clause: Some(LimitClause { + limit: Value::Integer(10), + offset: Some(Value::Integer(15)), + }), + }); + assert_eq!(expected, statement); + } + + #[test] + fn select_statement_with_order_by_and_parentheses_is_generated_correctly() { + // (SELECT A UNION ALL SELECT B) ORDER BY name LIMIT 10 OFFSET 15; + let mut tokens = vec![token(TokenTypes::LeftParen, "(")]; + tokens.append(&mut simple_select_statement_tokens("1")); + tokens.append(&mut vec![token(TokenTypes::Union, "UNION")]); + tokens.append(&mut vec![token(TokenTypes::All, "ALL")]); + tokens.append(&mut simple_select_statement_tokens("2")); + tokens.append(&mut vec![token(TokenTypes::RightParen, ")")]); + tokens.append(&mut vec![token(TokenTypes::Order, "ORDER")]); + tokens.append(&mut vec![token(TokenTypes::By, "BY")]); + tokens.append(&mut vec![token(TokenTypes::Identifier, "name")]); + tokens.append(&mut vec![token(TokenTypes::Limit, "LIMIT")]); + tokens.append(&mut vec![token(TokenTypes::IntLiteral, "10")]); + tokens.append(&mut vec![token(TokenTypes::Offset, "OFFSET")]); + tokens.append(&mut vec![token(TokenTypes::IntLiteral, "15")]); + tokens.append(&mut vec![token(TokenTypes::SemiColon, ";")]); + let mut parser = Parser::new(tokens); + let result = build(&mut parser); + assert!(result.is_ok()); + let statement = result.unwrap(); + let expected = SqlStatement::Select(SelectStatementStack { + elements: vec![ + expected_simple_select_statement(1), + expected_simple_select_statement(2), + SelectStatementStackElement::SetOperator(SetOperator::UnionAll), + ], + order_by_clause: Some(vec![OrderByClause { + column: "name".to_string(), + direction: OrderByDirection::Asc, + }]), + limit_clause: Some(LimitClause { + limit: Value::Integer(10), + offset: Some(Value::Integer(15)), + }), + }); + assert_eq!(expected, statement); + } + + #[test] + fn select_statement_intersect_with_limit_clause_and_parentheses_is_generated_correctly() { + // (SELECT A INTERSECT SELECT B) LIMIT 10 OFFSET 15; + let mut tokens = vec![token(TokenTypes::LeftParen, "(")]; + tokens.append(&mut simple_select_statement_tokens("1")); + tokens.append(&mut vec![token(TokenTypes::Intersect, "INTERSECT")]); + tokens.append(&mut simple_select_statement_tokens("2")); + tokens.append(&mut vec![token(TokenTypes::RightParen, ")")]); + tokens.append(&mut vec![token(TokenTypes::Limit, "LIMIT")]); + tokens.append(&mut vec![token(TokenTypes::IntLiteral, "10")]); + tokens.append(&mut vec![token(TokenTypes::Offset, "OFFSET")]); + tokens.append(&mut vec![token(TokenTypes::IntLiteral, "15")]); + tokens.append(&mut vec![token(TokenTypes::SemiColon, ";")]); + let mut parser = Parser::new(tokens); + let result = build(&mut parser); + assert!(result.is_ok()); + let statement = result.unwrap(); + let expected = SqlStatement::Select(SelectStatementStack { + elements: vec![ + expected_simple_select_statement(1), + expected_simple_select_statement(2), + SelectStatementStackElement::SetOperator(SetOperator::Intersect), + ], + order_by_clause: None, + limit_clause: Some(LimitClause { + limit: Value::Integer(10), + offset: Some(Value::Integer(15)), + }), }); assert_eq!(expected, statement); } From 2903b96185e2bd7767f43655576d571dfc99e001 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 17:17:23 -0400 Subject: [PATCH 5/6] First working version of some set operations need more tests --- src/db/table/mod.rs | 71 +++++++++++++-- src/db/table/select/set_operator_evaluator.rs | 87 +++++++++++++++++-- src/db/table/test_utils.rs | 20 ++++- 3 files changed, 159 insertions(+), 19 deletions(-) diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 7594169..066a317 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; use crate::interpreter::ast::OrderByDirection; @@ -32,7 +33,7 @@ pub struct ColumnConstraint { pub constraint_type: String, } -#[derive(Debug, PartialEq, PartialOrd, Clone)] +#[derive(Debug, PartialOrd, Clone)] pub enum Value { Integer(i64), Real(f64), @@ -59,24 +60,76 @@ impl Value { (_, Value::Null) => Ordering::Greater, (Value::Integer(a), Value::Integer(b)) => a.cmp(b), (Value::Real(a), Value::Real(b)) => { - if a > b { - Ordering::Greater - } else if a < b { + if a.is_nan() && b.is_nan() { + Ordering::Equal + } else if a.is_nan() { Ordering::Less + } else if b.is_nan() { + Ordering::Greater } else { - Ordering::Equal + a.partial_cmp(b).unwrap_or(Ordering::Equal) } - }, (Value::Text(a), Value::Text(b)) => a.cmp(b), (Value::Blob(a), Value::Blob(b)) => a.cmp(b), _ => return Ordering::Equal, // Bad - returns equal if data types are different }; - if direction == &OrderByDirection::Asc { - return result; + if direction == &OrderByDirection::Desc { + result.reverse() } else { - return result.reverse(); + result + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Integer(a), Value::Integer(b)) => a == b, + (Value::Real(a), Value::Real(b)) => { + if a.is_nan() && b.is_nan() { + true + } else { + a == b + } + }, + (Value::Text(a), Value::Text(b)) => a == b, + (Value::Blob(a), Value::Blob(b)) => a == b, + (Value::Null, Value::Null) => true, // Bad - NULL == NULL should be false but this breaks assert_eq! + _ => false, + } + } +} + +impl Eq for Value {} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Value::Integer(i) => { + 0u8.hash(state); + i.hash(state); + } + Value::Real(f) => { + 1u8.hash(state); + if f.is_nan() { + u64::MAX.hash(state); + } else { + f.to_bits().hash(state); + } + } + Value::Text(s) => { + 2u8.hash(state); + s.hash(state); + } + Value::Blob(b) => { + 3u8.hash(state); + b.hash(state); + } + Value::Null => { + 4u8.hash(state); + } } } } diff --git a/src/db/table/select/set_operator_evaluator.rs b/src/db/table/select/set_operator_evaluator.rs index 24e3923..2b994f1 100644 --- a/src/db/table/select/set_operator_evaluator.rs +++ b/src/db/table/select/set_operator_evaluator.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use crate::db::table::Value; pub struct SetOperatorEvaluator { @@ -26,16 +28,17 @@ impl SetOperatorEvaluator { self.stack.pop().ok_or("Error processing SELECT statement. Stack is empty".to_string()) } - // Keeps duplicates - pub fn union_all(&mut self) -> Result<(), String> { + pub fn union(&mut self) -> Result<(), String> { let mut first = self.pop()?; let second = self.pop()?; - first.extend(second); - self.push(first); + first.extend(second.into_iter()); + let set = first.into_iter().collect::>>(); + let result = set.into_iter().collect::>>(); + self.push(result); Ok(()) } - pub fn union(&mut self) -> Result<(), String> { + pub fn union_all(&mut self) -> Result<(), String> { let mut first = self.pop()?; let second = self.pop()?; first.extend(second); @@ -43,10 +46,78 @@ impl SetOperatorEvaluator { Ok(()) } pub fn intersect(&mut self) -> Result<(), String> { - todo!() + let mut first = self.pop()?; + let second = self.pop()?.into_iter().collect::>>(); + let mut index: usize = 0; + while index < first.len() { + if second.contains(&first[index]) { + index += 1; + } + else { + first.swap_remove(index); + } + } + self.push(first); + Ok(()) } pub fn except(&mut self) -> Result<(), String> { - todo!() + let mut first = self.pop()?; + let second = self.pop()?.into_iter().collect::>>(); + let mut index: usize = 0; + while index < first.len() { + if second.contains(&first[index]) { + first.swap_remove(index); + } + else { + index += 1; + } + } + self.push(first); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::db::table::test_utils::assert_table_rows_eq_unordered; + + fn rows_1() -> Vec> { + vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + ] } -} \ No newline at end of file + fn rows_2() -> Vec> { + vec![ + vec![Value::Integer(1), Value::Text("Fletcher".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Null, Value::Real(5000.0)], + ] + } + + #[test] + fn union_all_works_correctly() { + let mut evaluator = SetOperatorEvaluator::new(); + evaluator.push(rows_1()); + evaluator.push(rows_2()); + assert!(evaluator.union_all().is_ok()); + let result = evaluator.result(); + println!("{:?}", result); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(1), Value::Text("Fletcher".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Null, Value::Real(5000.0)], + ]; + assert!(result.is_ok()); + assert_table_rows_eq_unordered(expected, result.unwrap()); + } +} + + diff --git a/src/db/table/test_utils.rs b/src/db/table/test_utils.rs index 8e44468..c6d90d6 100644 --- a/src/db/table/test_utils.rs +++ b/src/db/table/test_utils.rs @@ -32,14 +32,30 @@ pub fn assert_table_rows_eq_unordered(mut expected: Vec>, mut actual: while i < a.len() && i < b.len() && a[i].compare(&b[i], &OrderByDirection::Asc) == Ordering::Equal { i += 1; } - a[i].compare(&b[i], &OrderByDirection::Asc) + if i >= a.len() && i >= b.len() { + Ordering::Equal + } else if i >= a.len() { + Ordering::Less + } else if i >= b.len() { + Ordering::Greater + } else { + a[i].compare(&b[i], &OrderByDirection::Asc) + } }); actual.sort_by(|a, b| { let mut i = 0; while i < a.len() && i < b.len() && a[i].compare(&b[i], &OrderByDirection::Asc) == Ordering::Equal { i += 1; } - a[i].compare(&b[i], &OrderByDirection::Asc) + if i >= a.len() && i >= b.len() { + Ordering::Equal + } else if i >= a.len() { + Ordering::Less + } else if i >= b.len() { + Ordering::Greater + } else { + a[i].compare(&b[i], &OrderByDirection::Asc) + } }); assert_eq!(expected, actual); } \ No newline at end of file From 1b92d8f30521bf4b56ba4e4f45081b27aa894e6d Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 7 Sep 2025 23:08:20 -0400 Subject: [PATCH 6/6] Update Set Operator evaluation --- src/db/database.rs | 2 +- src/db/table/select/mod.rs | 123 ++++++++++++++++++ src/db/table/select/set_operator_evaluator.rs | 40 +++++- src/db/table/test_utils.rs | 23 ++++ tests/set_operators.rs | 49 +++++++ tests/test_utils.rs | 42 ++++++ 6 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 tests/set_operators.rs create mode 100644 tests/test_utils.rs diff --git a/src/db/database.rs b/src/db/database.rs index 1a6a142..5e027ab 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -7,7 +7,7 @@ use crate::db::table::update; use std::collections::HashMap; pub struct Database { - tables: HashMap, + pub tables: HashMap, } impl Database { diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 7463a40..bceb871 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -34,3 +34,126 @@ pub fn select_statement_stack(database: &Database, statement: SelectStatementSta Ok(result) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::test_utils::default_database; + use crate::interpreter::ast::{SelectStatement, SelectStatementColumns, WhereStackElement, WhereCondition, Operand, Operator, LogicalOperator}; + + + #[test] + fn select_statement_stack_with_multiple_set_operators_works_correctly() { + let database = default_database(); + let statement = SelectStatementStack { + elements: vec![SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: None, + limit_clause: None, + })], + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement_stack(&database, statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_statement_stack_with_set_operator_works_correctly() { + let database = default_database(); + let statement = SelectStatementStack { + elements: vec![ + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Integer(1)), + })]), + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SetOperator(SetOperator::Intersect), + ], + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement_stack(&database, statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn select_statement_stack_works_correctly_with_multiple_set_operators() { + let database = default_database(); + let statement = SelectStatementStack { + elements: vec![SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: None, + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Integer(1)), + }), + WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Integer(2)), + }), + WhereStackElement::LogicalOperator(LogicalOperator::Or), + ]), + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SetOperator(SetOperator::Intersect), + SelectStatementStackElement::SelectStatement(SelectStatement { + table_name: "users".to_string(), + columns: SelectStatementColumns::All, + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Integer(1)), + })]), + order_by_clause: None, + limit_clause: None, + }), + SelectStatementStackElement::SetOperator(SetOperator::Except), + ], + order_by_clause: None, + limit_clause: None, + }; + let result = select_statement_stack(&database, statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } +} \ No newline at end of file diff --git a/src/db/table/select/set_operator_evaluator.rs b/src/db/table/select/set_operator_evaluator.rs index 2b994f1..6f4f146 100644 --- a/src/db/table/select/set_operator_evaluator.rs +++ b/src/db/table/select/set_operator_evaluator.rs @@ -29,8 +29,8 @@ impl SetOperatorEvaluator { } pub fn union(&mut self) -> Result<(), String> { - let mut first = self.pop()?; let second = self.pop()?; + let mut first = self.pop()?; first.extend(second.into_iter()); let set = first.into_iter().collect::>>(); let result = set.into_iter().collect::>>(); @@ -39,15 +39,16 @@ impl SetOperatorEvaluator { } pub fn union_all(&mut self) -> Result<(), String> { - let mut first = self.pop()?; let second = self.pop()?; + let mut first = self.pop()?; first.extend(second); self.push(first); Ok(()) } + pub fn intersect(&mut self) -> Result<(), String> { - let mut first = self.pop()?; let second = self.pop()?.into_iter().collect::>>(); + let mut first = self.pop()?; let mut index: usize = 0; while index < first.len() { if second.contains(&first[index]) { @@ -60,9 +61,10 @@ impl SetOperatorEvaluator { self.push(first); Ok(()) } + pub fn except(&mut self) -> Result<(), String> { - let mut first = self.pop()?; let second = self.pop()?.into_iter().collect::>>(); + let mut first = self.pop()?; let mut index: usize = 0; while index < first.len() { if second.contains(&first[index]) { @@ -105,7 +107,6 @@ mod test { evaluator.push(rows_2()); assert!(evaluator.union_all().is_ok()); let result = evaluator.result(); - println!("{:?}", result); assert!(result.is_ok()); let expected = vec![ vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], @@ -118,6 +119,35 @@ mod test { assert!(result.is_ok()); assert_table_rows_eq_unordered(expected, result.unwrap()); } + + #[test] + fn intersect_works_correctly() { + let mut evaluator = SetOperatorEvaluator::new(); + evaluator.push(rows_1()); + evaluator.push(rows_2()); + assert!(evaluator.intersect().is_ok()); + let result = evaluator.result(); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + ]; + assert_table_rows_eq_unordered(expected, result.unwrap()); + } + + #[test] + fn except_works_correctly() { + let mut evaluator = SetOperatorEvaluator::new(); + evaluator.push(rows_1()); + evaluator.push(rows_2()); + assert!(evaluator.except().is_ok()); + let result = evaluator.result(); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + ]; + assert_table_rows_eq_unordered(expected, result.unwrap()); + } } diff --git a/src/db/table/test_utils.rs b/src/db/table/test_utils.rs index c6d90d6..e7acb27 100644 --- a/src/db/table/test_utils.rs +++ b/src/db/table/test_utils.rs @@ -1,4 +1,6 @@ #[cfg(test)] +use crate::db::database::Database; +#[cfg(test)] use crate::db::table::{Table, Value, DataType, ColumnDefinition}; #[cfg(test)] use crate::interpreter::ast::OrderByDirection; @@ -25,6 +27,27 @@ pub fn default_table() -> Table { } } +#[cfg(test)] +pub fn default_database() -> Database { + let mut database = Database::new(); + database.tables.insert("users".to_string(), Table { + name: "users".to_string(), + columns: 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![]}, + ColumnDefinition {name: "money".to_string(), data_type: DataType::Real, constraints: vec![]}, + ], + rows: vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)], + ], + }); + database +} + #[cfg(test)] pub fn assert_table_rows_eq_unordered(mut expected: Vec>, mut actual: Vec>) { expected.sort_by(|a, b| { diff --git a/tests/set_operators.rs b/tests/set_operators.rs new file mode 100644 index 0000000..3ff8aa2 --- /dev/null +++ b/tests/set_operators.rs @@ -0,0 +1,49 @@ +mod test_utils; + +use mollydb::db::database::Database; +use mollydb::interpreter::run_sql; +use mollydb::db::table::Value; + +#[test] +fn test_set_operators() { + let mut database = Database::new(); + let sql = " + CREATE TABLE users ( + id INTEGER, + name TEXT + ); + INSERT INTO users (id, name) VALUES (1, 'John'), (2, 'Jane'), (3, 'Jim'), (4, 'Jack'); + SELECT name FROM users WHERE id = 1 UNION SELECT name FROM users WHERE id = 4; + "; + let mut result = run_sql(&mut database, sql); + assert!(result.iter().all(|result| result.is_ok())); + let expected = vec![ + vec![Value::Text("John".to_string())], + vec![Value::Text("Jack".to_string())], + ]; + test_utils::assert_table_rows_eq_unordered(expected, result.pop().unwrap().unwrap().unwrap()); + assert!(result.into_iter().all(|result| result.is_ok() && result.unwrap().is_none())); +} + +#[test] +fn test_set_operators_with_clauses_and_parentheses() { + // let mut database = Database::new(); + // let sql = " + // CREATE TABLE users ( + // id INTEGER, + // name TEXT + // ); + // INSERT INTO users (id, name) VALUES (1, 'John'), (2, 'Jane'), (3, 'Jane'), (4, 'Jack'); + // (SELECT id, name FROM users WHERE id > 1 INTERSECT SELECT id, name FROM users WHERE id < 4) + // ORDER BY name ASC, id DESC LIMIT 1; + // "; + // let mut result = run_sql(&mut database, sql); + // println!("{:?}", result); + // assert!(result.iter().all(|result| result.is_ok())); + // let expected = vec![ + // vec![Value::Integer(3), Value::Text("Jane".to_string())], + // ]; + // test_utils::assert_table_rows_eq_unordered(expected, result.pop().unwrap().unwrap().unwrap()); + // assert!(result.into_iter().all(|result| result.is_ok() && result.unwrap().is_none())); + // NOT CURRENTLY SUPPORTED NEED TO ADD. +} \ No newline at end of file diff --git a/tests/test_utils.rs b/tests/test_utils.rs new file mode 100644 index 0000000..6575f74 --- /dev/null +++ b/tests/test_utils.rs @@ -0,0 +1,42 @@ +#[cfg(test)] +use mollydb::db::table::Value; +#[cfg(test)] +use mollydb::interpreter::ast::OrderByDirection; +#[cfg(test)] +use std::cmp::Ordering; + + +#[cfg(test)] +pub fn assert_table_rows_eq_unordered(mut expected: Vec>, mut actual: Vec>) { + expected.sort_by(|a, b| { + let mut i = 0; + while i < a.len() && i < b.len() && a[i].compare(&b[i], &OrderByDirection::Asc) == Ordering::Equal { + i += 1; + } + if i >= a.len() && i >= b.len() { + Ordering::Equal + } else if i >= a.len() { + Ordering::Less + } else if i >= b.len() { + Ordering::Greater + } else { + a[i].compare(&b[i], &OrderByDirection::Asc) + } + }); + actual.sort_by(|a, b| { + let mut i = 0; + while i < a.len() && i < b.len() && a[i].compare(&b[i], &OrderByDirection::Asc) == Ordering::Equal { + i += 1; + } + if i >= a.len() && i >= b.len() { + Ordering::Equal + } else if i >= a.len() { + Ordering::Less + } else if i >= b.len() { + Ordering::Greater + } else { + a[i].compare(&b[i], &OrderByDirection::Asc) + } + }); + assert_eq!(expected, actual); +} \ No newline at end of file