Skip to content

Commit

Permalink
feat: Implement AnyTable trait, add condition and entity support (#8)
Browse files Browse the repository at this point in the history
* feat: Implement AnyTable trait, add condition and entity support

Add support for the AnyTable trait for Table with Entity.
Implement methods for AnyTable trait: as_any, get_field, add_condition.
Define EmptyEntity struct and implement Entity trait.
Update Table struct to support the new entity type and add conditions.
Update TableDelegate trait to accept Entity type.

* doc: Updated README file.
  • Loading branch information
romaninsh authored Sep 22, 2024
1 parent 191700a commit d7e617b
Show file tree
Hide file tree
Showing 30 changed files with 417 additions and 289 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 17 additions & 8 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MVP:

0.1.0: Query Building
0.0.1: Query Building

- [x] create a basic query type
- [x] have query ability to render into a SQL query
Expand All @@ -15,7 +15,7 @@ MVP:
- [x] implement operations: (field.eq(otherfield))
- [x] implement parametric queries

0.2.0: Nested Query Building
0.0.2: Nested Query Building

- [x] properly handle nested queries
- [x] table should own DataSource, which should be cloneable and use Arc for client
Expand All @@ -26,7 +26,7 @@ MVP:
- [x] implemented TableDelegate trait
- [x] implemented Query::add_join()

0.3.0: Table Structure
0.0.3: Table Structure

- [x] add uniq id vendor
- [x] implemented Table::join_table() for merging tables
Expand All @@ -36,9 +36,18 @@ MVP:
- [x] When joining table, combine their UniqueIdVendors into one
- [x] Implement has_one and has_many in a lazy way
- [x] Implement expressions in a lazy way

0.4.0: Features for multiple table queries

- [x] Implemented bakery example

0.0.4: Improve Entity tracking and add target documentation

- [x] Add documentation for target vision of the library
- [x] Add "Entity" concept into Table
- [x] Add example on how to use traits for augmenting Table of specific Entity
- [ ] Check on "Join", they should allow for Entity mutation (joined table associated with a different entity)
- [ ] Implement has_one and has_many in a correct way, moving functionality to Related Reference
- [ ] Implement Unrelated Reference (when ref leads to a table with different Data Source)
- [ ] Implement a better data fetching mechanism, using default entity
- [ ] Restore functionality of bakery example
- [ ] Implement ability to include sub-queries based on related tables

Create integration test-suite for SQL testing
Expand All @@ -47,8 +56,8 @@ Create integration test-suite for SQL testing
- [x] Implement testcontainers postgres connectivity
- [ ] Get rid of testcontainers (they don't work anyway), use regular Postgres
- [ ] Create separate test-suite, connect DB etc
- [ ] Populate Bakery tables for tests
- [ ] Seed some data into Bakery tests
- [x] Populate Bakery tables for tests
- [x] Seed some data into Bakery tests
- [ ] Make use of Postgres snapshots in the tests

Control field queries
Expand Down
1 change: 1 addition & 0 deletions bakery_model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2018"
anyhow = "1.0.86"
dorm = { path = "../dorm" }
pretty_assertions = "1.4.0"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.120"
testcontainers-modules = { version = "0.8.0", features = [
"postgres",
Expand Down
43 changes: 23 additions & 20 deletions bakery_model/examples/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bakery_model::product::Product;
use dorm::prelude::*;
use serde_json::Value;

Expand All @@ -15,37 +16,38 @@ async fn main() {
.unwrap();
client.batch_execute(&schema).await.unwrap();

// Ok, now lets work with the models directly
let bakery_set = bakery_model::bakery::BakerySet::new();
let query = bakery_set.get_select_query();
let result = dorm_client.query_raw(&query).await.unwrap();
// // Ok, now lets work with the models directly
// let bakery_set = bakery_model::bakery::BakerySet::new();
// let query = bakery_set.get_select_query();
// let result = dorm_client.query_raw(&query).await.unwrap();

let Some(Value::String(bakery)) = result[0].get("name") else {
panic!("No bakery found");
};
println!("-----------------------------");
println!("Working for the bakery: {}", bakery);
println!("");
// let Some(Value::String(bakery)) = result[0].get("name") else {
// panic!("No bakery found");
// };
// println!("-----------------------------");
// println!("Working for the bakery: {}", bakery);
// println!("");

// Now, lets see how many clients bakery has
let client_set = bakery_set.get_ref("clients").unwrap();
let client_count = client_set.count();
// // Now, lets see how many clients bakery has
// let client_set = bakery_set.get_ref("clients").unwrap();
// let client_count = client_set.count();

println!(
"There are {} clients in the bakery.",
client_count.get_one().await.unwrap()
);
// println!(
// "There are {} clients in the bakery.",
// client_count.get_one().await.unwrap()
// );

// Finally lets see how many products we have in the bakery
// // Finally lets see how many products we have in the bakery

let product_set = bakery_set.get_ref("products").unwrap();
let product_count = product_set.count();
// let product_set = bakery_set.get_ref("products").unwrap();
let product_count = Product::table().count();

println!(
"There are {} products in the bakery.",
product_count.get_one().await.unwrap()
);

/*
// How many products are there with the name
// Now for every product, lets calculate how many orders it has
Expand Down Expand Up @@ -91,4 +93,5 @@ async fn main() {
for row in res.into_iter() {
println!(" name: {} orders: {}", row["name"], row["orders_count"]);
}
*/
}
12 changes: 6 additions & 6 deletions bakery_model/schema-pg.sql
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ INSERT INTO client (name, contact_details, bakery_id) VALUES
('Doc Brown', '555-1885', 1),
('Biff Tannen', '555-1955', 1);

INSERT INTO product (name, bakery_id) VALUES
('Flux Capacitor Cupcake', 1),
('DeLorean Doughnut', 1),
('Time Traveler Tart', 1),
('Enchantment Under the Sea Pie', 1),
('Hoverboard Cookies', 1);
INSERT INTO product (name, calories, bakery_id) VALUES
('Flux Capacitor Cupcake', 300, 1),
('DeLorean Doughnut', 250, 1),
('Time Traveler Tart', 200, 1),
('Enchantment Under the Sea Pie', 350, 1),
('Hoverboard Cookies', 150, 1);

INSERT INTO inventory (product_id, stock) VALUES
(1, 50),
Expand Down
6 changes: 3 additions & 3 deletions bakery_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ use tokio_postgres::NoTls;

use dorm::prelude::Postgres;

pub mod bakery;
// pub mod bakery;
// pub mod cake;
// pub mod cakes_bakers;
pub mod client;
// pub mod client;
// pub mod customer;
pub mod product;

// pub mod lineitem;
pub mod order;
// pub mod order;

// pub use bakery::BakerySet;
// pub use product::ProductSet;
Expand Down
113 changes: 60 additions & 53 deletions bakery_model/src/product.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,83 @@
use std::{
ops::Deref,
sync::{Arc, OnceLock},
};
use std::sync::{Arc, OnceLock};

use crate::bakery::BakerySet;
// use crate::bakery::BakerySet;
use dorm::prelude::*;
use serde::{Deserialize, Serialize};

use crate::postgres;

#[derive(Debug)]
pub struct Products {
table: Table<Postgres>,
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub struct Product {
id: i64,
name: String,
}
impl Products {
pub fn new() -> Products {
Products {
table: Products::static_table().clone(),
}
}
pub fn from_table(table: Table<Postgres>) -> Self {
Self { table }
}
pub fn static_table() -> &'static Table<Postgres> {
static TABLE: OnceLock<Table<Postgres>> = OnceLock::new();
impl Entity for Product {}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub struct ProductInventory {
id: i64,
product_id: i64,
stock: i64,
}
impl Entity for ProductInventory {}

impl Product {
pub fn static_table() -> &'static Table<Postgres, Product> {
static TABLE: OnceLock<Table<Postgres, Product>> = OnceLock::new();

TABLE.get_or_init(|| {
Table::new("product", postgres())
Table::new_with_entity("product", postgres())
.with_id_field("id")
.with_field("name")
.with_field("bakery_id")
.has_one("bakery", "bakery_id", || BakerySet::new())
// .has_one("bakery", "bakery_id", || BakerySet::new())
})
}
pub fn table(&self) -> Table<Postgres> {
self.table.clone()
}
pub fn mod_table(self, func: impl FnOnce(Table<Postgres>) -> Table<Postgres>) -> Self {
let table = self.table.clone();
let table = func(table);
Self { table }
}
pub fn with_inventory(self) -> Self {
self.mod_table(|t| {
t.with_join(
Table::new("inventory", postgres())
.with_alias("i")
.with_id_field("product_id")
.with_field("stock"),
"id",
)
})
pub fn table() -> Table<Postgres, Product> {
Product::static_table().clone()
}
}

trait ProductTable: AnyTable {
fn with_inventory(self) -> Table<Postgres, Product>;

pub fn id() -> Arc<Field> {
Products::static_table().get_field("id").unwrap()
fn id(&self) -> &Arc<Field> {
self.get_field("id").unwrap()
}
pub fn name() -> Arc<Field> {
Products::static_table().get_field("name").unwrap()
fn name(&self) -> &Arc<Field> {
self.get_field("name").unwrap()
}
pub fn bakery_id() -> Arc<Field> {
Products::static_table().get_field("bakery_id").unwrap()
fn bakery_id(&self) -> &Arc<Field> {
self.get_field("bakery_id").unwrap()
}

pub fn stock(&self) -> Arc<Field> {
self.get_join("i").unwrap().get_field("stock").unwrap()
}
// pub fn stock(&self) -> Arc<Field> {
// self.get_join("i").unwrap().get_field("stock").unwrap()
// }
}

impl Deref for Products {
type Target = Table<Postgres>;

fn deref(&self) -> &Self::Target {
&self.table
impl ProductTable for Table<Postgres, Product> {
fn with_inventory(self) -> Table<Postgres, Product> {
self.with_join(
Table::new_with_entity("inventory", postgres())
.with_alias("i")
.with_id_field("product_id")
.with_field("stock"),
"id",
)
}
}

// #[cfg(test)]
// mod tests {
// use super::*;

// #[tokio::test]
// async fn test_product() {
// let table = Product::table();
// let _field = table.name();

// let table = table.with_inventory();
// // let _field = table.stock();
// }
// }
Loading

0 comments on commit d7e617b

Please sign in to comment.