From d80bf31e2e08034712fc66fef482ed7e29313e12 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 16 Sep 2025 13:57:11 -0400 Subject: [PATCH 1/2] Start of database transactions --- src/db/database.rs | 10 ++++++---- src/db/table/alter_table/mod.rs | 6 +++--- src/db/table/mod.rs | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/db/database.rs b/src/db/database.rs index bd95578..5995f8b 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -84,10 +84,12 @@ impl Database { Ok(None) } SqlStatement::Commit => { - self.transaction = None; - self.tables.iter_mut().for_each(|(_, table)| { - table.commit_transaction(); - }); + if let Some(transaction) = self.transaction.take() { + for transaction_entry in transaction.entries.iter() { + let table = self.get_table_mut(transaction_entry.table_name.as_str())?; + table.commit_transaction(&transaction_entry.affected_rows)?; + } + } Ok(None) } SqlStatement::Rollback(_) => { diff --git a/src/db/table/alter_table/mod.rs b/src/db/table/alter_table/mod.rs index 00a2273..746b367 100644 --- a/src/db/table/alter_table/mod.rs +++ b/src/db/table/alter_table/mod.rs @@ -4,7 +4,7 @@ use crate::interpreter::ast::{AlterTableAction, AlterTableStatement}; pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> Result<(), String> { return match statement.action { - AlterTableAction::RenameTable { new_table_name } => { + AlterTableAction::RenameTable { new_table_name } => { // TODO: add transaction support to table name changes let table = database.tables.remove(&statement.table_name); match table { Some(table) => database.tables.insert(new_table_name, table), @@ -12,9 +12,9 @@ pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> R }; Ok(()) } - AlterTableAction::RenameColumn { + AlterTableAction::RenameColumn { // TODO: add transaction support to column name changes old_column_name, - new_column_name, + new_column_name, } => { let table = database.get_table_mut(&statement.table_name)?; if !table.has_column(&old_column_name) { diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 1c9d000..7177513 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -203,8 +203,6 @@ impl IndexMut for Table { } } -// TODO: add swap -// TODO: add pop impl Table { pub fn new(name: String, columns: Vec) -> Self { Self { @@ -263,9 +261,18 @@ impl Table { pub fn pop(&mut self) -> Option { self.rows.pop().and_then(|mut value| value.stack.pop()) } - - pub fn commit_transaction(&mut self) { - todo!() + + + pub fn commit_transaction(&mut self, affected_row_indices: &Vec) -> Result<(), String> { + // Keep only the top of the each row stack. + for index in affected_row_indices { + if let Some(row_stack) = self.rows.get_mut(*index) { + row_stack.stack = vec![row_stack.stack.last().unwrap().clone()]; + } else { + return Err("Error committing transaction. Row stack is empty".to_string()); + } + } + Ok(()) } pub fn rollback_transaction(&mut self) { @@ -305,3 +312,4 @@ impl Table { self.columns.iter().map(|column| &column.name).collect() } } + From bf69cc25bb92e3b261d576ff5b011994bf2b53da Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Tue, 16 Sep 2025 14:09:48 -0400 Subject: [PATCH 2/2] Replace column with column stack --- src/db/table/alter_table/mod.rs | 25 +++++--- src/db/table/helpers/common.rs | 8 ++- src/db/table/insert/mod.rs | 9 ++- src/db/table/mod.rs | 79 ++++++++++++++++++------- src/db/table/select/mod.rs | 2 +- src/db/table/select/select_statement.rs | 9 +-- src/db/table/test_utils.rs | 42 ++++++------- src/db/table/update/mod.rs | 2 +- 8 files changed, 115 insertions(+), 61 deletions(-) diff --git a/src/db/table/alter_table/mod.rs b/src/db/table/alter_table/mod.rs index 746b367..36d478d 100644 --- a/src/db/table/alter_table/mod.rs +++ b/src/db/table/alter_table/mod.rs @@ -4,7 +4,8 @@ use crate::interpreter::ast::{AlterTableAction, AlterTableStatement}; pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> Result<(), String> { return match statement.action { - AlterTableAction::RenameTable { new_table_name } => { // TODO: add transaction support to table name changes + AlterTableAction::RenameTable { new_table_name } => { + // TODO: add transaction support to table name changes let table = database.tables.remove(&statement.table_name); match table { Some(table) => database.tables.insert(new_table_name, table), @@ -12,9 +13,10 @@ pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> R }; Ok(()) } - AlterTableAction::RenameColumn { // TODO: add transaction support to column name changes + AlterTableAction::RenameColumn { + // TODO: add transaction support to column name changes old_column_name, - new_column_name, + new_column_name, } => { let table = database.get_table_mut(&statement.table_name)?; if !table.has_column(&old_column_name) { @@ -23,7 +25,7 @@ pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> R old_column_name, statement.table_name )); } - table.columns.iter_mut().for_each(|column| { + table.get_columns_mut().iter_mut().for_each(|column| { if column.name == old_column_name { column.name = new_column_name.clone(); } @@ -38,7 +40,7 @@ pub fn alter_table(database: &mut Database, statement: AlterTableStatement) -> R column_def.name, statement.table_name )); } - table.columns.push(column_def); + table.push_column(column_def); table.get_rows_mut().iter_mut().for_each(|row| { row.push(Value::Null); }); @@ -102,7 +104,7 @@ mod tests { assert!( table .unwrap() - .columns + .get_columns() .iter() .any(|column| column.name == "new_name") ); @@ -126,7 +128,7 @@ mod tests { let table = database.get_table("users"); assert!(table.is_ok()); let table = table.unwrap(); - assert!(table.columns.last().unwrap().name == "new_column"); + assert!(table.get_columns().last().unwrap().name == "new_column"); assert!(table.columns.len() == table[0].len()); assert!( table @@ -150,7 +152,12 @@ mod tests { let table = database.get_table("users"); assert!(table.is_ok()); let table = table.unwrap(); - assert!(!table.columns.iter().any(|column| column.name == "age")); + assert!( + !table + .get_columns() + .iter() + .any(|column| column.name == "age") + ); assert!(table.columns.len() == table[0].len()); let expected_columns_in_order = vec![ ColumnDefinition { @@ -169,7 +176,7 @@ mod tests { constraints: vec![], }, ]; - assert_eq!(expected_columns_in_order, table.columns); + assert_eq!(expected_columns_in_order, table.get_columns_clone()); let expected_rows = vec![ Row(vec![ Value::Integer(1), diff --git a/src/db/table/helpers/common.rs b/src/db/table/helpers/common.rs index 556361f..a3712b6 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/helpers/common.rs @@ -16,10 +16,12 @@ pub fn validate_and_clone_row(table: &Table, row: &Row) -> Result { let mut row_values: Row = Row(vec![]); for (i, value) in row.iter().enumerate() { - if value.get_type() != table.columns[i].data_type && value.get_type() != DataType::Null { + if value.get_type() != table.get_columns()[i].data_type + && value.get_type() != DataType::Null + { return Err(format!( "Data type mismatch for column {}", - table.columns[i].name + table.get_columns()[i].name )); } row_values.push(row[i].clone()); @@ -35,7 +37,7 @@ pub fn get_columns_from_row( let mut row_values: Row = Row(vec![]); let mut column_values = HashMap::new(); - for (i, column) in table.get_columns().into_iter().enumerate() { + for (i, column) in table.get_column_names().into_iter().enumerate() { if let Some(value) = row.get(i) { column_values.insert(column, value); } else { diff --git a/src/db/table/insert/mod.rs b/src/db/table/insert/mod.rs index 50dfc0d..00f18a1 100644 --- a/src/db/table/insert/mod.rs +++ b/src/db/table/insert/mod.rs @@ -8,7 +8,12 @@ pub fn insert(table: &mut Table, statement: InsertIntoStatement) -> Result Result, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ColumnConstraint { - pub constraint_type: String, -} - #[derive(Debug, PartialOrd, Clone)] pub enum Value { Integer(i64), @@ -182,10 +170,35 @@ impl RowStack { } } +#[derive(Debug, PartialEq, Clone)] +pub struct ColumnDefinition { + pub name: String, + pub data_type: DataType, + pub constraints: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ColumnConstraint { + pub constraint_type: String, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ColumnStack { + pub stack: Vec, +} + +impl ColumnStack { + pub fn new(column: ColumnDefinition) -> Self { + Self { + stack: vec![column], + } + } +} + #[derive(Debug)] pub struct Table { pub name: String, - pub columns: Vec, + columns: Vec, rows: Vec, } @@ -207,7 +220,7 @@ impl Table { pub fn new(name: String, columns: Vec) -> Self { Self { name, - columns, + columns: columns.into_iter().map(|c| ColumnStack::new(c)).collect(), rows: vec![], } } @@ -261,7 +274,6 @@ impl Table { pub fn pop(&mut self) -> Option { self.rows.pop().and_then(|mut value| value.stack.pop()) } - pub fn commit_transaction(&mut self, affected_row_indices: &Vec) -> Result<(), String> { // Keep only the top of the each row stack. @@ -281,7 +293,7 @@ impl Table { pub fn get_column_from_row<'a>(&self, row: &'a Vec, column: &String) -> &'a Value { for (i, value) in row.iter().enumerate() { - if self.columns[i].name == *column { + if self.get_column_names()[i] == column { return &value; } } @@ -289,7 +301,7 @@ impl Table { } pub fn has_column(&self, column: &String) -> bool { - self.columns.iter().any(|c| c.name == *column) + self.get_columns().iter().any(|c| c.name == *column) } fn width(&self) -> usize { @@ -297,7 +309,7 @@ impl Table { } pub fn get_index_of_column(&self, column: &String) -> Result { - for (i, c) in self.columns.iter().enumerate() { + for (i, c) in self.get_columns().iter().enumerate() { if c.name == *column { return Ok(i); } @@ -308,8 +320,33 @@ impl Table { )); } - pub fn get_columns(&self) -> Vec<&String> { - self.columns.iter().map(|column| &column.name).collect() + pub fn get_columns(&self) -> Vec<&ColumnDefinition> { + self.columns + .iter() + .map(|c| c.stack.last().unwrap()) + .collect() } -} + pub fn get_columns_mut(&mut self) -> Vec<&mut ColumnDefinition> { + self.columns + .iter_mut() + .map(|c| c.stack.last_mut().unwrap()) + .collect() + } + + pub fn get_column_names(&self) -> Vec<&String> { + self.get_columns() + .iter() + .map(|column| &column.name) + .collect() + } + + pub fn push_column(&mut self, column: ColumnDefinition) { + self.columns.push(ColumnStack::new(column)); + } + + #[cfg(test)] + pub fn get_columns_clone(&self) -> Vec { + self.get_columns().iter().map(|c| (*c).clone()).collect() + } +} diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 7fa58e2..28ca11b 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -119,7 +119,7 @@ fn expand_all_column_names(table: &Table, column_names: &Vec) -> Vec Result Table { Table { name: "users".to_string(), columns: vec![ - ColumnDefinition { - name: "id".to_string(), - data_type: DataType::Integer, - constraints: vec![], - }, - ColumnDefinition { + ColumnStack::new({ + ColumnDefinition { + name: "id".to_string(), + data_type: DataType::Integer, + constraints: vec![], + } + }), + ColumnStack::new(ColumnDefinition { name: "name".to_string(), data_type: DataType::Text, constraints: vec![], - }, - ColumnDefinition { + }), + ColumnStack::new(ColumnDefinition { name: "age".to_string(), data_type: DataType::Integer, constraints: vec![], - }, - ColumnDefinition { + }), + ColumnStack::new(ColumnDefinition { name: "money".to_string(), data_type: DataType::Real, constraints: vec![], - }, + }), ], rows: vec![ RowStack::new(Row(vec![ @@ -72,26 +74,26 @@ pub fn default_database() -> Database { Table { name: "users".to_string(), columns: vec![ - ColumnDefinition { + ColumnStack::new(ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, constraints: vec![], - }, - ColumnDefinition { + }), + ColumnStack::new(ColumnDefinition { name: "name".to_string(), data_type: DataType::Text, constraints: vec![], - }, - ColumnDefinition { + }), + ColumnStack::new(ColumnDefinition { name: "age".to_string(), data_type: DataType::Integer, constraints: vec![], - }, - ColumnDefinition { + }), + ColumnStack::new(ColumnDefinition { name: "money".to_string(), data_type: DataType::Real, constraints: vec![], - }, + }), ], rows: vec![ RowStack::new(Row(vec![ diff --git a/src/db/table/update/mod.rs b/src/db/table/update/mod.rs index 8e7c0af..56ee3cf 100644 --- a/src/db/table/update/mod.rs +++ b/src/db/table/update/mod.rs @@ -22,7 +22,7 @@ fn update_rows_from_indicies( 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() + if table.get_columns()[column_index].data_type != update_value.value.get_type() && update_value.value.get_type() != DataType::Null { return Err(format!(