fix: deep relation selections

This commit is contained in:
Elias Renman
2023-11-23 23:52:38 +01:00
parent b6e8c79164
commit 5f351422d6
2 changed files with 118 additions and 56 deletions

View File

@@ -1,7 +1,11 @@
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use crate::database::{relation::{OneToMany, OneToOne}, Database, row::Row, query::select_parser::SelectParser}; use crate::database::{
query::select_parser::SelectParser,
relation::{OneToMany, OneToOne},
row::Row,
Database,
};
pub struct SelectProcessor {} pub struct SelectProcessor {}
@@ -14,9 +18,8 @@ impl SelectProcessor {
row: &Row, row: &Row,
select: Vec<&str>, select: Vec<&str>,
) -> Map<String, Value> { ) -> Map<String, Value> {
let asd = SelectParser::parse_selector_recursive(select); let selector = SelectParser::parse_selector_recursive(select);
println!("Debugging parsed selector {asd}"); SelectProcessor::recursive_traverse_resolver(database, table_name, row, &selector)
SelectProcessor::recursive_traverse_resolver(database, table_name, row, &asd)
} }
fn recursive_traverse_resolver( fn recursive_traverse_resolver(
@@ -28,32 +31,20 @@ impl SelectProcessor {
let object = selector.as_object().unwrap(); let object = selector.as_object().unwrap();
let mut output: Map<String, Value> = Map::new(); let mut output: Map<String, Value> = Map::new();
for (key, value) in object.into_iter() { for (key, value) in object.into_iter() {
if value.is_object() { match value {
let relation_rows: Value = Value::Object(_val) => {
SelectProcessor::resolve_relation(database, table_name, row, key); output.insert(
key.to_owned(),
if relation_rows.is_array() { SelectProcessor::resolve_foreign_relation(
let mut row_vec: Vec<Map<String, Value>> = vec![]; database, table_name, row, key, value,
for row in relation_rows.as_array().unwrap() { ),
let asd = row.as_object().unwrap();
let parsed_row = SelectProcessor::recursive_traverse_resolver(
database, table_name, &asd, &value,
);
row_vec.push(parsed_row);
}
output.insert(key.to_owned(), json!(row_vec));
} else {
let foreign_row = relation_rows.as_object().unwrap();
let parsed_row = SelectProcessor::recursive_traverse_resolver(
database, table_name, foreign_row, &value,
); );
output.insert(key.to_owned(), json!(parsed_row));
} }
} else { Value::String(val) => {
if value.as_str().unwrap() == "*" { if val == "*" {
output = row.to_owned(); output = row.to_owned();
} else { continue;
}
let val = row.get(key); let val = row.get(key);
if val.is_none() { if val.is_none() {
let pretty_json = serde_json::to_string_pretty(row).unwrap(); let pretty_json = serde_json::to_string_pretty(row).unwrap();
@@ -63,29 +54,60 @@ impl SelectProcessor {
} }
output.insert(key.to_owned(), json!(val.unwrap())); output.insert(key.to_owned(), json!(val.unwrap()));
} }
_ => panic!("Value has invalid type"),
} }
} }
return output; return output;
} }
fn resolve_foreign_relation(
database: &Database,
table_name: &String,
row: &Row,
key: &String,
selector: &Value,
) -> Value {
let (new_table_name,relation_row_value) =
SelectProcessor::resolve_relation(database, table_name, row, key);
if relation_row_value.is_array() {
let mut row_vec: Vec<Map<String, Value>> = vec![];
for row in relation_row_value.as_array().unwrap() {
let asd = row.as_object().unwrap();
let parsed_row = SelectProcessor::recursive_traverse_resolver(
database, &new_table_name, &asd, &selector,
);
row_vec.push(parsed_row);
}
return json!(row_vec);
}
let foreign_row = relation_row_value.as_object().unwrap();
let parsed_row = SelectProcessor::recursive_traverse_resolver(
database,
table_name,
foreign_row,
&selector,
);
return json!(parsed_row);
}
pub fn resolve_relation( pub fn resolve_relation(
database: &Database, database: &Database,
table_name: &String, table_name: &String,
row: &Row, row: &Row,
key: &String, key: &String,
) -> Value { ) -> (String,Value) {
let table = database.get_table(table_name.clone()).unwrap(); let table = database.get_table(table_name.clone()).unwrap();
println!("Attempting to find relation for '{key}' in table: '{table_name}'"); println!("Attempting to find relation for '{key}' in table: '{table_name}'");
let value = row.get(key).unwrap(); let value = row.get(key).unwrap();
let relation_name = match value.get("relation_name") { let relation_name = match value.get("relation_name") {
Some(val) => String::from(val.as_str().unwrap()), Some(val) => String::from(val.as_str().unwrap()),
None => panic!("Value was not a valid relation"), None => panic!("Value was not a valid relation"),
}; };
let relation = table.get_relation(&relation_name).unwrap(); let relation = table.get_relation(&relation_name).unwrap();
let relation_variation = relation.variation.as_str(); let relation_value = match relation.variation.as_str() {
return match relation_variation {
"one_to_one" => json!(OneToOne::from_value(value.to_owned()) "one_to_one" => json!(OneToOne::from_value(value.to_owned())
.get(table_name.clone(), database) .get(table_name.clone(), database)
.unwrap()), .unwrap()),
@@ -94,9 +116,7 @@ impl SelectProcessor {
.unwrap()), .unwrap()),
other => panic!("Unsupported relationship type: {other}"), other => panic!("Unsupported relationship type: {other}"),
}; };
(relation.table_name.to_owned(), relation_value)
} }
fn is_valid_key(key: &str) -> bool {
key.chars().all(|c| c.is_ascii_lowercase() || c == '_')
}
} }

