mirror of
https://github.com/eliasrenman/r-database.git
synced 2026-03-16 20:46:08 +01:00
feat: select processor
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
|
||||
3
src/database/query_builder.rs
Normal file
3
src/database/query_builder.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// select query builder
|
||||
// where query builder
|
||||
// Combine them
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
79
src/database/select_processor.rs
Normal file
79
src/database/select_processor.rs
Normal 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ mod database_test;
|
||||
mod index_test;
|
||||
mod relation_test;
|
||||
mod table_test;
|
||||
mod select_processor_test;
|
||||
@@ -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();
|
||||
|
||||
91
src/tests/select_processor_test.rs
Normal file
91
src/tests/select_processor_test.rs
Normal 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]
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user