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.
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]]
name = "db"
version = "0.1.0"
dependencies = [
"assert-json-diff",
"serde",
"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
[dependencies]
assert-json-diff = "2.0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@@ -8,6 +8,8 @@ pub mod index;
pub mod relation;
pub mod row;
pub mod table;
pub mod select_processor;
pub mod query_builder;
#[derive(Serialize, Deserialize)]
pub struct Database {
@@ -37,7 +39,15 @@ 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() {
if table.name == name {
return Ok(table);
}
}
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);

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)]
pub struct Relation {
pub table_name: String,
variation: String,
pub variation: String,
}
impl 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
let table = database.get_table(self.table_name.clone()).unwrap();
// Get row from foreign table
@@ -43,10 +43,10 @@ impl OneToOne {
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> = {
// Get table
let table: &mut Table = match database.get_table(table_name) {
let table: &Table = match database.get_table(table_name) {
Ok(relation) => relation,
Err(error) => return Err(error),
};
@@ -86,9 +86,9 @@ impl OneToMany {
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 table: &mut Table = match database.get_table(table_name) {
let table: &Table = match database.get_table(table_name) {
Ok(relation) => relation,
Err(error) => return Err(error),
};

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());
}
pub fn get_relation(&self, name: &String) -> Result<&Relation, String> {
let relation = self.relations.get(name);
if relation.is_some() {
pub fn get_relation(&self, column_name: &String) -> Result<&Relation, String> {
let relation = self.relations.get(column_name);
if !relation.is_none() {
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 relation_test;
mod table_test;
mod select_processor_test;

View File

@@ -11,20 +11,20 @@ mod relation_test {
#[test]
fn should_succed_one_to_one_relationship_found() {
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 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]);
_ = database
.get_table("Food".to_string())
.get_table_mut("Foods".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 _ = 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_food = cat_1.get("food").unwrap();
@@ -52,17 +52,17 @@ mod relation_test {
fn should_fail_no_one_to_one_relation_found() {
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]);
_ = database
.get_table("Food".to_string())
.get_table_mut("Foods".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 _ = 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_food = cat_1.get("food").unwrap();
@@ -70,7 +70,7 @@ mod relation_test {
let relation = OneToOne::from_value(cat_1_food.to_owned());
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);
}
@@ -81,9 +81,9 @@ mod relation_test {
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_food = cat_1.get("food").unwrap();
@@ -91,7 +91,7 @@ mod relation_test {
let relation = OneToOne::from_value(cat_1_food.to_owned());
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!(
"Printing foreign row error {}\n",
&foreign_row.unwrap_err().as_str()
@@ -101,13 +101,13 @@ mod relation_test {
fn should_fail_no_one_to_one_row_found() {
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 _ = 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_food = cat_1.get("food").unwrap();
@@ -126,22 +126,22 @@ mod relation_test {
#[test]
fn should_succed_one_to_many_relationship_found() {
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 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]);
_ = database
.get_table("Food".to_string())
.get_table_mut("Foods".to_string())
.unwrap()
.insert_row(row!["id" => 123, "name" => "Dry Feed"]);
_ = database
.get_table("Food".to_string())
.get_table_mut("Foods".to_string())
.unwrap()
.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_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]
}
})
);
}
}