From 20615508a221800d99ba853ff0e02c071de8ce85 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:51:17 +0100 Subject: [PATCH 01/28] First attempt at SELECT --- src/main.rs | 217 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 161 insertions(+), 56 deletions(-) diff --git a/src/main.rs b/src/main.rs index 31b1c7b..da02d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; // ==============SQL operations================ // TODO: Note that every operation has a table name. @@ -45,15 +45,23 @@ type UUID = u64; // TODO: What about nulls? I would rather not have that as in SQL, it sucks. // I would rather have non-nullable values by default, // and something like an explicit Option type for nulls. +#[derive(Debug, Clone, PartialEq)] enum DbValue { + Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to + // do? + Indexable(IndexableDbValue), +} + +#[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq)] +enum IndexableDbValue { String(String), Int(u64), - Number(f64), UUID(UUID), // TODO: what bout null? } // TODO: Can this be autogenerated from the values? +#[derive(Debug, Clone, Copy)] enum DbType { String, Int, @@ -65,10 +73,13 @@ impl DbValue { // TODO: Can this be autogenerated? fn to_type(self) -> DbType { match self { - Self::String(_) => DbType::String, - Self::Int(_) => DbType::Int, Self::Number(_) => DbType::Number, - Self::UUID(_) => DbType::UUID, + Self::Indexable(val) => + match val { + IndexableDbValue::String(_) => DbType::String, + IndexableDbValue::Int(_) => DbType::Int, + IndexableDbValue::UUID(_) => DbType::UUID, + } } } } @@ -90,13 +101,17 @@ struct Table { // TODO: Is this really indexed by DbValues? // Maybe we should have a separate index type for each type of value we're indexing over +// TODO: I should have a set of UUID, not just a single UUID, e.g. +// a user table can have multiple different users with the same name. struct ColumnIndex { - index: BTreeMap + index: BTreeMap> } // Note that it is nice to split metadata from the data because // then you can give the metadata to the parser without giving it the data. struct TableSchema { + table_name: TableName, // used for descriptive errors + primary_key: ColumnPosition, columns: HashMap } @@ -104,13 +119,12 @@ struct TableSchema { fn column_position(table_meta: TableSchema, column_name: ColumnName) -> Option { todo!() } - // Use `TablePosition` as index type Tables = Vec; type ColumnName = String; -type ColumnPosition = u32; +type ColumnPosition = usize; // Use `ColumnPosition` as index type Row = Vec; @@ -123,6 +137,11 @@ type Rows = // interface // insert(id, value) +fn select_columns(row: &Row, columns: &Vec) -> Row { + // row.column_position + todo!() +} + // ==============Interpreter================ struct State { table_positions: HashMap, @@ -134,7 +153,7 @@ impl State { todo!() } - fn attach_table(&mut self, table: Table) { + fn attach_table(&mut self, table_name: TableName, table: Table) { todo!() } } @@ -155,104 +174,190 @@ fn get_table<'tables_life: 'table_life, 'table_life>(tables: &'tables_life Table // because you'll be forced to put an iterator/slice into the Response data-structure. // Alternative is to pass a row-consumer to the functionas that knows how to communicate with // the client, but the details of communication are hidden behind an interface -fn interpret(table_name: TableName, operation: Operation, state: &mut State, consumer: impl SqlConsumer) -> () { +fn interpret(table_name: TableName, operation: Operation, state: &mut State, consumer: impl SqlConsumer) -> DbResult { // TODO: lock stuff use Operation::*; match operation { Select(table_name, column_selection, maybe_condition) => { let table: &Table = todo!(); - table.select_where(column_selection, maybe_condition, consumer) + Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) }, Insert(table_name, values) => { let table: &mut Table = todo!(); - table.insert(values, consumer) + table.insert(values); + todo!() }, Delete(table_name, maybe_condition) => { let table: &mut Table = todo!(); - table.delete_where(maybe_condition, consumer) + table.delete_where(maybe_condition); + todo!() }, CreateTable(table_name, table_schema) => { - let table = Table::new(table_name, table_schema); - state.attach_table(table); + let table = Table::new(table_schema); + state.attach_table(table_name, table); todo!() }, CreateIndex(table_name, column_name) => { let table: &mut Table = todo!(); + let column_position: ColumnPosition = todo!(); - let index: ColumnIndex = ColumnIndex::new(table, column_name); - table.attach_index(index); - }, // TODO: Is this sufficient? - // + let index: ColumnIndex = ColumnIndex::new(); + table.attach_index(column_position, index); + todo!() + }, } } impl ColumnIndex { - fn new(table: &Table, column_name: ColumnName) -> ColumnIndex { - todo!() + fn new() -> Self { + Self { index: BTreeMap::new() } } } +impl TableSchema { + fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { + match self.columns.get(column_name) { + Some((type_, column_position)) => Ok((*type_, *column_position)), + None => Err(Error::ColumnDoesNotExist(self.table_name.clone(), column_name.clone())) + } + } + + fn is_primary(&self, column_position: ColumnPosition) -> bool { + self.primary_key == column_position + } + + fn column_positions_from_column_names(&self, column_names: &[ColumnName]) -> DbResult> { + let mut positions: Vec = Vec::with_capacity(column_names.len()); + for column_name in column_names { + let (_, column_position) = self.get_column(column_name)?; + positions.push(column_position) + } + Ok(positions) + } + + fn column_positions_from_column_selection(&self, column_selection: &ColumnSelection) -> DbResult> { + match column_selection { + ColumnSelection::All => { + let mut column_positions: Vec = self.columns.values().map(|(_, column_position)| *column_position).collect(); + column_positions.sort(); + Ok(column_positions) + }, + + ColumnSelection::Columns(column_names) => { + self.column_positions_from_column_names(column_names) + }, + } + } + +} impl Table { - fn new(table_name: TableName, table_schema: TableSchema) -> Table { - todo!() + fn new(table_schema: TableSchema) -> Self { + Self { + schema: table_schema, + rows: BTreeMap::new(), + indexes: HashMap::new(), + } } - fn attach_index(&mut self, column_index: ColumnIndex) { - todo!() + fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { + self.indexes.insert(column_position, column_index); } - fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option, consumer: impl SqlConsumer) { + fn get_row_by_id(&self, id: UUID) -> Option { + self.rows.get(&id).cloned() + } + + fn get_rows_by_ids(&self, ids: HashSet) -> Vec { + ids.into_iter() + .filter_map(|id| self.get_row_by_id(id)) + .collect() + } + + fn get_rows_by_value(&self, column_position: ColumnPosition, value: &DbValue) -> Vec { + // brute-force search + self.rows.values() + .filter_map(|row| if row.get(column_position) == Some(value) { Some(row.clone()) } else { None }) + .collect() + } + + fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option) -> DbResult> { + let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; match maybe_condition { - None => { - // .iter() will give us an iterator over all the rows + None => + Ok(self.rows.values().map(|row| select_columns(row, &selected_column_positions)).collect()), - // two choices - // 1. optimized version - // self.iter_with_columns(column_selection).for_each(|row| { - // consumer.send(row) - // }); - // 2. - // self.iter() - // .map(|row| row.select_columns(column_selection)) - // .for_each(|reduced_row| { - // consumer.send(row) - // }); - todo!() - }, - Some(Condition::Eq(column_name, value)) => { - // is column_name primary key? then it is easy - // self.get(id) - // is column_name indexed? Then get the index, and then it is not easy, because you - // may get a set of ids. - // what if it is not primary nor indexed? then you need to brute force your way - // through the whole table? - todo!() + Some(Condition::Eq(eq_column_name, value)) => { + let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; + if self.schema.is_primary(eq_column_position) { + match value { + DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { + match self.get_row_by_id(uuid) { + Some(row) => Ok(vec![select_columns(&row, &selected_column_positions)]), + None => Ok(vec![]), + } + }, + _ => Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), eq_column_name.clone(), type_, value.clone())) + } + } else { + match value { + DbValue::Indexable(value) => { + match self.indexes.get(&eq_column_position) { + Some(column_index) => { + let ids = column_index.get(value); + Ok(self.get_rows_by_ids(ids).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + }, + None => { + Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + } + } + }, + _ => { + Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + } + } + } } } } - fn insert(&mut self, values: InsertionValues, consumer: impl SqlConsumer) { + fn insert(&mut self, values: InsertionValues) { // 1. You need to update indices // 2. you simply insert the data todo!() } - fn delete_where(&mut self, maybe_condition: Option, consumer: impl SqlConsumer) { + fn delete_where(&mut self, maybe_condition: Option) { // kinda similar to select with respect to the conditions // update index todo!() } } -// enum Response { -// Selected(impl Iter), // TODO: How to do this? Some reference to an iterator somehow... slice..? -// Inserted(???), -// Deleted(usize), // how many were deleted -// } +impl ColumnIndex { + fn get(&self, value: IndexableDbValue) -> HashSet { + match self.index.get(&value) { + Some(set) => set.clone(), + None => HashSet::new(), + } + } +} + +enum Response { + Selected(Vec), + Inserted(), + Deleted(usize), // how many were deleted +} + +type DbResult = Result; + +enum Error { + ColumnDoesNotExist(TableName, ColumnName), + ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue) +} fn main() { println!("Hello, world!"); From e0876bb0f164d9293809abac9fe1a4cffcf4428f Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:07:04 +0100 Subject: [PATCH 02/28] First attempt at UPDATE --- Cargo.lock | 9 ++++ Cargo.toml | 1 + src/main.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 124 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e87788c..7254440 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "minisql" version = "0.1.0" +dependencies = [ + "bimap", +] diff --git a/Cargo.toml b/Cargo.toml index df68143..1a108f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bimap = "0.6.3" diff --git a/src/main.rs b/src/main.rs index da02d7b..91035a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use bimap::BiMap; // ==============SQL operations================ // TODO: Note that every operation has a table name. @@ -112,7 +113,8 @@ struct ColumnIndex { struct TableSchema { table_name: TableName, // used for descriptive errors primary_key: ColumnPosition, - columns: HashMap + column_name_position_mapping: BiMap, + types: Vec, } // TODO @@ -219,8 +221,17 @@ impl ColumnIndex { impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { - match self.columns.get(column_name) { - Some((type_, column_position)) => Ok((*type_, *column_position)), + match self.column_name_position_mapping.get_by_left(column_name) { + Some(column_position) => { + match self.types.get(*column_position) { + Some(type_) => { + Ok((*type_, *column_position)) + }, + None => { + Err(Error::MissingTypeAnnotationOfColumn(self.table_name.clone(), *column_position)) + } + } + }, None => Err(Error::ColumnDoesNotExist(self.table_name.clone(), column_name.clone())) } } @@ -238,10 +249,17 @@ impl TableSchema { Ok(positions) } + fn column_name_from_column_position(&self, column_position: ColumnPosition) -> DbResult { + match self.column_name_position_mapping.get_by_right(&column_position) { + Some(column_name) => Ok(column_name.clone()), + None => Err(Error::ColumnPositionDoesNotExist(self.table_name.clone(), column_position)) + } + } + fn column_positions_from_column_selection(&self, column_selection: &ColumnSelection) -> DbResult> { match column_selection { ColumnSelection::All => { - let mut column_positions: Vec = self.columns.values().map(|(_, column_position)| *column_position).collect(); + let mut column_positions: Vec = self.column_name_position_mapping.iter().map(|(_, column_position)| *column_position).collect(); column_positions.sort(); Ok(column_positions) }, @@ -252,6 +270,57 @@ impl TableSchema { } } + fn number_of_columns(&self) -> usize { + self.column_name_position_mapping.len() + } + + fn row_from_insertion_values(&self, insertion_values: InsertionValues) -> DbResult<(UUID, Row)> { + // TODO: There should be proper validation of the insertion_values. + // And it shouldn't really be done here. + // + // In the below we don't check for duplicate column names + // + let number_of_columns = self.number_of_columns(); + if number_of_columns != insertion_values.len() { + return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) + } + + let mut row: Vec = Vec::with_capacity(number_of_columns); + + let mut values: HashMap = HashMap::new(); + for (column_name, db_value) in &insertion_values { + values.insert(column_name.clone(), db_value.clone()); + } + + for column_position in 0..number_of_columns { + let column_name: ColumnName = self.column_name_from_column_position(column_position)?; + match values.get(&column_name) { + Some(db_value) => { + row.push(db_value.clone()) + }, + None => { + return Err(Error::MissingColumnInInsertValues(self.table_name.clone(), column_name, insertion_values)) + } + } + } + + let id = match row.get(self.primary_key) { + Some(val) => { + match val { + DbValue::Indexable(IndexableDbValue::UUID(id)) => { + id + }, + _ => + unreachable!() + } + }, + None => + unreachable!() + }; + + Ok((*id, row)) + } + } impl Table { @@ -324,10 +393,25 @@ impl Table { } } - fn insert(&mut self, values: InsertionValues) { - // 1. You need to update indices - // 2. you simply insert the data - todo!() + fn insert(&mut self, values: InsertionValues) -> DbResult<()> { + let (id, row) = self.schema.row_from_insertion_values(values)?; + + if self.rows.get(&id).is_some() { + return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) + } + + for (column_position, column_index) in &mut self.indexes { + match row.get(*column_position) { + Some(DbValue::Indexable(val)) => { + column_index.add(val.clone(), id) + }, + Some(_) => {}, + None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) + } + } + + let _ = self.rows.insert(id, row); + Ok(()) } fn delete_where(&mut self, maybe_condition: Option) { @@ -344,6 +428,21 @@ impl ColumnIndex { None => HashSet::new(), } } + + fn add(&mut self, value: IndexableDbValue, id: UUID) { + match self.index.get_mut(&value) { + Some(ids) => { + ids.insert(id); + }, + None => { + self.index.insert(value, HashSet::from([id])); + } + } + } + + fn remove(&mut self, id: UUID) { + todo!() + } } enum Response { @@ -354,9 +453,15 @@ enum Response { type DbResult = Result; +// #[derive(Debug)] enum Error { ColumnDoesNotExist(TableName, ColumnName), - ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue) + ColumnPositionDoesNotExist(TableName, ColumnPosition), + ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue), + AttemptingToInsertAlreadyPresentId(TableName, UUID), + MissingTypeAnnotationOfColumn(TableName, ColumnPosition), + MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), + MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), } fn main() { From d555e8565fd58c8f1170bc7abf7b5800a94dd42b Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:36:13 +0100 Subject: [PATCH 03/28] First attempt at DELETE --- src/main.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 91035a1..142732a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -188,14 +188,14 @@ fn interpret(table_name: TableName, operation: Operation, state: &mut State, con Insert(table_name, values) => { let table: &mut Table = todo!(); - table.insert(values); - todo!() + let _ = table.insert(values)?; + Ok(Response::Inserted()) }, Delete(table_name, maybe_condition) => { let table: &mut Table = todo!(); - table.delete_where(maybe_condition); - todo!() + let rows_affected = table.delete_where(maybe_condition)?; + Ok(Response::Deleted(rows_affected)) }, CreateTable(table_name, table_schema) => { let table = Table::new(table_schema); @@ -353,6 +353,34 @@ impl Table { .collect() } + fn delete_row_by_id(&mut self, id: UUID) -> usize { + if let Some(row) = self.rows.remove(&id) { + for (column_position, column_index) in &mut self.indexes { + if let DbValue::Indexable(value) = &row[*column_position] { + column_index.remove(value, id) + }; + } + 1 + } else { + 0 + } + } + + fn delete_rows_by_ids(&mut self, ids: HashSet) -> usize { + let mut total_count = 0; + for id in ids { + total_count += self.delete_row_by_id(id) + } + total_count + } + + fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &DbValue) -> usize { + let matched_ids: HashSet = self.rows.iter() + .filter_map(|(id, row)| if row.get(column_position) == Some(value) { Some(*id) } else { None }) + .collect(); + self.delete_rows_by_ids(matched_ids) + } + fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option) -> DbResult> { let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; match maybe_condition { @@ -414,10 +442,47 @@ impl Table { Ok(()) } - fn delete_where(&mut self, maybe_condition: Option) { + fn delete_where(&mut self, maybe_condition: Option) -> DbResult { // kinda similar to select with respect to the conditions // update index - todo!() + match maybe_condition { + None => { + // delete all + let number_of_rows = self.rows.len(); + self.rows = BTreeMap::new(); + self.indexes = HashMap::new(); + Ok(number_of_rows) + }, + + Some(Condition::Eq(eq_column_name, value)) => { + let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; + if self.schema.is_primary(eq_column_position) { + match value { + DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { + Ok(self.delete_row_by_id(uuid)) + }, + _ => + return Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), eq_column_name.clone(), type_, value.clone())) + } + + } else { + match value { + DbValue::Indexable(value) => { + match self.indexes.get(&eq_column_position) { + Some(column_index) => { + let ids = column_index.get(value); + Ok(self.delete_rows_by_ids(ids)) + }, + None => + Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) + } + }, + _ => + Ok(self.delete_rows_by_value(eq_column_position, &value)) + } + } + } + } } } @@ -440,7 +505,7 @@ impl ColumnIndex { } } - fn remove(&mut self, id: UUID) { + fn remove(&mut self, value: &IndexableDbValue, id: UUID) { todo!() } } From 0aa3b28e7489e0e19d4c4900a9bb9ecce6b6dbdb Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:59:01 +0100 Subject: [PATCH 04/28] Refine CREATE TABLE and CREATE INDEX --- src/main.rs | 78 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 142732a..8c4ec20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,7 +90,7 @@ impl DbValue { // table-metadata and data type TableName = String; -type TablePosition = u32; +type TablePosition = usize; struct Table { schema: TableSchema, @@ -117,10 +117,6 @@ struct TableSchema { types: Vec, } -// TODO -fn column_position(table_meta: TableSchema, column_name: ColumnName) -> Option { - todo!() -} // Use `TablePosition` as index type Tables = Vec
; @@ -146,17 +142,35 @@ fn select_columns(row: &Row, columns: &Vec) -> Row { // ==============Interpreter================ struct State { - table_positions: HashMap, + table_name_position_mapping: BiMap, tables: Vec
, } impl State { - fn table_from_name<'b: 'a, 'a>(&'b self, table_name: TableName) -> Option<&'a Table> { - todo!() + fn table_from_name<'b: 'a, 'a>(&'b self, table_name: &TableName) -> DbResult<&'a Table> { + match self.table_name_position_mapping.get_by_left(table_name) { + Some(table_position) => { + let table = &self.tables[*table_position]; + Ok(table) + }, + None => Err(Error::TableDoesNotExist(table_name.clone())) + } + } + + fn table_from_name_mut<'b: 'a, 'a>(&'b mut self, table_name: &TableName) -> DbResult<&'a mut Table> { + match self.table_name_position_mapping.get_by_left(table_name) { + Some(table_position) => { + let table = &mut self.tables[*table_position]; + Ok(table) + }, + None => Err(Error::TableDoesNotExist(table_name.clone())) + } } fn attach_table(&mut self, table_name: TableName, table: Table) { - todo!() + let new_table_position: TablePosition = self.tables.len(); + self.table_name_position_mapping.insert(table_name, new_table_position); + self.tables.push(table); } } @@ -165,13 +179,6 @@ trait SqlConsumer { // TODO: } -// TODO: This should return a reference to the table -// 'tables_life contains 'table_life -fn get_table<'tables_life: 'table_life, 'table_life>(tables: &'tables_life Tables, table_name: &TableName) -> &'table_life Table { - // let table_position: - todo!() -} - // TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes, // because you'll be forced to put an iterator/slice into the Response data-structure. // Alternative is to pass a row-consumer to the functionas that knows how to communicate with @@ -182,17 +189,17 @@ fn interpret(table_name: TableName, operation: Operation, state: &mut State, con match operation { Select(table_name, column_selection, maybe_condition) => { - let table: &Table = todo!(); + let table: &Table = state.table_from_name(&table_name)?; Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) }, Insert(table_name, values) => { - let table: &mut Table = todo!(); + let table: &mut Table = state.table_from_name_mut(&table_name)?; let _ = table.insert(values)?; - Ok(Response::Inserted()) + Ok(Response::Inserted) }, Delete(table_name, maybe_condition) => { - let table: &mut Table = todo!(); + let table: &mut Table = state.table_from_name_mut(&table_name)?; let rows_affected = table.delete_where(maybe_condition)?; Ok(Response::Deleted(rows_affected)) @@ -200,22 +207,28 @@ fn interpret(table_name: TableName, operation: Operation, state: &mut State, con CreateTable(table_name, table_schema) => { let table = Table::new(table_schema); state.attach_table(table_name, table); - todo!() + Ok(Response::TableCreated) }, CreateIndex(table_name, column_name) => { - let table: &mut Table = todo!(); - let column_position: ColumnPosition = todo!(); + // TODO: This is incomplete. It can happen that an index is created + // after the table has some rows for a while. + // In such a case the index needs to be built over all those existing rows. + let table: &mut Table = state.table_from_name_mut(&table_name)?; + let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; - let index: ColumnIndex = ColumnIndex::new(); + let index: ColumnIndex = ColumnIndex::new(&table.rows); table.attach_index(column_position, index); - todo!() + Ok(Response::IndexCreated) }, } } impl ColumnIndex { - fn new() -> Self { - Self { index: BTreeMap::new() } + fn new(rows: &Rows) -> Self { + let index = BTreeMap::new(); + // TODO: Take into account already existing rows + todo!(); + Self { index } } } @@ -236,6 +249,10 @@ impl TableSchema { } } + fn column_position_from_column_name(&self, column_name: &ColumnName) -> DbResult { + self.get_column(column_name).map(|(_, column_position)| column_position) + } + fn is_primary(&self, column_position: ColumnPosition) -> bool { self.primary_key == column_position } @@ -243,7 +260,7 @@ impl TableSchema { fn column_positions_from_column_names(&self, column_names: &[ColumnName]) -> DbResult> { let mut positions: Vec = Vec::with_capacity(column_names.len()); for column_name in column_names { - let (_, column_position) = self.get_column(column_name)?; + let column_position = self.column_position_from_column_name(column_name)?; positions.push(column_position) } Ok(positions) @@ -512,14 +529,17 @@ impl ColumnIndex { enum Response { Selected(Vec), - Inserted(), + Inserted, Deleted(usize), // how many were deleted + TableCreated, + IndexCreated, } type DbResult = Result; // #[derive(Debug)] enum Error { + TableDoesNotExist(TableName), ColumnDoesNotExist(TableName, ColumnName), ColumnPositionDoesNotExist(TableName, ColumnPosition), ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue), From 77f4ae514eb82ab080345b0d87cfc2a9674e787c Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 11 Dec 2023 22:09:26 +0100 Subject: [PATCH 05/28] Implement index removal --- src/main.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8c4ec20..7c01ff8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -372,12 +372,13 @@ impl Table { fn delete_row_by_id(&mut self, id: UUID) -> usize { if let Some(row) = self.rows.remove(&id) { + let mut something_was_deleted = false; for (column_position, column_index) in &mut self.indexes { if let DbValue::Indexable(value) = &row[*column_position] { - column_index.remove(value, id) + something_was_deleted = something_was_deleted || column_index.remove(value, id); }; } - 1 + if something_was_deleted { 1 } else { 0 } } else { 0 } @@ -522,8 +523,16 @@ impl ColumnIndex { } } - fn remove(&mut self, value: &IndexableDbValue, id: UUID) { - todo!() + fn remove(&mut self, value: &IndexableDbValue, id_to_be_removed: UUID) -> bool { + match self.index.get_mut(value) { + Some(ids) => { + let was_present = ids.remove(&id_to_be_removed); + was_present + }, + None => { + false + } + } } } From c8e398a2384f4a73fbf77898e1e30feaa80659ae Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 09:55:42 +0100 Subject: [PATCH 06/28] Implement select_columns --- minisql/src/main.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 7c01ff8..63e809e 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -136,8 +136,17 @@ type Rows = // insert(id, value) fn select_columns(row: &Row, columns: &Vec) -> Row { - // row.column_position - todo!() + // If the index from `columns` is non-existant in `row`, it will just ignore it. + let mut subrow: Row = vec![]; + for column_position in columns { + match row.get(*column_position) { + Some(value) => { + subrow.push(value.clone()) + }, + None => {} + } + } + subrow } // ==============Interpreter================ From e111c4fc61449e5747f3286471b9efdc132e20ca Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 10:49:32 +0100 Subject: [PATCH 07/28] Implement proper index creation --- minisql/src/main.rs | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 63e809e..999f49a 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -225,22 +225,15 @@ fn interpret(table_name: TableName, operation: Operation, state: &mut State, con let table: &mut Table = state.table_from_name_mut(&table_name)?; let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; - let index: ColumnIndex = ColumnIndex::new(&table.rows); + let mut index: ColumnIndex = ColumnIndex::new(); + let _ = index.update_from_table(&table, column_position)?; + table.attach_index(column_position, index); Ok(Response::IndexCreated) }, } } -impl ColumnIndex { - fn new(rows: &Rows) -> Self { - let index = BTreeMap::new(); - // TODO: Take into account already existing rows - todo!(); - Self { index } - } -} - impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { match self.column_name_position_mapping.get_by_left(column_name) { @@ -514,6 +507,11 @@ impl Table { } impl ColumnIndex { + fn new() -> Self { + let index = BTreeMap::new(); + Self { index } + } + fn get(&self, value: IndexableDbValue) -> HashSet { match self.index.get(&value) { Some(set) => set.clone(), @@ -532,6 +530,27 @@ impl ColumnIndex { } } + // Should be used in the case when an indexed is created after the table has existed for a + // while. In such a case you need to build the index from the already existing rows. + fn update_from_table(&mut self, table: &Table, column_position: ColumnPosition) -> DbResult<()> { + for (id, row) in &table.rows { + let value = match row.get(column_position) { + Some(DbValue::Indexable(value)) => { + value.clone() + }, + Some(_) => { + let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; + return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) + }, + None => { + return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) + } + }; + self.add(value, *id) + } + Ok(()) + } + fn remove(&mut self, value: &IndexableDbValue, id_to_be_removed: UUID) -> bool { match self.index.get_mut(value) { Some(ids) => { @@ -565,6 +584,7 @@ enum Error { MissingTypeAnnotationOfColumn(TableName, ColumnPosition), MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), + AttemptToIndexNonIndexableColumn(TableName, ColumnName), } fn main() { From dc3e9b0077bf4edef3e72347d8834f3eafdf0280 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:33:00 +0100 Subject: [PATCH 08/28] Add some basic tests --- minisql/src/main.rs | 295 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 248 insertions(+), 47 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 999f49a..2008db9 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -5,6 +5,7 @@ use bimap::BiMap; // TODO: Note that every operation has a table name. // Perhaps consider factoring the table name out // and think of the operations as operating on a unique table. +// TODO: `TableName` should be replaced by `TablePosition` enum Operation { Select(TableName, ColumnSelection, Option), Insert(TableName, InsertionValues), @@ -92,6 +93,7 @@ impl DbValue { type TableName = String; type TablePosition = usize; +#[derive(Debug)] struct Table { schema: TableSchema, rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the @@ -104,12 +106,14 @@ struct Table { // Maybe we should have a separate index type for each type of value we're indexing over // TODO: I should have a set of UUID, not just a single UUID, e.g. // a user table can have multiple different users with the same name. +#[derive(Debug)] struct ColumnIndex { index: BTreeMap> } // Note that it is nice to split metadata from the data because // then you can give the metadata to the parser without giving it the data. +#[derive(Debug)] struct TableSchema { table_name: TableName, // used for descriptive errors primary_key: ColumnPosition, @@ -150,12 +154,20 @@ fn select_columns(row: &Row, columns: &Vec) -> Row { } // ==============Interpreter================ +#[derive(Debug)] struct State { table_name_position_mapping: BiMap, tables: Vec
, } impl State { + fn new() -> Self { + Self { + table_name_position_mapping: BiMap::new(), + tables: vec![], + } + } + fn table_from_name<'b: 'a, 'a>(&'b self, table_name: &TableName) -> DbResult<&'a Table> { match self.table_name_position_mapping.get_by_left(table_name) { Some(table_position) => { @@ -181,6 +193,54 @@ impl State { self.table_name_position_mapping.insert(table_name, new_table_position); self.tables.push(table); } + + // TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes, + // because you'll be forced to put an iterator/slice into the Response data-structure. + // Alternative is to pass a row-consumer to the functionas that knows how to communicate with + // the client, but the details of communication are hidden behind an interface + // + // writer: impl SqlConsumer + fn interpret(&mut self, operation: Operation) -> DbResult { + // TODO: lock stuff + use Operation::*; + + match operation { + Select(table_name, column_selection, maybe_condition) => { + let table: &Table = self.table_from_name(&table_name)?; + Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) + }, + Insert(table_name, values) => { + let table: &mut Table = self.table_from_name_mut(&table_name)?; + + let _ = table.insert(values)?; + Ok(Response::Inserted) + }, + Delete(table_name, maybe_condition) => { + let table: &mut Table = self.table_from_name_mut(&table_name)?; + + let rows_affected = table.delete_where(maybe_condition)?; + Ok(Response::Deleted(rows_affected)) + }, + CreateTable(table_name, table_schema) => { + let table = Table::new(table_schema); + self.attach_table(table_name, table); + Ok(Response::TableCreated) + }, + CreateIndex(table_name, column_name) => { + // TODO: This is incomplete. It can happen that an index is created + // after the table has some rows for a while. + // In such a case the index needs to be built over all those existing rows. + let table: &mut Table = self.table_from_name_mut(&table_name)?; + let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; + + let mut index: ColumnIndex = ColumnIndex::new(); + let _ = index.update_from_table(&table, column_position)?; + + table.attach_index(column_position, index); + Ok(Response::IndexCreated) + }, + } + } } // TODO: Give a better name to something that you can respond to with rows @@ -188,51 +248,6 @@ trait SqlConsumer { // TODO: } -// TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes, -// because you'll be forced to put an iterator/slice into the Response data-structure. -// Alternative is to pass a row-consumer to the functionas that knows how to communicate with -// the client, but the details of communication are hidden behind an interface -fn interpret(table_name: TableName, operation: Operation, state: &mut State, consumer: impl SqlConsumer) -> DbResult { - // TODO: lock stuff - use Operation::*; - - match operation { - Select(table_name, column_selection, maybe_condition) => { - let table: &Table = state.table_from_name(&table_name)?; - Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) - }, - Insert(table_name, values) => { - let table: &mut Table = state.table_from_name_mut(&table_name)?; - - let _ = table.insert(values)?; - Ok(Response::Inserted) - }, - Delete(table_name, maybe_condition) => { - let table: &mut Table = state.table_from_name_mut(&table_name)?; - - let rows_affected = table.delete_where(maybe_condition)?; - Ok(Response::Deleted(rows_affected)) - }, - CreateTable(table_name, table_schema) => { - let table = Table::new(table_schema); - state.attach_table(table_name, table); - Ok(Response::TableCreated) - }, - CreateIndex(table_name, column_name) => { - // TODO: This is incomplete. It can happen that an index is created - // after the table has some rows for a while. - // In such a case the index needs to be built over all those existing rows. - let table: &mut Table = state.table_from_name_mut(&table_name)?; - let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; - - let mut index: ColumnIndex = ColumnIndex::new(); - let _ = index.update_from_table(&table, column_position)?; - - table.attach_index(column_position, index); - Ok(Response::IndexCreated) - }, - } -} impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { @@ -304,7 +319,7 @@ impl TableSchema { return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) } - let mut row: Vec = Vec::with_capacity(number_of_columns); + let mut row: Row = Vec::with_capacity(number_of_columns); let mut values: HashMap = HashMap::new(); for (column_name, db_value) in &insertion_values { @@ -564,6 +579,7 @@ impl ColumnIndex { } } +#[derive(Debug)] enum Response { Selected(Vec), Inserted, @@ -574,7 +590,7 @@ enum Response { type DbResult = Result; -// #[derive(Debug)] +#[derive(Debug)] enum Error { TableDoesNotExist(TableName), ColumnDoesNotExist(TableName, ColumnName), @@ -590,3 +606,188 @@ enum Error { fn main() { println!("Hello, world!"); } + + + +#[cfg(test)] +mod tests { + use super::*; + + fn users_schema() -> TableSchema { + let id: ColumnPosition = 0; + let name: ColumnPosition = 1; + let age: ColumnPosition = 2; + + TableSchema { + table_name: "users".to_string(), + primary_key: 0, + column_name_position_mapping: { + let mut mapping: BiMap = BiMap::new(); + mapping.insert("id".to_string(), id); + mapping.insert("name".to_string(), name); + mapping.insert("age".to_string(), age); + mapping + }, + types: vec![DbType::UUID, DbType::String, DbType::Int], + } + } + + #[test] + fn test_table_creation() { + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + assert!(state.tables.len() == 1); + let table = &state.tables[0]; + assert!(table.rows.len() == 0); + + assert!(table.schema.table_name == users); + } + + #[test] + fn test_select_empty() { + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 0); + + } + + #[test] + fn test_select_nonexistant_table() { + let mut state = State::new(); + + let response: DbResult = state.interpret(Operation::Select("table_that_doesnt_exist".to_string(), ColumnSelection::All, None)); + assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); + } + + #[test] + fn test_insert_select_basic1() { + use DbValue::*; + use IndexableDbValue::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + let (id, name, age) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Operation::Insert(users.clone(), vec![ + ("id".to_string(), id.clone()), + ("name".to_string(), name.clone()), + ("age".to_string(), age.clone()), + ])).unwrap(); + + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row = &rows[0]; + + assert!(row.len() == 3); + assert!(row[0] == id); + assert!(row[1] == name); + assert!(row[2] == age); + } + + #[test] + fn test_insert_select_basic2() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + + let response0: Response = state.interpret(Select(users.clone(), ColumnSelection::All, None)).unwrap(); + assert!(matches!(response0, Response::Selected(_))); + let Response::Selected(rows0) = response0 else { todo!() }; + assert!(rows0.len() == 0); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + + { + let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 2); + let row0 = &rows[0]; + let row1 = &rows[1]; + + assert!(row0.len() == 3); + assert!(row0[0] == id0); + assert!(row0[1] == name0); + assert!(row0[2] == age0); + + assert!(row1.len() == 3); + assert!(row1[0] == id1); + assert!(row1[1] == name1); + assert!(row1[2] == age1); + } + + { + let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row0 = &rows[0]; + + assert!(row0.len() == 3); + assert!(row0[0] == id0); + assert!(row0[1] == name0); + assert!(row0[2] == age0); + } + + { + let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row0 = &rows[0]; + + assert!(row0.len() == 2); + assert!(row0[0] == name0); + assert!(row0[1] == id0); + } + } +} From 14f4fe4f3b699ae31255af57b8a53bed380ebe0d Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:56:16 +0100 Subject: [PATCH 09/28] Test delete --- minisql/src/main.rs | 69 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 2008db9..c2c6cb6 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -224,6 +224,7 @@ impl State { CreateTable(table_name, table_schema) => { let table = Table::new(table_schema); self.attach_table(table_name, table); + // TODO: What about attaching index on the primary column? Ok(Response::TableCreated) }, CreateIndex(table_name, column_name) => { @@ -389,13 +390,12 @@ impl Table { fn delete_row_by_id(&mut self, id: UUID) -> usize { if let Some(row) = self.rows.remove(&id) { - let mut something_was_deleted = false; for (column_position, column_index) in &mut self.indexes { if let DbValue::Indexable(value) = &row[*column_position] { - something_was_deleted = something_was_deleted || column_index.remove(value, id); + let _ = column_index.remove(value, id); }; } - if something_was_deleted { 1 } else { 0 } + 1 } else { 0 } @@ -491,6 +491,8 @@ impl Table { Some(Condition::Eq(eq_column_name, value)) => { let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; + // TODO: This shouldn't be necessary - we should index the primary column by + // default. The first case shouldn't be a special case! if self.schema.is_primary(eq_column_position) { match value { DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { @@ -718,11 +720,6 @@ mod tests { state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); - let response0: Response = state.interpret(Select(users.clone(), ColumnSelection::All, None)).unwrap(); - assert!(matches!(response0, Response::Selected(_))); - let Response::Selected(rows0) = response0 else { todo!() }; - assert!(rows0.len() == 0); - let (id0, name0, age0) = ( Indexable(UUID(0)), Indexable(String("Plato".to_string())), @@ -790,4 +787,60 @@ mod tests { assert!(row0[1] == id0); } } + + #[test] + fn test_delete() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + + + let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + // TODO: WTF, why is it 0? Should be 1 + assert!(matches!(delete_response, Response::Deleted(1))); + // assert!(matches!(delete_response, Response::Deleted(_))); + + + let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row = &rows[0]; + + assert!(row.len() == 3); + assert!(row[0] == id1); + assert!(row[1] == name1); + assert!(row[2] == age1); + } } From 291f90ef03b69a4a520fb02b6566bfcfd2cbe58c Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:03:38 +0100 Subject: [PATCH 10/28] cleanup --- minisql/src/main.rs | 69 +++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index c2c6cb6..f3344bd 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -12,7 +12,7 @@ enum Operation { Delete(TableName, Option), // Update(...), CreateTable(TableName, TableSchema), - CreateIndex(TableName, ColumnName), // TODO: Is this sufficient? + CreateIndex(TableName, ColumnName), // DropTable(TableName), } @@ -44,7 +44,7 @@ enum Condition { // ==============Values and Types================ type UUID = u64; -// TODO: What about nulls? I would rather not have that as in SQL, it sucks. +// TODO: What about nulls? I would rather not have that in SQL, it sucks. // I would rather have non-nullable values by default, // and something like an explicit Option type for nulls. #[derive(Debug, Clone, PartialEq)] @@ -62,7 +62,6 @@ enum IndexableDbValue { // TODO: what bout null? } -// TODO: Can this be autogenerated from the values? #[derive(Debug, Clone, Copy)] enum DbType { String, @@ -72,7 +71,6 @@ enum DbType { } impl DbValue { - // TODO: Can this be autogenerated? fn to_type(self) -> DbType { match self { Self::Number(_) => DbType::Number, @@ -88,8 +86,6 @@ impl DbValue { // ==============Tables================ -// table-metadata and data - type TableName = String; type TablePosition = usize; @@ -102,10 +98,6 @@ struct Table { HashMap // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc } -// TODO: Is this really indexed by DbValues? -// Maybe we should have a separate index type for each type of value we're indexing over -// TODO: I should have a set of UUID, not just a single UUID, e.g. -// a user table can have multiple different users with the same name. #[derive(Debug)] struct ColumnIndex { index: BTreeMap> @@ -139,7 +131,7 @@ type Rows = // interface // insert(id, value) -fn select_columns(row: &Row, columns: &Vec) -> Row { +fn restrict_columns(row: &Row, columns: &Vec) -> Row { // If the index from `columns` is non-existant in `row`, it will just ignore it. let mut subrow: Row = vec![]; for column_position in columns { @@ -199,7 +191,7 @@ impl State { // Alternative is to pass a row-consumer to the functionas that knows how to communicate with // the client, but the details of communication are hidden behind an interface // - // writer: impl SqlConsumer + // writer: impl SqlResponseConsumer fn interpret(&mut self, operation: Operation) -> DbResult { // TODO: lock stuff use Operation::*; @@ -224,13 +216,10 @@ impl State { CreateTable(table_name, table_schema) => { let table = Table::new(table_schema); self.attach_table(table_name, table); - // TODO: What about attaching index on the primary column? + Ok(Response::TableCreated) }, CreateIndex(table_name, column_name) => { - // TODO: This is incomplete. It can happen that an index is created - // after the table has some rows for a while. - // In such a case the index needs to be built over all those existing rows. let table: &mut Table = self.table_from_name_mut(&table_name)?; let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; @@ -245,14 +234,14 @@ impl State { } // TODO: Give a better name to something that you can respond to with rows -trait SqlConsumer { +trait SqlResponseConsumer { // TODO: } impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { - match self.column_name_position_mapping.get_by_left(column_name) { + match self.column_name_position_mapping.get_by_left(column_name) { Some(column_position) => { match self.types.get(*column_position) { Some(type_) => { @@ -389,15 +378,16 @@ impl Table { } fn delete_row_by_id(&mut self, id: UUID) -> usize { - if let Some(row) = self.rows.remove(&id) { - for (column_position, column_index) in &mut self.indexes { - if let DbValue::Indexable(value) = &row[*column_position] { - let _ = column_index.remove(value, id); - }; - } - 1 - } else { - 0 + match self.rows.remove(&id) { + Some(row) => { + for (column_position, column_index) in &mut self.indexes { + if let DbValue::Indexable(value) = &row[*column_position] { + let _ = column_index.remove(value, id); + }; + } + 1 + }, + None => 0 } } @@ -416,11 +406,12 @@ impl Table { self.delete_rows_by_ids(matched_ids) } - fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option) -> DbResult> { + fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; - match maybe_condition { + match condition { None => - Ok(self.rows.values().map(|row| select_columns(row, &selected_column_positions)).collect()), + // select all + Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()), Some(Condition::Eq(eq_column_name, value)) => { let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; @@ -428,7 +419,7 @@ impl Table { match value { DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { match self.get_row_by_id(uuid) { - Some(row) => Ok(vec![select_columns(&row, &selected_column_positions)]), + Some(row) => Ok(vec![restrict_columns(&row, &selected_column_positions)]), None => Ok(vec![]), } }, @@ -440,15 +431,15 @@ impl Table { match self.indexes.get(&eq_column_position) { Some(column_index) => { let ids = column_index.get(value); - Ok(self.get_rows_by_ids(ids).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) }, None => { - Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) } } }, _ => { - Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) + Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) } } } @@ -478,8 +469,6 @@ impl Table { } fn delete_where(&mut self, maybe_condition: Option) -> DbResult { - // kinda similar to select with respect to the conditions - // update index match maybe_condition { None => { // delete all @@ -491,8 +480,6 @@ impl Table { Some(Condition::Eq(eq_column_name, value)) => { let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; - // TODO: This shouldn't be necessary - we should index the primary column by - // default. The first case shouldn't be a special case! if self.schema.is_primary(eq_column_position) { match value { DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { @@ -610,7 +597,6 @@ fn main() { } - #[cfg(test)] mod tests { use super::*; @@ -622,7 +608,7 @@ mod tests { TableSchema { table_name: "users".to_string(), - primary_key: 0, + primary_key: id, column_name_position_mapping: { let mut mapping: BiMap = BiMap::new(); mapping.insert("id".to_string(), id); @@ -826,10 +812,7 @@ mod tests { let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); - // TODO: WTF, why is it 0? Should be 1 assert!(matches!(delete_response, Response::Deleted(1))); - // assert!(matches!(delete_response, Response::Deleted(_))); - let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); From 5e4abc3cb1c679f89c7e579ea391bd3b29dd52e5 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:39:13 +0100 Subject: [PATCH 11/28] Simplify select_where, delete_where --- minisql/src/main.rs | 107 +++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index f3344bd..26d346f 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -59,7 +59,7 @@ enum IndexableDbValue { String(String), Int(u64), UUID(UUID), - // TODO: what bout null? + // TODO: what about null? } #[derive(Debug, Clone, Copy)] @@ -149,7 +149,7 @@ fn restrict_columns(row: &Row, columns: &Vec) -> Row { #[derive(Debug)] struct State { table_name_position_mapping: BiMap, - tables: Vec
, + tables: Tables, } impl State { @@ -414,33 +414,18 @@ impl Table { Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()), Some(Condition::Eq(eq_column_name, value)) => { - let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; - if self.schema.is_primary(eq_column_position) { - match value { - DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { - match self.get_row_by_id(uuid) { - Some(row) => Ok(vec![restrict_columns(&row, &selected_column_positions)]), - None => Ok(vec![]), - } - }, - _ => Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), eq_column_name.clone(), type_, value.clone())) - } - } else { - match value { - DbValue::Indexable(value) => { - match self.indexes.get(&eq_column_position) { - Some(column_index) => { - let ids = column_index.get(value); - Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - }, - None => { - Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - } - } - }, - _ => { - Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(eq_column_position, &value)? { + Some(ids) => + Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + None => + Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) } + }, + _ => { + Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) } } } @@ -479,31 +464,42 @@ impl Table { }, Some(Condition::Eq(eq_column_name, value)) => { - let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; - if self.schema.is_primary(eq_column_position) { - match value { - DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { - Ok(self.delete_row_by_id(uuid)) - }, - _ => - return Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), eq_column_name.clone(), type_, value.clone())) - } + let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(eq_column_position, &value)? { + Some(ids) => + Ok(self.delete_rows_by_ids(ids)), + None => + Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) + } + }, + _ => + Ok(self.delete_rows_by_value(eq_column_position, &value)) + } + } + } + } - } else { - match value { - DbValue::Indexable(value) => { - match self.indexes.get(&eq_column_position) { - Some(column_index) => { - let ids = column_index.get(value); - Ok(self.delete_rows_by_ids(ids)) - }, - None => - Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) - } - }, - _ => - Ok(self.delete_rows_by_value(eq_column_position, &value)) - } + fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableDbValue) -> DbResult>> { + if self.schema.is_primary(column_position) { + match value { + IndexableDbValue::UUID(id) => + Ok(Some(HashSet::from([*id]))), + _ => { + let column_name: ColumnName = self.schema.column_name_from_column_position(column_position)?; + let type_ = self.schema.types[column_position]; + Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, DbValue::Indexable(value.clone()))) + } + } + } else { + match self.indexes.get(&column_position) { + Some(index) => { + let ids = index.get(value); + Ok(Some(ids)) + }, + None => { + Ok(None) } } } @@ -516,8 +512,8 @@ impl ColumnIndex { Self { index } } - fn get(&self, value: IndexableDbValue) -> HashSet { - match self.index.get(&value) { + fn get(&self, value: &IndexableDbValue) -> HashSet { + match self.index.get(value) { Some(set) => set.clone(), None => HashSet::new(), } @@ -593,7 +589,6 @@ enum Error { } fn main() { - println!("Hello, world!"); } @@ -826,4 +821,6 @@ mod tests { assert!(row[1] == name1); assert!(row[2] == age1); } + + // TODO: Test CreateIndex } From 3c57b0eb6c8afbe4d74b1f17c2c21aeef5cd521c Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:39:36 +0100 Subject: [PATCH 12/28] Add an example to fn main() --- minisql/src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 26d346f..3e5b530 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -589,6 +589,83 @@ enum Error { } fn main() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let users_schema = { + let id: ColumnPosition = 0; + let name: ColumnPosition = 1; + let age: ColumnPosition = 2; + + TableSchema { + table_name: "users".to_string(), + primary_key: id, + column_name_position_mapping: { + let mut mapping: BiMap = BiMap::new(); + mapping.insert("id".to_string(), id); + mapping.insert("name".to_string(), name); + mapping.insert("age".to_string(), age); + mapping + }, + types: vec![DbType::UUID, DbType::String, DbType::Int], + } + }; + let users = users_schema.table_name.clone(); + + + let mut state = State::new(); + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + println!("==INSERT Plato=="); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + println!("==INSERT Aristotle=="); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + println!(); + + { + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + println!("==SELECT ALL=="); + println!("{:?}", response); + println!(); + } + { + let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + println!("==SELECT Plato=="); + println!("{:?}", response); + println!(); + } + + { + let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + println!("==DELETE Plato=="); + let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), None)).unwrap(); + println!("==SELECT All=="); + println!("{:?}", response); + println!(); + } + } From 6b58c3cb9b5be7213b865d59f6d5297ac202801f Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 06:03:04 +0100 Subject: [PATCH 13/28] Reorganization --- minisql/src/base.rs | 132 ++++++ minisql/src/column_index.rs | 71 +++ minisql/src/error.rs | 16 + minisql/src/interpreter.rs | 430 +++++++++++++++++ minisql/src/main.rs | 908 +----------------------------------- minisql/src/operation.rs | 42 ++ minisql/src/table.rs | 203 ++++++++ minisql/src/type_system.rs | 43 ++ 8 files changed, 945 insertions(+), 900 deletions(-) create mode 100644 minisql/src/base.rs create mode 100644 minisql/src/column_index.rs create mode 100644 minisql/src/error.rs create mode 100644 minisql/src/interpreter.rs create mode 100644 minisql/src/operation.rs create mode 100644 minisql/src/table.rs create mode 100644 minisql/src/type_system.rs diff --git a/minisql/src/base.rs b/minisql/src/base.rs new file mode 100644 index 0000000..eeb74a1 --- /dev/null +++ b/minisql/src/base.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; +use bimap::BiMap; +use crate::operation::{InsertionValues, ColumnSelection}; +use crate::table::Row; +use crate::type_system::{DbType, DbValue, IndexableDbValue, UUID}; +use crate::error::Error; + +// Note that it is nice to split metadata from the data because +// then you can give the metadata to the parser without giving it the data. +#[derive(Debug)] +pub struct TableSchema { + pub table_name: TableName, // used for descriptive errors + pub primary_key: ColumnPosition, + pub column_name_position_mapping: BiMap, + pub types: Vec, +} + +pub type TableName = String; + +pub type ColumnName = String; +pub type ColumnPosition = usize; + +pub type DbResult = Result; + +impl TableSchema { + fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { + match self.column_name_position_mapping.get_by_left(column_name) { + Some(column_position) => { + match self.types.get(*column_position) { + Some(type_) => { + Ok((*type_, *column_position)) + }, + None => { + Err(Error::MissingTypeAnnotationOfColumn(self.table_name.clone(), *column_position)) + } + } + }, + None => Err(Error::ColumnDoesNotExist(self.table_name.clone(), column_name.clone())) + } + } + + pub fn column_position_from_column_name(&self, column_name: &ColumnName) -> DbResult { + self.get_column(column_name).map(|(_, column_position)| column_position) + } + + pub fn is_primary(&self, column_position: ColumnPosition) -> bool { + self.primary_key == column_position + } + + fn column_positions_from_column_names(&self, column_names: &[ColumnName]) -> DbResult> { + let mut positions: Vec = Vec::with_capacity(column_names.len()); + for column_name in column_names { + let column_position = self.column_position_from_column_name(column_name)?; + positions.push(column_position) + } + Ok(positions) + } + + pub fn column_name_from_column_position(&self, column_position: ColumnPosition) -> DbResult { + match self.column_name_position_mapping.get_by_right(&column_position) { + Some(column_name) => Ok(column_name.clone()), + None => Err(Error::ColumnPositionDoesNotExist(self.table_name.clone(), column_position)) + } + } + + pub fn column_positions_from_column_selection(&self, column_selection: &ColumnSelection) -> DbResult> { + match column_selection { + ColumnSelection::All => { + let mut column_positions: Vec = self.column_name_position_mapping.iter().map(|(_, column_position)| *column_position).collect(); + column_positions.sort(); + Ok(column_positions) + }, + + ColumnSelection::Columns(column_names) => { + self.column_positions_from_column_names(column_names) + }, + } + } + + fn number_of_columns(&self) -> usize { + self.column_name_position_mapping.len() + } + + // TODO: IS THIS THE RIGHT PLACE? + pub fn row_from_insertion_values(&self, insertion_values: InsertionValues) -> DbResult<(UUID, Row)> { + // TODO: There should be proper validation of the insertion_values. + // And it shouldn't really be done here. + // + // In the below we don't check for duplicate column names + // + let number_of_columns = self.number_of_columns(); + if number_of_columns != insertion_values.len() { + return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) + } + + let mut row: Row = Vec::with_capacity(number_of_columns); + + let mut values: HashMap = HashMap::new(); + for (column_name, db_value) in &insertion_values { + values.insert(column_name.clone(), db_value.clone()); + } + + for column_position in 0..number_of_columns { + let column_name: ColumnName = self.column_name_from_column_position(column_position)?; + match values.get(&column_name) { + Some(db_value) => { + row.push(db_value.clone()) + }, + None => { + return Err(Error::MissingColumnInInsertValues(self.table_name.clone(), column_name, insertion_values)) + } + } + } + + let id = match row.get(self.primary_key) { + Some(val) => { + match val { + DbValue::Indexable(IndexableDbValue::UUID(id)) => { + id + }, + _ => + unreachable!() + } + }, + None => + unreachable!() + }; + + Ok((*id, row)) + } + +} diff --git a/minisql/src/column_index.rs b/minisql/src/column_index.rs new file mode 100644 index 0000000..7be8671 --- /dev/null +++ b/minisql/src/column_index.rs @@ -0,0 +1,71 @@ +use std::collections::{BTreeMap, HashSet}; +use crate::base::{ColumnPosition, ColumnName, DbResult}; +use crate::type_system::{UUID, DbValue, IndexableDbValue}; +use crate::table::Table; +use crate::error::Error; + +#[derive(Debug)] +pub struct ColumnIndex { + index: BTreeMap> +} + +impl ColumnIndex { + pub fn new() -> Self { + let index = BTreeMap::new(); + Self { index } + } + + pub fn get(&self, value: &IndexableDbValue) -> HashSet { + match self.index.get(value) { + Some(set) => set.clone(), + None => HashSet::new(), + } + } + + pub fn add(&mut self, value: IndexableDbValue, id: UUID) { + match self.index.get_mut(&value) { + Some(ids) => { + ids.insert(id); + }, + None => { + self.index.insert(value, HashSet::from([id])); + } + } + } + + + // TODO: IS THIS THE RIGHT PLACE? + // Should be used in the case when an indexed is created after the table has existed for a + // while. In such a case you need to build the index from the already existing rows. + pub fn update_from_table(&mut self, table: &Table, column_position: ColumnPosition) -> DbResult<()> { + for (id, row) in &table.rows { + let value = match row.get(column_position) { + Some(DbValue::Indexable(value)) => { + value.clone() + }, + Some(_) => { + let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; + return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) + }, + None => { + return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) + } + }; + self.add(value, *id) + } + Ok(()) + } + + pub fn remove(&mut self, value: &IndexableDbValue, id_to_be_removed: UUID) -> bool { + match self.index.get_mut(value) { + Some(ids) => { + let was_present = ids.remove(&id_to_be_removed); + was_present + }, + None => { + false + } + } + } +} + diff --git a/minisql/src/error.rs b/minisql/src/error.rs new file mode 100644 index 0000000..126a855 --- /dev/null +++ b/minisql/src/error.rs @@ -0,0 +1,16 @@ +use crate::base::{ColumnName, TableName, ColumnPosition}; +use crate::type_system::{DbType, DbValue, UUID}; +use crate::operation::InsertionValues; + +#[derive(Debug)] +pub enum Error { + TableDoesNotExist(TableName), + ColumnDoesNotExist(TableName, ColumnName), + ColumnPositionDoesNotExist(TableName, ColumnPosition), + ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue), + AttemptingToInsertAlreadyPresentId(TableName, UUID), + MissingTypeAnnotationOfColumn(TableName, ColumnPosition), + MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), + MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), + AttemptToIndexNonIndexableColumn(TableName, ColumnName), +} diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs new file mode 100644 index 0000000..ee77ece --- /dev/null +++ b/minisql/src/interpreter.rs @@ -0,0 +1,430 @@ +use bimap::BiMap; +use crate::type_system::{DbValue, DbType, IndexableDbValue}; +use crate::base::{TableName, TableSchema, ColumnPosition, ColumnName, DbResult}; +use crate::table::{Table, Row}; +use crate::error::Error; +use crate::operation::{Operation, Condition, ColumnSelection}; +use crate::column_index::ColumnIndex; + + +// Use `TablePosition` as index +pub type Tables = Vec
; +pub type TablePosition = usize; + +// ==============Interpreter================ +#[derive(Debug)] +pub struct State { + table_name_position_mapping: BiMap, + tables: Tables, +} + +#[derive(Debug)] +pub enum Response { + Selected(Vec), + Inserted, + Deleted(usize), // how many were deleted + TableCreated, + IndexCreated, +} + + +impl State { + fn new() -> Self { + Self { + table_name_position_mapping: BiMap::new(), + tables: vec![], + } + } + + fn table_from_name<'b: 'a, 'a>(&'b self, table_name: &TableName) -> DbResult<&'a Table> { + match self.table_name_position_mapping.get_by_left(table_name) { + Some(table_position) => { + let table = &self.tables[*table_position]; + Ok(table) + }, + None => Err(Error::TableDoesNotExist(table_name.clone())) + } + } + + fn table_from_name_mut<'b: 'a, 'a>(&'b mut self, table_name: &TableName) -> DbResult<&'a mut Table> { + match self.table_name_position_mapping.get_by_left(table_name) { + Some(table_position) => { + let table = &mut self.tables[*table_position]; + Ok(table) + }, + None => Err(Error::TableDoesNotExist(table_name.clone())) + } + } + + fn attach_table(&mut self, table_name: TableName, table: Table) { + let new_table_position: TablePosition = self.tables.len(); + self.table_name_position_mapping.insert(table_name, new_table_position); + self.tables.push(table); + } + + // TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes, + // because you'll be forced to put an iterator/slice into the Response data-structure. + // Alternative is to pass a row-consumer to the functionas that knows how to communicate with + // the client, but the details of communication are hidden behind an interface + // + // writer: impl SqlResponseConsumer + fn interpret(&mut self, operation: Operation) -> DbResult { + // TODO: lock stuff + use Operation::*; + + match operation { + Select(table_name, column_selection, maybe_condition) => { + let table: &Table = self.table_from_name(&table_name)?; + Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) + }, + Insert(table_name, values) => { + let table: &mut Table = self.table_from_name_mut(&table_name)?; + + let _ = table.insert(values)?; + Ok(Response::Inserted) + }, + Delete(table_name, maybe_condition) => { + let table: &mut Table = self.table_from_name_mut(&table_name)?; + + let rows_affected = table.delete_where(maybe_condition)?; + Ok(Response::Deleted(rows_affected)) + }, + CreateTable(table_name, table_schema) => { + let table = Table::new(table_schema); + self.attach_table(table_name, table); + + Ok(Response::TableCreated) + }, + CreateIndex(table_name, column_name) => { + let table: &mut Table = self.table_from_name_mut(&table_name)?; + let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; + + let mut index: ColumnIndex = ColumnIndex::new(); + let _ = index.update_from_table(&table, column_position)?; + + table.attach_index(column_position, index); + Ok(Response::IndexCreated) + }, + } + } +} + +// TODO: Give a better name to something that you can respond to with rows +trait SqlResponseConsumer { + // TODO: +} + + +#[cfg(test)] +mod tests { + use super::*; + + fn users_schema() -> TableSchema { + let id: ColumnPosition = 0; + let name: ColumnPosition = 1; + let age: ColumnPosition = 2; + + TableSchema { + table_name: "users".to_string(), + primary_key: id, + column_name_position_mapping: { + let mut mapping: BiMap = BiMap::new(); + mapping.insert("id".to_string(), id); + mapping.insert("name".to_string(), name); + mapping.insert("age".to_string(), age); + mapping + }, + types: vec![DbType::UUID, DbType::String, DbType::Int], + } + } + + #[test] + fn test_table_creation() { + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + assert!(state.tables.len() == 1); + let table = &state.tables[0]; + assert!(table.rows.len() == 0); + + assert!(table.schema.table_name == users); + } + + #[test] + fn test_select_empty() { + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 0); + + } + + #[test] + fn test_select_nonexistant_table() { + let mut state = State::new(); + + let response: DbResult = state.interpret(Operation::Select("table_that_doesnt_exist".to_string(), ColumnSelection::All, None)); + assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); + } + + #[test] + fn test_insert_select_basic1() { + use DbValue::*; + use IndexableDbValue::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + let (id, name, age) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Operation::Insert(users.clone(), vec![ + ("id".to_string(), id.clone()), + ("name".to_string(), name.clone()), + ("age".to_string(), age.clone()), + ])).unwrap(); + + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row = &rows[0]; + + assert!(row.len() == 3); + assert!(row[0] == id); + assert!(row[1] == name); + assert!(row[2] == age); + } + + #[test] + fn test_insert_select_basic2() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + + { + let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 2); + let row0 = &rows[0]; + let row1 = &rows[1]; + + assert!(row0.len() == 3); + assert!(row0[0] == id0); + assert!(row0[1] == name0); + assert!(row0[2] == age0); + + assert!(row1.len() == 3); + assert!(row1[0] == id1); + assert!(row1[1] == name1); + assert!(row1[2] == age1); + } + + { + let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row0 = &rows[0]; + + assert!(row0.len() == 3); + assert!(row0[0] == id0); + assert!(row0[1] == name0); + assert!(row0[2] == age0); + } + + { + let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row0 = &rows[0]; + + assert!(row0.len() == 2); + assert!(row0[0] == name0); + assert!(row0[1] == id0); + } + } + + #[test] + fn test_delete() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + + + let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + assert!(matches!(delete_response, Response::Deleted(1))); + + let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + + assert!(matches!(response, Response::Selected(_))); + let Response::Selected(rows) = response else { todo!() }; + assert!(rows.len() == 1); + let row = &rows[0]; + + assert!(row.len() == 3); + assert!(row[0] == id1); + assert!(row[1] == name1); + assert!(row[2] == age1); + } + + // TODO: Test CreateIndex +} + + +pub fn example() { + use DbValue::*; + use IndexableDbValue::*; + use Operation::*; + use ColumnSelection::*; + use Condition::*; + + let users_schema = { + let id: ColumnPosition = 0; + let name: ColumnPosition = 1; + let age: ColumnPosition = 2; + + TableSchema { + table_name: "users".to_string(), + primary_key: id, + column_name_position_mapping: { + let mut mapping: BiMap = BiMap::new(); + mapping.insert("id".to_string(), id); + mapping.insert("name".to_string(), name); + mapping.insert("age".to_string(), age); + mapping + }, + types: vec![DbType::UUID, DbType::String, DbType::Int], + } + }; + let users = users_schema.table_name.clone(); + + + let mut state = State::new(); + state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + + let (id0, name0, age0) = ( + Indexable(UUID(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)) + ); + println!("==INSERT Plato=="); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ])).unwrap(); + + let (id1, name1, age1) = ( + Indexable(UUID(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)) + ); + println!("==INSERT Aristotle=="); + state.interpret(Insert(users.clone(), vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ])).unwrap(); + println!(); + + { + let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + println!("==SELECT ALL=="); + println!("{:?}", response); + println!(); + } + { + let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + println!("==SELECT Plato=="); + println!("{:?}", response); + println!(); + } + + { + let _delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + println!("==DELETE Plato=="); + let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), None)).unwrap(); + println!("==SELECT All=="); + println!("{:?}", response); + println!(); + } + +} diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 3e5b530..0a9fd94 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -1,903 +1,11 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; -use bimap::BiMap; - -// ==============SQL operations================ -// TODO: Note that every operation has a table name. -// Perhaps consider factoring the table name out -// and think of the operations as operating on a unique table. -// TODO: `TableName` should be replaced by `TablePosition` -enum Operation { - Select(TableName, ColumnSelection, Option), - Insert(TableName, InsertionValues), - Delete(TableName, Option), - // Update(...), - CreateTable(TableName, TableSchema), - CreateIndex(TableName, ColumnName), - // DropTable(TableName), -} - -type InsertionValues = Vec<(ColumnName, DbValue)>; - -enum ColumnSelection { - All, - Columns(Vec), -} - -enum Condition { - // And(Box, Box), - // Or(Box, Box), - // Not(Box), - - Eq(ColumnName, DbValue), - // LessOrEqual(ColumnName, DbValue), - // Less(ColumnName, DbValue), - - // StringCondition(StringCondition), -} - -// enum StringCondition { -// Prefix(ColumnName, String), -// Substring(ColumnName, String), -// } - - -// ==============Values and Types================ -type UUID = u64; - -// TODO: What about nulls? I would rather not have that in SQL, it sucks. -// I would rather have non-nullable values by default, -// and something like an explicit Option type for nulls. -#[derive(Debug, Clone, PartialEq)] -enum DbValue { - Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to - // do? - Indexable(IndexableDbValue), -} - -#[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq)] -enum IndexableDbValue { - String(String), - Int(u64), - UUID(UUID), - // TODO: what about null? -} - -#[derive(Debug, Clone, Copy)] -enum DbType { - String, - Int, - Number, - UUID, -} - -impl DbValue { - fn to_type(self) -> DbType { - match self { - Self::Number(_) => DbType::Number, - Self::Indexable(val) => - match val { - IndexableDbValue::String(_) => DbType::String, - IndexableDbValue::Int(_) => DbType::Int, - IndexableDbValue::UUID(_) => DbType::UUID, - } - } - } -} - - -// ==============Tables================ -type TableName = String; -type TablePosition = usize; - -#[derive(Debug)] -struct Table { - schema: TableSchema, - rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the - // same lock for both rows and indexes - indexes: - HashMap // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc -} - -#[derive(Debug)] -struct ColumnIndex { - index: BTreeMap> -} - -// Note that it is nice to split metadata from the data because -// then you can give the metadata to the parser without giving it the data. -#[derive(Debug)] -struct TableSchema { - table_name: TableName, // used for descriptive errors - primary_key: ColumnPosition, - column_name_position_mapping: BiMap, - types: Vec, -} - -// Use `TablePosition` as index -type Tables = Vec
; - - -type ColumnName = String; -type ColumnPosition = usize; - -// Use `ColumnPosition` as index -type Row = Vec; - -type Rows = - // TODO: This should be some sort of an interface to a dictionary - // s.t. in the background it may modify stuff in memory or talk to the disk - BTreeMap; - - // interface - // insert(id, value) - -fn restrict_columns(row: &Row, columns: &Vec) -> Row { - // If the index from `columns` is non-existant in `row`, it will just ignore it. - let mut subrow: Row = vec![]; - for column_position in columns { - match row.get(*column_position) { - Some(value) => { - subrow.push(value.clone()) - }, - None => {} - } - } - subrow -} - -// ==============Interpreter================ -#[derive(Debug)] -struct State { - table_name_position_mapping: BiMap, - tables: Tables, -} - -impl State { - fn new() -> Self { - Self { - table_name_position_mapping: BiMap::new(), - tables: vec![], - } - } - - fn table_from_name<'b: 'a, 'a>(&'b self, table_name: &TableName) -> DbResult<&'a Table> { - match self.table_name_position_mapping.get_by_left(table_name) { - Some(table_position) => { - let table = &self.tables[*table_position]; - Ok(table) - }, - None => Err(Error::TableDoesNotExist(table_name.clone())) - } - } - - fn table_from_name_mut<'b: 'a, 'a>(&'b mut self, table_name: &TableName) -> DbResult<&'a mut Table> { - match self.table_name_position_mapping.get_by_left(table_name) { - Some(table_position) => { - let table = &mut self.tables[*table_position]; - Ok(table) - }, - None => Err(Error::TableDoesNotExist(table_name.clone())) - } - } - - fn attach_table(&mut self, table_name: TableName, table: Table) { - let new_table_position: TablePosition = self.tables.len(); - self.table_name_position_mapping.insert(table_name, new_table_position); - self.tables.push(table); - } - - // TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes, - // because you'll be forced to put an iterator/slice into the Response data-structure. - // Alternative is to pass a row-consumer to the functionas that knows how to communicate with - // the client, but the details of communication are hidden behind an interface - // - // writer: impl SqlResponseConsumer - fn interpret(&mut self, operation: Operation) -> DbResult { - // TODO: lock stuff - use Operation::*; - - match operation { - Select(table_name, column_selection, maybe_condition) => { - let table: &Table = self.table_from_name(&table_name)?; - Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) - }, - Insert(table_name, values) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - - let _ = table.insert(values)?; - Ok(Response::Inserted) - }, - Delete(table_name, maybe_condition) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - - let rows_affected = table.delete_where(maybe_condition)?; - Ok(Response::Deleted(rows_affected)) - }, - CreateTable(table_name, table_schema) => { - let table = Table::new(table_schema); - self.attach_table(table_name, table); - - Ok(Response::TableCreated) - }, - CreateIndex(table_name, column_name) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; - - let mut index: ColumnIndex = ColumnIndex::new(); - let _ = index.update_from_table(&table, column_position)?; - - table.attach_index(column_position, index); - Ok(Response::IndexCreated) - }, - } - } -} - -// TODO: Give a better name to something that you can respond to with rows -trait SqlResponseConsumer { - // TODO: -} - - -impl TableSchema { - fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { - match self.column_name_position_mapping.get_by_left(column_name) { - Some(column_position) => { - match self.types.get(*column_position) { - Some(type_) => { - Ok((*type_, *column_position)) - }, - None => { - Err(Error::MissingTypeAnnotationOfColumn(self.table_name.clone(), *column_position)) - } - } - }, - None => Err(Error::ColumnDoesNotExist(self.table_name.clone(), column_name.clone())) - } - } - - fn column_position_from_column_name(&self, column_name: &ColumnName) -> DbResult { - self.get_column(column_name).map(|(_, column_position)| column_position) - } - - fn is_primary(&self, column_position: ColumnPosition) -> bool { - self.primary_key == column_position - } - - fn column_positions_from_column_names(&self, column_names: &[ColumnName]) -> DbResult> { - let mut positions: Vec = Vec::with_capacity(column_names.len()); - for column_name in column_names { - let column_position = self.column_position_from_column_name(column_name)?; - positions.push(column_position) - } - Ok(positions) - } - - fn column_name_from_column_position(&self, column_position: ColumnPosition) -> DbResult { - match self.column_name_position_mapping.get_by_right(&column_position) { - Some(column_name) => Ok(column_name.clone()), - None => Err(Error::ColumnPositionDoesNotExist(self.table_name.clone(), column_position)) - } - } - - fn column_positions_from_column_selection(&self, column_selection: &ColumnSelection) -> DbResult> { - match column_selection { - ColumnSelection::All => { - let mut column_positions: Vec = self.column_name_position_mapping.iter().map(|(_, column_position)| *column_position).collect(); - column_positions.sort(); - Ok(column_positions) - }, - - ColumnSelection::Columns(column_names) => { - self.column_positions_from_column_names(column_names) - }, - } - } - - fn number_of_columns(&self) -> usize { - self.column_name_position_mapping.len() - } - - fn row_from_insertion_values(&self, insertion_values: InsertionValues) -> DbResult<(UUID, Row)> { - // TODO: There should be proper validation of the insertion_values. - // And it shouldn't really be done here. - // - // In the below we don't check for duplicate column names - // - let number_of_columns = self.number_of_columns(); - if number_of_columns != insertion_values.len() { - return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) - } - - let mut row: Row = Vec::with_capacity(number_of_columns); - - let mut values: HashMap = HashMap::new(); - for (column_name, db_value) in &insertion_values { - values.insert(column_name.clone(), db_value.clone()); - } - - for column_position in 0..number_of_columns { - let column_name: ColumnName = self.column_name_from_column_position(column_position)?; - match values.get(&column_name) { - Some(db_value) => { - row.push(db_value.clone()) - }, - None => { - return Err(Error::MissingColumnInInsertValues(self.table_name.clone(), column_name, insertion_values)) - } - } - } - - let id = match row.get(self.primary_key) { - Some(val) => { - match val { - DbValue::Indexable(IndexableDbValue::UUID(id)) => { - id - }, - _ => - unreachable!() - } - }, - None => - unreachable!() - }; - - Ok((*id, row)) - } - -} - -impl Table { - fn new(table_schema: TableSchema) -> Self { - Self { - schema: table_schema, - rows: BTreeMap::new(), - indexes: HashMap::new(), - } - } - - fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { - self.indexes.insert(column_position, column_index); - } - - fn get_row_by_id(&self, id: UUID) -> Option { - self.rows.get(&id).cloned() - } - - fn get_rows_by_ids(&self, ids: HashSet) -> Vec { - ids.into_iter() - .filter_map(|id| self.get_row_by_id(id)) - .collect() - } - - fn get_rows_by_value(&self, column_position: ColumnPosition, value: &DbValue) -> Vec { - // brute-force search - self.rows.values() - .filter_map(|row| if row.get(column_position) == Some(value) { Some(row.clone()) } else { None }) - .collect() - } - - fn delete_row_by_id(&mut self, id: UUID) -> usize { - match self.rows.remove(&id) { - Some(row) => { - for (column_position, column_index) in &mut self.indexes { - if let DbValue::Indexable(value) = &row[*column_position] { - let _ = column_index.remove(value, id); - }; - } - 1 - }, - None => 0 - } - } - - fn delete_rows_by_ids(&mut self, ids: HashSet) -> usize { - let mut total_count = 0; - for id in ids { - total_count += self.delete_row_by_id(id) - } - total_count - } - - fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &DbValue) -> usize { - let matched_ids: HashSet = self.rows.iter() - .filter_map(|(id, row)| if row.get(column_position) == Some(value) { Some(*id) } else { None }) - .collect(); - self.delete_rows_by_ids(matched_ids) - } - - fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { - let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; - match condition { - None => - // select all - Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()), - - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - match value { - DbValue::Indexable(value) => { - match self.fetch_ids_from_index(eq_column_position, &value)? { - Some(ids) => - Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), - None => - Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - } - }, - _ => { - Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - } - } - } - } - } - - fn insert(&mut self, values: InsertionValues) -> DbResult<()> { - let (id, row) = self.schema.row_from_insertion_values(values)?; - - if self.rows.get(&id).is_some() { - return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) - } - - for (column_position, column_index) in &mut self.indexes { - match row.get(*column_position) { - Some(DbValue::Indexable(val)) => { - column_index.add(val.clone(), id) - }, - Some(_) => {}, - None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) - } - } - - let _ = self.rows.insert(id, row); - Ok(()) - } - - fn delete_where(&mut self, maybe_condition: Option) -> DbResult { - match maybe_condition { - None => { - // delete all - let number_of_rows = self.rows.len(); - self.rows = BTreeMap::new(); - self.indexes = HashMap::new(); - Ok(number_of_rows) - }, - - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - match value { - DbValue::Indexable(value) => { - match self.fetch_ids_from_index(eq_column_position, &value)? { - Some(ids) => - Ok(self.delete_rows_by_ids(ids)), - None => - Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) - } - }, - _ => - Ok(self.delete_rows_by_value(eq_column_position, &value)) - } - } - } - } - - fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableDbValue) -> DbResult>> { - if self.schema.is_primary(column_position) { - match value { - IndexableDbValue::UUID(id) => - Ok(Some(HashSet::from([*id]))), - _ => { - let column_name: ColumnName = self.schema.column_name_from_column_position(column_position)?; - let type_ = self.schema.types[column_position]; - Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, DbValue::Indexable(value.clone()))) - } - } - } else { - match self.indexes.get(&column_position) { - Some(index) => { - let ids = index.get(value); - Ok(Some(ids)) - }, - None => { - Ok(None) - } - } - } - } -} - -impl ColumnIndex { - fn new() -> Self { - let index = BTreeMap::new(); - Self { index } - } - - fn get(&self, value: &IndexableDbValue) -> HashSet { - match self.index.get(value) { - Some(set) => set.clone(), - None => HashSet::new(), - } - } - - fn add(&mut self, value: IndexableDbValue, id: UUID) { - match self.index.get_mut(&value) { - Some(ids) => { - ids.insert(id); - }, - None => { - self.index.insert(value, HashSet::from([id])); - } - } - } - - // Should be used in the case when an indexed is created after the table has existed for a - // while. In such a case you need to build the index from the already existing rows. - fn update_from_table(&mut self, table: &Table, column_position: ColumnPosition) -> DbResult<()> { - for (id, row) in &table.rows { - let value = match row.get(column_position) { - Some(DbValue::Indexable(value)) => { - value.clone() - }, - Some(_) => { - let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; - return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) - }, - None => { - return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) - } - }; - self.add(value, *id) - } - Ok(()) - } - - fn remove(&mut self, value: &IndexableDbValue, id_to_be_removed: UUID) -> bool { - match self.index.get_mut(value) { - Some(ids) => { - let was_present = ids.remove(&id_to_be_removed); - was_present - }, - None => { - false - } - } - } -} - -#[derive(Debug)] -enum Response { - Selected(Vec), - Inserted, - Deleted(usize), // how many were deleted - TableCreated, - IndexCreated, -} - -type DbResult = Result; - -#[derive(Debug)] -enum Error { - TableDoesNotExist(TableName), - ColumnDoesNotExist(TableName, ColumnName), - ColumnPositionDoesNotExist(TableName, ColumnPosition), - ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue), - AttemptingToInsertAlreadyPresentId(TableName, UUID), - MissingTypeAnnotationOfColumn(TableName, ColumnPosition), - MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), - MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), - AttemptToIndexNonIndexableColumn(TableName, ColumnName), -} +mod base; +mod table; +mod column_index; +mod operation; +mod interpreter; +mod error; +mod type_system; fn main() { - use DbValue::*; - use IndexableDbValue::*; - use Operation::*; - use ColumnSelection::*; - use Condition::*; - - let users_schema = { - let id: ColumnPosition = 0; - let name: ColumnPosition = 1; - let age: ColumnPosition = 2; - - TableSchema { - table_name: "users".to_string(), - primary_key: id, - column_name_position_mapping: { - let mut mapping: BiMap = BiMap::new(); - mapping.insert("id".to_string(), id); - mapping.insert("name".to_string(), name); - mapping.insert("age".to_string(), age); - mapping - }, - types: vec![DbType::UUID, DbType::String, DbType::Int], - } - }; - let users = users_schema.table_name.clone(); - - - let mut state = State::new(); - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); - - let (id0, name0, age0) = ( - Indexable(UUID(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)) - ); - println!("==INSERT Plato=="); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); - - let (id1, name1, age1) = ( - Indexable(UUID(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) - ); - println!("==INSERT Aristotle=="); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); - println!(); - - { - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); - println!("==SELECT ALL=="); - println!("{:?}", response); - println!(); - } - { - let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); - println!("==SELECT Plato=="); - println!("{:?}", response); - println!(); - } - - { - let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); - println!("==DELETE Plato=="); - let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), None)).unwrap(); - println!("==SELECT All=="); - println!("{:?}", response); - println!(); - } - -} - - -#[cfg(test)] -mod tests { - use super::*; - - fn users_schema() -> TableSchema { - let id: ColumnPosition = 0; - let name: ColumnPosition = 1; - let age: ColumnPosition = 2; - - TableSchema { - table_name: "users".to_string(), - primary_key: id, - column_name_position_mapping: { - let mut mapping: BiMap = BiMap::new(); - mapping.insert("id".to_string(), id); - mapping.insert("name".to_string(), name); - mapping.insert("age".to_string(), age); - mapping - }, - types: vec![DbType::UUID, DbType::String, DbType::Int], - } - } - - #[test] - fn test_table_creation() { - let mut state = State::new(); - let users_schema = users_schema(); - let users = users_schema.table_name.clone(); - - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); - - assert!(state.tables.len() == 1); - let table = &state.tables[0]; - assert!(table.rows.len() == 0); - - assert!(table.schema.table_name == users); - } - - #[test] - fn test_select_empty() { - let mut state = State::new(); - let users_schema = users_schema(); - let users = users_schema.table_name.clone(); - - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 0); - - } - - #[test] - fn test_select_nonexistant_table() { - let mut state = State::new(); - - let response: DbResult = state.interpret(Operation::Select("table_that_doesnt_exist".to_string(), ColumnSelection::All, None)); - assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); - } - - #[test] - fn test_insert_select_basic1() { - use DbValue::*; - use IndexableDbValue::*; - - let mut state = State::new(); - let users_schema = users_schema(); - let users = users_schema.table_name.clone(); - - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); - - let (id, name, age) = ( - Indexable(UUID(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)) - ); - state.interpret(Operation::Insert(users.clone(), vec![ - ("id".to_string(), id.clone()), - ("name".to_string(), name.clone()), - ("age".to_string(), age.clone()), - ])).unwrap(); - - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); - - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 1); - let row = &rows[0]; - - assert!(row.len() == 3); - assert!(row[0] == id); - assert!(row[1] == name); - assert!(row[2] == age); - } - - #[test] - fn test_insert_select_basic2() { - use DbValue::*; - use IndexableDbValue::*; - use Operation::*; - use ColumnSelection::*; - use Condition::*; - - let mut state = State::new(); - let users_schema = users_schema(); - let users = users_schema.table_name.clone(); - - state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); - - let (id0, name0, age0) = ( - Indexable(UUID(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)) - ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); - - let (id1, name1, age1) = ( - Indexable(UUID(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) - ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); - - { - let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); - - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 2); - let row0 = &rows[0]; - let row1 = &rows[1]; - - assert!(row0.len() == 3); - assert!(row0[0] == id0); - assert!(row0[1] == name0); - assert!(row0[2] == age0); - - assert!(row1.len() == 3); - assert!(row1[0] == id1); - assert!(row1[1] == name1); - assert!(row1[2] == age1); - } - - { - let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 1); - let row0 = &rows[0]; - - assert!(row0.len() == 3); - assert!(row0[0] == id0); - assert!(row0[1] == name0); - assert!(row0[2] == age0); - } - - { - let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), Some(Eq("id".to_string(), id0.clone())))).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 1); - let row0 = &rows[0]; - - assert!(row0.len() == 2); - assert!(row0[0] == name0); - assert!(row0[1] == id0); - } - } - - #[test] - fn test_delete() { - use DbValue::*; - use IndexableDbValue::*; - use Operation::*; - use ColumnSelection::*; - use Condition::*; - - let mut state = State::new(); - let users_schema = users_schema(); - let users = users_schema.table_name.clone(); - - state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); - - let (id0, name0, age0) = ( - Indexable(UUID(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)) - ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); - - let (id1, name1, age1) = ( - Indexable(UUID(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) - ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); - - - let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); - assert!(matches!(delete_response, Response::Deleted(1))); - - let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); - - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; - assert!(rows.len() == 1); - let row = &rows[0]; - - assert!(row.len() == 3); - assert!(row[0] == id1); - assert!(row[1] == name1); - assert!(row[2] == age1); - } - - // TODO: Test CreateIndex + interpreter::example(); } diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs new file mode 100644 index 0000000..7235fed --- /dev/null +++ b/minisql/src/operation.rs @@ -0,0 +1,42 @@ +use crate::base::{TableName, ColumnName}; +use crate::type_system::DbValue; +use crate::base::TableSchema; + +// ==============SQL operations================ +// TODO: Note that every operation has a table name. +// Perhaps consider factoring the table name out +// and think of the operations as operating on a unique table. +// TODO: `TableName` should be replaced by `TablePosition` +pub enum Operation { + Select(TableName, ColumnSelection, Option), + Insert(TableName, InsertionValues), + Delete(TableName, Option), + // Update(...), + CreateTable(TableName, TableSchema), + CreateIndex(TableName, ColumnName), + // DropTable(TableName), +} + +pub type InsertionValues = Vec<(ColumnName, DbValue)>; + +pub enum ColumnSelection { + All, + Columns(Vec), +} + +pub enum Condition { + // And(Box, Box), + // Or(Box, Box), + // Not(Box), + + Eq(ColumnName, DbValue), + // LessOrEqual(ColumnName, DbValue), + // Less(ColumnName, DbValue), + + // StringCondition(StringCondition), +} + +// enum StringCondition { +// Prefix(ColumnName, String), +// Substring(ColumnName, String), +// } diff --git a/minisql/src/table.rs b/minisql/src/table.rs new file mode 100644 index 0000000..a6bf175 --- /dev/null +++ b/minisql/src/table.rs @@ -0,0 +1,203 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; + +use crate::base::{TableSchema, ColumnPosition, ColumnName, DbResult}; +use crate::type_system::{UUID, DbValue, IndexableDbValue}; +use crate::column_index::ColumnIndex; +use crate::operation::{Condition, ColumnSelection, InsertionValues}; +use crate::error::Error; + + +#[derive(Debug)] +pub struct Table { + pub schema: TableSchema, + pub rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the + // same lock for both rows and indexes + pub indexes: + HashMap // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc +} + + +// Use `ColumnPosition` as index +pub type Row = Vec; + +pub type Rows = + // TODO: This should be some sort of an interface to a dictionary + // s.t. in the background it may modify stuff in memory or talk to the disk + BTreeMap; + + // interface + // insert(id, value) + +fn restrict_columns(row: &Row, columns: &Vec) -> Row { + // If the index from `columns` is non-existant in `row`, it will just ignore it. + let mut subrow: Row = vec![]; + for column_position in columns { + match row.get(*column_position) { + Some(value) => { + subrow.push(value.clone()) + }, + None => {} + } + } + subrow +} + +impl Table { + pub fn new(table_schema: TableSchema) -> Self { + Self { + schema: table_schema, + rows: BTreeMap::new(), + indexes: HashMap::new(), + } + } + + pub fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { + self.indexes.insert(column_position, column_index); + } + + fn get_row_by_id(&self, id: UUID) -> Option { + self.rows.get(&id).cloned() + } + + fn get_rows_by_ids(&self, ids: HashSet) -> Vec { + ids.into_iter() + .filter_map(|id| self.get_row_by_id(id)) + .collect() + } + + fn get_rows_by_value(&self, column_position: ColumnPosition, value: &DbValue) -> Vec { + // brute-force search + self.rows.values() + .filter_map(|row| if row.get(column_position) == Some(value) { Some(row.clone()) } else { None }) + .collect() + } + + fn delete_row_by_id(&mut self, id: UUID) -> usize { + match self.rows.remove(&id) { + Some(row) => { + for (column_position, column_index) in &mut self.indexes { + if let DbValue::Indexable(value) = &row[*column_position] { + let _ = column_index.remove(value, id); + }; + } + 1 + }, + None => 0 + } + } + + fn delete_rows_by_ids(&mut self, ids: HashSet) -> usize { + let mut total_count = 0; + for id in ids { + total_count += self.delete_row_by_id(id) + } + total_count + } + + fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &DbValue) -> usize { + let matched_ids: HashSet = self.rows.iter() + .filter_map(|(id, row)| if row.get(column_position) == Some(value) { Some(*id) } else { None }) + .collect(); + self.delete_rows_by_ids(matched_ids) + } + + pub fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { + let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; + match condition { + None => + // select all + Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + + Some(Condition::Eq(eq_column_name, value)) => { + let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(eq_column_position, &value)? { + Some(ids) => + Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + None => + Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + } + }, + _ => { + Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + } + } + } + } + } + + pub fn insert(&mut self, values: InsertionValues) -> DbResult<()> { + let (id, row) = self.schema.row_from_insertion_values(values)?; + + if self.rows.get(&id).is_some() { + return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) + } + + for (column_position, column_index) in &mut self.indexes { + match row.get(*column_position) { + Some(DbValue::Indexable(val)) => { + column_index.add(val.clone(), id) + }, + Some(_) => {}, + None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) + } + } + + let _ = self.rows.insert(id, row); + Ok(()) + } + + // TODO: Split into delete all and delete_where(condition) + pub fn delete_where(&mut self, maybe_condition: Option) -> DbResult { + match maybe_condition { + None => { + // delete all + let number_of_rows = self.rows.len(); + self.rows = BTreeMap::new(); + self.indexes = HashMap::new(); + Ok(number_of_rows) + }, + + Some(Condition::Eq(eq_column_name, value)) => { + let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(eq_column_position, &value)? { + Some(ids) => + Ok(self.delete_rows_by_ids(ids)), + None => + Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) + } + }, + _ => + Ok(self.delete_rows_by_value(eq_column_position, &value)) + } + } + } + } + + fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableDbValue) -> DbResult>> { + if self.schema.is_primary(column_position) { + match value { + IndexableDbValue::UUID(id) => + Ok(Some(HashSet::from([*id]))), + _ => { + let column_name: ColumnName = self.schema.column_name_from_column_position(column_position)?; + let type_ = self.schema.types[column_position]; + Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, DbValue::Indexable(value.clone()))) + } + } + } else { + match self.indexes.get(&column_position) { + Some(index) => { + let ids = index.get(value); + Ok(Some(ids)) + }, + None => { + Ok(None) + } + } + } + } +} diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs new file mode 100644 index 0000000..15c46b9 --- /dev/null +++ b/minisql/src/type_system.rs @@ -0,0 +1,43 @@ +// ==============Types================ +#[derive(Debug, Clone, Copy)] +pub enum DbType { + String, + Int, + Number, + UUID, +} + +// ==============Values================ +pub type UUID = u64; + +// TODO: What about nulls? I would rather not have that in SQL, it sucks. +// I would rather have non-nullable values by default, +// and something like an explicit Option type for nulls. +#[derive(Debug, Clone, PartialEq)] +pub enum DbValue { + Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to + // do? + Indexable(IndexableDbValue), +} + +#[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq)] +pub enum IndexableDbValue { + String(String), + Int(u64), + UUID(UUID), + // TODO: what about null? +} + +impl DbValue { + fn to_type(self) -> DbType { + match self { + Self::Number(_) => DbType::Number, + Self::Indexable(val) => + match val { + IndexableDbValue::String(_) => DbType::String, + IndexableDbValue::Int(_) => DbType::Int, + IndexableDbValue::UUID(_) => DbType::UUID, + } + } + } +} From f9b874f30212fbbdcb912b9efbba1de8030fb711 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 06:33:30 +0100 Subject: [PATCH 14/28] Simplify select_all and delete_all --- minisql/src/interpreter.rs | 2 +- minisql/src/table.rs | 160 ++++++++++++++++++++----------------- 2 files changed, 86 insertions(+), 76 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index ee77ece..aba4abe 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -86,7 +86,7 @@ impl State { Delete(table_name, maybe_condition) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; - let rows_affected = table.delete_where(maybe_condition)?; + let rows_affected = table.delete_rows_where(maybe_condition)?; Ok(Response::Deleted(rows_affected)) }, CreateTable(table_name, table_schema) => { diff --git a/minisql/src/table.rs b/minisql/src/table.rs index a6bf175..80c24c4 100644 --- a/minisql/src/table.rs +++ b/minisql/src/table.rs @@ -17,16 +17,13 @@ pub struct Table { } -// Use `ColumnPosition` as index -pub type Row = Vec; - pub type Rows = // TODO: This should be some sort of an interface to a dictionary // s.t. in the background it may modify stuff in memory or talk to the disk BTreeMap; - // interface - // insert(id, value) +// Use `ColumnPosition` as index +pub type Row = Vec; fn restrict_columns(row: &Row, columns: &Vec) -> Row { // If the index from `columns` is non-existant in `row`, it will just ignore it. @@ -51,10 +48,7 @@ impl Table { } } - pub fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { - self.indexes.insert(column_position, column_index); - } - + // ======Selection====== fn get_row_by_id(&self, id: UUID) -> Option { self.rows.get(&id).cloned() } @@ -72,6 +66,61 @@ impl Table { .collect() } + fn select_all_rows(&self, selected_column_positions: &Vec) -> Vec { + self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect() + } + + fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: DbValue) -> DbResult> { + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(column_position, &value)? { + Some(ids) => + Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + None => + Ok(self.get_rows_by_value(column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + } + }, + _ => { + Ok(self.get_rows_by_value(column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + } + } + } + + pub fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { + let selected_column_positions: Vec = self.schema.column_positions_from_column_selection(&column_selection)?; + match condition { + None => Ok(self.select_all_rows(&selected_column_positions)), + + Some(Condition::Eq(eq_column_name, value)) => { + let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; + self.select_rows_where_eq(&selected_column_positions, eq_column_position, value) + } + } + } + + // ======Insertion====== + pub fn insert(&mut self, values: InsertionValues) -> DbResult<()> { + let (id, row) = self.schema.row_from_insertion_values(values)?; + + if self.rows.get(&id).is_some() { + return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) + } + + for (column_position, column_index) in &mut self.indexes { + match row.get(*column_position) { + Some(DbValue::Indexable(val)) => { + column_index.add(val.clone(), id) + }, + Some(_) => {}, + None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) + } + } + + let _ = self.rows.insert(id, row); + Ok(()) + } + + // ======Deletion====== fn delete_row_by_id(&mut self, id: UUID) -> usize { match self.rows.remove(&id) { Some(row) => { @@ -101,82 +150,43 @@ impl Table { self.delete_rows_by_ids(matched_ids) } - pub fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { - let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; - match condition { - None => - // select all - Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + fn delete_all_rows(&mut self) -> usize { + let number_of_rows = self.rows.len(); + self.rows = BTreeMap::new(); + self.indexes = HashMap::new(); + number_of_rows + } - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - match value { - DbValue::Indexable(value) => { - match self.fetch_ids_from_index(eq_column_position, &value)? { - Some(ids) => - Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), - None => - Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - } - }, - _ => { - Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) - } + fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: DbValue) -> DbResult { + match value { + DbValue::Indexable(value) => { + match self.fetch_ids_from_index(column_position, &value)? { + Some(ids) => + Ok(self.delete_rows_by_ids(ids)), + None => + Ok(self.delete_rows_by_value(column_position, &DbValue::Indexable(value))) } - } - } - } - - pub fn insert(&mut self, values: InsertionValues) -> DbResult<()> { - let (id, row) = self.schema.row_from_insertion_values(values)?; - - if self.rows.get(&id).is_some() { - return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) - } - - for (column_position, column_index) in &mut self.indexes { - match row.get(*column_position) { - Some(DbValue::Indexable(val)) => { - column_index.add(val.clone(), id) - }, - Some(_) => {}, - None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) - } - } - - let _ = self.rows.insert(id, row); - Ok(()) - } - - // TODO: Split into delete all and delete_where(condition) - pub fn delete_where(&mut self, maybe_condition: Option) -> DbResult { - match maybe_condition { - None => { - // delete all - let number_of_rows = self.rows.len(); - self.rows = BTreeMap::new(); - self.indexes = HashMap::new(); - Ok(number_of_rows) }, + _ => + Ok(self.delete_rows_by_value(column_position, &value)) + } + } + pub fn delete_rows_where(&mut self, maybe_condition: Option) -> DbResult { + match maybe_condition { + None => Ok(self.delete_all_rows()), Some(Condition::Eq(eq_column_name, value)) => { let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - match value { - DbValue::Indexable(value) => { - match self.fetch_ids_from_index(eq_column_position, &value)? { - Some(ids) => - Ok(self.delete_rows_by_ids(ids)), - None => - Ok(self.delete_rows_by_value(eq_column_position, &DbValue::Indexable(value))) - } - }, - _ => - Ok(self.delete_rows_by_value(eq_column_position, &value)) - } + self.delete_rows_where_eq(eq_column_position, value) } } } + // ======Indexing====== + pub fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { + self.indexes.insert(column_position, column_index); + } + fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableDbValue) -> DbResult>> { if self.schema.is_primary(column_position) { match value { From bfb5042896393e25637d1bce90ac1405b25f290b Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 06:57:24 +0100 Subject: [PATCH 15/28] Disentangle table.rs from operation.rs --- minisql/src/interpreter.rs | 27 +++++++++++++++++++++++---- minisql/src/table.rs | 35 +++++------------------------------ 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index aba4abe..050d3a8 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -68,25 +68,44 @@ impl State { // the client, but the details of communication are hidden behind an interface // // writer: impl SqlResponseConsumer - fn interpret(&mut self, operation: Operation) -> DbResult { + pub fn interpret(&mut self, operation: Operation) -> DbResult { // TODO: lock stuff use Operation::*; match operation { Select(table_name, column_selection, maybe_condition) => { let table: &Table = self.table_from_name(&table_name)?; - Ok(Response::Selected(table.select_where(column_selection, maybe_condition)?)) + + let selected_column_positions: Vec = table.schema.column_positions_from_column_selection(&column_selection)?; + let selected_rows = match maybe_condition { + None => table.select_all_rows(&selected_column_positions), + + Some(Condition::Eq(eq_column_name, value)) => { + let eq_column_position = table.schema.column_position_from_column_name(&eq_column_name)?; + table.select_rows_where_eq(&selected_column_positions, eq_column_position, value)? + } + }; + + Ok(Response::Selected(selected_rows)) }, Insert(table_name, values) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; - let _ = table.insert(values)?; + let (id, row) = table.schema.row_from_insertion_values(values)?; + let _ = table.insert_row_at(id, row)?; Ok(Response::Inserted) }, Delete(table_name, maybe_condition) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; - let rows_affected = table.delete_rows_where(maybe_condition)?; + let rows_affected = match maybe_condition { + None => table.delete_all_rows(), + Some(Condition::Eq(eq_column_name, value)) => { + let eq_column_position = table.schema.column_position_from_column_name(&eq_column_name)?; + table.delete_rows_where_eq(eq_column_position, value)? + } + }; + Ok(Response::Deleted(rows_affected)) }, CreateTable(table_name, table_schema) => { diff --git a/minisql/src/table.rs b/minisql/src/table.rs index 80c24c4..716a940 100644 --- a/minisql/src/table.rs +++ b/minisql/src/table.rs @@ -3,7 +3,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use crate::base::{TableSchema, ColumnPosition, ColumnName, DbResult}; use crate::type_system::{UUID, DbValue, IndexableDbValue}; use crate::column_index::ColumnIndex; -use crate::operation::{Condition, ColumnSelection, InsertionValues}; use crate::error::Error; @@ -66,11 +65,11 @@ impl Table { .collect() } - fn select_all_rows(&self, selected_column_positions: &Vec) -> Vec { + pub fn select_all_rows(&self, selected_column_positions: &Vec) -> Vec { self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect() } - fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: DbValue) -> DbResult> { + pub fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: DbValue) -> DbResult> { match value { DbValue::Indexable(value) => { match self.fetch_ids_from_index(column_position, &value)? { @@ -86,22 +85,8 @@ impl Table { } } - pub fn select_where(&self, column_selection: ColumnSelection, condition: Option) -> DbResult> { - let selected_column_positions: Vec = self.schema.column_positions_from_column_selection(&column_selection)?; - match condition { - None => Ok(self.select_all_rows(&selected_column_positions)), - - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - self.select_rows_where_eq(&selected_column_positions, eq_column_position, value) - } - } - } - // ======Insertion====== - pub fn insert(&mut self, values: InsertionValues) -> DbResult<()> { - let (id, row) = self.schema.row_from_insertion_values(values)?; - + pub fn insert_row_at(&mut self, id: UUID, row: Row) -> DbResult<()> { if self.rows.get(&id).is_some() { return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) } @@ -150,14 +135,14 @@ impl Table { self.delete_rows_by_ids(matched_ids) } - fn delete_all_rows(&mut self) -> usize { + pub fn delete_all_rows(&mut self) -> usize { let number_of_rows = self.rows.len(); self.rows = BTreeMap::new(); self.indexes = HashMap::new(); number_of_rows } - fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: DbValue) -> DbResult { + pub fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: DbValue) -> DbResult { match value { DbValue::Indexable(value) => { match self.fetch_ids_from_index(column_position, &value)? { @@ -172,16 +157,6 @@ impl Table { } } - pub fn delete_rows_where(&mut self, maybe_condition: Option) -> DbResult { - match maybe_condition { - None => Ok(self.delete_all_rows()), - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = self.schema.column_position_from_column_name(&eq_column_name)?; - self.delete_rows_where_eq(eq_column_position, value) - } - } - } - // ======Indexing====== pub fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { self.indexes.insert(column_position, column_index); From f7a6cc2549fa7d1a86eff04868aec28df638feeb Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 07:24:46 +0100 Subject: [PATCH 16/28] Make Row a wrapper type --- minisql/src/base.rs | 9 ++- minisql/src/interpreter.rs | 2 +- minisql/src/table.rs | 111 ++++++++++++++++++++++++++++--------- minisql/src/type_system.rs | 2 +- 4 files changed, 91 insertions(+), 33 deletions(-) diff --git a/minisql/src/base.rs b/minisql/src/base.rs index eeb74a1..7163840 100644 --- a/minisql/src/base.rs +++ b/minisql/src/base.rs @@ -93,7 +93,7 @@ impl TableSchema { return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) } - let mut row: Row = Vec::with_capacity(number_of_columns); + let mut row: Row = Row::with_number_of_columns(number_of_columns); let mut values: HashMap = HashMap::new(); for (column_name, db_value) in &insertion_values { @@ -112,11 +112,11 @@ impl TableSchema { } } - let id = match row.get(self.primary_key) { + let id: UUID = match row.get(self.primary_key) { Some(val) => { match val { DbValue::Indexable(IndexableDbValue::UUID(id)) => { - id + *id }, _ => unreachable!() @@ -126,7 +126,6 @@ impl TableSchema { unreachable!() }; - Ok((*id, row)) + Ok((id, row)) } - } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 050d3a8..3037d82 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -68,7 +68,7 @@ impl State { // the client, but the details of communication are hidden behind an interface // // writer: impl SqlResponseConsumer - pub fn interpret(&mut self, operation: Operation) -> DbResult { + pub fn interpret(&mut self, operation: Operation) -> DbResult { // TODO: lock stuff use Operation::*; diff --git a/minisql/src/table.rs b/minisql/src/table.rs index 716a940..01e11ec 100644 --- a/minisql/src/table.rs +++ b/minisql/src/table.rs @@ -1,43 +1,102 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use std::slice::SliceIndex; +use std::ops::{Index, IndexMut}; use crate::base::{TableSchema, ColumnPosition, ColumnName, DbResult}; use crate::type_system::{UUID, DbValue, IndexableDbValue}; use crate::column_index::ColumnIndex; use crate::error::Error; -#[derive(Debug)] -pub struct Table { - pub schema: TableSchema, - pub rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the - // same lock for both rows and indexes - pub indexes: - HashMap // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc -} - - +// ======Table Row====== pub type Rows = // TODO: This should be some sort of an interface to a dictionary // s.t. in the background it may modify stuff in memory or talk to the disk BTreeMap; // Use `ColumnPosition` as index -pub type Row = Vec; +#[derive(Debug, Clone)] +pub struct Row(Vec); -fn restrict_columns(row: &Row, columns: &Vec) -> Row { - // If the index from `columns` is non-existant in `row`, it will just ignore it. - let mut subrow: Row = vec![]; - for column_position in columns { - match row.get(*column_position) { - Some(value) => { - subrow.push(value.clone()) - }, - None => {} - } +impl Index for Row +where + Idx: SliceIndex<[DbValue]>, +{ + type Output = Idx::Output; + + fn index(&self, index: Idx) -> &Self::Output { + &self.0[index] } - subrow } +impl IndexMut for Row +where + Idx: SliceIndex<[DbValue]>, +{ + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl FromIterator for Row { + fn from_iter>(iter: I) -> Self { + let mut v = vec![]; + for x in iter { + v.push(x) + } + Row(v) + } +} + +impl Row { + pub fn new() -> Self { + Row(vec![]) + } + + pub fn with_number_of_columns(number_of_columns: usize) -> Self { + Row(Vec::with_capacity(number_of_columns)) + } + + pub fn push(&mut self, value: DbValue) { + self.0.push(value) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, column_position: ColumnPosition) -> Option<&DbValue> { + self.0.get(column_position) + } + + pub fn restrict_columns(&self, columns: &Vec) -> Row { + // If the index from `columns` is non-existant in `row`, it will just ignore it. + let mut subrow: Row = Row::new(); + for column_position in columns { + match self.get(*column_position) { + Some(value) => { + subrow.0.push(value.clone()) + }, + None => {} + } + } + subrow + } + +} + + +// ======Table====== +#[derive(Debug)] +pub struct Table { + pub schema: TableSchema, + pub rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the + // same lock for both rows and indexes + pub indexes: + HashMap +} + + impl Table { pub fn new(table_schema: TableSchema) -> Self { Self { @@ -66,7 +125,7 @@ impl Table { } pub fn select_all_rows(&self, selected_column_positions: &Vec) -> Vec { - self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect() + self.rows.values().map(|row| row.restrict_columns(&selected_column_positions)).collect() } pub fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: DbValue) -> DbResult> { @@ -74,13 +133,13 @@ impl Table { DbValue::Indexable(value) => { match self.fetch_ids_from_index(column_position, &value)? { Some(ids) => - Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()), + Ok(self.get_rows_by_ids(ids).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()), None => - Ok(self.get_rows_by_value(column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + Ok(self.get_rows_by_value(column_position, &DbValue::Indexable(value)).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) } }, _ => { - Ok(self.get_rows_by_value(column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect()) + Ok(self.get_rows_by_value(column_position, &value).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) } } } diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 15c46b9..a6b1ce5 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -29,7 +29,7 @@ pub enum IndexableDbValue { } impl DbValue { - fn to_type(self) -> DbType { + pub fn to_type(self) -> DbType { match self { Self::Number(_) => DbType::Number, Self::Indexable(val) => From e9d3df7a226705ca07733678432b4be8c0589278 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 07:39:59 +0100 Subject: [PATCH 17/28] Reorganization --- minisql/src/error.rs | 7 +- minisql/src/{ => internals}/column_index.rs | 17 +-- minisql/src/internals/mod.rs | 4 + minisql/src/internals/row.rs | 74 ++++++++++++ minisql/src/{base.rs => internals/schema.rs} | 10 +- minisql/src/{ => internals}/table.rs | 117 ++++--------------- minisql/src/interpreter.rs | 25 ++-- minisql/src/main.rs | 4 +- minisql/src/operation.rs | 9 +- minisql/src/type_system.rs | 14 +-- 10 files changed, 140 insertions(+), 141 deletions(-) rename minisql/src/{ => internals}/column_index.rs (78%) create mode 100644 minisql/src/internals/mod.rs create mode 100644 minisql/src/internals/row.rs rename minisql/src/{base.rs => internals/schema.rs} (93%) rename minisql/src/{ => internals}/table.rs (66%) diff --git a/minisql/src/error.rs b/minisql/src/error.rs index 126a855..be6bfd8 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,5 +1,6 @@ -use crate::base::{ColumnName, TableName, ColumnPosition}; -use crate::type_system::{DbType, DbValue, UUID}; +use crate::internals::schema::{ColumnName, TableName}; +use crate::internals::row::ColumnPosition; +use crate::type_system::{DbType, Value, UUID}; use crate::operation::InsertionValues; #[derive(Debug)] @@ -7,7 +8,7 @@ pub enum Error { TableDoesNotExist(TableName), ColumnDoesNotExist(TableName, ColumnName), ColumnPositionDoesNotExist(TableName, ColumnPosition), - ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, DbValue), + ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value), AttemptingToInsertAlreadyPresentId(TableName, UUID), MissingTypeAnnotationOfColumn(TableName, ColumnPosition), MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), diff --git a/minisql/src/column_index.rs b/minisql/src/internals/column_index.rs similarity index 78% rename from minisql/src/column_index.rs rename to minisql/src/internals/column_index.rs index 7be8671..c9184c3 100644 --- a/minisql/src/column_index.rs +++ b/minisql/src/internals/column_index.rs @@ -1,12 +1,13 @@ use std::collections::{BTreeMap, HashSet}; -use crate::base::{ColumnPosition, ColumnName, DbResult}; -use crate::type_system::{UUID, DbValue, IndexableDbValue}; -use crate::table::Table; +use crate::type_system::{UUID, Value, IndexableValue}; +use crate::internals::schema::{ColumnName, DbResult}; +use crate::internals::table::Table; +use crate::internals::row::ColumnPosition; use crate::error::Error; #[derive(Debug)] pub struct ColumnIndex { - index: BTreeMap> + index: BTreeMap> } impl ColumnIndex { @@ -15,14 +16,14 @@ impl ColumnIndex { Self { index } } - pub fn get(&self, value: &IndexableDbValue) -> HashSet { + pub fn get(&self, value: &IndexableValue) -> HashSet { match self.index.get(value) { Some(set) => set.clone(), None => HashSet::new(), } } - pub fn add(&mut self, value: IndexableDbValue, id: UUID) { + pub fn add(&mut self, value: IndexableValue, id: UUID) { match self.index.get_mut(&value) { Some(ids) => { ids.insert(id); @@ -40,7 +41,7 @@ impl ColumnIndex { pub fn update_from_table(&mut self, table: &Table, column_position: ColumnPosition) -> DbResult<()> { for (id, row) in &table.rows { let value = match row.get(column_position) { - Some(DbValue::Indexable(value)) => { + Some(Value::Indexable(value)) => { value.clone() }, Some(_) => { @@ -56,7 +57,7 @@ impl ColumnIndex { Ok(()) } - pub fn remove(&mut self, value: &IndexableDbValue, id_to_be_removed: UUID) -> bool { + pub fn remove(&mut self, value: &IndexableValue, id_to_be_removed: UUID) -> bool { match self.index.get_mut(value) { Some(ids) => { let was_present = ids.remove(&id_to_be_removed); diff --git a/minisql/src/internals/mod.rs b/minisql/src/internals/mod.rs new file mode 100644 index 0000000..0817ee8 --- /dev/null +++ b/minisql/src/internals/mod.rs @@ -0,0 +1,4 @@ +pub mod table; +pub mod row; +pub mod column_index; +pub mod schema; diff --git a/minisql/src/internals/row.rs b/minisql/src/internals/row.rs new file mode 100644 index 0000000..430a26f --- /dev/null +++ b/minisql/src/internals/row.rs @@ -0,0 +1,74 @@ +use std::slice::SliceIndex; +use std::ops::{Index, IndexMut}; +use crate::type_system::Value; + +pub type ColumnPosition = usize; + +#[derive(Debug, Clone)] +pub struct Row(Vec); + +impl Index for Row +where + Idx: SliceIndex<[Value]>, +{ + type Output = Idx::Output; + + fn index(&self, index: Idx) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Row +where + Idx: SliceIndex<[Value]>, +{ + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl FromIterator for Row { + fn from_iter>(iter: I) -> Self { + let mut v = vec![]; + for x in iter { + v.push(x) + } + Row(v) + } +} + +impl Row { + pub fn new() -> Self { + Row(vec![]) + } + + pub fn with_number_of_columns(number_of_columns: usize) -> Self { + Row(Vec::with_capacity(number_of_columns)) + } + + pub fn push(&mut self, value: Value) { + self.0.push(value) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, column_position: ColumnPosition) -> Option<&Value> { + self.0.get(column_position) + } + + pub fn restrict_columns(&self, columns: &Vec) -> Row { + // If the index from `columns` is non-existant in `row`, it will just ignore it. + let mut subrow: Row = Row::new(); + for column_position in columns { + match self.get(*column_position) { + Some(value) => { + subrow.0.push(value.clone()) + }, + None => {} + } + } + subrow + } +} diff --git a/minisql/src/base.rs b/minisql/src/internals/schema.rs similarity index 93% rename from minisql/src/base.rs rename to minisql/src/internals/schema.rs index 7163840..eb65451 100644 --- a/minisql/src/base.rs +++ b/minisql/src/internals/schema.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use bimap::BiMap; use crate::operation::{InsertionValues, ColumnSelection}; -use crate::table::Row; -use crate::type_system::{DbType, DbValue, IndexableDbValue, UUID}; +use crate::internals::row::{Row, ColumnPosition}; +use crate::type_system::{DbType, Value, IndexableValue, UUID}; use crate::error::Error; // Note that it is nice to split metadata from the data because @@ -18,7 +18,6 @@ pub struct TableSchema { pub type TableName = String; pub type ColumnName = String; -pub type ColumnPosition = usize; pub type DbResult = Result; @@ -81,7 +80,6 @@ impl TableSchema { self.column_name_position_mapping.len() } - // TODO: IS THIS THE RIGHT PLACE? pub fn row_from_insertion_values(&self, insertion_values: InsertionValues) -> DbResult<(UUID, Row)> { // TODO: There should be proper validation of the insertion_values. // And it shouldn't really be done here. @@ -95,7 +93,7 @@ impl TableSchema { let mut row: Row = Row::with_number_of_columns(number_of_columns); - let mut values: HashMap = HashMap::new(); + let mut values: HashMap = HashMap::new(); for (column_name, db_value) in &insertion_values { values.insert(column_name.clone(), db_value.clone()); } @@ -115,7 +113,7 @@ impl TableSchema { let id: UUID = match row.get(self.primary_key) { Some(val) => { match val { - DbValue::Indexable(IndexableDbValue::UUID(id)) => { + Value::Indexable(IndexableValue::UUID(id)) => { *id }, _ => diff --git a/minisql/src/table.rs b/minisql/src/internals/table.rs similarity index 66% rename from minisql/src/table.rs rename to minisql/src/internals/table.rs index 01e11ec..33dc5cd 100644 --- a/minisql/src/table.rs +++ b/minisql/src/internals/table.rs @@ -1,92 +1,12 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use std::slice::SliceIndex; -use std::ops::{Index, IndexMut}; -use crate::base::{TableSchema, ColumnPosition, ColumnName, DbResult}; -use crate::type_system::{UUID, DbValue, IndexableDbValue}; -use crate::column_index::ColumnIndex; +use crate::type_system::{UUID, Value, IndexableValue}; use crate::error::Error; +use crate::internals::schema::{TableSchema, ColumnName, DbResult}; +use crate::internals::row::{Row, ColumnPosition}; +use crate::internals::column_index::ColumnIndex; -// ======Table Row====== -pub type Rows = - // TODO: This should be some sort of an interface to a dictionary - // s.t. in the background it may modify stuff in memory or talk to the disk - BTreeMap; - -// Use `ColumnPosition` as index -#[derive(Debug, Clone)] -pub struct Row(Vec); - -impl Index for Row -where - Idx: SliceIndex<[DbValue]>, -{ - type Output = Idx::Output; - - fn index(&self, index: Idx) -> &Self::Output { - &self.0[index] - } -} - -impl IndexMut for Row -where - Idx: SliceIndex<[DbValue]>, -{ - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - &mut self.0[index] - } -} - -impl FromIterator for Row { - fn from_iter>(iter: I) -> Self { - let mut v = vec![]; - for x in iter { - v.push(x) - } - Row(v) - } -} - -impl Row { - pub fn new() -> Self { - Row(vec![]) - } - - pub fn with_number_of_columns(number_of_columns: usize) -> Self { - Row(Vec::with_capacity(number_of_columns)) - } - - pub fn push(&mut self, value: DbValue) { - self.0.push(value) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get(&self, column_position: ColumnPosition) -> Option<&DbValue> { - self.0.get(column_position) - } - - pub fn restrict_columns(&self, columns: &Vec) -> Row { - // If the index from `columns` is non-existant in `row`, it will just ignore it. - let mut subrow: Row = Row::new(); - for column_position in columns { - match self.get(*column_position) { - Some(value) => { - subrow.0.push(value.clone()) - }, - None => {} - } - } - subrow - } - -} - - -// ======Table====== #[derive(Debug)] pub struct Table { pub schema: TableSchema, @@ -96,6 +16,9 @@ pub struct Table { HashMap } +pub type Rows = + BTreeMap; + impl Table { pub fn new(table_schema: TableSchema) -> Self { @@ -117,7 +40,7 @@ impl Table { .collect() } - fn get_rows_by_value(&self, column_position: ColumnPosition, value: &DbValue) -> Vec { + fn get_rows_by_value(&self, column_position: ColumnPosition, value: &Value) -> Vec { // brute-force search self.rows.values() .filter_map(|row| if row.get(column_position) == Some(value) { Some(row.clone()) } else { None }) @@ -128,14 +51,14 @@ impl Table { self.rows.values().map(|row| row.restrict_columns(&selected_column_positions)).collect() } - pub fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: DbValue) -> DbResult> { + pub fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: Value) -> DbResult> { match value { - DbValue::Indexable(value) => { + Value::Indexable(value) => { match self.fetch_ids_from_index(column_position, &value)? { Some(ids) => Ok(self.get_rows_by_ids(ids).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()), None => - Ok(self.get_rows_by_value(column_position, &DbValue::Indexable(value)).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) + Ok(self.get_rows_by_value(column_position, &Value::Indexable(value)).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) } }, _ => { @@ -152,7 +75,7 @@ impl Table { for (column_position, column_index) in &mut self.indexes { match row.get(*column_position) { - Some(DbValue::Indexable(val)) => { + Some(Value::Indexable(val)) => { column_index.add(val.clone(), id) }, Some(_) => {}, @@ -169,7 +92,7 @@ impl Table { match self.rows.remove(&id) { Some(row) => { for (column_position, column_index) in &mut self.indexes { - if let DbValue::Indexable(value) = &row[*column_position] { + if let Value::Indexable(value) = &row[*column_position] { let _ = column_index.remove(value, id); }; } @@ -187,7 +110,7 @@ impl Table { total_count } - fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &DbValue) -> usize { + fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &Value) -> usize { let matched_ids: HashSet = self.rows.iter() .filter_map(|(id, row)| if row.get(column_position) == Some(value) { Some(*id) } else { None }) .collect(); @@ -201,14 +124,14 @@ impl Table { number_of_rows } - pub fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: DbValue) -> DbResult { + pub fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: Value) -> DbResult { match value { - DbValue::Indexable(value) => { + Value::Indexable(value) => { match self.fetch_ids_from_index(column_position, &value)? { Some(ids) => Ok(self.delete_rows_by_ids(ids)), None => - Ok(self.delete_rows_by_value(column_position, &DbValue::Indexable(value))) + Ok(self.delete_rows_by_value(column_position, &Value::Indexable(value))) } }, _ => @@ -221,15 +144,15 @@ impl Table { self.indexes.insert(column_position, column_index); } - fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableDbValue) -> DbResult>> { + fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableValue) -> DbResult>> { if self.schema.is_primary(column_position) { match value { - IndexableDbValue::UUID(id) => + IndexableValue::UUID(id) => Ok(Some(HashSet::from([*id]))), _ => { let column_name: ColumnName = self.schema.column_name_from_column_position(column_position)?; let type_ = self.schema.types[column_position]; - Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, DbValue::Indexable(value.clone()))) + Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, Value::Indexable(value.clone()))) } } } else { diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 3037d82..4e4ce35 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,10 +1,11 @@ use bimap::BiMap; -use crate::type_system::{DbValue, DbType, IndexableDbValue}; -use crate::base::{TableName, TableSchema, ColumnPosition, ColumnName, DbResult}; -use crate::table::{Table, Row}; +use crate::type_system::{Value, DbType, IndexableValue}; +use crate::internals::schema::{TableName, TableSchema, ColumnName, DbResult}; +use crate::internals::table::Table; +use crate::internals::row::{Row, ColumnPosition}; use crate::error::Error; use crate::operation::{Operation, Condition, ColumnSelection}; -use crate::column_index::ColumnIndex; +use crate::internals::column_index::ColumnIndex; // Use `TablePosition` as index @@ -196,8 +197,8 @@ mod tests { #[test] fn test_insert_select_basic1() { - use DbValue::*; - use IndexableDbValue::*; + use Value::*; + use IndexableValue::*; let mut state = State::new(); let users_schema = users_schema(); @@ -231,8 +232,8 @@ mod tests { #[test] fn test_insert_select_basic2() { - use DbValue::*; - use IndexableDbValue::*; + use Value::*; + use IndexableValue::*; use Operation::*; use ColumnSelection::*; use Condition::*; @@ -313,8 +314,8 @@ mod tests { #[test] fn test_delete() { - use DbValue::*; - use IndexableDbValue::*; + use Value::*; + use IndexableValue::*; use Operation::*; use ColumnSelection::*; use Condition::*; @@ -369,8 +370,8 @@ mod tests { pub fn example() { - use DbValue::*; - use IndexableDbValue::*; + use Value::*; + use IndexableValue::*; use Operation::*; use ColumnSelection::*; use Condition::*; diff --git a/minisql/src/main.rs b/minisql/src/main.rs index 0a9fd94..f3c39ae 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -1,6 +1,4 @@ -mod base; -mod table; -mod column_index; +mod internals; mod operation; mod interpreter; mod error; diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 7235fed..8e49477 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -1,6 +1,5 @@ -use crate::base::{TableName, ColumnName}; -use crate::type_system::DbValue; -use crate::base::TableSchema; +use crate::type_system::Value; +use crate::internals::schema::{TableSchema, TableName, ColumnName}; // ==============SQL operations================ // TODO: Note that every operation has a table name. @@ -17,7 +16,7 @@ pub enum Operation { // DropTable(TableName), } -pub type InsertionValues = Vec<(ColumnName, DbValue)>; +pub type InsertionValues = Vec<(ColumnName, Value)>; pub enum ColumnSelection { All, @@ -29,7 +28,7 @@ pub enum Condition { // Or(Box, Box), // Not(Box), - Eq(ColumnName, DbValue), + Eq(ColumnName, Value), // LessOrEqual(ColumnName, DbValue), // Less(ColumnName, DbValue), diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index a6b1ce5..7674c16 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -14,29 +14,29 @@ pub type UUID = u64; // I would rather have non-nullable values by default, // and something like an explicit Option type for nulls. #[derive(Debug, Clone, PartialEq)] -pub enum DbValue { +pub enum Value { Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to // do? - Indexable(IndexableDbValue), + Indexable(IndexableValue), } #[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq)] -pub enum IndexableDbValue { +pub enum IndexableValue { String(String), Int(u64), UUID(UUID), // TODO: what about null? } -impl DbValue { +impl Value { pub fn to_type(self) -> DbType { match self { Self::Number(_) => DbType::Number, Self::Indexable(val) => match val { - IndexableDbValue::String(_) => DbType::String, - IndexableDbValue::Int(_) => DbType::Int, - IndexableDbValue::UUID(_) => DbType::UUID, + IndexableValue::String(_) => DbType::String, + IndexableValue::Int(_) => DbType::Int, + IndexableValue::UUID(_) => DbType::UUID, } } } From d87c95f1e13d41a2b39836fc348067371da0ba4b Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 07:45:40 +0100 Subject: [PATCH 18/28] Move index update from table logic to interpreter --- minisql/src/internals/column_index.rs | 29 +-------------------------- minisql/src/interpreter.rs | 23 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/minisql/src/internals/column_index.rs b/minisql/src/internals/column_index.rs index c9184c3..f02e8ce 100644 --- a/minisql/src/internals/column_index.rs +++ b/minisql/src/internals/column_index.rs @@ -1,9 +1,5 @@ use std::collections::{BTreeMap, HashSet}; -use crate::type_system::{UUID, Value, IndexableValue}; -use crate::internals::schema::{ColumnName, DbResult}; -use crate::internals::table::Table; -use crate::internals::row::ColumnPosition; -use crate::error::Error; +use crate::type_system::{UUID, IndexableValue}; #[derive(Debug)] pub struct ColumnIndex { @@ -34,29 +30,6 @@ impl ColumnIndex { } } - - // TODO: IS THIS THE RIGHT PLACE? - // Should be used in the case when an indexed is created after the table has existed for a - // while. In such a case you need to build the index from the already existing rows. - pub fn update_from_table(&mut self, table: &Table, column_position: ColumnPosition) -> DbResult<()> { - for (id, row) in &table.rows { - let value = match row.get(column_position) { - Some(Value::Indexable(value)) => { - value.clone() - }, - Some(_) => { - let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; - return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) - }, - None => { - return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) - } - }; - self.add(value, *id) - } - Ok(()) - } - pub fn remove(&mut self, value: &IndexableValue, id_to_be_removed: UUID) -> bool { match self.index.get_mut(value) { Some(ids) => { diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 4e4ce35..34d6b14 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -120,7 +120,7 @@ impl State { let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; let mut index: ColumnIndex = ColumnIndex::new(); - let _ = index.update_from_table(&table, column_position)?; + let _ = update_index_from_table(&mut index, &table, column_position)?; table.attach_index(column_position, index); Ok(Response::IndexCreated) @@ -129,6 +129,27 @@ impl State { } } +// Should be used in the case when an indexed is created after the table has existed for a +// while. In such a case you need to build the index from the already existing rows. +fn update_index_from_table(column_index: &mut ColumnIndex, table: &Table, column_position: ColumnPosition) -> DbResult<()> { + for (id, row) in &table.rows { + let value = match row.get(column_position) { + Some(Value::Indexable(value)) => { + value.clone() + }, + Some(_) => { + let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; + return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) + }, + None => { + return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) + } + }; + column_index.add(value, *id) + } + Ok(()) +} + // TODO: Give a better name to something that you can respond to with rows trait SqlResponseConsumer { // TODO: From 4f2c864d7a5ca2e5d4b30abb2cfccfed530d3094 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 07:49:17 +0100 Subject: [PATCH 19/28] Move DbResult into its own file --- minisql/src/internals/schema.rs | 4 +--- minisql/src/internals/table.rs | 3 ++- minisql/src/interpreter.rs | 3 ++- minisql/src/main.rs | 1 + minisql/src/result.rs | 3 +++ 5 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 minisql/src/result.rs diff --git a/minisql/src/internals/schema.rs b/minisql/src/internals/schema.rs index eb65451..afda2c3 100644 --- a/minisql/src/internals/schema.rs +++ b/minisql/src/internals/schema.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use bimap::BiMap; +use crate::result::DbResult; use crate::operation::{InsertionValues, ColumnSelection}; use crate::internals::row::{Row, ColumnPosition}; use crate::type_system::{DbType, Value, IndexableValue, UUID}; @@ -16,11 +17,8 @@ pub struct TableSchema { } pub type TableName = String; - pub type ColumnName = String; -pub type DbResult = Result; - impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { match self.column_name_position_mapping.get_by_left(column_name) { diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 33dc5cd..f2e4ab6 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -1,8 +1,9 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use crate::result::DbResult; use crate::type_system::{UUID, Value, IndexableValue}; use crate::error::Error; -use crate::internals::schema::{TableSchema, ColumnName, DbResult}; +use crate::internals::schema::{TableSchema, ColumnName}; use crate::internals::row::{Row, ColumnPosition}; use crate::internals::column_index::ColumnIndex; diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 34d6b14..20d803c 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,6 +1,7 @@ use bimap::BiMap; +use crate::result::DbResult; use crate::type_system::{Value, DbType, IndexableValue}; -use crate::internals::schema::{TableName, TableSchema, ColumnName, DbResult}; +use crate::internals::schema::{TableName, TableSchema, ColumnName}; use crate::internals::table::Table; use crate::internals::row::{Row, ColumnPosition}; use crate::error::Error; diff --git a/minisql/src/main.rs b/minisql/src/main.rs index f3c39ae..ec3dd18 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -1,3 +1,4 @@ +mod result; mod internals; mod operation; mod interpreter; diff --git a/minisql/src/result.rs b/minisql/src/result.rs new file mode 100644 index 0000000..fcad8b5 --- /dev/null +++ b/minisql/src/result.rs @@ -0,0 +1,3 @@ +use crate::error::Error; + +pub type DbResult = Result; From 25b26acde09420a9fff575185a234385e3165d46 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 07:58:35 +0100 Subject: [PATCH 20/28] format & clippy --- minisql/src/error.rs | 6 +- minisql/src/internals/column_index.rs | 20 +- minisql/src/internals/mod.rs | 4 +- minisql/src/internals/row.rs | 13 +- minisql/src/internals/schema.rs | 111 +++++--- minisql/src/internals/table.rs | 154 +++++++---- minisql/src/interpreter.rs | 375 +++++++++++++++++--------- minisql/src/main.rs | 8 +- minisql/src/operation.rs | 3 +- minisql/src/type_system.rs | 21 +- 10 files changed, 444 insertions(+), 271 deletions(-) diff --git a/minisql/src/error.rs b/minisql/src/error.rs index be6bfd8..51ca0cf 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,7 +1,7 @@ -use crate::internals::schema::{ColumnName, TableName}; use crate::internals::row::ColumnPosition; -use crate::type_system::{DbType, Value, UUID}; +use crate::internals::schema::{ColumnName, TableName}; use crate::operation::InsertionValues; +use crate::type_system::{DbType, Uuid, Value}; #[derive(Debug)] pub enum Error { @@ -9,7 +9,7 @@ pub enum Error { ColumnDoesNotExist(TableName, ColumnName), ColumnPositionDoesNotExist(TableName, ColumnPosition), ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value), - AttemptingToInsertAlreadyPresentId(TableName, UUID), + AttemptingToInsertAlreadyPresentId(TableName, Uuid), MissingTypeAnnotationOfColumn(TableName, ColumnPosition), MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), diff --git a/minisql/src/internals/column_index.rs b/minisql/src/internals/column_index.rs index f02e8ce..d69f615 100644 --- a/minisql/src/internals/column_index.rs +++ b/minisql/src/internals/column_index.rs @@ -1,9 +1,9 @@ +use crate::type_system::{IndexableValue, Uuid}; use std::collections::{BTreeMap, HashSet}; -use crate::type_system::{UUID, IndexableValue}; #[derive(Debug)] pub struct ColumnIndex { - index: BTreeMap> + index: BTreeMap>, } impl ColumnIndex { @@ -12,34 +12,30 @@ impl ColumnIndex { Self { index } } - pub fn get(&self, value: &IndexableValue) -> HashSet { + pub fn get(&self, value: &IndexableValue) -> HashSet { match self.index.get(value) { Some(set) => set.clone(), None => HashSet::new(), } } - pub fn add(&mut self, value: IndexableValue, id: UUID) { + pub fn add(&mut self, value: IndexableValue, id: Uuid) { match self.index.get_mut(&value) { Some(ids) => { ids.insert(id); - }, + } None => { self.index.insert(value, HashSet::from([id])); } } } - pub fn remove(&mut self, value: &IndexableValue, id_to_be_removed: UUID) -> bool { + pub fn remove(&mut self, value: &IndexableValue, id_to_be_removed: Uuid) -> bool { match self.index.get_mut(value) { Some(ids) => { - let was_present = ids.remove(&id_to_be_removed); - was_present - }, - None => { - false + ids.remove(&id_to_be_removed) // true iff was present } + None => false, } } } - diff --git a/minisql/src/internals/mod.rs b/minisql/src/internals/mod.rs index 0817ee8..864d9d7 100644 --- a/minisql/src/internals/mod.rs +++ b/minisql/src/internals/mod.rs @@ -1,4 +1,4 @@ -pub mod table; -pub mod row; pub mod column_index; +pub mod row; pub mod schema; +pub mod table; diff --git a/minisql/src/internals/row.rs b/minisql/src/internals/row.rs index 430a26f..ad8dc1e 100644 --- a/minisql/src/internals/row.rs +++ b/minisql/src/internals/row.rs @@ -1,6 +1,6 @@ -use std::slice::SliceIndex; -use std::ops::{Index, IndexMut}; use crate::type_system::Value; +use std::ops::{Index, IndexMut}; +use std::slice::SliceIndex; pub type ColumnPosition = usize; @@ -28,7 +28,7 @@ where } impl FromIterator for Row { - fn from_iter>(iter: I) -> Self { + fn from_iter>(iter: I) -> Self { let mut v = vec![]; for x in iter { v.push(x) @@ -62,11 +62,8 @@ impl Row { // If the index from `columns` is non-existant in `row`, it will just ignore it. let mut subrow: Row = Row::new(); for column_position in columns { - match self.get(*column_position) { - Some(value) => { - subrow.0.push(value.clone()) - }, - None => {} + if let Some(value) = self.get(*column_position) { + subrow.0.push(value.clone()) } } subrow diff --git a/minisql/src/internals/schema.rs b/minisql/src/internals/schema.rs index afda2c3..d174215 100644 --- a/minisql/src/internals/schema.rs +++ b/minisql/src/internals/schema.rs @@ -1,10 +1,10 @@ -use std::collections::HashMap; -use bimap::BiMap; -use crate::result::DbResult; -use crate::operation::{InsertionValues, ColumnSelection}; -use crate::internals::row::{Row, ColumnPosition}; -use crate::type_system::{DbType, Value, IndexableValue, UUID}; use crate::error::Error; +use crate::internals::row::{ColumnPosition, Row}; +use crate::operation::{ColumnSelection, InsertionValues}; +use crate::result::DbResult; +use crate::type_system::{DbType, IndexableValue, Uuid, Value}; +use bimap::BiMap; +use std::collections::HashMap; // Note that it is nice to split metadata from the data because // then you can give the metadata to the parser without giving it the data. @@ -22,29 +22,36 @@ pub type ColumnName = String; impl TableSchema { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { match self.column_name_position_mapping.get_by_left(column_name) { - Some(column_position) => { - match self.types.get(*column_position) { - Some(type_) => { - Ok((*type_, *column_position)) - }, - None => { - Err(Error::MissingTypeAnnotationOfColumn(self.table_name.clone(), *column_position)) - } - } + Some(column_position) => match self.types.get(*column_position) { + Some(type_) => Ok((*type_, *column_position)), + None => Err(Error::MissingTypeAnnotationOfColumn( + self.table_name.clone(), + *column_position, + )), }, - None => Err(Error::ColumnDoesNotExist(self.table_name.clone(), column_name.clone())) + None => Err(Error::ColumnDoesNotExist( + self.table_name.clone(), + column_name.clone(), + )), } } - pub fn column_position_from_column_name(&self, column_name: &ColumnName) -> DbResult { - self.get_column(column_name).map(|(_, column_position)| column_position) + pub fn column_position_from_column_name( + &self, + column_name: &ColumnName, + ) -> DbResult { + self.get_column(column_name) + .map(|(_, column_position)| column_position) } pub fn is_primary(&self, column_position: ColumnPosition) -> bool { self.primary_key == column_position } - fn column_positions_from_column_names(&self, column_names: &[ColumnName]) -> DbResult> { + fn column_positions_from_column_names( + &self, + column_names: &[ColumnName], + ) -> DbResult> { let mut positions: Vec = Vec::with_capacity(column_names.len()); for column_name in column_names { let column_position = self.column_position_from_column_name(column_name)?; @@ -53,24 +60,40 @@ impl TableSchema { Ok(positions) } - pub fn column_name_from_column_position(&self, column_position: ColumnPosition) -> DbResult { - match self.column_name_position_mapping.get_by_right(&column_position) { + pub fn column_name_from_column_position( + &self, + column_position: ColumnPosition, + ) -> DbResult { + match self + .column_name_position_mapping + .get_by_right(&column_position) + { Some(column_name) => Ok(column_name.clone()), - None => Err(Error::ColumnPositionDoesNotExist(self.table_name.clone(), column_position)) + None => Err(Error::ColumnPositionDoesNotExist( + self.table_name.clone(), + column_position, + )), } } - pub fn column_positions_from_column_selection(&self, column_selection: &ColumnSelection) -> DbResult> { + pub fn column_positions_from_column_selection( + &self, + column_selection: &ColumnSelection, + ) -> DbResult> { match column_selection { ColumnSelection::All => { - let mut column_positions: Vec = self.column_name_position_mapping.iter().map(|(_, column_position)| *column_position).collect(); + let mut column_positions: Vec = self + .column_name_position_mapping + .iter() + .map(|(_, column_position)| *column_position) + .collect(); column_positions.sort(); Ok(column_positions) - }, + } ColumnSelection::Columns(column_names) => { self.column_positions_from_column_names(column_names) - }, + } } } @@ -78,7 +101,10 @@ impl TableSchema { self.column_name_position_mapping.len() } - pub fn row_from_insertion_values(&self, insertion_values: InsertionValues) -> DbResult<(UUID, Row)> { + pub fn row_from_insertion_values( + &self, + insertion_values: InsertionValues, + ) -> DbResult<(Uuid, Row)> { // TODO: There should be proper validation of the insertion_values. // And it shouldn't really be done here. // @@ -86,7 +112,10 @@ impl TableSchema { // let number_of_columns = self.number_of_columns(); if number_of_columns != insertion_values.len() { - return Err(Error::MismatchBetweenInsertValuesAndColumns(self.table_name.clone(), insertion_values)) + return Err(Error::MismatchBetweenInsertValuesAndColumns( + self.table_name.clone(), + insertion_values, + )); } let mut row: Row = Row::with_number_of_columns(number_of_columns); @@ -99,27 +128,21 @@ impl TableSchema { for column_position in 0..number_of_columns { let column_name: ColumnName = self.column_name_from_column_position(column_position)?; match values.get(&column_name) { - Some(db_value) => { - row.push(db_value.clone()) - }, + Some(db_value) => row.push(db_value.clone()), None => { - return Err(Error::MissingColumnInInsertValues(self.table_name.clone(), column_name, insertion_values)) + return Err(Error::MissingColumnInInsertValues( + self.table_name.clone(), + column_name, + insertion_values, + )) } } } - let id: UUID = match row.get(self.primary_key) { - Some(val) => { - match val { - Value::Indexable(IndexableValue::UUID(id)) => { - *id - }, - _ => - unreachable!() - } - }, - None => - unreachable!() + let id: Uuid = match row.get(self.primary_key) { + Some(Value::Indexable(IndexableValue::Uuid(id))) => *id, + Some(_) => unreachable!(), + None => unreachable!(), }; Ok((id, row)) diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index f2e4ab6..9cf0cac 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -1,25 +1,21 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use crate::result::DbResult; -use crate::type_system::{UUID, Value, IndexableValue}; use crate::error::Error; -use crate::internals::schema::{TableSchema, ColumnName}; -use crate::internals::row::{Row, ColumnPosition}; use crate::internals::column_index::ColumnIndex; - +use crate::internals::row::{ColumnPosition, Row}; +use crate::internals::schema::{ColumnName, TableSchema}; +use crate::result::DbResult; +use crate::type_system::{IndexableValue, Uuid, Value}; #[derive(Debug)] pub struct Table { pub schema: TableSchema, pub rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the - // same lock for both rows and indexes - pub indexes: - HashMap + // same lock for both rows and indexes + pub indexes: HashMap, } -pub type Rows = - BTreeMap; - +pub type Rows = BTreeMap; impl Table { pub fn new(table_schema: TableSchema) -> Self { @@ -31,11 +27,11 @@ impl Table { } // ======Selection====== - fn get_row_by_id(&self, id: UUID) -> Option { + fn get_row_by_id(&self, id: Uuid) -> Option { self.rows.get(&id).cloned() } - fn get_rows_by_ids(&self, ids: HashSet) -> Vec { + fn get_rows_by_ids(&self, ids: HashSet) -> Vec { ids.into_iter() .filter_map(|id| self.get_row_by_id(id)) .collect() @@ -43,44 +39,71 @@ impl Table { fn get_rows_by_value(&self, column_position: ColumnPosition, value: &Value) -> Vec { // brute-force search - self.rows.values() - .filter_map(|row| if row.get(column_position) == Some(value) { Some(row.clone()) } else { None }) + self.rows + .values() + .filter_map(|row| { + if row.get(column_position) == Some(value) { + Some(row.clone()) + } else { + None + } + }) .collect() } pub fn select_all_rows(&self, selected_column_positions: &Vec) -> Vec { - self.rows.values().map(|row| row.restrict_columns(&selected_column_positions)).collect() + self.rows + .values() + .map(|row| row.restrict_columns(selected_column_positions)) + .collect() } - pub fn select_rows_where_eq(&self, selected_column_positions: &Vec, column_position: ColumnPosition, value: Value) -> DbResult> { + pub fn select_rows_where_eq( + &self, + selected_column_positions: &Vec, + column_position: ColumnPosition, + value: Value, + ) -> DbResult> { match value { - Value::Indexable(value) => { - match self.fetch_ids_from_index(column_position, &value)? { - Some(ids) => - Ok(self.get_rows_by_ids(ids).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()), - None => - Ok(self.get_rows_by_value(column_position, &Value::Indexable(value)).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) - } + Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? { + Some(ids) => Ok(self + .get_rows_by_ids(ids) + .iter() + .map(|row| row.restrict_columns(selected_column_positions)) + .collect()), + None => Ok(self + .get_rows_by_value(column_position, &Value::Indexable(value)) + .iter() + .map(|row| row.restrict_columns(selected_column_positions)) + .collect()), }, - _ => { - Ok(self.get_rows_by_value(column_position, &value).iter().map(|row| row.restrict_columns(&selected_column_positions)).collect()) - } + _ => Ok(self + .get_rows_by_value(column_position, &value) + .iter() + .map(|row| row.restrict_columns(selected_column_positions)) + .collect()), } } // ======Insertion====== - pub fn insert_row_at(&mut self, id: UUID, row: Row) -> DbResult<()> { + pub fn insert_row_at(&mut self, id: Uuid, row: Row) -> DbResult<()> { if self.rows.get(&id).is_some() { - return Err(Error::AttemptingToInsertAlreadyPresentId(self.schema.table_name.clone(), id)) + return Err(Error::AttemptingToInsertAlreadyPresentId( + self.schema.table_name.clone(), + id, + )); } for (column_position, column_index) in &mut self.indexes { match row.get(*column_position) { - Some(Value::Indexable(val)) => { - column_index.add(val.clone(), id) - }, - Some(_) => {}, - None => return Err(Error::ColumnPositionDoesNotExist(self.schema.table_name.clone(), *column_position)) + Some(Value::Indexable(val)) => column_index.add(val.clone(), id), + Some(_) => {} + None => { + return Err(Error::ColumnPositionDoesNotExist( + self.schema.table_name.clone(), + *column_position, + )) + } } } @@ -89,7 +112,7 @@ impl Table { } // ======Deletion====== - fn delete_row_by_id(&mut self, id: UUID) -> usize { + fn delete_row_by_id(&mut self, id: Uuid) -> usize { match self.rows.remove(&id) { Some(row) => { for (column_position, column_index) in &mut self.indexes { @@ -98,12 +121,12 @@ impl Table { }; } 1 - }, - None => 0 + } + None => 0, } } - fn delete_rows_by_ids(&mut self, ids: HashSet) -> usize { + fn delete_rows_by_ids(&mut self, ids: HashSet) -> usize { let mut total_count = 0; for id in ids { total_count += self.delete_row_by_id(id) @@ -112,8 +135,16 @@ impl Table { } fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &Value) -> usize { - let matched_ids: HashSet = self.rows.iter() - .filter_map(|(id, row)| if row.get(column_position) == Some(value) { Some(*id) } else { None }) + let matched_ids: HashSet = self + .rows + .iter() + .filter_map(|(id, row)| { + if row.get(column_position) == Some(value) { + Some(*id) + } else { + None + } + }) .collect(); self.delete_rows_by_ids(matched_ids) } @@ -125,18 +156,17 @@ impl Table { number_of_rows } - pub fn delete_rows_where_eq(&mut self, column_position: ColumnPosition, value: Value) -> DbResult { + pub fn delete_rows_where_eq( + &mut self, + column_position: ColumnPosition, + value: Value, + ) -> DbResult { match value { - Value::Indexable(value) => { - match self.fetch_ids_from_index(column_position, &value)? { - Some(ids) => - Ok(self.delete_rows_by_ids(ids)), - None => - Ok(self.delete_rows_by_value(column_position, &Value::Indexable(value))) - } + Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? { + Some(ids) => Ok(self.delete_rows_by_ids(ids)), + None => Ok(self.delete_rows_by_value(column_position, &Value::Indexable(value))), }, - _ => - Ok(self.delete_rows_by_value(column_position, &value)) + _ => Ok(self.delete_rows_by_value(column_position, &value)), } } @@ -145,15 +175,25 @@ impl Table { self.indexes.insert(column_position, column_index); } - fn fetch_ids_from_index(&self, column_position: ColumnPosition, value: &IndexableValue) -> DbResult>> { + fn fetch_ids_from_index( + &self, + column_position: ColumnPosition, + value: &IndexableValue, + ) -> DbResult>> { if self.schema.is_primary(column_position) { match value { - IndexableValue::UUID(id) => - Ok(Some(HashSet::from([*id]))), + IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))), _ => { - let column_name: ColumnName = self.schema.column_name_from_column_position(column_position)?; + let column_name: ColumnName = self + .schema + .column_name_from_column_position(column_position)?; let type_ = self.schema.types[column_position]; - Err(Error::ValueDoesNotMatchExpectedType(self.schema.table_name.clone(), column_name, type_, Value::Indexable(value.clone()))) + Err(Error::ValueDoesNotMatchExpectedType( + self.schema.table_name.clone(), + column_name, + type_, + Value::Indexable(value.clone()), + )) } } } else { @@ -161,10 +201,8 @@ impl Table { Some(index) => { let ids = index.get(value); Ok(Some(ids)) - }, - None => { - Ok(None) } + None => Ok(None), } } } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 20d803c..b5a98b5 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,13 +1,12 @@ -use bimap::BiMap; -use crate::result::DbResult; -use crate::type_system::{Value, DbType, IndexableValue}; -use crate::internals::schema::{TableName, TableSchema, ColumnName}; -use crate::internals::table::Table; -use crate::internals::row::{Row, ColumnPosition}; use crate::error::Error; -use crate::operation::{Operation, Condition, ColumnSelection}; use crate::internals::column_index::ColumnIndex; - +use crate::internals::row::{ColumnPosition, Row}; +use crate::internals::schema::{ColumnName, TableName, TableSchema}; +use crate::internals::table::Table; +use crate::operation::{ColumnSelection, Condition, Operation}; +use crate::result::DbResult; +use crate::type_system::{DbType, IndexableValue, Value}; +use bimap::BiMap; // Use `TablePosition` as index pub type Tables = Vec
; @@ -27,8 +26,7 @@ pub enum Response { Deleted(usize), // how many were deleted TableCreated, IndexCreated, -} - +} impl State { fn new() -> Self { @@ -43,24 +41,28 @@ impl State { Some(table_position) => { let table = &self.tables[*table_position]; Ok(table) - }, - None => Err(Error::TableDoesNotExist(table_name.clone())) + } + None => Err(Error::TableDoesNotExist(table_name.clone())), } } - fn table_from_name_mut<'b: 'a, 'a>(&'b mut self, table_name: &TableName) -> DbResult<&'a mut Table> { + fn table_from_name_mut<'b: 'a, 'a>( + &'b mut self, + table_name: &TableName, + ) -> DbResult<&'a mut Table> { match self.table_name_position_mapping.get_by_left(table_name) { Some(table_position) => { let table = &mut self.tables[*table_position]; Ok(table) - }, - None => Err(Error::TableDoesNotExist(table_name.clone())) + } + None => Err(Error::TableDoesNotExist(table_name.clone())), } } fn attach_table(&mut self, table_name: TableName, table: Table) { let new_table_position: TablePosition = self.tables.len(); - self.table_name_position_mapping.insert(table_name, new_table_position); + self.table_name_position_mapping + .insert(table_name, new_table_position); self.tables.push(table); } @@ -78,72 +80,94 @@ impl State { Select(table_name, column_selection, maybe_condition) => { let table: &Table = self.table_from_name(&table_name)?; - let selected_column_positions: Vec = table.schema.column_positions_from_column_selection(&column_selection)?; + let selected_column_positions: Vec = table + .schema + .column_positions_from_column_selection(&column_selection)?; let selected_rows = match maybe_condition { None => table.select_all_rows(&selected_column_positions), Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = table.schema.column_position_from_column_name(&eq_column_name)?; - table.select_rows_where_eq(&selected_column_positions, eq_column_position, value)? + let eq_column_position = table + .schema + .column_position_from_column_name(&eq_column_name)?; + table.select_rows_where_eq( + &selected_column_positions, + eq_column_position, + value, + )? } }; Ok(Response::Selected(selected_rows)) - }, + } Insert(table_name, values) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; let (id, row) = table.schema.row_from_insertion_values(values)?; - let _ = table.insert_row_at(id, row)?; + table.insert_row_at(id, row)?; Ok(Response::Inserted) - }, + } Delete(table_name, maybe_condition) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; let rows_affected = match maybe_condition { None => table.delete_all_rows(), Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = table.schema.column_position_from_column_name(&eq_column_name)?; + let eq_column_position = table + .schema + .column_position_from_column_name(&eq_column_name)?; table.delete_rows_where_eq(eq_column_position, value)? } }; Ok(Response::Deleted(rows_affected)) - }, + } CreateTable(table_name, table_schema) => { let table = Table::new(table_schema); self.attach_table(table_name, table); Ok(Response::TableCreated) - }, + } CreateIndex(table_name, column_name) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; - let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; + let column_position: ColumnPosition = table + .schema + .column_position_from_column_name(&column_name)?; let mut index: ColumnIndex = ColumnIndex::new(); - let _ = update_index_from_table(&mut index, &table, column_position)?; + update_index_from_table(&mut index, table, column_position)?; - table.attach_index(column_position, index); + table.attach_index(column_position, index); Ok(Response::IndexCreated) - }, + } } } } // Should be used in the case when an indexed is created after the table has existed for a // while. In such a case you need to build the index from the already existing rows. -fn update_index_from_table(column_index: &mut ColumnIndex, table: &Table, column_position: ColumnPosition) -> DbResult<()> { +fn update_index_from_table( + column_index: &mut ColumnIndex, + table: &Table, + column_position: ColumnPosition, +) -> DbResult<()> { for (id, row) in &table.rows { let value = match row.get(column_position) { - Some(Value::Indexable(value)) => { - value.clone() - }, + Some(Value::Indexable(value)) => value.clone(), Some(_) => { - let column_name: ColumnName = table.schema.column_name_from_column_position(column_position)?; - return Err(Error::AttemptToIndexNonIndexableColumn(table.schema.table_name.to_string(), column_name)) - }, + let column_name: ColumnName = table + .schema + .column_name_from_column_position(column_position)?; + return Err(Error::AttemptToIndexNonIndexableColumn( + table.schema.table_name.to_string(), + column_name, + )); + } None => { - return Err(Error::ColumnPositionDoesNotExist(table.schema.table_name.to_string(), column_position)) + return Err(Error::ColumnPositionDoesNotExist( + table.schema.table_name.to_string(), + column_position, + )) } }; column_index.add(value, *id) @@ -153,10 +177,9 @@ fn update_index_from_table(column_index: &mut ColumnIndex, table: &Table, column // TODO: Give a better name to something that you can respond to with rows trait SqlResponseConsumer { - // TODO: + // TODO: } - #[cfg(test)] mod tests { use super::*; @@ -176,7 +199,7 @@ mod tests { mapping.insert("age".to_string(), age); mapping }, - types: vec![DbType::UUID, DbType::String, DbType::Int], + types: vec![DbType::Uuid, DbType::String, DbType::Int], } } @@ -186,7 +209,9 @@ mod tests { let users_schema = users_schema(); let users = users_schema.table_name.clone(); - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + state + .interpret(Operation::CreateTable(users.clone(), users_schema)) + .unwrap(); assert!(state.tables.len() == 1); let table = &state.tables[0]; @@ -201,48 +226,68 @@ mod tests { let users_schema = users_schema(); let users = users_schema.table_name.clone(); - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + state + .interpret(Operation::CreateTable(users.clone(), users_schema)) + .unwrap(); + let response: Response = state + .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 0); - } #[test] fn test_select_nonexistant_table() { let mut state = State::new(); - let response: DbResult = state.interpret(Operation::Select("table_that_doesnt_exist".to_string(), ColumnSelection::All, None)); + let response: DbResult = state.interpret(Operation::Select( + "table_that_doesnt_exist".to_string(), + ColumnSelection::All, + None, + )); assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); } #[test] fn test_insert_select_basic1() { - use Value::*; use IndexableValue::*; + use Value::*; let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name.clone(); - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + state + .interpret(Operation::CreateTable(users.clone(), users_schema)) + .unwrap(); let (id, name, age) = ( - Indexable(UUID(0)), + Indexable(Uuid(0)), Indexable(String("Plato".to_string())), - Indexable(Int(64)) + Indexable(Int(64)), ); - state.interpret(Operation::Insert(users.clone(), vec![ - ("id".to_string(), id.clone()), - ("name".to_string(), name.clone()), - ("age".to_string(), age.clone()), - ])).unwrap(); + state + .interpret(Operation::Insert( + users.clone(), + vec![ + ("id".to_string(), id.clone()), + ("name".to_string(), name.clone()), + ("age".to_string(), age.clone()), + ], + )) + .unwrap(); - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + let response: Response = state + .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 1); let row = &rows[0]; @@ -254,45 +299,59 @@ mod tests { #[test] fn test_insert_select_basic2() { - use Value::*; - use IndexableValue::*; - use Operation::*; use ColumnSelection::*; use Condition::*; + use IndexableValue::*; + use Operation::*; + use Value::*; let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name.clone(); - state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + state + .interpret(CreateTable(users.clone(), users_schema)) + .unwrap(); let (id0, name0, age0) = ( - Indexable(UUID(0)), + Indexable(Uuid(0)), Indexable(String("Plato".to_string())), - Indexable(Int(64)) + Indexable(Int(64)), ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ], + )) + .unwrap(); let (id1, name1, age1) = ( - Indexable(UUID(1)), + Indexable(Uuid(1)), Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) + Indexable(Int(20)), ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ], + )) + .unwrap(); { let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 2); let row0 = &rows[0]; let row1 = &rows[1]; @@ -309,9 +368,17 @@ mod tests { } { - let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + let response: Response = state + .interpret(Select( + users.clone(), + All, + Some(Eq("id".to_string(), id0.clone())), + )) + .unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 1); let row0 = &rows[0]; @@ -322,9 +389,17 @@ mod tests { } { - let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + let response: Response = state + .interpret(Select( + users.clone(), + Columns(vec!["name".to_string(), "id".to_string()]), + Some(Eq("id".to_string(), id0.clone())), + )) + .unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 1); let row0 = &rows[0]; @@ -336,48 +411,66 @@ mod tests { #[test] fn test_delete() { - use Value::*; - use IndexableValue::*; - use Operation::*; use ColumnSelection::*; use Condition::*; + use IndexableValue::*; + use Operation::*; + use Value::*; let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name.clone(); - state.interpret(CreateTable(users.clone(), users_schema)).unwrap(); + state + .interpret(CreateTable(users.clone(), users_schema)) + .unwrap(); let (id0, name0, age0) = ( - Indexable(UUID(0)), + Indexable(Uuid(0)), Indexable(String("Plato".to_string())), - Indexable(Int(64)) + Indexable(Int(64)), ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ], + )) + .unwrap(); let (id1, name1, age1) = ( - Indexable(UUID(1)), + Indexable(Uuid(1)), Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) + Indexable(Int(20)), ); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ], + )) + .unwrap(); - - let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + let delete_response: Response = state + .interpret(Delete( + users.clone(), + Some(Eq("id".to_string(), id0.clone())), + )) + .unwrap(); assert!(matches!(delete_response, Response::Deleted(1))); let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { todo!() }; + let Response::Selected(rows) = response else { + todo!() + }; assert!(rows.len() == 1); let row = &rows[0]; @@ -390,13 +483,12 @@ mod tests { // TODO: Test CreateIndex } - pub fn example() { - use Value::*; - use IndexableValue::*; - use Operation::*; use ColumnSelection::*; use Condition::*; + use IndexableValue::*; + use Operation::*; + use Value::*; let users_schema = { let id: ColumnPosition = 0; @@ -413,60 +505,89 @@ pub fn example() { mapping.insert("age".to_string(), age); mapping }, - types: vec![DbType::UUID, DbType::String, DbType::Int], + types: vec![DbType::Uuid, DbType::String, DbType::Int], } }; let users = users_schema.table_name.clone(); - let mut state = State::new(); - state.interpret(Operation::CreateTable(users.clone(), users_schema)).unwrap(); + state + .interpret(Operation::CreateTable(users.clone(), users_schema)) + .unwrap(); let (id0, name0, age0) = ( - Indexable(UUID(0)), + Indexable(Uuid(0)), Indexable(String("Plato".to_string())), - Indexable(Int(64)) + Indexable(Int(64)), ); println!("==INSERT Plato=="); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ], + )) + .unwrap(); let (id1, name1, age1) = ( - Indexable(UUID(1)), + Indexable(Uuid(1)), Indexable(String("Aristotle".to_string())), - Indexable(Int(20)) + Indexable(Int(20)), ); println!("==INSERT Aristotle=="); - state.interpret(Insert(users.clone(), vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), - ])).unwrap(); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ], + )) + .unwrap(); println!(); { - let response: Response = state.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)).unwrap(); + let response: Response = state + .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .unwrap(); println!("==SELECT ALL=="); println!("{:?}", response); println!(); } { - let response: Response = state.interpret(Select(users.clone(), All, Some(Eq("id".to_string(), id0.clone())))).unwrap(); + let response: Response = state + .interpret(Select( + users.clone(), + All, + Some(Eq("id".to_string(), id0.clone())), + )) + .unwrap(); println!("==SELECT Plato=="); println!("{:?}", response); println!(); } { - let _delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); + let _delete_response: Response = state + .interpret(Delete( + users.clone(), + Some(Eq("id".to_string(), id0.clone())), + )) + .unwrap(); println!("==DELETE Plato=="); - let response: Response = state.interpret(Select(users.clone(), Columns(vec!["name".to_string(), "id".to_string()]), None)).unwrap(); + let response: Response = state + .interpret(Select( + users.clone(), + Columns(vec!["name".to_string(), "id".to_string()]), + None, + )) + .unwrap(); println!("==SELECT All=="); println!("{:?}", response); println!(); } - } diff --git a/minisql/src/main.rs b/minisql/src/main.rs index ec3dd18..9a4f292 100644 --- a/minisql/src/main.rs +++ b/minisql/src/main.rs @@ -1,8 +1,8 @@ -mod result; -mod internals; -mod operation; -mod interpreter; mod error; +mod internals; +mod interpreter; +mod operation; +mod result; mod type_system; fn main() { diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 8e49477..a5fd0b7 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -1,5 +1,5 @@ +use crate::internals::schema::{ColumnName, TableName, TableSchema}; use crate::type_system::Value; -use crate::internals::schema::{TableSchema, TableName, ColumnName}; // ==============SQL operations================ // TODO: Note that every operation has a table name. @@ -27,7 +27,6 @@ pub enum Condition { // And(Box, Box), // Or(Box, Box), // Not(Box), - Eq(ColumnName, Value), // LessOrEqual(ColumnName, DbValue), // Less(ColumnName, DbValue), diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 7674c16..4edc3ec 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -4,11 +4,11 @@ pub enum DbType { String, Int, Number, - UUID, + Uuid, } // ==============Values================ -pub type UUID = u64; +pub type Uuid = u64; // TODO: What about nulls? I would rather not have that in SQL, it sucks. // I would rather have non-nullable values by default, @@ -16,7 +16,7 @@ pub type UUID = u64; #[derive(Debug, Clone, PartialEq)] pub enum Value { Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to - // do? + // do? Indexable(IndexableValue), } @@ -24,20 +24,19 @@ pub enum Value { pub enum IndexableValue { String(String), Int(u64), - UUID(UUID), + Uuid(Uuid), // TODO: what about null? } impl Value { - pub fn to_type(self) -> DbType { + pub fn to_type(&self) -> DbType { match self { Self::Number(_) => DbType::Number, - Self::Indexable(val) => - match val { - IndexableValue::String(_) => DbType::String, - IndexableValue::Int(_) => DbType::Int, - IndexableValue::UUID(_) => DbType::UUID, - } + Self::Indexable(val) => match val { + IndexableValue::String(_) => DbType::String, + IndexableValue::Int(_) => DbType::Int, + IndexableValue::Uuid(_) => DbType::Uuid, + }, } } } From 8b9249be36853f402251303b82614fda9bbacc64 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 08:14:29 +0100 Subject: [PATCH 21/28] Test for column index --- minisql/src/interpreter.rs | 68 +++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index b5a98b5..9b9f818 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -480,7 +480,73 @@ mod tests { assert!(row[2] == age1); } - // TODO: Test CreateIndex + #[test] + fn test_index() { + use ColumnSelection::*; + use Condition::*; + use IndexableValue::*; + use Operation::*; + use Value::*; + + let mut state = State::new(); + let users_schema = users_schema(); + let users = users_schema.table_name.clone(); + + state + .interpret(CreateTable(users.clone(), users_schema)) + .unwrap(); + + state.interpret(CreateIndex(users.clone(), "name".to_string())).unwrap(); + + let (id0, name0, age0) = ( + Indexable(Uuid(0)), + Indexable(String("Plato".to_string())), + Indexable(Int(64)), + ); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id0.clone()), + ("name".to_string(), name0.clone()), + ("age".to_string(), age0.clone()), + ], + )) + .unwrap(); + + let (id1, name1, age1) = ( + Indexable(Uuid(1)), + Indexable(String("Aristotle".to_string())), + Indexable(Int(20)), + ); + state + .interpret(Insert( + users.clone(), + vec![ + ("id".to_string(), id1.clone()), + ("name".to_string(), name1.clone()), + ("age".to_string(), age1.clone()), + ], + )) + .unwrap(); + + assert!(state.tables.len() == 1); + let table = &state.tables[0]; + assert!(table.rows.len() == 2); + + let user: ColumnPosition = 1; + assert!(table.indexes.contains_key(&user)); + + let index = table.indexes.get(&user).unwrap(); + + let plato_id = 0; + let aristotle_id = 1; + + let plato_ids = index.get(&String("Plato".to_string())); + assert!(plato_ids.contains(&plato_id)); + assert!(!plato_ids.contains(&aristotle_id)); + assert!(plato_ids.len() == 1); + } } pub fn example() { From 1712bc0e0ee1f72c8a5a65ac267d98a8c363c7d1 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Fri, 29 Dec 2023 08:49:58 +0100 Subject: [PATCH 22/28] Remove unnecessary import in a test --- minisql/src/interpreter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 9b9f818..e480e82 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -482,8 +482,6 @@ mod tests { #[test] fn test_index() { - use ColumnSelection::*; - use Condition::*; use IndexableValue::*; use Operation::*; use Value::*; From 14b82d1aa51db7d127e4dc22b3068ca07a78c7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Fri, 29 Dec 2023 17:03:39 +0100 Subject: [PATCH 23/28] fix: typos --- minisql/src/internals/column_index.rs | 2 +- minisql/src/interpreter.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/minisql/src/internals/column_index.rs b/minisql/src/internals/column_index.rs index d69f615..f6c2a43 100644 --- a/minisql/src/internals/column_index.rs +++ b/minisql/src/internals/column_index.rs @@ -33,7 +33,7 @@ impl ColumnIndex { pub fn remove(&mut self, value: &IndexableValue, id_to_be_removed: Uuid) -> bool { match self.index.get_mut(value) { Some(ids) => { - ids.remove(&id_to_be_removed) // true iff was present + ids.remove(&id_to_be_removed) // true if was present } None => false, } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index e480e82..8bce46b 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -144,7 +144,7 @@ impl State { } } -// Should be used in the case when an indexed is created after the table has existed for a +// Should be used in the case when an index is created after the table has existed for a // while. In such a case you need to build the index from the already existing rows. fn update_index_from_table( column_index: &mut ColumnIndex, From 0996d0dbe12df8860d374d46d5f361b4af18a02b Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:29:49 +0100 Subject: [PATCH 24/28] Move index update into attach_index --- minisql/src/internals/table.rs | 36 +++++++++++++++++++++++++++- minisql/src/interpreter.rs | 43 ++++------------------------------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 9cf0cac..975a4b8 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -171,8 +171,11 @@ impl Table { } // ======Indexing====== - pub fn attach_index(&mut self, column_position: ColumnPosition, column_index: ColumnIndex) { + pub fn attach_index(&mut self, column_position: ColumnPosition) -> DbResult<()> { + let mut column_index: ColumnIndex = ColumnIndex::new(); + update_index_from_table(&mut column_index, self, column_position)?; self.indexes.insert(column_position, column_index); + Ok(()) } fn fetch_ids_from_index( @@ -207,3 +210,34 @@ impl Table { } } } + +// Should be used in the case when an index is created after the table has existed for a +// while. In such a case you need to build the index from the already existing rows. +fn update_index_from_table( + column_index: &mut ColumnIndex, + table: &Table, + column_position: ColumnPosition, +) -> DbResult<()> { + for (id, row) in &table.rows { + let value = match row.get(column_position) { + Some(Value::Indexable(value)) => value.clone(), + Some(_) => { + let column_name: ColumnName = table + .schema + .column_name_from_column_position(column_position)?; + return Err(Error::AttemptToIndexNonIndexableColumn( + table.schema.table_name.to_string(), + column_name, + )); + } + None => { + return Err(Error::ColumnPositionDoesNotExist( + table.schema.table_name.to_string(), + column_position, + )) + } + }; + column_index.add(value, *id) + } + Ok(()) +} diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 8bce46b..1554570 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,5 +1,4 @@ use crate::error::Error; -use crate::internals::column_index::ColumnIndex; use crate::internals::row::{ColumnPosition, Row}; use crate::internals::schema::{ColumnName, TableName, TableSchema}; use crate::internals::table::Table; @@ -134,47 +133,13 @@ impl State { .schema .column_position_from_column_name(&column_name)?; - let mut index: ColumnIndex = ColumnIndex::new(); - update_index_from_table(&mut index, table, column_position)?; - - table.attach_index(column_position, index); + table.attach_index(column_position)?; Ok(Response::IndexCreated) } } } } -// Should be used in the case when an index is created after the table has existed for a -// while. In such a case you need to build the index from the already existing rows. -fn update_index_from_table( - column_index: &mut ColumnIndex, - table: &Table, - column_position: ColumnPosition, -) -> DbResult<()> { - for (id, row) in &table.rows { - let value = match row.get(column_position) { - Some(Value::Indexable(value)) => value.clone(), - Some(_) => { - let column_name: ColumnName = table - .schema - .column_name_from_column_position(column_position)?; - return Err(Error::AttemptToIndexNonIndexableColumn( - table.schema.table_name.to_string(), - column_name, - )); - } - None => { - return Err(Error::ColumnPositionDoesNotExist( - table.schema.table_name.to_string(), - column_position, - )) - } - }; - column_index.add(value, *id) - } - Ok(()) -} - // TODO: Give a better name to something that you can respond to with rows trait SqlResponseConsumer { // TODO: @@ -493,8 +458,10 @@ mod tests { state .interpret(CreateTable(users.clone(), users_schema)) .unwrap(); - - state.interpret(CreateIndex(users.clone(), "name".to_string())).unwrap(); + + state + .interpret(CreateIndex(users.clone(), "name".to_string())) + .unwrap(); let (id0, name0, age0) = ( Indexable(Uuid(0)), From 966c9bf28479593f940cc91cb35342603fefd8cd Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:37:09 +0100 Subject: [PATCH 25/28] Make Table struct fields private --- minisql/src/internals/table.rs | 18 +++++++++++++++--- minisql/src/interpreter.rs | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 975a4b8..a36b2e6 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -9,10 +9,10 @@ use crate::type_system::{IndexableValue, Uuid, Value}; #[derive(Debug)] pub struct Table { - pub schema: TableSchema, - pub rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the + schema: TableSchema, + rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the // same lock for both rows and indexes - pub indexes: HashMap, + indexes: HashMap, } pub type Rows = BTreeMap; @@ -26,6 +26,18 @@ impl Table { } } + pub fn schema(&self) -> &TableSchema { + &self.schema + } + + pub fn rows(&self) -> &Rows { + &self.rows + } + + pub fn indexes(&self) -> &HashMap { + &self.indexes + } + // ======Selection====== fn get_row_by_id(&self, id: Uuid) -> Option { self.rows.get(&id).cloned() diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 1554570..38bd354 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -80,14 +80,14 @@ impl State { let table: &Table = self.table_from_name(&table_name)?; let selected_column_positions: Vec = table - .schema + .schema() .column_positions_from_column_selection(&column_selection)?; let selected_rows = match maybe_condition { None => table.select_all_rows(&selected_column_positions), Some(Condition::Eq(eq_column_name, value)) => { let eq_column_position = table - .schema + .schema() .column_position_from_column_name(&eq_column_name)?; table.select_rows_where_eq( &selected_column_positions, @@ -102,7 +102,7 @@ impl State { Insert(table_name, values) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; - let (id, row) = table.schema.row_from_insertion_values(values)?; + let (id, row) = table.schema().row_from_insertion_values(values)?; table.insert_row_at(id, row)?; Ok(Response::Inserted) } @@ -113,7 +113,7 @@ impl State { None => table.delete_all_rows(), Some(Condition::Eq(eq_column_name, value)) => { let eq_column_position = table - .schema + .schema() .column_position_from_column_name(&eq_column_name)?; table.delete_rows_where_eq(eq_column_position, value)? } @@ -130,7 +130,7 @@ impl State { CreateIndex(table_name, column_name) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; let column_position: ColumnPosition = table - .schema + .schema() .column_position_from_column_name(&column_name)?; table.attach_index(column_position)?; @@ -180,9 +180,9 @@ mod tests { assert!(state.tables.len() == 1); let table = &state.tables[0]; - assert!(table.rows.len() == 0); + assert!(table.rows().len() == 0); - assert!(table.schema.table_name == users); + assert!(table.schema().table_name == users); } #[test] @@ -497,12 +497,12 @@ mod tests { assert!(state.tables.len() == 1); let table = &state.tables[0]; - assert!(table.rows.len() == 2); + assert!(table.rows().len() == 2); let user: ColumnPosition = 1; - assert!(table.indexes.contains_key(&user)); + assert!(table.indexes().contains_key(&user)); - let index = table.indexes.get(&user).unwrap(); + let index = table.indexes().get(&user).unwrap(); let plato_id = 0; let aristotle_id = 1; From f0d29eb3996b93dc72c8b21093121b4a8bb6bcab Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:56:38 +0100 Subject: [PATCH 26/28] Make TableSchema struct fields private --- minisql/src/internals/schema.rs | 14 +++++++- minisql/src/internals/table.rs | 16 +++++---- minisql/src/interpreter.rs | 64 ++++++++++++++++----------------- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/minisql/src/internals/schema.rs b/minisql/src/internals/schema.rs index d174215..c23c24a 100644 --- a/minisql/src/internals/schema.rs +++ b/minisql/src/internals/schema.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; // then you can give the metadata to the parser without giving it the data. #[derive(Debug)] pub struct TableSchema { - pub table_name: TableName, // used for descriptive errors + table_name: TableName, // used for descriptive errors pub primary_key: ColumnPosition, pub column_name_position_mapping: BiMap, pub types: Vec, @@ -20,6 +20,18 @@ pub type TableName = String; pub type ColumnName = String; impl TableSchema { + pub fn new(table_name: TableName, primary_key: ColumnPosition, column_name_position_map: Vec<(ColumnName, ColumnPosition)>, types: Vec) -> Self { + let mut column_name_position_mapping: BiMap = BiMap::new(); + for (column_name, column_position) in column_name_position_map { + column_name_position_mapping.insert(column_name, column_position); + } + Self { table_name, primary_key, column_name_position_mapping, types } + } + + pub fn table_name(&self) -> &TableName { + &self.table_name + } + fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { match self.column_name_position_mapping.get_by_left(column_name) { Some(column_position) => match self.types.get(*column_position) { diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index a36b2e6..6728a5d 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use crate::error::Error; use crate::internals::column_index::ColumnIndex; use crate::internals::row::{ColumnPosition, Row}; -use crate::internals::schema::{ColumnName, TableSchema}; +use crate::internals::schema::{ColumnName, TableSchema, TableName}; use crate::result::DbResult; use crate::type_system::{IndexableValue, Uuid, Value}; @@ -38,6 +38,10 @@ impl Table { &self.indexes } + pub fn table_name(&self) -> &TableName { + &self.schema.table_name() + } + // ======Selection====== fn get_row_by_id(&self, id: Uuid) -> Option { self.rows.get(&id).cloned() @@ -101,7 +105,7 @@ impl Table { pub fn insert_row_at(&mut self, id: Uuid, row: Row) -> DbResult<()> { if self.rows.get(&id).is_some() { return Err(Error::AttemptingToInsertAlreadyPresentId( - self.schema.table_name.clone(), + self.table_name().clone(), id, )); } @@ -112,7 +116,7 @@ impl Table { Some(_) => {} None => { return Err(Error::ColumnPositionDoesNotExist( - self.schema.table_name.clone(), + self.schema.table_name().clone(), // Note that I can't simply use self.table_name() here because of rust borrowing rules. *column_position, )) } @@ -204,7 +208,7 @@ impl Table { .column_name_from_column_position(column_position)?; let type_ = self.schema.types[column_position]; Err(Error::ValueDoesNotMatchExpectedType( - self.schema.table_name.clone(), + self.table_name().clone(), column_name, type_, Value::Indexable(value.clone()), @@ -238,13 +242,13 @@ fn update_index_from_table( .schema .column_name_from_column_position(column_position)?; return Err(Error::AttemptToIndexNonIndexableColumn( - table.schema.table_name.to_string(), + table.table_name().to_string(), column_name, )); } None => { return Err(Error::ColumnPositionDoesNotExist( - table.schema.table_name.to_string(), + table.table_name().to_string(), column_position, )) } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 38bd354..2ee109d 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,6 +1,6 @@ use crate::error::Error; use crate::internals::row::{ColumnPosition, Row}; -use crate::internals::schema::{ColumnName, TableName, TableSchema}; +use crate::internals::schema::{TableName, TableSchema}; use crate::internals::table::Table; use crate::operation::{ColumnSelection, Condition, Operation}; use crate::result::DbResult; @@ -154,25 +154,23 @@ mod tests { let name: ColumnPosition = 1; let age: ColumnPosition = 2; - TableSchema { - table_name: "users".to_string(), - primary_key: id, - column_name_position_mapping: { - let mut mapping: BiMap = BiMap::new(); - mapping.insert("id".to_string(), id); - mapping.insert("name".to_string(), name); - mapping.insert("age".to_string(), age); - mapping - }, - types: vec![DbType::Uuid, DbType::String, DbType::Int], - } + TableSchema::new( + "users".to_string(), + id, + vec!( + ("id".to_string(), id), + ("name".to_string(), name), + ("age".to_string(), age), + ), + vec![DbType::Uuid, DbType::String, DbType::Int], + ) } #[test] fn test_table_creation() { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(Operation::CreateTable(users.clone(), users_schema)) @@ -182,14 +180,14 @@ mod tests { let table = &state.tables[0]; assert!(table.rows().len() == 0); - assert!(table.schema().table_name == users); + assert!(table.table_name() == &users); } #[test] fn test_select_empty() { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(Operation::CreateTable(users.clone(), users_schema)) @@ -223,7 +221,7 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(Operation::CreateTable(users.clone(), users_schema)) @@ -272,7 +270,7 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(CreateTable(users.clone(), users_schema)) @@ -384,7 +382,7 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(CreateTable(users.clone(), users_schema)) @@ -453,7 +451,7 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); state .interpret(CreateTable(users.clone(), users_schema)) @@ -521,25 +519,23 @@ pub fn example() { use Operation::*; use Value::*; - let users_schema = { + let users_schema: TableSchema = { let id: ColumnPosition = 0; let name: ColumnPosition = 1; let age: ColumnPosition = 2; - TableSchema { - table_name: "users".to_string(), - primary_key: id, - column_name_position_mapping: { - let mut mapping: BiMap = BiMap::new(); - mapping.insert("id".to_string(), id); - mapping.insert("name".to_string(), name); - mapping.insert("age".to_string(), age); - mapping - }, - types: vec![DbType::Uuid, DbType::String, DbType::Int], - } + TableSchema::new( + "users".to_string(), + id, + vec!( + ("id".to_string(), id), + ("name".to_string(), name), + ("age".to_string(), age), + ), + vec![DbType::Uuid, DbType::String, DbType::Int], + ) }; - let users = users_schema.table_name.clone(); + let users = users_schema.table_name().clone(); let mut state = State::new(); state From b04f3d167b76725d76b843e19df992efac38390b Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:24:30 +0100 Subject: [PATCH 27/28] Return reference in index_column.get() --- minisql/src/internals/column_index.rs | 7 ++----- minisql/src/internals/table.rs | 4 ++-- minisql/src/interpreter.rs | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/minisql/src/internals/column_index.rs b/minisql/src/internals/column_index.rs index f6c2a43..cdd331d 100644 --- a/minisql/src/internals/column_index.rs +++ b/minisql/src/internals/column_index.rs @@ -12,11 +12,8 @@ impl ColumnIndex { Self { index } } - pub fn get(&self, value: &IndexableValue) -> HashSet { - match self.index.get(value) { - Some(set) => set.clone(), - None => HashSet::new(), - } + pub fn get(&self, value: &IndexableValue) -> Option<&HashSet> { + self.index.get(value) } pub fn add(&mut self, value: IndexableValue, id: Uuid) { diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 6728a5d..d4210b8 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -218,8 +218,8 @@ impl Table { } else { match self.indexes.get(&column_position) { Some(index) => { - let ids = index.get(value); - Ok(Some(ids)) + let ids = index.get(value).cloned(); + Ok(ids) } None => Ok(None), } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 2ee109d..4bd3f35 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -148,6 +148,7 @@ trait SqlResponseConsumer { #[cfg(test)] mod tests { use super::*; + use std::collections::HashSet; fn users_schema() -> TableSchema { let id: ColumnPosition = 0; @@ -505,7 +506,7 @@ mod tests { let plato_id = 0; let aristotle_id = 1; - let plato_ids = index.get(&String("Plato".to_string())); + let plato_ids = index.get(&String("Plato".to_string())).cloned().unwrap_or(HashSet::new()); assert!(plato_ids.contains(&plato_id)); assert!(!plato_ids.contains(&aristotle_id)); assert!(plato_ids.len() == 1); From ba2c1bae6d9e516935cf7d0038d093456a75cd94 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:20:22 +0100 Subject: [PATCH 28/28] Add a comment about potentiall returning reference to id hash-set in fetch_ids_from_index. --- minisql/src/internals/table.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index d4210b8..e5506e0 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -218,6 +218,9 @@ impl Table { } else { match self.indexes.get(&column_position) { Some(index) => { + // Note that we are cloning the ids here! This can be very wasteful in some cases. + // It would be possible to just return a reference, + // but this seems fairly non-trivial. let ids = index.get(value).cloned(); Ok(ids) }