feat: select processor

This commit is contained in:
Elias Renman
2023-11-20 01:25:25 +01:00
parent e94fcc36c4
commit 26bc065a00
11 changed files with 234 additions and 37 deletions

11
Cargo.lock generated
View File

@@ -2,10 +2,21 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "db" name = "db"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"assert-json-diff",
"serde", "serde",
"serde_json", "serde_json",
] ]

View File

@@ -6,5 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
assert-json-diff = "2.0.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@@ -8,6 +8,8 @@ pub mod index;
pub mod relation; pub mod relation;
pub mod row; pub mod row;
pub mod table; pub mod table;
pub mod select_processor;
pub mod query_builder;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Database { pub struct Database {
@@ -37,14 +39,22 @@ impl Database {
} }
} }
pub fn get_table(&mut self, name: String) -> Result<&mut Table, String> { pub fn get_table(&self, name: String) -> Result<&Table, String> {
for table in self.tables.iter_mut() { for table in self.tables.iter() {
if table.name == name { if table.name == name {
return Ok(table); return Ok(table);
} }
} }
Err("Failed to find table".to_string()) Err("Failed to find table".to_string())
} }
pub fn get_table_mut(&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())
}
pub fn get_table_relation( pub fn get_table_relation(
&self, &self,

View File

@@ -0,0 +1,3 @@
// select query builder
// where query builder
// Combine them

View File

@@ -5,7 +5,7 @@ use serde_json::Value;
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Relation { pub struct Relation {
pub table_name: String, pub table_name: String,
variation: String, pub variation: String,
} }
impl Relation { impl Relation {
pub fn new(table_name: String, variation: String) -> Relation { pub fn new(table_name: String, variation: String) -> Relation {
@@ -15,7 +15,7 @@ impl Relation {
} }
} }
pub fn get_foreign_row(&self, database: &mut Database, foreign_id: u64) -> Result<Row, String> { pub fn get_foreign_row(&self, database: &Database, foreign_id: u64) -> Result<Row, String> {
// Find foreign table // Find foreign table
let table = database.get_table(self.table_name.clone()).unwrap(); let table = database.get_table(self.table_name.clone()).unwrap();
// Get row from foreign table // Get row from foreign table
@@ -43,10 +43,10 @@ impl OneToOne {
self.foreign_id self.foreign_id
} }
pub fn get(&self, table_name: String, database: &mut Database) -> Result<Row, String> { pub fn get(&self, table_name: String, database: &Database) -> Result<Row, String> {
let relation_result: Result<&Relation, String> = { let relation_result: Result<&Relation, String> = {
// Get table // Get table
let table: &mut Table = match database.get_table(table_name) { let table: &Table = match database.get_table(table_name) {
Ok(relation) => relation, Ok(relation) => relation,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
@@ -86,9 +86,9 @@ impl OneToMany {
self.foreign_ids.clone() self.foreign_ids.clone()
} }
pub fn get(&self, table_name: String, database: &mut Database) -> Result<Vec<Row>, String> { pub fn get(&self, table_name: String, database: &Database) -> Result<Vec<Row>, String> {
let relation_result: Result<&Relation, String> = { let relation_result: Result<&Relation, String> = {
let table: &mut Table = match database.get_table(table_name) { let table: &Table = match database.get_table(table_name) {
Ok(relation) => relation, Ok(relation) => relation,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
@@ -111,4 +111,4 @@ impl OneToMany {
pub fn from_value(value: Value) -> OneToMany { pub fn from_value(value: Value) -> OneToMany {
serde_json::from_value(value).unwrap() serde_json::from_value(value).unwrap()
} }
} }

View File

@@ -1,4 +1,4 @@
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
pub type Row = HashMap<String, Value>; pub type Row = HashMap<String, Value>;

View File

@@ -0,0 +1,79 @@
use std::{borrow::Borrow, collections::HashMap, str::FromStr};
use serde_json::{map::Values, Value, json};
use crate::database::relation::{OneToOne, OneToMany};
use super::{row::Row, table::Table, Database};
pub struct SelectProcessor {
}
/**
{
select: [
"id",
"name",
"relation.*"
]
}
{
select: [
"id",
"name",
"relation.id"
]
}
//
*/
impl SelectProcessor {
// recursively select relations
pub fn selector(database: &Database, table_name: &String, row: &Row, select: Vec<String>, mut output: HashMap<String,Value>) -> HashMap<String,Value> {
for value in select {
let key = value.to_string();
if(key.eq("*")) {
return row.clone();// recursive call
}
else if(key.contains(".")) {
let split: Vec<String> = key.split(".").map(|part| String::from_str(part).unwrap()).collect();
if(split.len() == 2 && split[1].eq("*")) {
let relation_rows = SelectProcessor::resolve_relation(database, table_name, row, &split[0]);
output.insert(split[0].clone(), json!(relation_rows));
}
// recursive call
} else {
let value = row.get(&key);
match value.is_some() {
true => output.insert(key, value.unwrap().to_owned()),
false => panic!("Failed to read key: {key} on table: {table_name}"),
};
}
}
return output;
}
pub fn resolve_relation(database: &Database, table_name: &String, row: &Row, key: &String) -> Vec<Row> {
let table = database.get_table(table_name.clone()).unwrap();
println!("Attempting to find relation for '{key}' in table: '{table_name}'");
let value = row.get(key).unwrap();
let relation_name = match value.get("relation_name") {
Some(val) => String::from(val.as_str().unwrap()),
None => panic!("Value was not a valid relation")
};
let relation = table.get_relation(&relation_name).unwrap();
let relation_variation = relation.variation.as_str();
return match relation_variation {
"one_to_one" => vec![OneToOne::from_value(value.to_owned()).get(table_name.clone(), database).unwrap()],
"one_to_many" => OneToMany::from_value(value.to_owned()).get(table_name.clone(), database).unwrap(),
other => panic!("Unsupported relationship type: {other}")
}
}
}

View File

@@ -115,11 +115,12 @@ impl Table {
.clone()); .clone());
} }
pub fn get_relation(&self, name: &String) -> Result<&Relation, String> { pub fn get_relation(&self, column_name: &String) -> Result<&Relation, String> {
let relation = self.relations.get(name); let relation = self.relations.get(column_name);
if relation.is_some() {
if !relation.is_none() {
return Ok(relation.unwrap()); return Ok(relation.unwrap());
} }
Err("Failed to find relation".to_string()) Err(format!("Failed to find relation, '{column_name}'").to_string())
} }
} }

View File

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

View File

@@ -11,20 +11,20 @@ mod relation_test {
#[test] #[test]
fn should_succed_one_to_one_relationship_found() { fn should_succed_one_to_one_relationship_found() {
let cat_relations = let cat_relations =
hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_one".to_string())]; hashmap!["cat_food" => Relation::new("Foods".to_string(), "one_to_one".to_string())];
let cat_table: Table = Table::new("Cats", "id", None, Some(cat_relations)); let cat_table: Table = Table::new("Cats", "id", None, Some(cat_relations));
let food_table: Table = Table::new("Food", "id", None, None); let food_table: Table = Table::new("Foods", "id", None, None);
let mut database: Database = Database::new(vec![cat_table, food_table]); let mut database: Database = Database::new(vec![cat_table, food_table]);
_ = database _ = database
.get_table("Food".to_string()) .get_table_mut("Foods".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 123, "name" => "Dry Feed"]); .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 _ = database.get_table_mut("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_table = database.get_table_mut("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let cat_1_food = cat_1.get("food").unwrap(); let cat_1_food = cat_1.get("food").unwrap();
@@ -52,17 +52,17 @@ mod relation_test {
fn should_fail_no_one_to_one_relation_found() { fn should_fail_no_one_to_one_relation_found() {
let cat_table: Table = Table::new("Cats", "id", None, None); let cat_table: Table = Table::new("Cats", "id", None, None);
let food_table: Table = Table::new("Food", "id", None, None); let food_table: Table = Table::new("Foods", "id", None, None);
let mut database: Database = Database::new(vec![cat_table, food_table]); let mut database: Database = Database::new(vec![cat_table, food_table]);
_ = database _ = database
.get_table("Food".to_string()) .get_table_mut("Foods".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 123, "name" => "Dry Feed"]); .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 _ = database.get_table_mut("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_table = database.get_table_mut("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let cat_1_food = cat_1.get("food").unwrap(); let cat_1_food = cat_1.get("food").unwrap();
@@ -70,20 +70,20 @@ mod relation_test {
let relation = OneToOne::from_value(cat_1_food.to_owned()); let relation = OneToOne::from_value(cat_1_food.to_owned());
assert_eq!(relation.get_id(), 123u64); assert_eq!(relation.get_id(), 123u64);
let foreign_row = relation.get("Food".to_string(), database.borrow_mut()); let foreign_row = relation.get("Foods".to_string(), database.borrow_mut());
assert_eq!(foreign_row.is_err(), true); assert_eq!(foreign_row.is_err(), true);
} }
#[test] #[test]
fn should_fail_no_table_found() { fn should_fail_no_table_found() {
let cat_table: Table = Table::new("Cats", "id", None, None); let cat_table: Table = Table::new("Cats", "id", None, None);
let mut database: Database = Database::new(vec![cat_table]); let mut database: Database = Database::new(vec![cat_table]);
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 _ = database.get_table_mut("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_table = database.get_table_mut("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let cat_1_food = cat_1.get("food").unwrap(); let cat_1_food = cat_1.get("food").unwrap();
@@ -91,7 +91,7 @@ mod relation_test {
let relation = OneToOne::from_value(cat_1_food.to_owned()); let relation = OneToOne::from_value(cat_1_food.to_owned());
assert_eq!(relation.get_id(), 123u64); assert_eq!(relation.get_id(), 123u64);
let foreign_row = relation.get("Food".to_string(), database.borrow_mut()); let foreign_row = relation.get("Foods".to_string(), database.borrow_mut());
print!( print!(
"Printing foreign row error {}\n", "Printing foreign row error {}\n",
&foreign_row.unwrap_err().as_str() &foreign_row.unwrap_err().as_str()
@@ -101,13 +101,13 @@ mod relation_test {
fn should_fail_no_one_to_one_row_found() { fn should_fail_no_one_to_one_row_found() {
let cat_table: Table = Table::new("Cats", "id", None, None); let cat_table: Table = Table::new("Cats", "id", None, None);
let food_table: Table = Table::new("Food", "id", None, None); let food_table: Table = Table::new("Foodss", "id", None, None);
let mut database: Database = Database::new(vec![cat_table, food_table]); let mut database: Database = Database::new(vec![cat_table, food_table]);
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 _ = database.get_table_mut("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_table = database.get_table_mut("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let cat_1_food = cat_1.get("food").unwrap(); let cat_1_food = cat_1.get("food").unwrap();
@@ -126,22 +126,22 @@ mod relation_test {
#[test] #[test]
fn should_succed_one_to_many_relationship_found() { fn should_succed_one_to_many_relationship_found() {
let cat_relations = let cat_relations =
hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_many".to_string())]; hashmap!["cat_food" => Relation::new("Foods".to_string(), "one_to_many".to_string())];
let cat_table: Table = Table::new("Cats", "id", None, Some(cat_relations)); let cat_table: Table = Table::new("Cats", "id", None, Some(cat_relations));
let food_table: Table = Table::new("Food", "id", None, None); let food_table: Table = Table::new("Foods", "id", None, None);
let mut database: Database = Database::new(vec![cat_table, food_table]); let mut database: Database = Database::new(vec![cat_table, food_table]);
_ = database _ = database
.get_table("Food".to_string()) .get_table_mut("Foods".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 123, "name" => "Dry Feed"]); .insert_row(row!["id" => 123, "name" => "Dry Feed"]);
_ = database _ = database
.get_table("Food".to_string()) .get_table_mut("Foods".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 12, "name" => "Dry Feed"]); .insert_row(row!["id" => 12, "name" => "Dry Feed"]);
let _ = database.get_table("Cats".to_string()).unwrap().insert_row(row!["id" => 1, "name" => "Ozzy", "breed" => "mixed", "foods" => OneToMany::new(vec![123u64,12u64], "cat_food".to_string())]); let _ = database.get_table_mut("Cats".to_string()).unwrap().insert_row(row!["id" => 1, "name" => "Ozzy", "breed" => "mixed", "foods" => OneToMany::new(vec![123u64,12u64], "cat_food".to_string())]);
let cat_table = database.get_table("Cats".to_string()).unwrap(); let cat_table = database.get_table("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); let cat_1 = cat_table.find_by_pk(1u64).unwrap();

View File

@@ -0,0 +1,91 @@
//hashmap!(["food" => Relation::new("food","one_to_one")])
#[cfg(test)]
mod select_processor_test {
use crate::database::{
relation::{OneToMany, Relation},
select_processor::SelectProcessor,
table::Table,
Database,
};
use assert_json_diff::assert_json_include;
use serde_json::json;
use std::collections::HashMap;
pub fn initialize() -> Database {
let cat_relations =
hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_many".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_mut("Food".to_string())
.unwrap()
.insert_row(row!["id" => 123, "name" => "Wet Feed"]);
_ = database
.get_table_mut("Food".to_string())
.unwrap()
.insert_row(row!["id" => 12, "name" => "Dry Feed"]);
_ = database.get_table_mut("Cats".to_string()).unwrap().insert_row(row!["id" => 1, "name" => "Ozzy", "breed" => "mixed", "foods" => OneToMany::new(vec![123u64,12u64], "cat_food".to_string())]);
return database;
}
#[test]
fn should_succed_select_processor_one_level_resolved() {
let database = initialize();
let cat_table = database.get_table("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let select = vec!["id".to_string(), "name".to_string(), "foods.*".to_string()];
let output =
SelectProcessor::selector(&database, &cat_table.name, cat_1, select, HashMap::new());
assert_json_include!(
actual: &output,
expected:
&row!["id" => 1, "name" =>"Ozzy", "foods" =>
vec![
row!["id" => 123, "name" => "Wet Feed"],
row!["id" => 12, "name" => "Dry Feed"]
]]
);
}
#[test]
fn should_succed_select_processor_zero_level_resolved() {
let database = initialize();
let cat_table = database.get_table("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let select = vec!["id".to_string(), "name".to_string(), "breed".to_string()];
let output =
SelectProcessor::selector(&database, &cat_table.name, cat_1, select, HashMap::new());
assert_json_include!(
actual: &output,
expected: &row!["id" => 1, "name" =>"Ozzy", "breed" => "mixed"]
);
}
#[test]
fn should_succed_select_processor_zero_level_asterix_resolved() {
let database = initialize();
let cat_table = database.get_table("Cats".to_string()).unwrap();
let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let select = vec!["*".to_string()];
let output =
SelectProcessor::selector(&database, &cat_table.name, cat_1, select, HashMap::new());
assert_json_include!(
actual: &output,
expected:
&json!({"id": 1, "name":"Ozzy", "breed": "mixed", "foods":
{
"relation_name": "cat_food",
"foreign_ids": [123,12]
}
})
);
}
}