feat: basic one to one relation

This commit is contained in:
Elias Renman
2023-08-14 16:46:02 +02:00
parent e1b0575a8a
commit 2a731c5dc2
8 changed files with 202 additions and 44 deletions

View File

@@ -1,20 +1,21 @@
use std::{collections::HashMap, fs}; use std::{borrow::BorrowMut, fs, ops::Deref};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::table::Table; use self::{relation::Relation, table::Table};
pub mod index; pub mod index;
pub mod relation;
pub mod row; pub mod row;
pub mod table; pub mod table;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Database { pub struct Database {
tables: HashMap<String, Table>, pub tables: Vec<Table>,
} }
impl Database { impl Database {
pub fn new(tables: HashMap<String, Table>) -> Database { pub fn new(tables: Vec<Table>) -> Database {
Database { tables: tables } Database { tables: tables }
} }
@@ -35,38 +36,27 @@ impl Database {
panic!("Failed writing table to file") panic!("Failed writing table to file")
} }
} }
}
#[cfg(test)] pub fn get_table(&mut self, name: String) -> Result<&mut Table, String> {
mod tests { for table in self.tables.iter_mut() {
use crate::database::{table::Table, Database}; if table.name == name {
use std::{fs, path::Path}; return Ok(table);
}
#[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");
} }
Err("Failed to find table".to_string())
}
let row = table.unwrap().find_by_pk(1u64); pub fn get_table_relation(
assert_eq!(row.is_ok(), true); &self,
table_name: String,
// Cleanup file relation_name: &String,
let _result = fs::remove_file("./db.json"); ) -> 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())
} }
} }

66
src/database/relation.rs Normal file
View File

@@ -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<Row, String> {
// 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<Row, String> {
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()
}
}

View File

@@ -1,4 +1,4 @@
use super::{index::Index, row::Row}; use super::{index::Index, relation::Relation, row::Row};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::{ use std::{
@@ -10,17 +10,24 @@ use std::{
pub struct Table { pub struct Table {
indexes: HashMap<String, Index>, indexes: HashMap<String, Index>,
rows: HashMap<u64, Row>, rows: HashMap<u64, Row>,
name: String, pub name: String,
pk: String, pk: String,
relations: HashMap<String, Relation>,
} }
impl Table { impl Table {
pub fn new(name: &str, pk: &str, indexes: Option<HashMap<String, Index>>) -> Table { pub fn new(
name: &str,
pk: &str,
indexes: Option<HashMap<String, Index>>,
relations: Option<HashMap<String, Relation>>,
) -> Table {
Table { Table {
indexes: indexes.unwrap_or(HashMap::new()), indexes: indexes.unwrap_or(HashMap::new()),
rows: HashMap::new(), rows: HashMap::new(),
name: name.to_string(), name: name.to_string(),
pk: pk.to_string(), pk: pk.to_string(),
relations: relations.unwrap_or(HashMap::new()),
} }
} }
@@ -107,4 +114,12 @@ impl Table {
.unwrap() .unwrap()
.clone()); .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())
}
} }

View File

@@ -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");
}
}

View File

@@ -8,7 +8,7 @@ mod index_test {
#[test] #[test]
fn should_create_string_index_and_find_two_results() { fn should_create_string_index_and_find_two_results() {
let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(false))]; 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"]); 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" => 2, "name" => "Simon", "race" => "cat"]);
_ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]);
@@ -33,7 +33,7 @@ mod index_test {
#[test] #[test]
fn should_create_reverse_index() { fn should_create_reverse_index() {
let indexes = hashmap!["race" => Index::new("race".to_string(), Some(true), Some(false))]; 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"]); 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" => 2, "name" => "Simon", "race" => "cat"]);
_ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]);
@@ -49,7 +49,7 @@ mod index_test {
#[test] #[test]
fn should_create_unique_index_and_fail_insert() { fn should_create_unique_index_and_fail_insert() {
let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(true))]; 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"]); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]);
_ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]);
@@ -60,7 +60,7 @@ mod index_test {
#[test] #[test]
fn should_create_unique_index() { fn should_create_unique_index() {
let indexes = hashmap!["race" => Index::new("race".to_string(), Some(false), Some(true))]; 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"]); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy", "race" => "cat"]);
_ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]); _ = table.insert_row(row!["id" => 3, "name" => "Gosa", "race" => "dog"]);

View File

@@ -1,2 +1,4 @@
mod database_test;
mod index_test; mod index_test;
mod relation_test;
mod table_test; mod table_test;

View File

@@ -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
}

View File

@@ -5,7 +5,7 @@ mod table_test {
#[test] #[test]
fn should_find_two_rows() { 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"]); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]);
_ = table.insert_row(row!["id" => 2, "name" => "Simon"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon"]);
@@ -22,7 +22,7 @@ mod table_test {
#[test] #[test]
fn should_fail_to_find_row() { 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"]); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]);
_ = table.insert_row(row!["id" => 2, "name" => "Simon"]); _ = table.insert_row(row!["id" => 2, "name" => "Simon"]);
@@ -32,7 +32,7 @@ mod table_test {
#[test] #[test]
fn should_fail_to_insert_row_with_same_id() { 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"]); let _ = table.insert_row(row!["id" => 1, "name" => "Ozzy"]);
// Make sure the rows actually exists // Make sure the rows actually exists