From ac8bfbc566db4d07e20841c91ef5ed1610a8076b Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Fri, 5 Sep 2025 23:17:25 -0400 Subject: [PATCH] UPDATE statement working with database --- src/db/database.rs | 14 ++- src/db/table/delete/mod.rs | 25 +---- src/db/table/helpers/common.rs | 17 +++- src/db/table/mod.rs | 1 + src/db/table/select/mod.rs | 16 +-- src/db/table/update/mod.rs | 178 +++++++++++++++++++++++++++++++++ 6 files changed, 210 insertions(+), 41 deletions(-) create mode 100644 src/db/table/update/mod.rs diff --git a/src/db/database.rs b/src/db/database.rs index c36ea74..c1a001b 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -1,8 +1,9 @@ use crate::db::table::{Table, Value}; -use crate::cli::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatement, DeleteStatement}; +use crate::cli::ast::{SqlStatement, CreateTableStatement, InsertIntoStatement, SelectStatement, DeleteStatement, UpdateStatement}; use crate::db::table::select; use crate::db::table::insert; use crate::db::table::delete; +use crate::db::table::update; use std::collections::HashMap; pub struct Database { @@ -30,8 +31,9 @@ impl Database { let rows = self.select_from_table(statement)?; Ok(Some(rows)) }, - SqlStatement::UpdateStatement(_statement) => { - todo!(); + SqlStatement::UpdateStatement(statement) => { + self.update_table(statement)?; + Ok(None) }, SqlStatement::DeleteStatement(statement) => { self.delete_from_table(statement)?; @@ -67,6 +69,12 @@ impl Database { Ok(()) } + fn update_table(&mut self, statement: UpdateStatement) -> Result<(), String> { + let table = self.get_table_mut(&statement.table_name)?; + update::update(table, statement)?; + Ok(()) + } + fn has_table(&self, table_name: &str) -> bool { self.tables.contains_key(table_name) } diff --git a/src/db/table/delete/mod.rs b/src/db/table/delete/mod.rs index 8e91fb1..f2b3e06 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/delete/mod.rs @@ -2,34 +2,15 @@ use std::collections::HashSet; use crate::db::table::Table; use crate::cli::ast::DeleteStatement; -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 -}; +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_to_delete(table, statement)?; + 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(()) } - -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> { if table.rows.len() == 0 { if row_indicies.len() != 0 { @@ -167,7 +148,5 @@ mod tests { }; 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 15793aa..dba9466 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -1,6 +1,7 @@ use crate::db::table::{Table, Value, DataType}; -use crate::cli::ast::{SelectStatementColumns, WhereStackElement}; +use crate::cli::ast::{SelectStatementColumns, WhereStackElement, OrderByClause, LimitClause}; use crate::db::table::helpers::where_stack::matches_where_stack; +use crate::db::table::helpers::{order_by_clause::get_ordered_row_indicies, limit_clause::get_limited_row_indicies}; pub fn validate_and_clone_row(table: &Table, row: &Vec) -> Result, String> { if row.len() != table.width() { @@ -59,4 +60,18 @@ 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> { + let mut row_indicies = get_row_indicies_matching_where_clause(table, where_clause)?; + + if let Some(order_by_clause) = order_by_clause { + row_indicies = get_ordered_row_indicies(table, row_indicies, &order_by_clause)?; + } + + if let Some(limit_clause) = limit_clause { + row_indicies = get_limited_row_indicies(row_indicies, &limit_clause)?; + } + + return Ok(row_indicies); } \ No newline at end of file diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 3503e52..0b16bd8 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -5,6 +5,7 @@ use crate::cli::ast::OrderByDirection; pub mod select; pub mod insert; pub mod delete; +pub mod update; pub mod helpers; #[cfg(test)] pub mod test_utils; diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 899bcd6..d31fb1a 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,23 +1,11 @@ use crate::db::table::{Table, Value}; use crate::cli::ast::{SelectStatement}; -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 -}; +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 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)?; - } + 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))?); } diff --git a/src/db/table/update/mod.rs b/src/db/table/update/mod.rs new file mode 100644 index 0000000..d294753 --- /dev/null +++ b/src/db/table/update/mod.rs @@ -0,0 +1,178 @@ +use crate::db::table::Table; +use crate::cli::ast::{UpdateStatement, ColumnValue}; +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)?; + update_rows_from_indicies(table, row_indicies, statement.update_values)?; + Ok(()) +} + +fn update_rows_from_indicies(table: &mut Table, row_indicies: Vec, update_values: Vec) -> Result<(), String> { + for row_index in row_indicies { + for update_value in &update_values { + let column_index = table.get_index_of_column(&update_value.column)?; + if table.columns[column_index].data_type != update_value.value.get_type() && update_value.value.get_type() != DataType::Null { + return Err(format!("Found different data types for column: {} and value: {:?}", update_value.column, update_value.value.get_type())); + } + table.rows[row_index][column_index] = update_value.value.clone(); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::table::{Value, DataType, ColumnDefinition}; + use crate::cli::ast::ColumnValue; + use crate::db::table::test_utils::{default_table, assert_table_rows_eq_unordered}; + use crate::cli::ast::{WhereStackElement, WhereCondition, Operand, Operator, OrderByClause, OrderByDirection, LimitClause}; + + #[test] + fn update_works_correctly() { + let mut table = default_table(); + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "name".to_string(), value: Value::Text("John".to_string()) }], + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = update(&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("John".to_string()), Value::Integer(30), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("John".to_string()), Value::Integer(35), Value::Real(3000.0)], + vec![Value::Integer(4), Value::Text("John".to_string()), Value::Integer(40), Value::Real(4000.0)], + ]; + assert_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn update_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 = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "name".to_string(), value: Value::Text("Fletcher".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 = update(&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(5), Value::Text("Fletcher".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)], + ]; + assert_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn update_multiple_columns_and_rows_works_correctly() { + let mut table = default_table(); + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "name".to_string(), value: Value::Text("Fletcher".to_string()) }, ColumnValue { column: "age".to_string(), value: Value::Integer(50) }], + 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 = update(&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("Fletcher".to_string()), Value::Integer(50), Value::Real(2000.0)], + vec![Value::Integer(3), Value::Text("Fletcher".to_string()), Value::Integer(50), Value::Real(3000.0)], + vec![Value::Integer(4), Value::Text("Fletcher".to_string()), Value::Integer(50), Value::Real(4000.0)], + ]; + assert_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn update_empty_table_works_correctly() { + let mut table = Table::new( + "users".to_string(), + vec![ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, constraints: vec![] }], + ); + table.rows = vec![]; + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "name".to_string(), value: Value::Text("Fletcher".to_string()) }], + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = update(&mut table, statement); + assert!(result.is_ok()); + let expected = vec![]; + assert_table_rows_eq_unordered(expected, table.rows); + } + + #[test] + fn update_with_invalid_column_works_correctly() { + let mut table = default_table(); + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "invalid".to_string(), value: Value::Text("Fletcher".to_string()) }], + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = update(&mut table, statement); + assert!(result.is_err()); + assert_eq!(result.err().unwrap(), "Column invalid does not exist in table users"); + } + + #[test] + fn update_with_invalid_value_works_correctly() { + let mut table = default_table(); + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "name".to_string(), value: Value::Integer(1) }], + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = update(&mut table, statement); + assert!(result.is_err()); + assert_eq!(result.err().unwrap(), "Found different data types for column: name and value: Integer"); + } + + #[test] + fn update_with_null_value_works_correctly() { + let mut table = default_table(); + let statement = UpdateStatement { + table_name: "users".to_string(), + update_values: vec![ColumnValue { column: "money".to_string(), value: Value::Null }], + where_clause: None, + order_by_clause: None, + limit_clause: None, + }; + let result = update(&mut table, statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Null], + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Null], + vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Null], + vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Null], + ]; + assert_table_rows_eq_unordered(expected, table.rows); + } +} \ No newline at end of file