View File

@@ -3,37 +3,54 @@
#[cfg(test)] #[cfg(test)]
mod select_processor_test { mod select_processor_test {
use crate::database::{ use crate::database::{
relation::{OneToMany, Relation, OneToOne}, query::select_processor::SelectProcessor,
relation::{OneToMany, OneToOne, Relation},
table::Table, table::Table,
Database, query::select_processor::SelectProcessor, Database,
}; };
use assert_json_diff::assert_json_include; use assert_json_diff::assert_json_include;
use serde_json::json; use serde_json::json;
pub fn initialize() -> Database { pub fn initialize() -> Database {
let cat_relations = let cat_relations = hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_many".to_string()),
hashmap!["cat_food" => Relation::new("Food".to_string(), "one_to_many".to_string()),
"cat_age_group" => Relation::new("AgeGroup".to_string(), "one_to_one".to_string())]; "cat_age_group" => Relation::new("AgeGroup".to_string(), "one_to_one".to_string())];
let food_relations = hashmap!["food_ingredient" => Relation::new("Ingredient".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("Food", "id", None, Some(food_relations));
let ingredient_table: Table = Table::new("Ingredient", "id", None, None);
let age_table: Table = Table::new("AgeGroup", "id", None, None); let age_table: Table = Table::new("AgeGroup", "id", None, None);
let mut database: Database = Database::new(vec![cat_table, food_table,age_table]); let mut database: Database =
Database::new(vec![cat_table, food_table, age_table, ingredient_table]);
_ = database _ = database
.get_table_mut("Food".to_string()) .get_table_mut("Food".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 123, "name" => "Wet Feed"]); .insert_row(row![
"id" => 123,
"name" => "Wet Feed",
"ingridients" => OneToMany::new(vec![], "food_ingredient".to_string())]);
_ = database _ = database
.get_table_mut("Food".to_string()) .get_table_mut("Food".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 12, "name" => "Dry Feed"]); .insert_row(row![
"id" => 12,
"name" => "Dry Feed",
"ingridients" => OneToMany::new(vec![33u64], "food_ingredient".to_string())
]);
_ = database
.get_table_mut("Ingredient".to_string())
.unwrap()
.insert_row(row!["id" => 33, "name" => "Chicken Meat"]);
_ = database _ = database
.get_table_mut("AgeGroup".to_string()) .get_table_mut("AgeGroup".to_string())
.unwrap() .unwrap()
.insert_row(row!["id" => 99, "age" => "Young"]); .insert_row(row!["id" => 99, "age" => "Young"]);
_ = database.get_table_mut("Cats".to_string()).unwrap().insert_row(row![ _ = database
.get_table_mut("Cats".to_string())
.unwrap()
.insert_row(row![
"id" => 1, "id" => 1,
"name" => "Ozzy", "name" => "Ozzy",
"breed" => "mixed", "breed" => "mixed",
@@ -42,6 +59,28 @@ mod select_processor_test {
return database; return database;
} }
#[test]
fn should_succed_select_processor_two_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", "name", "foods.*", "foods.ingridients.name"];
let output = SelectProcessor::selector(&database, &cat_table.name, cat_1, select);
let pretty = serde_json::to_string_pretty(&output).unwrap();
println!("Json output {pretty}");
assert_json_include!(
actual: &output,
expected:
&row!["id" => 1, "name" =>"Ozzy", "foods" =>
vec![
row!["id" => 123, "name" => "Wet Feed", "ingridients" => json!([])],
row!["id" => 12, "name" => "Dry Feed", "ingridients" => vec![row!["name" => "Chicken Meat"]]]
]]
);
}
#[test] #[test]
fn should_succed_select_processor_one_level_resolved() { fn should_succed_select_processor_one_level_resolved() {
let database = initialize(); let database = initialize();
@@ -61,22 +100,24 @@ mod select_processor_test {
]] ]]
); );
} }
#[test]
fn should_succed_select_processor_one_level_resolved_onetoone() {
let database = initialize();
let cat_table = database.get_table("Cats".to_string()).unwrap(); #[test]
let cat_1 = cat_table.find_by_pk(1u64).unwrap(); fn should_succed_select_processor_one_level_resolved_onetoone() {
let select = vec!["id", "name", "group.age"]; let database = initialize();
let output = SelectProcessor::selector(&database, &cat_table.name, cat_1, select);
assert_json_include!( let cat_table = database.get_table("Cats".to_string()).unwrap();
actual: &output, let cat_1 = cat_table.find_by_pk(1u64).unwrap();
let select = vec!["id", "name", "group.age"];
let output = SelectProcessor::selector(&database, &cat_table.name, cat_1, select);
assert_json_include!(
actual: &output,
expected: expected:
&row!["id" => 1, "name" =>"Ozzy", "group"=> row![ &row!["id" => 1, "name" =>"Ozzy", "group"=> row![
"age" => "Young" "age" => "Young"
]] ]]
); );
} }
#[test] #[test]
fn should_succed_select_processor_zero_level_resolved() { fn should_succed_select_processor_zero_level_resolved() {
let database = initialize(); let database = initialize();
@@ -90,6 +131,7 @@ mod select_processor_test {
expected: &row!["id" => 1, "name" =>"Ozzy", "breed" => "mixed"] expected: &row!["id" => 1, "name" =>"Ozzy", "breed" => "mixed"]
); );
} }
#[test] #[test]
fn should_succed_select_processor_zero_level_asterix_resolved() { fn should_succed_select_processor_zero_level_asterix_resolved() {
let database = initialize(); let database = initialize();