From 2a731c5dc2a74ebcdd53064e12a4de75602720a0 Mon Sep 17 00:00:00 2001 From: Elias Renman Date: Mon, 14 Aug 2023 16:46:02 +0200 Subject: [PATCH] feat: basic one to one relation --- src/database/mod.rs | 58 ++++++++++++++------------------- src/database/relation.rs | 66 ++++++++++++++++++++++++++++++++++++++ src/database/table.rs | 21 ++++++++++-- src/tests/database_test.rs | 33 +++++++++++++++++++ src/tests/index_test.rs | 8 ++--- src/tests/mod.rs | 2 ++ src/tests/relation_test.rs | 52 ++++++++++++++++++++++++++++++ src/tests/table_test.rs | 6 ++-- 8 files changed, 202 insertions(+), 44 deletions(-) create mode 100644 src/database/relation.rs create mode 100644 src/tests/database_test.rs create mode 100644 src/tests/relation_test.rs diff --git a/src/database/mod.rs b/src/database/mod.rs index 26fe017..c31025e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,20 +1,21 @@ -use std::{collections::HashMap, fs}; +use std::{borrow::BorrowMut, fs, ops::Deref}; use serde::{Deserialize, Serialize}; -use self::table::Table; +use self::{relation::Relation, table::Table}; pub mod index; +pub mod relation; pub mod row; pub mod table; #[derive(Serialize, Deserialize)] pub struct Database { - tables: HashMap, + pub tables: Vec, } impl Database { - pub fn new(tables: HashMap) -> Database { + pub fn new(tables: Vec
) -> Database { Database { tables: tables } } @@ -35,38 +36,27 @@ impl Database { panic!("Failed writing table to file") } } -} -#[cfg(test)] -mod tests { - use crate::database::{table::Table, Database}; - use std::{fs, path::Path}; - - #[test] - fn should_write_and_read_to_file() { - let mut table: Table = Table::new("Cats", "id", None); - let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]); - _ = table.insert_row(row!["id" => 2, "name" => "Simon"]); - let database = Database::new(hashmap!["Cats" => table]); - - database.to_file("./db.json"); - - let exists = Path::try_exists(Path::new("./db.json")); - assert_eq!(exists.unwrap(), true); - - let table = Database::from_file("./db.json".to_owned()); - assert_eq!(table.is_ok(), true); - - let db = table.unwrap(); - let table = db.tables.get("Cats"); - if table.is_none() { - panic!("Unable to find table"); + pub fn get_table(&mut self, name: String) -> Result<&mut Table, String> { + for table in self.tables.iter_mut() { + if table.name == name { + return Ok(table); + } } + Err("Failed to find table".to_string()) + } - let row = table.unwrap().find_by_pk(1u64); - assert_eq!(row.is_ok(), true); - - // Cleanup file - let _result = fs::remove_file("./db.json"); + pub fn get_table_relation( + &self, + table_name: String, + relation_name: &String, + ) -> Result<&Relation, String> { + for table in &self.tables { + if table.name == table_name { + let relation = table.get_relation(relation_name).unwrap(); + return Ok(relation); + } + } + Err("Failed to find table".to_string()) } } diff --git a/src/database/relation.rs b/src/database/relation.rs new file mode 100644 index 0000000..3a02d0f --- /dev/null +++ b/src/database/relation.rs @@ -0,0 +1,66 @@ +use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut}; + +use super::{row::Row, table::Table, Database}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Relation { + pub table_name: String, + variation: String, +} +impl Relation { + pub fn new(table_name: String, variation: String) -> Relation { + Relation { + table_name, + variation, + } + } + + pub fn get_foreign_row(&self, database: &mut Database, foreign_id: u64) -> Result { + // Find foreign table + let table = database.get_table(self.table_name.clone()).unwrap(); + // Get row from foreign table + let row = table.find_by_pk(foreign_id).unwrap().clone(); + + Ok(row) + } +} + +#[derive(Serialize, Deserialize)] +pub struct OneToOne { + foreign_id: u64, + relation_name: String, +} + +impl OneToOne { + pub fn new(foreign_id: u64, relation_name: String) -> OneToOne { + OneToOne { + foreign_id, + relation_name, + } + } + + pub fn get_id(&self) -> u64 { + self.foreign_id + } + + pub fn get(&self, table_name: String, database: &mut Database) -> Result { + let relation: Relation = { + // Get table + let table: &mut Table = database.get_table(table_name).unwrap(); + + // Fetch the Relation + table.get_relation(&self.relation_name).unwrap().clone() + }; + // Fetch row from foreign table + let foreign_row = relation.get_foreign_row(database, self.foreign_id)?; + // Now you can continue using 'relation' or anything else + + Ok(foreign_row) + } + + pub fn from_value(value: Value) -> OneToOne { + serde_json::from_value(value).unwrap() + } +} diff --git a/src/database/table.rs b/src/database/table.rs index 2a5786d..8234b5f 100644 --- a/src/database/table.rs +++ b/src/database/table.rs @@ -1,4 +1,4 @@ -use super::{index::Index, row::Row}; +use super::{index::Index, relation::Relation, row::Row}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{ @@ -10,17 +10,24 @@ use std::{ pub struct Table { indexes: HashMap, rows: HashMap, - name: String, + pub name: String, pk: String, + relations: HashMap, } impl Table { - pub fn new(name: &str, pk: &str, indexes: Option>) -> Table { + pub fn new( + name: &str, + pk: &str, + indexes: Option>, + relations: Option>, + ) -> Table { Table { indexes: indexes.unwrap_or(HashMap::new()), rows: HashMap::new(), name: name.to_string(), pk: pk.to_string(), + relations: relations.unwrap_or(HashMap::new()), } } @@ -107,4 +114,12 @@ impl Table { .unwrap() .clone()); } + + pub fn get_relation(&self, name: &String) -> Result<&Relation, String> { + let relation = self.relations.get(name); + if relation.is_some() { + return Ok(relation.unwrap()); + } + Err("Failed to find relation".to_string()) + } } diff --git a/src/tests/database_test.rs b/src/tests/database_test.rs new file mode 100644 index 0000000..4427ada --- /dev/null +++ b/src/tests/database_test.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +mod database_test { + use crate::database::{table::Table, Database}; + use std::{fs, path::Path}; + + #[test] + fn should_write_and_read_to_file() { + let mut table: Table = Table::new("Cats", "id", None, None); + let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]); + _ = table.insert_row(row!["id" => 2, "name" => "Simon"]); + let database = Database::new(vec![table]); + + database.to_file("./db.json"); + + let exists = Path::try_exists(Path::new("./db.json")); + assert_eq!(exists.unwrap(), true); + + let table = Database::from_file("./db.json".to_owned()); + assert_eq!(table.is_ok(), true); + + let mut db = table.unwrap(); + let table = db.get_table("Cats".to_string()); + if table.is_err() { + panic!("Unable to find table"); + } + let table = table.unwrap(); + let row = table.find_by_pk(1u64); + assert_eq!(row.is_ok(), true); + + // Cleanup file + let _result = fs::remove_file("./db.json"); + } +} diff --git a/src/tests/index_test.rs b/src/tests/index_test.rs index a6972c1..44e5d51 100644 --- a/src/tests/index_test.rs +++ b/src/tests/index_test.rs @@ -8,7 +8,7 @@ mod index_test { #[test] fn should_create_string_index_and_find_two_results() { let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(false))]; - let mut table: Table = Table::new("Cats", "id", Some(indexes)); + let mut table: Table = Table::new("Cats", "id", Some(indexes), None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon", "race" => "cat"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); @@ -33,7 +33,7 @@ mod index_test { #[test] fn should_create_reverse_index() { let indexes = hashmap!["race" => Index::new("race".to_string(), Some(true), Some(false))]; - let mut table: Table = Table::new("Cats", "id", Some(indexes)); + let mut table: Table = Table::new("Cats", "id", Some(indexes), None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon", "race" => "cat"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); @@ -49,7 +49,7 @@ mod index_test { #[test] fn should_create_unique_index_and_fail_insert() { let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(true))]; - let mut table: Table = Table::new("Cats", "id", Some(indexes)); + let mut table: Table = Table::new("Cats", "id", Some(indexes), None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); @@ -60,7 +60,7 @@ mod index_test { #[test] fn should_create_unique_index() { let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(true))]; - let mut table: Table = Table::new("Cats", "id", Some(indexes)); + let mut table: Table = Table::new("Cats", "id", Some(indexes), None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 5e58ebc..2aa5457 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,2 +1,4 @@ +mod database_test; mod index_test; +mod relation_test; mod table_test; diff --git a/src/tests/relation_test.rs b/src/tests/relation_test.rs new file mode 100644 index 0000000..e5a6dbb --- /dev/null +++ b/src/tests/relation_test.rs @@ -0,0 +1,52 @@ +#[cfg(test)] +mod relation_test { + use std::borrow::BorrowMut; + + use crate::database::{ + relation::{OneToOne, Relation}, + table::Table, + Database, + }; + + #[test] + fn should_fail_no_relation_found() { + let cat_relations = + hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_one".to_string())]; + let cat_table: Table = Table::new("Cats", "id", None, Some(cat_relations)); + + let food_table: Table = Table::new("Food", "id", None, None); + + let mut database: Database = Database::new(vec![cat_table, food_table]); + + _ = database + .get_table("Food".to_string()) + .unwrap() + .insert_row(row!["id" => 123, "name" => "Dry Feed"]); + let _ = database.get_table("Cats".to_string()).unwrap().insert_row(row!["id" => 1, "name" => "Ozzy", "breed" => "mixed", "food" => OneToOne::new(123u64, "cat_food".to_string())]); + + let cat_table = database.get_table("Cats".to_string()).unwrap(); + let cat_1 = cat_table.find_by_pk(1u64).unwrap(); + + let cat_1_food = cat_1.get("food").unwrap(); + + let relation = OneToOne::from_value(cat_1_food.to_owned()); + assert_eq!(relation.get_id(), 123u64); + + let foreign_row = relation.get("Cats".to_string(), database.borrow_mut()); + + assert_eq!(foreign_row.is_ok(), true); + + let foreign_row_unwrapped = foreign_row.unwrap(); + + assert_eq!( + foreign_row_unwrapped.get("id").unwrap().as_u64().unwrap(), + 123u64 + ); + } + #[test] + fn should_fail_no_table_found() {} + #[test] + fn should_fail_no_row_found() {} + // Fail without table found, invalid relation + // Fail row and table found but not row +} diff --git a/src/tests/table_test.rs b/src/tests/table_test.rs index 8869961..96901aa 100644 --- a/src/tests/table_test.rs +++ b/src/tests/table_test.rs @@ -5,7 +5,7 @@ mod table_test { #[test] fn should_find_two_rows() { - let mut table: Table = Table::new("Cats", "id", None); + let mut table: Table = Table::new("Cats", "id", None, None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon"]); @@ -22,7 +22,7 @@ mod table_test { #[test] fn should_fail_to_find_row() { - let mut table: Table = Table::new("Cats", "id", None); + let mut table: Table = Table::new("Cats", "id", None, None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon"]); @@ -32,7 +32,7 @@ mod table_test { #[test] fn should_fail_to_insert_row_with_same_id() { - let mut table: Table = Table::new("Cats", "id", None); + let mut table: Table = Table::new("Cats", "id", None, None); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]); // Make sure the rows actually exists