mirror of
https://github.com/eliasrenman/r-database.git
synced 2026-03-16 20:46:08 +01:00
feat: basic one to one relation
This commit is contained in:
@@ -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
66
src/database/relation.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/tests/database_test.rs
Normal file
33
src/tests/database_test.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
mod database_test;
|
||||||
mod index_test;
|
mod index_test;
|
||||||
|
mod relation_test;
|
||||||
mod table_test;
|
mod table_test;
|
||||||
|
|||||||
52
src/tests/relation_test.rs
Normal file
52
src/tests/relation_test.rs
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user