From 3ee0aaef6aaa96feb309fe09b947ab45888c4ec5 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 21:08:58 -0400 Subject: [PATCH 1/4] Restructure files to support better abstraction --- src/db/database.rs | 14 ++++-- src/db/table/common.rs | 16 ------ src/db/table/delete/mod.rs | 15 ++++++ src/db/table/helpers/common.rs | 49 +++++++++++++++++++ .../table/{select => helpers}/limit_clause.rs | 0 src/db/table/helpers/mod.rs | 5 ++ .../{select => helpers}/order_by_clause.rs | 0 .../{select => helpers}/where_condition.rs | 0 .../table/{select => helpers}/where_stack.rs | 4 +- src/db/table/insert/mod.rs | 2 +- src/db/table/mod.rs | 4 +- src/db/table/select/mod.rs | 45 ++--------------- 12 files changed, 91 insertions(+), 63 deletions(-) delete mode 100644 src/db/table/common.rs create mode 100644 src/db/table/delete/mod.rs create mode 100644 src/db/table/helpers/common.rs rename src/db/table/{select => helpers}/limit_clause.rs (100%) create mode 100644 src/db/table/helpers/mod.rs rename src/db/table/{select => helpers}/order_by_clause.rs (100%) rename src/db/table/{select => helpers}/where_condition.rs (100%) rename src/db/table/{select => helpers}/where_stack.rs (97%) diff --git a/src/db/database.rs b/src/db/database.rs index 969a09c..c36ea74 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -1,7 +1,8 @@ use crate::db::table::{Table, Value}; -use crate::cli::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatement}; +use crate::cli::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatement, DeleteStatement}; use crate::db::table::select; use crate::db::table::insert; +use crate::db::table::delete; use std::collections::HashMap; pub struct Database { @@ -32,8 +33,9 @@ impl Database { SqlStatement::UpdateStatement(_statement) => { todo!(); }, - SqlStatement::DeleteStatement(_statement) => { - todo!(); + SqlStatement::DeleteStatement(statement) => { + self.delete_from_table(statement)?; + Ok(None) }, } } @@ -59,6 +61,12 @@ impl Database { Ok(rows) } + fn delete_from_table(&mut self, statement: DeleteStatement) -> Result<(), String> { + let table = self.get_table_mut(&statement.table_name)?; + delete::delete(table, statement)?; + Ok(()) + } + fn has_table(&self, table_name: &str) -> bool { self.tables.contains_key(table_name) } diff --git a/src/db/table/common.rs b/src/db/table/common.rs deleted file mode 100644 index b379a42..0000000 --- a/src/db/table/common.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::db::table::{Table, Value, DataType}; - -pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result, String> { - if row.len() != table.width() { - return Err(format!("Rows have incorrect width")); - } - - let mut row_values: Vec = vec![]; - for (i, value) in row.iter().enumerate() { - if value.get_type() != table.columns[i].data_type && value.get_type() != DataType::Null { - return Err(format!("Data type mismatch for column {}", table.columns[i].name)); - } - row_values.push(row[i].clone()); - } - return Ok(row_values); -} \ No newline at end of file diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs new file mode 100644 index 0000000..f89ad82 --- /dev/null +++ b/src/db/table/delete/mod.rs @@ -0,0 +1,15 @@ +use crate::db::table::Table; +use crate::cli::ast::DeleteStatement; +// use crate::db::table::helpers::common::get_initial_rows; + + +pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result<(), String> { + let _row_indexes_to_delete = get_row_indexes_to_delete(table, statement)?; + Ok(()) +} + + +fn get_row_indexes_to_delete(_table: &mut Table, _statement: DeleteStatement) -> Result, String> { + // let rows = get_initial_rows(table, &statement)?; + Ok(vec![]) +} \ No newline at end of file diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs new file mode 100644 index 0000000..029b318 --- /dev/null +++ b/src/db/table/helpers/common.rs @@ -0,0 +1,49 @@ +use crate::db::table::{Table, Value, DataType}; +use crate::cli::ast::{SelectStatement, SelectStatementColumns}; +use crate::db::table::helpers::where_stack::matches_where_stack; + +pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result, String> { + if row.len() != table.width() { + return Err(format!("Rows have incorrect width")); + } + + let mut row_values: Vec = vec![]; + for (i, value) in row.iter().enumerate() { + if value.get_type() != table.columns[i].data_type && value.get_type() != DataType::Null { + return Err(format!("Data type mismatch for column {}", table.columns[i].name)); + } + row_values.push(row[i].clone()); + } + return Ok(row_values); +} + +pub fn get_initial_rows(table: &Table, statement: &SelectStatement) -> Result>, String> { + let mut rows: Vec> = vec![]; + if let Some(where_stack) = &statement.where_clause { + for row in table.rows.iter() { + if matches_where_stack(table, &row, &where_stack)? { + rows.push(get_columns_from_row(table, &row, &statement.columns)?); + } + } + } else { + for row in table.rows.iter() { + rows.push(get_columns_from_row(table, &row, &statement.columns)?); + } + } + Ok(rows) +} + +pub fn get_columns_from_row(table: &Table, row: &Vec, selected_columns: &SelectStatementColumns) -> Result, String> { + let mut row_values: Vec = vec![]; + if *selected_columns == SelectStatementColumns::All { + return Ok(validate_and_clone_row(table, row)?); + } else { + let specific_selected_columns = selected_columns.columns()?; + for (i, column) in table.columns.iter().enumerate() { + if (*specific_selected_columns).contains(&column.name) { + row_values.push(row[i].clone()); + } + } + } + return Ok(row_values); +} \ No newline at end of file diff --git a/src/db/table/select/limit_clause.rs b/src/db/table/helpers/limit_clause.rs similarity index 100% rename from src/db/table/select/limit_clause.rs rename to src/db/table/helpers/limit_clause.rs diff --git a/src/db/table/helpers/mod.rs b/src/db/table/helpers/mod.rs new file mode 100644 index 0000000..a1901b7 --- /dev/null +++ b/src/db/table/helpers/mod.rs @@ -0,0 +1,5 @@ +pub mod limit_clause; +pub mod order_by_clause; +pub mod common; +pub mod where_stack; +pub mod where_condition; \ No newline at end of file diff --git a/src/db/table/select/order_by_clause.rs b/src/db/table/helpers/order_by_clause.rs similarity index 100% rename from src/db/table/select/order_by_clause.rs rename to src/db/table/helpers/order_by_clause.rs diff --git a/src/db/table/select/where_condition.rs b/src/db/table/helpers/where_condition.rs similarity index 100% rename from src/db/table/select/where_condition.rs rename to src/db/table/helpers/where_condition.rs diff --git a/src/db/table/select/where_stack.rs b/src/db/table/helpers/where_stack.rs similarity index 97% rename from src/db/table/select/where_stack.rs rename to src/db/table/helpers/where_stack.rs index d146ff4..08804f4 100644 --- a/src/db/table/select/where_stack.rs +++ b/src/db/table/helpers/where_stack.rs @@ -1,11 +1,11 @@ use crate::cli::ast::{WhereStackElement, LogicalOperator}; use crate::db::table::{Table, Value}; -use crate::db::table::select::where_condition::matches_where_clause; +use crate::db::table::helpers::where_condition::matches_where_clause; // This file holds the logic for whether a row matches a where stack which is a vec of WhereConditions // and logical operators stored in Reverse Polish Notation. pub fn matches_where_stack(table: &Table, row: &Vec, where_stack: &Vec) -> Result { - let mut result_stack = vec![]; + let mut result_stack = vec![]; for where_stack_element in where_stack { match where_stack_element { WhereStackElement::Condition(where_condition) => { diff --git a/src/db/table/insert/mod.rs b/src/db/table/insert/mod.rs index 49b0c50..f138c8e 100644 --- a/src/db/table/insert/mod.rs +++ b/src/db/table/insert/mod.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, VecDeque}; use crate::db::table::{Table, Value}; use crate::cli::ast::InsertIntoStatement; -use crate::db::table::common::validate_and_clone_row; +use crate::db::table::helpers::common::validate_and_clone_row; pub fn insert(table: &mut Table, statement: InsertIntoStatement) -> Result<(), String> { diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 173bdb4..90c4a43 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -4,7 +4,9 @@ use crate::cli::ast::OrderByDirection; pub mod select; pub mod insert; -pub mod common; +pub mod delete; +pub mod helpers; + #[derive(Debug, PartialEq)] pub enum DataType { diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index e4dd3cf..08a3bbc 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,58 +1,23 @@ -pub mod where_stack; -pub mod where_condition; -pub mod limit_clause; -pub mod order_by_clause; use crate::db::table::{Table, Value}; -use crate::cli::ast::{SelectStatement, SelectStatementColumns}; -use crate::db::table::common::validate_and_clone_row; +use crate::cli::ast::{SelectStatement}; +use crate::db::table::helpers::{common::get_initial_rows, order_by_clause::get_ordered_rows, limit_clause::get_limited_rows}; + pub fn select(table: &Table, statement: SelectStatement) -> Result>, String> { let mut rows = get_initial_rows(table, &statement)?; if let Some(order_by_clause) = statement.order_by_clause { - rows = order_by_clause::get_ordered_rows(table, rows, &order_by_clause)?; + rows = get_ordered_rows(table, rows, &order_by_clause)?; } if let Some(limit_clause) = &statement.limit_clause { - rows = limit_clause::get_limited_rows(rows, limit_clause)?; + rows = get_limited_rows(rows, limit_clause)?; } return Ok(rows); } -pub fn get_initial_rows(table: &Table, statement: &SelectStatement) -> Result>, String> { - let mut rows: Vec> = vec![]; - if let Some(where_stack) = &statement.where_clause { - for row in table.rows.iter() { - if where_stack::matches_where_stack(table, &row, &where_stack)? { - rows.push(get_columns_from_row(table, &row, &statement.columns)?); - } - } - } else { - for row in table.rows.iter() { - rows.push(get_columns_from_row(table, &row, &statement.columns)?); - } - } - Ok(rows) -} - - -pub fn get_columns_from_row(table: &Table, row: &Vec, selected_columns: &SelectStatementColumns) -> Result, String> { - let mut row_values: Vec = vec![]; - if *selected_columns == SelectStatementColumns::All { - return Ok(validate_and_clone_row(table, row)?); - } else { - let specific_selected_columns = selected_columns.columns()?; - for (i, column) in table.columns.iter().enumerate() { - if (*specific_selected_columns).contains(&column.name) { - row_values.push(row[i].clone()); - } - } - } - return Ok(row_values); -} - #[cfg(test)] mod tests { use super::*; From b896a815cf10cd7935ad3a649ebcfb7032d9f157 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 21:12:24 -0400 Subject: [PATCH 2/4] Abstract the get_initial_rows func to improve general purpose --- src/db/table/delete/mod.rs | 6 +++--- src/db/table/helpers/common.rs | 20 +++++++++++++++----- src/db/table/select/mod.rs | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs index f89ad82..7c34954 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/delete/mod.rs @@ -1,6 +1,6 @@ use crate::db::table::Table; use crate::cli::ast::DeleteStatement; -// use crate::db::table::helpers::common::get_initial_rows; +use crate::db::table::helpers::common::get_initial_rows; pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result<(), String> { @@ -9,7 +9,7 @@ pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result<(), Strin } -fn get_row_indexes_to_delete(_table: &mut Table, _statement: DeleteStatement) -> Result, String> { - // let rows = get_initial_rows(table, &statement)?; +fn get_row_indexes_to_delete(table: &mut Table, statement: DeleteStatement) -> Result, String> { + let _rows = get_initial_rows(table, statement.where_clause, None)?; Ok(vec![]) } \ No newline at end of file diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index 029b318..532e5ea 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -1,5 +1,5 @@ use crate::db::table::{Table, Value, DataType}; -use crate::cli::ast::{SelectStatement, SelectStatementColumns}; +use crate::cli::ast::{SelectStatementColumns, WhereStackElement}; use crate::db::table::helpers::where_stack::matches_where_stack; pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result, String> { @@ -17,17 +17,27 @@ pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result Result>, String> { +pub fn get_initial_rows(table: &Table, where_clause: Option>, columns: Option<&SelectStatementColumns>) -> Result>, String> { let mut rows: Vec> = vec![]; - if let Some(where_stack) = &statement.where_clause { + if let Some(where_stack) = &where_clause { for row in table.rows.iter() { if matches_where_stack(table, &row, &where_stack)? { - rows.push(get_columns_from_row(table, &row, &statement.columns)?); + if let Some(columns) = &columns { + rows.push(get_columns_from_row(table, &row, &columns)?); + } + else { + rows.push(validate_and_clone_row(table, &row)?); + } } } } else { for row in table.rows.iter() { - rows.push(get_columns_from_row(table, &row, &statement.columns)?); + if let Some(columns) = &columns { + rows.push(get_columns_from_row(table, &row, &columns)?); + } + else { + rows.push(validate_and_clone_row(table, &row)?); + } } } Ok(rows) diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 08a3bbc..b88f201 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -5,7 +5,7 @@ use crate::db::table::helpers::{common::get_initial_rows, order_by_clause::get_o pub fn select(table: &Table, statement: SelectStatement) -> Result>, String> { - let mut rows = get_initial_rows(table, &statement)?; + let mut rows = get_initial_rows(table, statement.where_clause, Some(&statement.columns))?; if let Some(order_by_clause) = statement.order_by_clause { rows = get_ordered_rows(table, rows, &order_by_clause)?; From d49d5b654c256c2d032ff0234c8a0f86641de9e6 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 22:39:07 -0400 Subject: [PATCH 3/4] Full delete functionality on database --- src/cli/ast/test_utils.rs | 1 + src/db/table/delete/mod.rs | 146 +++++++++++++++++++++++- src/db/table/helpers/common.rs | 41 ++++--- src/db/table/helpers/limit_clause.rs | 53 +++------ src/db/table/helpers/order_by_clause.rs | 86 ++++---------- src/db/table/mod.rs | 3 +- src/db/table/select/mod.rs | 35 ++---- src/db/table/test_utils.rs | 45 ++++++++ 8 files changed, 262 insertions(+), 148 deletions(-) create mode 100644 src/db/table/test_utils.rs diff --git a/src/cli/ast/test_utils.rs b/src/cli/ast/test_utils.rs index f76b02f..dbd796f 100644 --- a/src/cli/ast/test_utils.rs +++ b/src/cli/ast/test_utils.rs @@ -14,6 +14,7 @@ pub fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { } } +#[cfg(test)] pub fn token_with_location(tt: TokenTypes, val: &'static str, col: usize, line: usize) -> Token<'static> { Token { token_type: tt, diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs index 7c34954..7cbc185 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/delete/mod.rs @@ -1,15 +1,151 @@ +use std::collections::HashSet; + use crate::db::table::Table; use crate::cli::ast::DeleteStatement; -use crate::db::table::helpers::common::get_initial_rows; +use crate::db::table::helpers::{ + common::get_row_indicies_matching_where_clause, + order_by_clause::get_ordered_row_indicies, + limit_clause::get_limited_row_indicies +}; pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result<(), String> { - let _row_indexes_to_delete = get_row_indexes_to_delete(table, statement)?; + let row_indicies_to_delete = get_row_indicies_to_delete(table, statement)?; + swap_remove_bulk(table, row_indicies_to_delete)?; Ok(()) } -fn get_row_indexes_to_delete(table: &mut Table, statement: DeleteStatement) -> Result, String> { - let _rows = get_initial_rows(table, statement.where_clause, None)?; - Ok(vec![]) +fn get_row_indicies_to_delete(table: &mut Table, statement: DeleteStatement) -> Result, String> { + let mut row_indicies = get_row_indicies_matching_where_clause(table, statement.where_clause)?; + + if let Some(order_by_clause) = statement.order_by_clause { + row_indicies = get_ordered_row_indicies(table, row_indicies, &order_by_clause)?; + } + + if let Some(limit_clause) = statement.limit_clause { + row_indicies = get_limited_row_indicies(row_indicies, &limit_clause)?; + } + + return Ok(row_indicies); +} + +fn swap_remove_bulk(table: &mut Table, row_indicies: Vec) -> Result<(), String> { + let table_len = table.rows.len()-1; + let mut row_indicies_set = row_indicies.iter().collect::>(); + let mut right_pointer = 0; + let mut iter = row_indicies.iter(); + + while let Some(to_swap) = iter.next() { + if *to_swap == (table_len - right_pointer) { + row_indicies_set.remove(to_swap); + right_pointer += 1; + } + else { + table.rows.swap(*to_swap, table_len - right_pointer); + row_indicies_set.remove(to_swap); + right_pointer += 1; + } + } + for _ in 0..right_pointer { + table.rows.pop(); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::Value; + use crate::cli::ast::{WhereStackElement, Operator, Operand, WhereCondition, OrderByDirection, OrderByClause}; + use crate::db::table::test_utils::{default_table, assert_table_rows_eq_unordered}; + use crate::cli::ast::LimitClause; + + #[test] + fn delete_from_table_works_correctly() { + let mut table = default_table(); + let statement = DeleteStatement { + table_name: "users".to_string(), + where_clause: Some(vec![ + WhereStackElement::Condition(WhereCondition { + l_side: Operand::Identifier("id".to_string()), + operator: Operator::Equals, + r_side: Operand::Value(Value::Integer(2)), + }) + ]), + order_by_clause: None, + limit_clause: None, + }; + let result = delete(&mut 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(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_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn delete_from_table_with_all_clauses_works_correctly() { + let mut table = default_table(); + table.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)], + vec![Value::Integer(5), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)], + vec![Value::Integer(6), Value::Text("John".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(7), Value::Text("John".to_string()), Value::Integer(35), Value::Real(3000.0)], + ]; + let statement = DeleteStatement { + table_name: "users".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: Some(vec![OrderByClause { column: "id".to_string(), direction: OrderByDirection::Desc }]), + limit_clause: Some(LimitClause { limit: Value::Integer(1), offset: Some(Value::Integer(2)) }), + }; + let result = delete(&mut 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)], + vec![Value::Integer(6), Value::Text("John".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(7), Value::Text("John".to_string()), Value::Integer(35), Value::Real(3000.0)], + ]; + assert_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn delete_multiple_rows_works_correctly() { + let mut table = default_table(); + let statement = DeleteStatement { + table_name: "users".to_string(), + where_clause: Some(vec![WhereStackElement::Condition(WhereCondition { l_side: Operand::Identifier("id".to_string()), operator: Operator::GreaterThan, r_side: Operand::Value(Value::Integer(1)) })]), + order_by_clause: None, + limit_clause: None, + }; + let result = delete(&mut 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_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn delete_all_rows_works_correctly() { + let mut table = default_table(); + let statement = DeleteStatement { + table_name: "users".to_string(), + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = delete(&mut table, statement); + assert!(result.is_ok()); + let expected = vec![]; + assert_table_rows_eq_unordered(expected, table.rows); + } } \ No newline at end of file diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index 532e5ea..15793aa 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -17,32 +17,35 @@ pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result>, columns: Option<&SelectStatementColumns>) -> Result>, String> { +pub fn get_row_columns_from_indicies(table: &Table, row_indicies: Vec, columns: Option<&SelectStatementColumns>) -> Result>, String> { let mut rows: Vec> = vec![]; - if let Some(where_stack) = &where_clause { - for row in table.rows.iter() { - if matches_where_stack(table, &row, &where_stack)? { - if let Some(columns) = &columns { - rows.push(get_columns_from_row(table, &row, &columns)?); - } - else { - rows.push(validate_and_clone_row(table, &row)?); - } - } + for index in row_indicies { + let row = table.rows[index].clone(); + if let Some(columns) = columns { + rows.push(get_columns_from_row(table, &row, columns)?); } - } else { - for row in table.rows.iter() { - if let Some(columns) = &columns { - rows.push(get_columns_from_row(table, &row, &columns)?); - } - else { - rows.push(validate_and_clone_row(table, &row)?); - } + else { + rows.push(validate_and_clone_row(table, &row)?); } } Ok(rows) } +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() { + if matches_where_stack(table, &row, &where_clause)? { + row_indicies.push(i); + } + } + return Ok(row_indicies); + } + else { + return Ok((0..table.rows.len()).collect()); + } +} + pub fn get_columns_from_row(table: &Table, row: &Vec, selected_columns: &SelectStatementColumns) -> Result, String> { let mut row_values: Vec = vec![]; if *selected_columns == SelectStatementColumns::All { diff --git a/src/db/table/helpers/limit_clause.rs b/src/db/table/helpers/limit_clause.rs index f318ec4..dd6eced 100644 --- a/src/db/table/helpers/limit_clause.rs +++ b/src/db/table/helpers/limit_clause.rs @@ -4,8 +4,8 @@ use crate::cli::ast::LimitClause; use crate::db::table::Value; -pub fn get_limited_rows<'a>(rows: Vec>, limit_clause: &LimitClause) -> Result>, String> { - let mut index = 0; +pub fn get_limited_row_indicies(rows: Vec, limit_clause: &LimitClause) -> Result, String> { + let mut index: usize = 0; if let Some(offset) = &limit_clause.offset && let Value::Integer(offset) = offset { index = *offset as usize; } @@ -24,9 +24,9 @@ pub fn get_limited_rows<'a>(rows: Vec>, limit_clause: &LimitClause) - _ => return Err("Limit must be an integer".to_string()), // The parser should have already validated this }; - let mut limited_rows: Vec> = vec![]; + let mut limited_rows: Vec = vec![]; for i in index..limit { - limited_rows.push(rows[i].clone()); + limited_rows.push(rows[i]); } return Ok(limited_rows); } @@ -36,19 +36,8 @@ pub fn get_limited_rows<'a>(rows: Vec>, limit_clause: &LimitClause) - mod tests { use super::*; - fn default_rows() -> Vec> { - vec![ - vec![Value::Integer(1)], - vec![Value::Integer(2)], - vec![Value::Integer(3)], - vec![Value::Integer(4)], - vec![Value::Integer(5)], - vec![Value::Integer(6)], - vec![Value::Integer(7)], - vec![Value::Integer(8)], - vec![Value::Integer(9)], - vec![Value::Integer(10)], - ] + fn default_rows() -> Vec { + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9] } fn generate_limit_clause(limit: i64, offset: Option) -> LimitClause { @@ -61,7 +50,7 @@ mod tests { #[test] fn no_offset_and_limit_is_equal_to_rows_length() { let limit_clause = generate_limit_clause(10, None); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); assert_eq!(default_rows(), result.unwrap()); } @@ -69,7 +58,7 @@ mod tests { #[test] fn no_offset_and_limit_is_greater_than_rows_length() { let limit_clause = generate_limit_clause(15, None); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); assert_eq!(default_rows(), result.unwrap()); } @@ -77,22 +66,16 @@ mod tests { #[test] fn no_offset_and_limit_is_less_than_rows_length() { let limit_clause = generate_limit_clause(5, None); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(1)], - vec![Value::Integer(2)], - vec![Value::Integer(3)], - vec![Value::Integer(4)], - vec![Value::Integer(5)], - ]; + let expected = vec![0, 1, 2, 3, 4]; assert_eq!(expected, result.unwrap()); } #[test] fn no_offset_and_negative_limit_returns_all_rows() { let limit_clause = generate_limit_clause(-1, None); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); assert_eq!(default_rows(), result.unwrap()); } @@ -100,24 +83,18 @@ mod tests { #[test] fn offset_and_limit_is_generated_correctly() { let limit_clause = generate_limit_clause(5, Some(1)); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(2)], - vec![Value::Integer(3)], - vec![Value::Integer(4)], - vec![Value::Integer(5)], - vec![Value::Integer(6)], - ]; + let expected = vec![1, 2, 3, 4, 5]; assert_eq!(expected, result.unwrap()); } #[test] fn offset_is_greater_than_rows_length_returns_empty_rows() { let limit_clause = generate_limit_clause(5, Some(10)); - let result = get_limited_rows(default_rows(), &limit_clause); + let result = get_limited_row_indicies(default_rows(), &limit_clause); assert!(result.is_ok()); - let expected: Vec> = vec![]; + let expected: Vec = vec![]; assert_eq!(expected, result.unwrap()); } } \ No newline at end of file diff --git a/src/db/table/helpers/order_by_clause.rs b/src/db/table/helpers/order_by_clause.rs index cf1572e..af89f56 100644 --- a/src/db/table/helpers/order_by_clause.rs +++ b/src/db/table/helpers/order_by_clause.rs @@ -8,11 +8,11 @@ use crate::db::table::Value; // This sorting algorithm will always return a stable sort, this is given by all of the order columns // then the input order of the rows is maintained with any required tie breaking. -pub fn get_ordered_rows(table: &Table, mut rows: Vec>, order_by_clauses: &Vec) -> Result>, String> { - rows.sort_by(|a, b| { - perform_comparions(table, a, b, order_by_clauses) +pub fn get_ordered_row_indicies(table: &Table, mut row_indicies: Vec, order_by_clauses: &Vec) -> Result, String> { + row_indicies.sort_by(|a, b| { + perform_comparions(table, &table.rows[*a], &table.rows[*b], order_by_clauses) }); - return Ok(rows); + return Ok(row_indicies); } fn perform_comparions(table: &Table, row1: &Vec, row2: &Vec, order_by_clauses: &Vec) -> Ordering { @@ -47,95 +47,59 @@ mod tests { ColumnDefinition {name: "money".to_string(), data_type: DataType::Real, constraints: vec![]}, ColumnDefinition {name: "some_data".to_string(), data_type: DataType::Blob, constraints: vec![]}, ], - rows: vec![], + rows: vec![ + vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], + vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], + vec![Value::Null, Value::Null, Value::Null, Value::Null], + vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], + vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], + vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], + vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], + ], } } - - fn default_rows() -> Vec> { - vec![ - vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], - vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], - vec![Value::Null, Value::Null, Value::Null, Value::Null], - vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], - vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], - vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], - vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], - ] - } #[test] fn get_ordered_rows_returns_rows_with_id_column_returns_rows_in_correct_order() { let table = default_table(); - let rows = default_rows(); + let row_indicies: Vec = vec![0, 1, 2, 3, 4, 5, 6]; let order_by_clauses = vec![OrderByClause {column: "id".to_string(), direction: OrderByDirection::Asc}]; - let result = get_ordered_rows(&table, rows, &order_by_clauses); + let result = get_ordered_row_indicies(&table, row_indicies, &order_by_clauses); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Null, Value::Null, Value::Null, Value::Null], - vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], - vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], - vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], - vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], - vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], - vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], - ]; + let expected = vec![2, 1, 6, 3, 0, 4, 5]; assert_eq!(expected, result.unwrap()); } #[test] fn get_ordered_rows_returns_rows_with_name_column_returns_rows_in_correct_order() { let table = default_table(); - let rows = default_rows(); + let row_indicies: Vec = vec![0, 1, 2, 3, 4, 5, 6]; let order_by_clauses = vec![OrderByClause {column: "name".to_string(), direction: OrderByDirection::Asc}]; - let result = get_ordered_rows(&table, rows, &order_by_clauses); + let result = get_ordered_row_indicies(&table, row_indicies, &order_by_clauses); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Null, Value::Null, Value::Null, Value::Null], - vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], - vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], - vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], - vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], - vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], - vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], - ]; + let expected = vec![2, 5, 6, 1, 3, 4, 0]; assert_eq!(expected, result.unwrap()); } #[test] fn get_ordered_rows_ordered_descending_returns_rows_in_correct_order() { let table = default_table(); - let rows = default_rows(); + let row_indicies: Vec = vec![0, 1, 2, 3, 4, 5, 6]; let order_by_clauses = vec![OrderByClause {column: "money".to_string(), direction: OrderByDirection::Desc}]; - let result = get_ordered_rows(&table, rows, &order_by_clauses); + let result = get_ordered_row_indicies(&table, row_indicies, &order_by_clauses); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], - vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], - vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], - vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], - vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], - vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], - vec![Value::Null, Value::Null, Value::Null, Value::Null], - ]; + let expected = vec![6, 0, 3, 4, 1, 5, 2]; assert_eq!(expected, result.unwrap()); } #[test] fn get_ordered_rows_multiple_sort_orders_returns_rows_in_correct_order() { let table = default_table(); - let rows = default_rows(); + let row_indicies: Vec = vec![0, 1, 2, 3, 4, 5, 6]; let order_by_clauses = vec![OrderByClause {column: "name".to_string(), direction: OrderByDirection::Desc}, OrderByClause {column: "some_data".to_string(), direction: OrderByDirection::Asc}]; - let result = get_ordered_rows(&table, rows, &order_by_clauses); + let result = get_ordered_row_indicies(&table, row_indicies, &order_by_clauses); assert!(result.is_ok()); - let expected = vec![ - vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())], - vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())], - vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())], - vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())], - vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())], - vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())], - vec![Value::Null, Value::Null, Value::Null, Value::Null], - ]; + let expected = vec![0, 4, 3, 1, 5, 6, 2]; assert_eq!(expected, result.unwrap()); } } \ No newline at end of file diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 90c4a43..3503e52 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -6,6 +6,8 @@ pub mod select; pub mod insert; pub mod delete; pub mod helpers; +#[cfg(test)] +pub mod test_utils; #[derive(Debug, PartialEq)] @@ -38,7 +40,6 @@ pub enum Value { Null } - impl Value { pub fn get_type(&self) -> DataType { match self { diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index b88f201..899bcd6 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,49 +1,36 @@ use crate::db::table::{Table, Value}; use crate::cli::ast::{SelectStatement}; -use crate::db::table::helpers::{common::get_initial_rows, order_by_clause::get_ordered_rows, limit_clause::get_limited_rows}; +use crate::db::table::helpers::{ + common::{get_row_columns_from_indicies, get_row_indicies_matching_where_clause}, + order_by_clause::get_ordered_row_indicies, + limit_clause::get_limited_row_indicies +}; pub fn select(table: &Table, statement: SelectStatement) -> Result>, String> { - let mut rows = get_initial_rows(table, statement.where_clause, Some(&statement.columns))?; + let mut row_indicies = get_row_indicies_matching_where_clause(table, statement.where_clause)?; if let Some(order_by_clause) = statement.order_by_clause { - rows = get_ordered_rows(table, rows, &order_by_clause)?; + row_indicies = get_ordered_row_indicies(table, row_indicies, &order_by_clause)?; } if let Some(limit_clause) = &statement.limit_clause { - rows = get_limited_rows(rows, limit_clause)?; + row_indicies = get_limited_row_indicies(row_indicies, limit_clause)?; } - return Ok(rows); + return Ok(get_row_columns_from_indicies(table, row_indicies, Some(&statement.columns))?); } #[cfg(test)] mod tests { use super::*; - use crate::db::table::{Table, Value, DataType, ColumnDefinition}; + use crate::db::table::Value; use crate::cli::ast::{SelectStatementColumns, LimitClause, OrderByClause, OrderByDirection, Operator}; use crate::cli::ast::WhereStackElement; use crate::cli::ast::WhereCondition; use crate::cli::ast::Operand; - - fn default_table() -> Table { - 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)], - ], - } - } + use crate::db::table::test_utils::default_table; #[test] fn select_with_all_tokens_is_generated_correctly() { diff --git a/src/db/table/test_utils.rs b/src/db/table/test_utils.rs new file mode 100644 index 0000000..5363b29 --- /dev/null +++ b/src/db/table/test_utils.rs @@ -0,0 +1,45 @@ +#[cfg(test)] +use crate::db::table::{Table, Value, DataType, ColumnDefinition}; +#[cfg(test)] +use crate::cli::ast::OrderByDirection; +#[cfg(test)] +use std::cmp::Ordering; + + +#[cfg(test)] +pub fn default_table() -> Table { + 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)], + ], + } +} + +#[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; + } + 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) + }); + assert_eq!(expected, actual); +} \ No newline at end of file From ecb00b474722e1b1c167d0a324340a5dd4adb34f Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 22:42:47 -0400 Subject: [PATCH 4/4] Tests for deleting all rows and empty table --- src/db/table/delete/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs index 7cbc185..8e91fb1 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/delete/mod.rs @@ -31,6 +31,12 @@ fn get_row_indicies_to_delete(table: &mut Table, statement: DeleteStatement) -> } fn swap_remove_bulk(table: &mut Table, row_indicies: Vec) -> Result<(), String> { + if table.rows.len() == 0 { + if row_indicies.len() != 0 { + unreachable!(); + } + return Ok(()); + } let table_len = table.rows.len()-1; let mut row_indicies_set = row_indicies.iter().collect::>(); let mut right_pointer = 0; @@ -148,4 +154,20 @@ mod tests { let expected = vec![]; assert_table_rows_eq_unordered(expected, table.rows); } + + #[test] + fn delete_from_empty_table_works_correctly() { + let mut table = default_table(); + table.rows = vec![]; + let statement = DeleteStatement { + table_name: "users".to_string(), + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = delete(&mut table, statement); + assert!(result.is_ok()); + let expected = vec![]; + assert_table_rows_eq_unordered(expected, table.rows); + } } \ No newline at end of file