From cd7617f2d1125322c1cec0068c4350ed2ea289bb Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Wed, 17 Sep 2025 14:22:35 -0400 Subject: [PATCH 1/2] Setup improved transaction logging --- src/db/database.rs | 113 ++++++++++--------------------------- src/db/mod.rs | 1 + src/db/transactions/mod.rs | 90 +++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 83 deletions(-) create mode 100644 src/db/transactions/mod.rs diff --git a/src/db/database.rs b/src/db/database.rs index d480596..ecf12a6 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -4,35 +4,23 @@ use crate::db::table::delete; use crate::db::table::insert; use crate::db::table::select; use crate::db::table::update; -use crate::db::table::{Row, Table, drop_table}; +use crate::db::table::{Row, Table, drop_table}; +use crate::db::transactions::{TransactionLog, TransactionEntry}; use crate::interpreter::ast::SqlStatement; use std::collections::HashMap; pub struct Database { pub tables: HashMap, - pub transaction: Option, -} - -pub struct TransactionLog { - pub entries: Vec, - pub savepoint_name: Vec, -} - -pub struct TransactionEntry { - pub statement: SqlStatement, - pub table_name: String, - pub affected_rows: Vec, -} - -pub struct Savepoint { - pub name: String, + pub transaction: TransactionLog, } impl Database { pub fn new() -> Self { Self { tables: HashMap::new(), - transaction: None, + transaction: TransactionLog { + entries: None, + }, } } @@ -41,13 +29,13 @@ impl Database { return match sql_statement { SqlStatement::CreateTable(statement) => { create_table::create_table(self, statement)?; - self.append_to_transaction(sql_statement_clone, vec![])?; + self.transaction.append_entry(sql_statement_clone, vec![])?; Ok(None) } SqlStatement::InsertInto(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_inserted = insert::insert(table, statement)?; - self.append_to_transaction(sql_statement_clone, rows_inserted)?; + self.transaction.append_entry(sql_statement_clone, rows_inserted)?; Ok(None) } SqlStatement::Select(statement) => { @@ -57,73 +45,56 @@ impl Database { SqlStatement::UpdateStatement(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_updated = update::update(table, statement)?; - self.append_to_transaction(sql_statement_clone, rows_updated)?; + self.transaction.append_entry(sql_statement_clone, rows_updated)?; Ok(None) } SqlStatement::DeleteStatement(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_deleted = delete::delete(table, statement)?; - self.append_to_transaction(sql_statement_clone, rows_deleted)?; + self.transaction.append_entry(sql_statement_clone, rows_deleted)?; Ok(None) } SqlStatement::DropTable(statement) => { drop_table::drop_table(self, statement)?; - self.append_to_transaction(sql_statement_clone, vec![])?; + self.transaction.append_entry(sql_statement_clone, vec![])?; Ok(None) } SqlStatement::AlterTable(statement) => { - alter_table::alter_table(self, statement, self.transaction.is_some())?; - self.append_to_transaction(sql_statement_clone, vec![])?; + alter_table::alter_table(self, statement, self.transaction.in_transaction())?; + self.transaction.append_entry(sql_statement_clone, vec![])?; Ok(None) } SqlStatement::BeginTransaction(_) => { - self.transaction = Some(TransactionLog { - entries: vec![], - savepoint_name: vec![], - }); + self.transaction.begin_transaction(); Ok(None) } SqlStatement::Commit => { - 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)?; + let transaction_log = self.transaction.commit_transaction()?; + for transaction_entry in transaction_log.get_entries()?.iter() { + match transaction_entry { + TransactionEntry::Statement(statement) => { + let table = self.get_table_mut(statement.table_name.as_str())?; + table.commit_transaction(&statement.affected_rows)?; + } + TransactionEntry::Savepoint(_) => {} } } + Ok(None) } SqlStatement::Rollback(_) => { - self.transaction = None; + self.transaction.commit_transaction()?; self.tables.iter_mut().for_each(|(_, table)| { table.rollback_transaction(); }); Ok(None) } - SqlStatement::Savepoint(statement) => { - match &mut self.transaction { - Some(transaction) => { - transaction.savepoint_name.push(Savepoint { - name: statement.savepoint_name.clone(), - }); - } - None => { - return Err("No transaction is currently active".to_string()); - } - } - self.append_to_transaction(sql_statement_clone, vec![])?; + SqlStatement::Savepoint(_) => { + self.transaction.append_entry(sql_statement_clone, vec![])?; Ok(None) } SqlStatement::Release(statement) => { - match &mut self.transaction { - Some(transaction) => { - transaction - .savepoint_name - .retain(|savepoint| savepoint.name != statement.savepoint_name); - } - None => { - return Err("No transaction is currently active".to_string()); - } - } + self.transaction.release_savepoint(&statement.savepoint_name)?; Ok(None) } }; @@ -146,32 +117,6 @@ impl Database { } Ok(self.tables.get_mut(table_name).unwrap()) } - - fn append_to_transaction( - &mut self, - sql_statement: SqlStatement, - affected_rows: Vec, - ) -> Result<(), String> { - let table_name = match &sql_statement { - SqlStatement::CreateTable(statement) => statement.table_name.clone(), - SqlStatement::InsertInto(statement) => statement.table_name.clone(), - SqlStatement::UpdateStatement(statement) => statement.table_name.clone(), - SqlStatement::DeleteStatement(statement) => statement.table_name.clone(), - SqlStatement::DropTable(statement) => statement.table_name.clone(), - SqlStatement::AlterTable(statement) => statement.table_name.clone(), - SqlStatement::Savepoint(_) => "".to_string(), - _ => unreachable!(), - }; - - if let Some(transaction) = &mut self.transaction { - transaction.entries.push(TransactionEntry { - statement: sql_statement, - table_name: table_name, - affected_rows: affected_rows, - }); - } - Ok(()) - } } #[cfg(test)] @@ -199,7 +144,9 @@ mod tests { ], ), )]), - transaction: None, + transaction: TransactionLog { + entries: None, + }, } } diff --git a/src/db/mod.rs b/src/db/mod.rs index b66a9b6..d301a8b 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,2 +1,3 @@ pub mod database; pub mod table; +pub mod transactions; \ No newline at end of file diff --git a/src/db/transactions/mod.rs b/src/db/transactions/mod.rs new file mode 100644 index 0000000..ed4c317 --- /dev/null +++ b/src/db/transactions/mod.rs @@ -0,0 +1,90 @@ +use crate::interpreter::ast::SqlStatement; + +#[derive(Debug, PartialEq, Clone)] +pub struct TransactionLog { + pub entries: Option>, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TransactionEntry { + Statement(StatementEntry), + Savepoint(Savepoint), +} + +#[derive(Debug, PartialEq, Clone)] +pub struct StatementEntry { + pub statement: SqlStatement, + pub table_name: String, + pub affected_rows: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Savepoint { + pub name: String, +} + +impl TransactionLog { + pub fn in_transaction(&self) -> bool { + self.entries.is_some() + } + + pub fn append_entry(&mut self, sql_statement: SqlStatement, affected_rows: Vec) -> Result<(), String> { + if !self.in_transaction() { + return Ok(()) + } + let table_name = match &sql_statement { + SqlStatement::CreateTable(statement) => statement.table_name.clone(), + SqlStatement::InsertInto(statement) => statement.table_name.clone(), + SqlStatement::UpdateStatement(statement) => statement.table_name.clone(), + SqlStatement::DeleteStatement(statement) => statement.table_name.clone(), + SqlStatement::DropTable(statement) => statement.table_name.clone(), + SqlStatement::AlterTable(statement) => statement.table_name.clone(), + SqlStatement::Savepoint(statement) => { + self.append_savepoint(Savepoint { + name: statement.savepoint_name.clone(), + })?; + return Ok(()); + } + _ => return Err("Invalid transaction entry".to_string()), + }; + self.get_entries_mut()?.push(TransactionEntry::Statement(StatementEntry { + statement: sql_statement, + table_name: table_name, + affected_rows: affected_rows, + })); + Ok(()) + } + + pub fn append_savepoint(&mut self, savepoint: Savepoint) -> Result<(), String> { + self.get_entries_mut()?.push(TransactionEntry::Savepoint(savepoint)); + Ok(()) + } + + pub fn release_savepoint(&mut self, savepoint_name: &String) -> Result<(), String> { + self.get_entries_mut()?.retain(|entry| match entry { + TransactionEntry::Savepoint(savepoint) => savepoint.name != *savepoint_name, + _ => true, + }); + Ok(()) + } + + pub fn begin_transaction(&mut self) { + self.entries = Some(vec![]); + } + + pub fn commit_transaction(&mut self) -> Result { + let transaction_log = TransactionLog { + entries: self.entries.take(), + }; + self.entries = None; + Ok(transaction_log) + } + + pub fn get_entries(&self) -> Result<&Vec, String> { + self.entries.as_ref().ok_or_else(|| "No transaction is currently active".to_string()) + } + + fn get_entries_mut(&mut self) -> Result<&mut Vec, String> { + self.entries.as_mut().ok_or_else(|| "No transaction is currently active".to_string()) + } +} \ No newline at end of file From 990b8709bcc25ab7d60e80dd16f1c81940c60776 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Wed, 17 Sep 2025 14:57:51 -0400 Subject: [PATCH 2/2] Move Table and dependencies into a sub folder --- src/db/database.rs | 37 +- src/db/mod.rs | 2 +- src/db/table/core/column.rs | 112 +++++ src/db/table/core/mod.rs | 4 + src/db/table/core/row.rs | 30 ++ src/db/table/core/table.rs | 156 +++++++ src/db/table/core/value.rs | 129 ++++++ src/db/table/mod.rs | 432 +----------------- .../table/{ => operations}/alter_table/mod.rs | 4 +- .../{ => operations}/create_table/mod.rs | 4 +- src/db/table/{ => operations}/delete/mod.rs | 8 +- .../table/{ => operations}/drop_table/mod.rs | 0 .../table/{ => operations}/helpers/common.rs | 6 +- src/db/table/{ => operations}/helpers/mod.rs | 0 .../helpers/order_by_clause.rs | 4 +- .../helpers/where_clause/mod.rs | 6 +- .../helpers/where_clause/where_condition.rs | 7 +- .../helpers/where_clause/where_stack.rs | 10 +- src/db/table/{ => operations}/insert/mod.rs | 8 +- src/db/table/operations/mod.rs | 8 + src/db/table/{ => operations}/select/mod.rs | 13 +- .../select/select_statement.rs | 26 +- .../select/set_operator_evaluator.rs | 5 +- src/db/table/{ => operations}/update/mod.rs | 12 +- src/db/table/test_utils.rs | 122 ++--- src/db/transactions/mod.rs | 32 +- src/interpreter/ast/alter_table_statement.rs | 4 +- src/interpreter/ast/create_statement.rs | 4 +- src/interpreter/ast/delete_statement.rs | 2 +- src/interpreter/ast/helpers/limit_clause.rs | 2 +- .../ast/helpers/select_statement.rs | 2 +- src/interpreter/ast/helpers/token.rs | 2 +- .../ast/helpers/where_clause/mod.rs | 2 +- .../helpers/where_clause/where_condition.rs | 2 +- src/interpreter/ast/insert_statement.rs | 2 +- src/interpreter/ast/mod.rs | 2 +- src/interpreter/ast/select_statement_stack.rs | 2 +- src/interpreter/ast/update_statement.rs | 2 +- src/interpreter/mod.rs | 2 +- tests/crud_test.rs | 2 +- tests/set_operators.rs | 2 +- tests/test_utils.rs | 2 +- 42 files changed, 595 insertions(+), 618 deletions(-) create mode 100644 src/db/table/core/column.rs create mode 100644 src/db/table/core/mod.rs create mode 100644 src/db/table/core/row.rs create mode 100644 src/db/table/core/table.rs create mode 100644 src/db/table/core/value.rs rename src/db/table/{ => operations}/alter_table/mod.rs (98%) rename src/db/table/{ => operations}/create_table/mod.rs (95%) rename src/db/table/{ => operations}/delete/mod.rs (98%) rename src/db/table/{ => operations}/drop_table/mod.rs (100%) rename src/db/table/{ => operations}/helpers/common.rs (97%) rename src/db/table/{ => operations}/helpers/mod.rs (100%) rename src/db/table/{ => operations}/helpers/order_by_clause.rs (95%) rename src/db/table/{ => operations}/helpers/where_clause/mod.rs (96%) rename src/db/table/{ => operations}/helpers/where_clause/where_condition.rs (99%) rename src/db/table/{ => operations}/helpers/where_clause/where_stack.rs (95%) rename src/db/table/{ => operations}/insert/mod.rs (95%) create mode 100644 src/db/table/operations/mod.rs rename src/db/table/{ => operations}/select/mod.rs (97%) rename src/db/table/{ => operations}/select/select_statement.rs (95%) rename src/db/table/{ => operations}/select/set_operator_evaluator.rs (97%) rename src/db/table/{ => operations}/update/mod.rs (97%) diff --git a/src/db/database.rs b/src/db/database.rs index ecf12a6..5895df9 100644 --- a/src/db/database.rs +++ b/src/db/database.rs @@ -1,11 +1,8 @@ -use crate::db::table::alter_table; -use crate::db::table::create_table; -use crate::db::table::delete; -use crate::db::table::insert; -use crate::db::table::select; -use crate::db::table::update; -use crate::db::table::{Row, Table, drop_table}; -use crate::db::transactions::{TransactionLog, TransactionEntry}; +use crate::db::table::core::{row::Row, table::Table}; +use crate::db::table::operations::{ + alter_table, create_table, delete, drop_table, insert, select, update, +}; +use crate::db::transactions::{TransactionEntry, TransactionLog}; use crate::interpreter::ast::SqlStatement; use std::collections::HashMap; @@ -18,9 +15,7 @@ impl Database { pub fn new() -> Self { Self { tables: HashMap::new(), - transaction: TransactionLog { - entries: None, - }, + transaction: TransactionLog { entries: None }, } } @@ -35,7 +30,8 @@ impl Database { SqlStatement::InsertInto(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_inserted = insert::insert(table, statement)?; - self.transaction.append_entry(sql_statement_clone, rows_inserted)?; + self.transaction + .append_entry(sql_statement_clone, rows_inserted)?; Ok(None) } SqlStatement::Select(statement) => { @@ -45,13 +41,15 @@ impl Database { SqlStatement::UpdateStatement(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_updated = update::update(table, statement)?; - self.transaction.append_entry(sql_statement_clone, rows_updated)?; + self.transaction + .append_entry(sql_statement_clone, rows_updated)?; Ok(None) } SqlStatement::DeleteStatement(statement) => { let table = self.get_table_mut(&statement.table_name)?; let rows_deleted = delete::delete(table, statement)?; - self.transaction.append_entry(sql_statement_clone, rows_deleted)?; + self.transaction + .append_entry(sql_statement_clone, rows_deleted)?; Ok(None) } SqlStatement::DropTable(statement) => { @@ -79,7 +77,7 @@ impl Database { TransactionEntry::Savepoint(_) => {} } } - + Ok(None) } SqlStatement::Rollback(_) => { @@ -94,7 +92,8 @@ impl Database { Ok(None) } SqlStatement::Release(statement) => { - self.transaction.release_savepoint(&statement.savepoint_name)?; + self.transaction + .release_savepoint(&statement.savepoint_name)?; Ok(None) } }; @@ -122,7 +121,7 @@ impl Database { #[cfg(test)] mod tests { use super::*; - use crate::db::table::{ColumnDefinition, DataType}; + use crate::db::table::core::{column::ColumnDefinition, value::DataType}; fn default_database() -> Database { Database { @@ -144,9 +143,7 @@ mod tests { ], ), )]), - transaction: TransactionLog { - entries: None, - }, + transaction: TransactionLog { entries: None }, } } diff --git a/src/db/mod.rs b/src/db/mod.rs index d301a8b..dc96369 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,3 +1,3 @@ pub mod database; pub mod table; -pub mod transactions; \ No newline at end of file +pub mod transactions; diff --git a/src/db/table/core/column.rs b/src/db/table/core/column.rs new file mode 100644 index 0000000..a9c6fbb --- /dev/null +++ b/src/db/table/core/column.rs @@ -0,0 +1,112 @@ +use crate::db::table::core::value::DataType; + +#[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(columns: Vec) -> Self { + Self { + stack: vec![columns], + } + } + + fn append_clone(&mut self) -> Result<(), String> { + self.stack.push(self.peek()?.clone()); + Ok(()) + } + + pub fn push_column(&mut self, column: ColumnDefinition, is_transaction: bool) { + if is_transaction { + self.stack.push(self.stack.last().unwrap().clone()); + } + self.stack.last_mut().unwrap().push(column); + } + + pub fn rename_column( + &mut self, + old_column_name: &String, + new_column_name: &String, + table_name: &String, + is_transaction: bool, + ) -> Result<(), String> { + if is_transaction { + self.append_clone()?; + } + let columns = self + .peek_mut()? + .iter_mut() + .find(|column| column.name == *old_column_name); + match columns { + Some(column) => column.name = new_column_name.clone(), + None => { + return Err(format!( + "Column `{}` does not exist in table `{}`", + old_column_name, table_name + )); + } + } + Ok(()) + } + + pub fn drop_column( + &mut self, + column_name: &String, + table_name: &String, + is_transaction: bool, + ) -> Result<(), String> { + if is_transaction { + self.append_clone()?; + } + match self.get_index_of_column(column_name) { + Ok(index) => self.peek_mut()?.remove(index), + Err(_) => { + return Err(format!( + "Column `{}` does not exist in table `{}`", + column_name, table_name + )); + } + }; + Ok(()) + } + + pub fn get_index_of_column(&self, column_name: &String) -> Result { + let columns = self.peek(); + match columns { + Ok(columns) => { + if let Some(index) = columns + .iter() + .position(|column| column.name == *column_name) + { + Ok(index) + } else { + Err(format!("Column `{}` does not exist", column_name)) + } + } + Err(_) => Err("Column stack is empty".to_string()), + } + } + + fn peek(&self) -> Result<&Vec, String> { + self.stack.last().ok_or("Column stack is empty".to_string()) + } + + fn peek_mut(&mut self) -> Result<&mut Vec, String> { + self.stack + .last_mut() + .ok_or("Column stack is empty".to_string()) + } +} diff --git a/src/db/table/core/mod.rs b/src/db/table/core/mod.rs new file mode 100644 index 0000000..69001ac --- /dev/null +++ b/src/db/table/core/mod.rs @@ -0,0 +1,4 @@ +pub mod column; +pub mod row; +pub mod table; +pub mod value; diff --git a/src/db/table/core/row.rs b/src/db/table/core/row.rs new file mode 100644 index 0000000..73d8798 --- /dev/null +++ b/src/db/table/core/row.rs @@ -0,0 +1,30 @@ +use crate::db::table::core::value::Value; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +#[repr(transparent)] +pub struct Row(pub Vec); + +#[derive(Debug)] +pub struct RowStack { + pub stack: Vec, +} + +impl Deref for Row { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Row { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl RowStack { + pub fn new(stack: Row) -> Self { + Self { stack: vec![stack] } + } +} diff --git a/src/db/table/core/table.rs b/src/db/table/core/table.rs new file mode 100644 index 0000000..9897b6f --- /dev/null +++ b/src/db/table/core/table.rs @@ -0,0 +1,156 @@ +use crate::db::table::core::column::ColumnDefinition; +use crate::db::table::core::column::ColumnStack; +use crate::db::table::core::row::Row; +use crate::db::table::core::row::RowStack; +use crate::db::table::core::value::Value; +use std::ops::{Index, IndexMut}; + +#[derive(Debug)] +pub struct Table { + pub name: String, + pub columns: ColumnStack, + rows: Vec, +} + +impl Index for Table { + type Output = Row; + + fn index(&self, index: usize) -> &Self::Output { + self.rows[index].stack.last().unwrap() + } +} + +impl IndexMut for Table { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.rows[index].stack.last_mut().unwrap() + } +} + +impl Table { + pub fn new(name: String, columns: Vec) -> Self { + Self { + name, + columns: ColumnStack::new(columns), + rows: vec![], + } + } + + pub fn get(&self, i: usize) -> Option<&Row> { + self.rows.get(i)?.stack.last() + } + + pub fn iter(&self) -> impl Iterator { + self.rows.iter().map(|s| s.stack.last().unwrap()) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.rows.iter_mut().map(|s| s.stack.last_mut().unwrap()) + } + + pub fn len(&self) -> usize { + self.rows.len() + } + + pub fn swap(&mut self, a: usize, b: usize) -> () { + self.rows.swap(a, b); + } + + pub fn get_rows_clone(&self) -> Vec { + self.rows + .iter() + .map(|s| s.stack.last().unwrap().clone()) + .collect() + } + + pub fn get_rows(&self) -> Vec<&Row> { + self.rows.iter().map(|s| s.stack.last().unwrap()).collect() + } + + pub fn get_rows_mut(&mut self) -> Vec<&mut Row> { + self.rows + .iter_mut() + .map(|s| s.stack.last_mut().unwrap()) + .collect() + } + + pub fn set_rows(&mut self, rows: Vec) { + self.rows = rows.into_iter().map(|r| RowStack::new(r)).collect(); + } + + pub fn push(&mut self, row: Row) { + self.rows.push(RowStack::new(row)); + } + + 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. + 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) { + todo!() + } + + pub fn get_column_from_row<'a>(&self, row: &'a Vec, column: &String) -> &'a Value { + for (i, value) in row.iter().enumerate() { + if self.get_column_names()[i] == column { + return &value; + } + } + return &Value::Null; + } + + pub fn has_column(&self, column: &String) -> bool { + self.get_columns().iter().any(|c| c.name == *column) + } + + pub fn width(&self) -> usize { + self.get_columns().len() + } + + pub fn get_index_of_column(&self, column: &String) -> Result { + for (i, c) in self.get_columns().iter().enumerate() { + if c.name == *column { + return Ok(i); + } + } + return Err(format!( + "Column {} does not exist in table {}", + column, self.name + )); + } + + pub fn get_columns(&self) -> Vec<&ColumnDefinition> { + self.columns.stack.last().unwrap().iter().collect() + } + + pub fn get_columns_mut(&mut self) -> Vec<&mut ColumnDefinition> { + self.columns.stack.last_mut().unwrap().iter_mut().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_column(column, false); + } + + #[cfg(test)] + pub fn get_columns_clone(&self) -> Vec { + self.get_columns().iter().map(|c| (*c).clone()).collect() + } +} diff --git a/src/db/table/core/value.rs b/src/db/table/core/value.rs new file mode 100644 index 0000000..efc4d9d --- /dev/null +++ b/src/db/table/core/value.rs @@ -0,0 +1,129 @@ +use crate::interpreter::ast::OrderByDirection; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + +#[derive(Debug, PartialEq, Clone)] +pub enum DataType { + Integer, + Real, + Text, + Blob, + Null, +} + +#[derive(Debug, PartialOrd, Clone)] +pub enum Value { + Integer(i64), + Real(f64), + Text(String), + Blob(Vec), + Null, +} + +impl Value { + pub fn get_type(&self) -> DataType { + match self { + Value::Integer(_) => DataType::Integer, + Value::Real(_) => DataType::Real, + Value::Text(_) => DataType::Text, + Value::Blob(_) => DataType::Blob, + Value::Null => DataType::Null, + } + } + + pub fn compare(&self, other: &Value, direction: &OrderByDirection) -> Ordering { + let result = match (self, other) { + (Value::Null, Value::Null) => Ordering::Equal, + (Value::Null, _) => Ordering::Less, + (_, Value::Null) => Ordering::Greater, + (Value::Integer(a), Value::Integer(b)) => a.cmp(b), + (Value::Real(a), Value::Real(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 { + 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::Desc { + result.reverse() + } else { + result + } + } + + pub fn as_i64(&self) -> Option { + match self { + Value::Integer(i) => Some(*i), + Value::Real(f) => Some(*f as i64), + _ => None, + } + } + + pub fn as_f64(&self) -> Option { + match self { + Value::Integer(i) => Some(*i as f64), + Value::Real(f) => Some(*f), + _ => None, + } + } +} + +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, // TODO: 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/mod.rs b/src/db/table/mod.rs index 8d80ca5..db422e4 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -1,432 +1,4 @@ -use std::cmp::Eq; -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut, Index, IndexMut}; - -use crate::interpreter::ast::OrderByDirection; - -pub mod alter_table; -pub mod create_table; -pub mod delete; -pub mod drop_table; -pub mod helpers; -pub mod insert; -pub mod select; +pub mod core; +pub mod operations; #[cfg(test)] pub mod test_utils; -pub mod update; - -#[derive(Debug, PartialEq, Clone)] -pub enum DataType { - Integer, - Real, - Text, - Blob, - Null, -} - -#[derive(Debug, PartialOrd, Clone)] -pub enum Value { - Integer(i64), - Real(f64), - Text(String), - Blob(Vec), - Null, -} - -impl Value { - pub fn get_type(&self) -> DataType { - match self { - Value::Integer(_) => DataType::Integer, - Value::Real(_) => DataType::Real, - Value::Text(_) => DataType::Text, - Value::Blob(_) => DataType::Blob, - Value::Null => DataType::Null, - } - } - - pub fn compare(&self, other: &Value, direction: &OrderByDirection) -> Ordering { - let result = match (self, other) { - (Value::Null, Value::Null) => Ordering::Equal, - (Value::Null, _) => Ordering::Less, - (_, Value::Null) => Ordering::Greater, - (Value::Integer(a), Value::Integer(b)) => a.cmp(b), - (Value::Real(a), Value::Real(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 { - 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::Desc { - result.reverse() - } else { - result - } - } - - pub fn as_i64(&self) -> Option { - match self { - Value::Integer(i) => Some(*i), - Value::Real(f) => Some(*f as i64), - _ => None, - } - } - - pub fn as_f64(&self) -> Option { - match self { - Value::Integer(i) => Some(*i as f64), - Value::Real(f) => Some(*f), - _ => None, - } - } -} - -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, // TODO: 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); - } - } - } -} - -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -#[repr(transparent)] -pub struct Row(pub Vec); - -#[derive(Debug)] -pub struct RowStack { - pub stack: Vec, -} - -impl Deref for Row { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Row { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl RowStack { - pub fn new(stack: Row) -> Self { - Self { stack: vec![stack] } - } -} - -#[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(columns: Vec) -> Self { - Self { - stack: vec![columns], - } - } - - fn append_clone(&mut self) -> Result<(), String> { - self.stack.push(self.peek()?.clone()); - Ok(()) - } - - pub fn push_column(&mut self, column: ColumnDefinition, is_transaction: bool) { - if is_transaction { - self.stack.push(self.stack.last().unwrap().clone()); - } - self.stack.last_mut().unwrap().push(column); - } - - pub fn rename_column( - &mut self, - old_column_name: &String, - new_column_name: &String, - table_name: &String, - is_transaction: bool, - ) -> Result<(), String> { - if is_transaction { - self.append_clone()?; - } - let columns = self - .peek_mut()? - .iter_mut() - .find(|column| column.name == *old_column_name); - match columns { - Some(column) => column.name = new_column_name.clone(), - None => { - return Err(format!( - "Column `{}` does not exist in table `{}`", - old_column_name, table_name - )); - } - } - Ok(()) - } - - pub fn drop_column( - &mut self, - column_name: &String, - table_name: &String, - is_transaction: bool, - ) -> Result<(), String> { - if is_transaction { - self.append_clone()?; - } - match self.get_index_of_column(column_name) { - Ok(index) => self.peek_mut()?.remove(index), - Err(_) => { - return Err(format!( - "Column `{}` does not exist in table `{}`", - column_name, table_name - )); - } - }; - Ok(()) - } - - pub fn get_index_of_column(&self, column_name: &String) -> Result { - let columns = self.peek(); - match columns { - Ok(columns) => { - if let Some(index) = columns - .iter() - .position(|column| column.name == *column_name) - { - Ok(index) - } else { - Err(format!("Column `{}` does not exist", column_name)) - } - } - Err(_) => Err("Column stack is empty".to_string()), - } - } - - fn peek(&self) -> Result<&Vec, String> { - self.stack.last().ok_or("Column stack is empty".to_string()) - } - - fn peek_mut(&mut self) -> Result<&mut Vec, String> { - self.stack - .last_mut() - .ok_or("Column stack is empty".to_string()) - } -} - -#[derive(Debug)] -pub struct Table { - pub name: String, - columns: ColumnStack, - rows: Vec, -} - -impl Index for Table { - type Output = Row; - - fn index(&self, index: usize) -> &Self::Output { - self.rows[index].stack.last().unwrap() - } -} - -impl IndexMut for Table { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.rows[index].stack.last_mut().unwrap() - } -} - -impl Table { - pub fn new(name: String, columns: Vec) -> Self { - Self { - name, - columns: ColumnStack::new(columns), - rows: vec![], - } - } - - pub fn get(&self, i: usize) -> Option<&Row> { - self.rows.get(i)?.stack.last() - } - - pub fn iter(&self) -> impl Iterator { - self.rows.iter().map(|s| s.stack.last().unwrap()) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.rows.iter_mut().map(|s| s.stack.last_mut().unwrap()) - } - - pub fn len(&self) -> usize { - self.rows.len() - } - - pub fn swap(&mut self, a: usize, b: usize) -> () { - self.rows.swap(a, b); - } - - pub fn get_rows_clone(&self) -> Vec { - self.rows - .iter() - .map(|s| s.stack.last().unwrap().clone()) - .collect() - } - - pub fn get_rows(&self) -> Vec<&Row> { - self.rows.iter().map(|s| s.stack.last().unwrap()).collect() - } - - pub fn get_rows_mut(&mut self) -> Vec<&mut Row> { - self.rows - .iter_mut() - .map(|s| s.stack.last_mut().unwrap()) - .collect() - } - - pub fn set_rows(&mut self, rows: Vec) { - self.rows = rows.into_iter().map(|r| RowStack::new(r)).collect(); - } - - pub fn push(&mut self, row: Row) { - self.rows.push(RowStack::new(row)); - } - - 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. - 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) { - todo!() - } - - pub fn get_column_from_row<'a>(&self, row: &'a Vec, column: &String) -> &'a Value { - for (i, value) in row.iter().enumerate() { - if self.get_column_names()[i] == column { - return &value; - } - } - return &Value::Null; - } - - pub fn has_column(&self, column: &String) -> bool { - self.get_columns().iter().any(|c| c.name == *column) - } - - fn width(&self) -> usize { - self.get_columns().len() - } - - pub fn get_index_of_column(&self, column: &String) -> Result { - for (i, c) in self.get_columns().iter().enumerate() { - if c.name == *column { - return Ok(i); - } - } - return Err(format!( - "Column {} does not exist in table {}", - column, self.name - )); - } - - pub fn get_columns(&self) -> Vec<&ColumnDefinition> { - self.columns.stack.last().unwrap().iter().collect() - } - - pub fn get_columns_mut(&mut self) -> Vec<&mut ColumnDefinition> { - self.columns.stack.last_mut().unwrap().iter_mut().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_column(column, false); - } - - #[cfg(test)] - pub fn get_columns_clone(&self) -> Vec { - self.get_columns().iter().map(|c| (*c).clone()).collect() - } -} diff --git a/src/db/table/alter_table/mod.rs b/src/db/table/operations/alter_table/mod.rs similarity index 98% rename from src/db/table/alter_table/mod.rs rename to src/db/table/operations/alter_table/mod.rs index 44a2a92..87b52a2 100644 --- a/src/db/table/alter_table/mod.rs +++ b/src/db/table/operations/alter_table/mod.rs @@ -1,5 +1,5 @@ use crate::db::database::Database; -use crate::db::table::Value; +use crate::db::table::core::value::Value; use crate::interpreter::ast::{AlterTableAction, AlterTableStatement}; pub fn alter_table( @@ -71,8 +71,8 @@ pub fn alter_table( #[cfg(test)] mod tests { use super::*; + use crate::db::table::core::{column::ColumnDefinition, row::Row, value::DataType}; use crate::db::table::test_utils::default_database; - use crate::db::table::{ColumnDefinition, DataType, Row}; #[test] fn alter_table_rename_table_works_correctly() { diff --git a/src/db/table/create_table/mod.rs b/src/db/table/operations/create_table/mod.rs similarity index 95% rename from src/db/table/create_table/mod.rs rename to src/db/table/operations/create_table/mod.rs index f180a51..d61d419 100644 --- a/src/db/table/create_table/mod.rs +++ b/src/db/table/operations/create_table/mod.rs @@ -1,5 +1,5 @@ use crate::db::database::Database; -use crate::db::table::Table; +use crate::db::table::core::table::Table; use crate::interpreter::ast::{CreateTableStatement, ExistenceCheck}; pub fn create_table( @@ -24,8 +24,8 @@ pub fn create_table( #[cfg(test)] mod tests { use super::*; + use crate::db::table::core::{column::ColumnDefinition, value::DataType}; use crate::db::table::test_utils::default_database; - use crate::db::table::{ColumnDefinition, DataType}; use crate::interpreter::ast::CreateTableStatement; #[test] diff --git a/src/db/table/delete/mod.rs b/src/db/table/operations/delete/mod.rs similarity index 98% rename from src/db/table/delete/mod.rs rename to src/db/table/operations/delete/mod.rs index b0978d3..da00074 100644 --- a/src/db/table/delete/mod.rs +++ b/src/db/table/operations/delete/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; -use crate::db::table::Table; -use crate::db::table::helpers::common::get_row_indicies_matching_clauses; +use crate::db::table::core::table::Table; +use crate::db::table::operations::helpers::common::get_row_indicies_matching_clauses; use crate::interpreter::ast::DeleteStatement; pub fn delete(table: &mut Table, statement: DeleteStatement) -> Result, String> { @@ -46,8 +46,8 @@ fn swap_remove_bulk(table: &mut Table, row_indicies: &Vec) -> Result<(), #[cfg(test)] mod tests { use super::*; + use crate::db::table::core::{row::Row, value::Value}; use crate::db::table::test_utils::{assert_table_rows_eq_unordered, default_table}; - use crate::db::table::{Row, Value}; use crate::interpreter::ast::LimitClause; use crate::interpreter::ast::{ Operand, Operator, OrderByClause, OrderByDirection, SelectableStack, @@ -245,7 +245,7 @@ mod tests { #[test] fn delete_from_empty_table_works_correctly() { let mut table = default_table(); - table.rows = vec![]; + table.set_rows(vec![]); let statement = DeleteStatement { table_name: "users".to_string(), where_clause: None, diff --git a/src/db/table/drop_table/mod.rs b/src/db/table/operations/drop_table/mod.rs similarity index 100% rename from src/db/table/drop_table/mod.rs rename to src/db/table/operations/drop_table/mod.rs diff --git a/src/db/table/helpers/common.rs b/src/db/table/operations/helpers/common.rs similarity index 97% rename from src/db/table/helpers/common.rs rename to src/db/table/operations/helpers/common.rs index b288781..2c6228b 100644 --- a/src/db/table/helpers/common.rs +++ b/src/db/table/operations/helpers/common.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::collections::HashSet; -use crate::db::table::helpers::order_by_clause::apply_order_by_from_precomputed; -use crate::db::table::helpers::where_clause::row_matches_where_stack; -use crate::db::table::{DataType, Row, Table, Value}; +use crate::db::table::core::{row::Row, table::Table, value::DataType, value::Value}; +use crate::db::table::operations::helpers::order_by_clause::apply_order_by_from_precomputed; +use crate::db::table::operations::helpers::where_clause::row_matches_where_stack; use crate::interpreter::ast::{ LimitClause, LogicalOperator, MathOperator, Operator, OrderByClause, SelectableStack, SelectableStackElement, WhereStackElement, diff --git a/src/db/table/helpers/mod.rs b/src/db/table/operations/helpers/mod.rs similarity index 100% rename from src/db/table/helpers/mod.rs rename to src/db/table/operations/helpers/mod.rs diff --git a/src/db/table/helpers/order_by_clause.rs b/src/db/table/operations/helpers/order_by_clause.rs similarity index 95% rename from src/db/table/helpers/order_by_clause.rs rename to src/db/table/operations/helpers/order_by_clause.rs index 76ff3ef..7ba31cb 100644 --- a/src/db/table/helpers/order_by_clause.rs +++ b/src/db/table/operations/helpers/order_by_clause.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use crate::db::table::Row; +use crate::db::table::core::row::Row; use crate::interpreter::ast::OrderByClause; pub fn apply_order_by_from_precomputed( @@ -37,7 +37,7 @@ fn perform_comparisons(row1: &Row, row2: &Row, order_by_clause: &OrderByClause) #[cfg(test)] mod tests { use super::*; - use crate::db::table::{Row, Value}; + use crate::db::table::core::{row::Row, value::Value}; use crate::interpreter::ast::OrderByClause; use crate::interpreter::ast::OrderByDirection; use crate::interpreter::ast::SelectableStack; diff --git a/src/db/table/helpers/where_clause/mod.rs b/src/db/table/operations/helpers/where_clause/mod.rs similarity index 96% rename from src/db/table/helpers/where_clause/mod.rs rename to src/db/table/operations/helpers/where_clause/mod.rs index 8f9b276..bfc449f 100644 --- a/src/db/table/helpers/where_clause/mod.rs +++ b/src/db/table/operations/helpers/where_clause/mod.rs @@ -1,6 +1,6 @@ mod where_condition; mod where_stack; -use crate::db::table::{Row, Table}; +use crate::db::table::core::{row::Row, table::Table}; use crate::interpreter::ast::{WhereCondition, WhereStackElement}; // We create an interface here to allow us to create a spy for testing short circuiting. @@ -38,7 +38,9 @@ pub fn row_matches_where_stack( #[cfg(test)] mod tests { use super::*; - use crate::db::table::{ColumnDefinition, DataType, Table, Value}; + use crate::db::table::core::{ + column::ColumnDefinition, table::Table, value::DataType, value::Value, + }; use crate::interpreter::ast::{LogicalOperator, Operand, Operator, WhereStackElement}; struct SpyWhereConditionEvaluator { diff --git a/src/db/table/helpers/where_clause/where_condition.rs b/src/db/table/operations/helpers/where_clause/where_condition.rs similarity index 99% rename from src/db/table/helpers/where_clause/where_condition.rs rename to src/db/table/operations/helpers/where_clause/where_condition.rs index 99eb314..f096765 100644 --- a/src/db/table/helpers/where_clause/where_condition.rs +++ b/src/db/table/operations/helpers/where_clause/where_condition.rs @@ -1,5 +1,4 @@ -use crate::db::table::DataType; -use crate::db::table::{Row, Table, Value}; +use crate::db::table::core::{row::Row, table::Table, value::DataType, value::Value}; use crate::interpreter::ast::{Operand, Operator, WhereCondition}; // This file holds the logic for whether a row matches a where condition. @@ -133,7 +132,9 @@ fn expect_same_type(l_side: &Value, r_side: &Value) -> Result<(), String> { #[cfg(test)] mod tests { use super::*; - use crate::db::table::{ColumnDefinition, DataType, Table, Value}; + use crate::db::table::core::{ + column::ColumnDefinition, table::Table, value::DataType, value::Value, + }; use crate::interpreter::ast::{Operand, Operator, WhereCondition}; #[test] diff --git a/src/db/table/helpers/where_clause/where_stack.rs b/src/db/table/operations/helpers/where_clause/where_stack.rs similarity index 95% rename from src/db/table/helpers/where_clause/where_stack.rs rename to src/db/table/operations/helpers/where_clause/where_stack.rs index 05f7258..551443a 100644 --- a/src/db/table/helpers/where_clause/where_stack.rs +++ b/src/db/table/operations/helpers/where_clause/where_stack.rs @@ -1,5 +1,5 @@ -use crate::db::table::helpers::where_clause::MatchesWhereClause; -use crate::db::table::{Row, Table}; +use crate::db::table::core::{row::Row, table::Table}; +use crate::db::table::operations::helpers::where_clause::MatchesWhereClause; use crate::interpreter::ast::{LogicalOperator, WhereCondition, WhereStackElement}; // This file holds the logic for whether a row matches a where stack which is a vec of WhereConditions @@ -105,8 +105,10 @@ pub fn matches_where_stack( #[cfg(test)] mod tests { use super::*; - use crate::db::table::helpers::where_clause::WhereConditionEvaluator; - use crate::db::table::{ColumnDefinition, DataType, Row, Table, Value}; + use crate::db::table::core::{ + column::ColumnDefinition, row::Row, table::Table, value::DataType, value::Value, + }; + use crate::db::table::operations::helpers::where_clause::WhereConditionEvaluator; use crate::interpreter::ast::{LogicalOperator, WhereStackElement}; use crate::interpreter::ast::{Operand, Operator, WhereCondition}; diff --git a/src/db/table/insert/mod.rs b/src/db/table/operations/insert/mod.rs similarity index 95% rename from src/db/table/insert/mod.rs rename to src/db/table/operations/insert/mod.rs index 00f18a1..944c339 100644 --- a/src/db/table/insert/mod.rs +++ b/src/db/table/operations/insert/mod.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, VecDeque}; -use crate::db::table::helpers::common::validate_and_clone_row; -use crate::db::table::{Row, Table, Value}; +use crate::db::table::core::{row::Row, table::Table, value::Value}; +use crate::db::table::operations::helpers::common::validate_and_clone_row; use crate::interpreter::ast::InsertIntoStatement; pub fn insert(table: &mut Table, statement: InsertIntoStatement) -> Result, String> { @@ -65,7 +65,9 @@ pub fn insert(table: &mut Table, statement: InsertIntoStatement) -> Result Table { Table::new( diff --git a/src/db/table/operations/mod.rs b/src/db/table/operations/mod.rs new file mode 100644 index 0000000..15f3895 --- /dev/null +++ b/src/db/table/operations/mod.rs @@ -0,0 +1,8 @@ +pub mod alter_table; +pub mod create_table; +pub mod delete; +pub mod drop_table; +pub mod helpers; +pub mod insert; +pub mod select; +pub mod update; diff --git a/src/db/table/select/mod.rs b/src/db/table/operations/select/mod.rs similarity index 97% rename from src/db/table/select/mod.rs rename to src/db/table/operations/select/mod.rs index 28ca11b..af45d72 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/operations/select/mod.rs @@ -1,7 +1,10 @@ -mod select_statement; -mod set_operator_evaluator; -use crate::db::table::helpers::order_by_clause::apply_order_by_from_precomputed; -use crate::db::{database::Database, table::Row, table::Table}; +pub mod select_statement; +pub mod set_operator_evaluator; +use crate::db::table::operations::helpers::order_by_clause::apply_order_by_from_precomputed; +use crate::db::{ + database::Database, + table::core::{row::Row, table::Table}, +}; use crate::interpreter::ast::{SelectStatementStack, SelectStatementStackElement, SetOperator}; pub fn select_statement_stack( @@ -133,7 +136,7 @@ fn expand_all_column_names(table: &Table, column_names: &Vec) -> Vec Result>, @@ -79,7 +80,7 @@ impl SetOperatorEvaluator { #[cfg(test)] mod test { use super::*; - use crate::db::table::Value; + use crate::db::table::core::value::Value; use crate::db::table::test_utils::assert_table_rows_eq_unordered; fn rows_1() -> Vec { diff --git a/src/db/table/update/mod.rs b/src/db/table/operations/update/mod.rs similarity index 97% rename from src/db/table/update/mod.rs rename to src/db/table/operations/update/mod.rs index 56ee3cf..3fe8ab7 100644 --- a/src/db/table/update/mod.rs +++ b/src/db/table/operations/update/mod.rs @@ -1,6 +1,5 @@ -use crate::db::table::DataType; -use crate::db::table::Table; -use crate::db::table::helpers::common::get_row_indicies_matching_clauses; +use crate::db::table::core::{table::Table, value::DataType}; +use crate::db::table::operations::helpers::common::get_row_indicies_matching_clauses; use crate::interpreter::ast::{ColumnValue, UpdateStatement}; pub fn update(table: &mut Table, statement: UpdateStatement) -> Result, String> { @@ -40,9 +39,10 @@ fn update_rows_from_indicies( #[cfg(test)] mod tests { use super::*; - use crate::db::table::Row; + use crate::db::table::core::{ + column::ColumnDefinition, row::Row, value::DataType, value::Value, + }; use crate::db::table::test_utils::{assert_table_rows_eq_unordered, default_table}; - use crate::db::table::{ColumnDefinition, DataType, Value}; use crate::interpreter::ast::ColumnValue; use crate::interpreter::ast::{ LimitClause, Operand, Operator, OrderByClause, OrderByDirection, SelectableStack, @@ -278,7 +278,7 @@ mod tests { constraints: vec![], }], ); - table.rows = vec![]; + table.set_rows(vec![]); let statement = UpdateStatement { table_name: "users".to_string(), update_values: vec![ColumnValue { diff --git a/src/db/table/test_utils.rs b/src/db/table/test_utils.rs index d2c8a98..bf06133 100644 --- a/src/db/table/test_utils.rs +++ b/src/db/table/test_utils.rs @@ -1,9 +1,11 @@ #[cfg(test)] use crate::db::database::Database; #[cfg(test)] -use crate::db::table::{ColumnDefinition, ColumnStack, DataType, Table, Value}; +use crate::db::table::core::row::Row; #[cfg(test)] -use crate::db::table::{Row, RowStack}; +use crate::db::table::core::{ + column::ColumnDefinition, table::Table, value::DataType, value::Value, +}; #[cfg(test)] use crate::interpreter::ast::OrderByDirection; #[cfg(test)] @@ -11,9 +13,9 @@ use std::cmp::Ordering; #[cfg(test)] pub fn default_table() -> Table { - Table { - name: "users".to_string(), - columns: ColumnStack::new(vec![ + let mut table = Table::new( + "users".to_string(), + vec![ ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, @@ -34,93 +36,41 @@ pub fn default_table() -> Table { data_type: DataType::Real, constraints: vec![], }, - ]), - rows: vec![ - RowStack::new(Row(vec![ - Value::Integer(1), - Value::Text("John".to_string()), - Value::Integer(25), - Value::Real(1000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(2), - Value::Text("Jane".to_string()), - Value::Integer(30), - Value::Real(2000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(3), - Value::Text("Jim".to_string()), - Value::Integer(35), - Value::Real(3000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(4), - Value::Null, - Value::Integer(40), - Value::Real(4000.0), - ])), ], - } + ); + table.set_rows(vec![ + Row(vec![ + Value::Integer(1), + Value::Text("John".to_string()), + Value::Integer(25), + Value::Real(1000.0), + ]), + Row(vec![ + Value::Integer(2), + Value::Text("Jane".to_string()), + Value::Integer(30), + Value::Real(2000.0), + ]), + Row(vec![ + Value::Integer(3), + Value::Text("Jim".to_string()), + Value::Integer(35), + Value::Real(3000.0), + ]), + Row(vec![ + Value::Integer(4), + Value::Null, + Value::Integer(40), + Value::Real(4000.0), + ]), + ]); + 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: ColumnStack::new(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![ - RowStack::new(Row(vec![ - Value::Integer(1), - Value::Text("John".to_string()), - Value::Integer(25), - Value::Real(1000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(2), - Value::Text("Jane".to_string()), - Value::Integer(30), - Value::Real(2000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(3), - Value::Text("Jim".to_string()), - Value::Integer(35), - Value::Real(3000.0), - ])), - RowStack::new(Row(vec![ - Value::Integer(4), - Value::Null, - Value::Integer(40), - Value::Real(4000.0), - ])), - ], - }, - ); + database.tables.insert("users".to_string(), default_table()); database } diff --git a/src/db/transactions/mod.rs b/src/db/transactions/mod.rs index ed4c317..f952b5d 100644 --- a/src/db/transactions/mod.rs +++ b/src/db/transactions/mod.rs @@ -28,9 +28,13 @@ impl TransactionLog { self.entries.is_some() } - pub fn append_entry(&mut self, sql_statement: SqlStatement, affected_rows: Vec) -> Result<(), String> { + pub fn append_entry( + &mut self, + sql_statement: SqlStatement, + affected_rows: Vec, + ) -> Result<(), String> { if !self.in_transaction() { - return Ok(()) + return Ok(()); } let table_name = match &sql_statement { SqlStatement::CreateTable(statement) => statement.table_name.clone(), @@ -47,16 +51,18 @@ impl TransactionLog { } _ => return Err("Invalid transaction entry".to_string()), }; - self.get_entries_mut()?.push(TransactionEntry::Statement(StatementEntry { - statement: sql_statement, - table_name: table_name, - affected_rows: affected_rows, - })); + self.get_entries_mut()? + .push(TransactionEntry::Statement(StatementEntry { + statement: sql_statement, + table_name: table_name, + affected_rows: affected_rows, + })); Ok(()) } pub fn append_savepoint(&mut self, savepoint: Savepoint) -> Result<(), String> { - self.get_entries_mut()?.push(TransactionEntry::Savepoint(savepoint)); + self.get_entries_mut()? + .push(TransactionEntry::Savepoint(savepoint)); Ok(()) } @@ -81,10 +87,14 @@ impl TransactionLog { } pub fn get_entries(&self) -> Result<&Vec, String> { - self.entries.as_ref().ok_or_else(|| "No transaction is currently active".to_string()) + self.entries + .as_ref() + .ok_or_else(|| "No transaction is currently active".to_string()) } fn get_entries_mut(&mut self) -> Result<&mut Vec, String> { - self.entries.as_mut().ok_or_else(|| "No transaction is currently active".to_string()) + self.entries + .as_mut() + .ok_or_else(|| "No transaction is currently active".to_string()) } -} \ No newline at end of file +} diff --git a/src/interpreter/ast/alter_table_statement.rs b/src/interpreter/ast/alter_table_statement.rs index 2b98566..d2ca647 100644 --- a/src/interpreter/ast/alter_table_statement.rs +++ b/src/interpreter/ast/alter_table_statement.rs @@ -1,4 +1,4 @@ -use crate::db::table::ColumnDefinition; +use crate::db::table::core::column::ColumnDefinition; use crate::interpreter::{ ast::helpers::common::get_table_name, ast::helpers::token::{expect_token_type, token_to_data_type}, @@ -80,7 +80,7 @@ fn get_action(parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::db::table::DataType; + use crate::db::table::core::value::DataType; use crate::interpreter::ast::test_utils::token; #[test] diff --git a/src/interpreter/ast/create_statement.rs b/src/interpreter/ast/create_statement.rs index bb731fd..92bee21 100644 --- a/src/interpreter/ast/create_statement.rs +++ b/src/interpreter/ast/create_statement.rs @@ -1,4 +1,4 @@ -use crate::db::table::ColumnDefinition; +use crate::db::table::core::column::ColumnDefinition; use crate::interpreter::{ ast::{ CreateTableStatement, ExistenceCheck, @@ -88,7 +88,7 @@ fn column_definitions(parser: &mut Parser) -> Result, Stri #[cfg(test)] mod tests { use super::*; - use crate::db::table::DataType; + use crate::db::table::core::value::DataType; use crate::interpreter::ast::ExistenceCheck; use crate::interpreter::ast::test_utils::token; diff --git a/src/interpreter/ast/delete_statement.rs b/src/interpreter/ast/delete_statement.rs index dc47c8e..5cf0e5b 100644 --- a/src/interpreter/ast/delete_statement.rs +++ b/src/interpreter/ast/delete_statement.rs @@ -30,7 +30,7 @@ pub fn build(parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::db::table::Value; + use crate::db::table::core::value::Value; use crate::interpreter::ast::LimitClause; use crate::interpreter::ast::Operand; use crate::interpreter::ast::Operator; diff --git a/src/interpreter/ast/helpers/limit_clause.rs b/src/interpreter/ast/helpers/limit_clause.rs index f4cd835..baafcae 100644 --- a/src/interpreter/ast/helpers/limit_clause.rs +++ b/src/interpreter/ast/helpers/limit_clause.rs @@ -1,4 +1,4 @@ -use crate::db::table::Value; +use crate::db::table::core::value::Value; use crate::interpreter::ast::helpers::token::{expect_token_type, token_to_value}; use crate::interpreter::ast::{LimitClause, parser::Parser}; use crate::interpreter::tokenizer::token::TokenTypes; diff --git a/src/interpreter/ast/helpers/select_statement.rs b/src/interpreter/ast/helpers/select_statement.rs index 0a0c3dd..24db22f 100644 --- a/src/interpreter/ast/helpers/select_statement.rs +++ b/src/interpreter/ast/helpers/select_statement.rs @@ -52,7 +52,7 @@ fn get_columns_and_names(parser: &mut Parser) -> Result<(SelectableStack, Vec Result Result { #[cfg(test)] mod tests { use super::*; - use crate::db::table::Value; + use crate::db::table::core::value::Value; use crate::interpreter::ast::test_utils::token; use crate::interpreter::ast::{Operand, Operator, WhereCondition}; diff --git a/src/interpreter/ast/insert_statement.rs b/src/interpreter/ast/insert_statement.rs index 5e719b9..a4562fe 100644 --- a/src/interpreter/ast/insert_statement.rs +++ b/src/interpreter/ast/insert_statement.rs @@ -1,4 +1,4 @@ -use crate::db::table::Value; +use crate::db::table::core::value::Value; use crate::interpreter::{ ast::{ InsertIntoStatement, diff --git a/src/interpreter/ast/mod.rs b/src/interpreter/ast/mod.rs index 18e7d29..f606215 100644 --- a/src/interpreter/ast/mod.rs +++ b/src/interpreter/ast/mod.rs @@ -1,4 +1,4 @@ -use crate::db::table::{ColumnDefinition, Value}; +use crate::db::table::core::{column::ColumnDefinition, value::Value}; use crate::interpreter::tokenizer::{scanner::Token, token::TokenTypes}; mod alter_table_statement; diff --git a/src/interpreter/ast/select_statement_stack.rs b/src/interpreter/ast/select_statement_stack.rs index 5c3f57b..add3f12 100644 --- a/src/interpreter/ast/select_statement_stack.rs +++ b/src/interpreter/ast/select_statement_stack.rs @@ -160,7 +160,7 @@ fn get_set_operator(parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::db::table::Value; + use crate::db::table::core::value::Value; use crate::interpreter::ast::LimitClause; use crate::interpreter::ast::Operand; use crate::interpreter::ast::Operator; diff --git a/src/interpreter/ast/update_statement.rs b/src/interpreter/ast/update_statement.rs index c293916..7899cfd 100644 --- a/src/interpreter/ast/update_statement.rs +++ b/src/interpreter/ast/update_statement.rs @@ -64,7 +64,7 @@ fn get_update_values(parser: &mut Parser) -> Result, String> { #[cfg(test)] mod tests { use super::*; - use crate::db::table::Value; + use crate::db::table::core::value::Value; use crate::interpreter::ast::LimitClause; use crate::interpreter::ast::Operand; use crate::interpreter::ast::Operator; diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index c118cbf..7b69db1 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,5 +1,5 @@ use crate::db; -use crate::db::table::Row; +use crate::db::table::core::row::Row; pub mod ast; mod tokenizer; diff --git a/tests/crud_test.rs b/tests/crud_test.rs index de18944..af894f2 100644 --- a/tests/crud_test.rs +++ b/tests/crud_test.rs @@ -1,7 +1,7 @@ mod test_utils; use mollycache::db::database::Database; -use mollycache::db::table::{Row, Value}; +use mollycache::db::table::core::{row::Row, value::Value}; use mollycache::interpreter::run_sql; #[test] diff --git a/tests/set_operators.rs b/tests/set_operators.rs index 19818b5..e1570d3 100644 --- a/tests/set_operators.rs +++ b/tests/set_operators.rs @@ -1,7 +1,7 @@ mod test_utils; use mollycache::db::database::Database; -use mollycache::db::table::{Row, Value}; +use mollycache::db::table::core::{row::Row, value::Value}; use mollycache::interpreter::run_sql; #[test] diff --git a/tests/test_utils.rs b/tests/test_utils.rs index 671359e..edd43c1 100644 --- a/tests/test_utils.rs +++ b/tests/test_utils.rs @@ -1,5 +1,5 @@ #[cfg(test)] -use mollycache::db::table::Row; +use mollycache::db::table::core::row::Row; #[cfg(test)] use mollycache::interpreter::ast::OrderByDirection; #[cfg(test)]