mirror of
https://github.com/eliasrenman/r-database.git
synced 2026-03-16 20:46:08 +01:00
fix: deep relation selections
This commit is contained in:
@@ -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));
|
Value::String(val) => {
|
||||||
} else {
|
if val == "*" {
|
||||||
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 {
|
|
||||||
if value.as_str().unwrap() == "*" {
|
|
||||||
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 == '_')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +100,7 @@ mod select_processor_test {
|
|||||||
]]
|
]]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_succed_select_processor_one_level_resolved_onetoone() {
|
fn should_succed_select_processor_one_level_resolved_onetoone() {
|
||||||
let database = initialize();
|
let database = initialize();
|
||||||
@@ -77,6 +117,7 @@ mod select_processor_test {
|
|||||||
]]
|
]]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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();
|
||||||
|
|||||||
Reference in New Issue
Block a user