Skip to content

Commit

Permalink
feat: table columns now use RwLock to read table alias (#49)
Browse files Browse the repository at this point in the history
* feat: table columns now use RwLock to read table alias

* rewrote README slightly

* minor cleanups

* refactor joins entirely

* cleanup readme
  • Loading branch information
romaninsh authored Jan 5, 2025
1 parent 3810da2 commit e539119
Show file tree
Hide file tree
Showing 29 changed files with 1,374 additions and 423 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ Vantage is a type-safe, easy to use database toolkit for Rust that focuses on de
without compromising performance. It allows you to work with your database using Rust's strong type
system while abstracting away the complexity of SQL queries.

Vantage enables use of Model Driven Architecture (DSL/DDD) patterns in your Rust applications. This
approach separates business and application logic from underlying platform technology. Vantage uses
native Rust syntax to define Entities, Attributes, Validations, Relations, Actions and mapping them
to one or several persistence layers - such as SQL, NoSQL or APIs.
Vantage enables use of Model Driven Architecture, implementing Object Relationship Manager, Query
Builder, and Entity Framework with enterprise-grade enhancements such as soft-delete, aggregation,
event hooks, disjoint subtypes, and high-performance data mocking for your application.

The long-term goal for Vantage is to be a building block for configurable ERP/CRM/HR/Supply business
management system rivaling Odoo or Salesforce written entirely in Rust.
As a part of a broader ecosystem, Vantage enables creation of reusable HR, CRM, Payment and Supply
Chain management solutions rivaling the likes of [Odoo](https://odoo.com/),
[Salesforce](https://www.salesforce.com/) and SAP, while retaining Rust values - open source, performance,
extensibility and safety.

## Quick Start

Your application would typically require a model definition. Here is example:
[bakery_model](bakery_model/src/). You would also need a Postgres database populated with sample data
from [schema-pg.sql](bakery_model/schema-pg.sql) and create role `postgres`.
To get started with Vantage, you first need to define your business model. For example, take a look
at the provided [bakery_model](bakery_model/src/). This model represents a real-world domain that
you can interact with using Vantage. You'll also need a Postgres database with sample data preloaded
from [schema-pg.sql](bakery_model/schema-pg.sql) and a `postgres` role configured.

Once this is in place, you can use Vantage to interract with your data like this:
Once your environment is ready, Vantage enables you to interact with your data through its intuitive
interface, allowing you to focus on business logic without worrying about SQL intricacies. Here’s a
quick example:

```rust
use vantage::prelude::*;
Expand Down
6 changes: 3 additions & 3 deletions bakery_model/src/bakery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ impl Bakery {

pub trait BakeryTable: AnyTable {
// fields
fn id(&self) -> Arc<Column> {
fn id(&self) -> Arc<PgValueColumn> {
self.get_column("id").unwrap()
}
fn name(&self) -> Arc<Column> {
fn name(&self) -> Arc<PgValueColumn> {
self.get_column("name").unwrap()
}
fn profit_margin(&self) -> Arc<Column> {
fn profit_margin(&self) -> Arc<PgValueColumn> {
self.get_column("profit_margin").unwrap()
}

Expand Down
10 changes: 5 additions & 5 deletions bakery_model/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ impl Client {
}

pub trait ClientTable: SqlTable {
fn name(&self) -> Arc<Column> {
fn name(&self) -> Arc<PgValueColumn> {
self.get_column("name").unwrap()
}
fn email(&self) -> Arc<Column> {
fn email(&self) -> Arc<PgValueColumn> {
self.get_column("email").unwrap()
}
fn contact_details(&self) -> Arc<Column> {
fn contact_details(&self) -> Arc<PgValueColumn> {
self.get_column("contact_details").unwrap()
}
fn bakery_id(&self) -> Arc<Column> {
fn bakery_id(&self) -> Arc<PgValueColumn> {
self.get_column("bakery_id").unwrap()
}
fn is_paying_client(&self) -> Arc<Column> {
fn is_paying_client(&self) -> Arc<PgValueColumn> {
self.get_column("is_paying_client").unwrap()
}

Expand Down
6 changes: 3 additions & 3 deletions bakery_model/src/lineitem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ pub trait LineItemTable: AnyTable {
fn as_table(&self) -> &Table<Postgres, LineItem> {
self.as_any_ref().downcast_ref().unwrap()
}
fn quantity(&self) -> Arc<Column> {
fn quantity(&self) -> Arc<PgValueColumn> {
self.get_column("quantity").unwrap()
}
fn order_id(&self) -> Arc<Column> {
fn order_id(&self) -> Arc<PgValueColumn> {
self.get_column("order_id").unwrap()
}
fn product_id(&self) -> Arc<Column> {
fn product_id(&self) -> Arc<PgValueColumn> {
self.get_column("product_id").unwrap()
}
fn total(&self) -> Box<dyn SqlField> {
Expand Down
4 changes: 2 additions & 2 deletions bakery_model/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ impl Order {
}

pub trait OrderTable: SqlTable {
fn client_id(&self) -> Arc<Column> {
fn client_id(&self) -> Arc<PgValueColumn> {
Order::table().get_column("client_id").unwrap()
}
fn product_id(&self) -> Arc<Column> {
fn product_id(&self) -> Arc<PgValueColumn> {
Order::table().get_column("product_id").unwrap()
}

Expand Down
8 changes: 4 additions & 4 deletions bakery_model/src/product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ impl Product {
pub trait ProductTable: AnyTable {
fn with_inventory(self) -> Table<Postgres, ProductInventory>;

fn name(&self) -> Arc<Column> {
fn name(&self) -> Arc<PgValueColumn> {
self.get_column("name").unwrap()
}
fn price(&self) -> Arc<Column> {
fn price(&self) -> Arc<PgValueColumn> {
self.get_column("price").unwrap()
}
fn bakery_id(&self) -> Arc<Column> {
fn bakery_id(&self) -> Arc<PgValueColumn> {
self.get_column("bakery_id").unwrap()
}

Expand Down Expand Up @@ -77,7 +77,7 @@ pub trait ProductInventoryTable: RelatedTable<Postgres> {
j.table().clone()
}

fn stock(&self) -> Arc<Column> {
fn stock(&self) -> Arc<PgValueColumn> {
let j = self.j_stock();
j.get_column("stock").unwrap()
}
Expand Down
8 changes: 7 additions & 1 deletion vantage/src/datasource/sqlx/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::Arc;

use anyhow::{anyhow, Context, Result};
use serde_json::{Map, Value};
use sqlx::{postgres::PgArguments, Execute};
use sqlx::postgres::PgArguments;

use crate::{
prelude::Query,
Expand All @@ -12,6 +12,12 @@ use crate::{

use super::sql_to_json::row_to_json;

mod value_column;
pub use value_column::PgValueColumn;

// mod uuid_column;
// pub use uuid_column::PgUuidColumn;

#[derive(Debug, Clone)]
pub struct Postgres {
pub pool: Arc<sqlx::PgPool>,
Expand Down
173 changes: 173 additions & 0 deletions vantage/src/datasource/sqlx/postgres/uuid_column.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::Weak;

use crate::expr;
use crate::expr_arc;
use crate::prelude::column::SqlColumn;
use crate::prelude::Column;
use crate::prelude::SqlTable;
use crate::sql::chunk::Chunk;
use crate::sql::Condition;
use crate::sql::Operations;
use crate::sql::WrapArc;
use crate::sql::{Expression, ExpressionArc};
use crate::traits::column::SqlField;

#[derive(Debug, Clone)]
pub struct PgUuidColumn {
name: String,
table_alias: Option<Weak<RwLock<Option<String>>>>,
column_alias: Option<String>,
}

impl PgUuidColumn {
pub fn new(name: &str) -> PgUuidColumn {
PgUuidColumn {
name: name.to_string(),
table_alias: None,
column_alias: None,
}
}
pub fn with_alias(mut self, alias: &str) -> Self {
self.set_alias(alias.to_string());
self
}
}

impl SqlColumn for PgUuidColumn {
fn name(&self) -> String {
self.name.clone()
}
fn name_with_table(&self) -> String {
match self.get_table_alias() {
Some(table_alias) => format!("{}.{}", table_alias, self.name),
None => format!("{}", self.name),
}
}
fn set_table_alias(&mut self, table_alias: Weak<RwLock<Option<String>>>) {
self.table_alias = Some(table_alias);
}
fn get_table_alias(&self) -> Option<String> {
let weak_ref = self.table_alias.as_ref()?;
let arc_ref = weak_ref.upgrade()?;
let guard = arc_ref.read().ok()?;
guard.clone()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_alias(&mut self, alias: String) {
self.column_alias = Some(alias);
}

fn get_alias(&self) -> Option<String> {
self.column_alias.clone()
}
}

impl Chunk for PgUuidColumn {
fn render_chunk(&self) -> Expression {
Arc::new(self.clone()).render_chunk()
}
}
impl Operations for PgUuidColumn {}

impl Operations for Arc<PgUuidColumn> {
fn eq(&self, other: &impl Chunk) -> Condition {
let column: Arc<Column> = Arc::new(Box::new((**self).clone()) as Box<dyn SqlColumn>);

Condition::from_field(column, "=", WrapArc::wrap_arc(other.render_chunk()))
}

// fn add(&self, other: impl SqlChunk) -> Expression {
// let chunk = other.render_chunk();
// expr_arc!(format!("{} + {{}}", &self.name), chunk).render_chunk()
// }
}

impl Chunk for Arc<PgUuidColumn> {
fn render_chunk(&self) -> Expression {
expr!(self.name_with_table())
}
}

impl SqlField for Arc<PgUuidColumn> {
fn render_column(&self, mut alias: Option<&str>) -> Expression {
// If the alias is the same as the field name, we don't need to render it
if alias.is_some() && alias.unwrap() == self.name {
alias = None;
}

let alias = alias.or(self.column_alias.as_deref());

if let Some(alias) = alias {
expr!(format!(
"{} AS {}",
self.name_with_table(),
alias.to_string()
))
} else {
expr!(self.name_with_table())
}
}
fn calculated(&self) -> bool {
false
}
}

impl From<String> for PgUuidColumn {
fn from(name: String) -> Self {
PgUuidColumn {
name,
table_alias: None,
column_alias: None,
}
}
}

impl From<&str> for PgUuidColumn {
fn from(name: &str) -> Self {
name.to_string().into()
}
}

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

#[test]
fn test_field() {
let field = Arc::new(PgUuidColumn::new("id"));
let (sql, params) = field.render_chunk().split();

assert_eq!(sql, "id");
assert_eq!(params.len(), 0);

let (sql, params) = field.render_column(Some("id")).render_chunk().split();
assert_eq!(sql, "id");
assert_eq!(params.len(), 0);

let (sql, params) = &field.render_column(Some("id_alias")).render_chunk().split();
assert_eq!(sql, "id AS id_alias");
assert_eq!(params.len(), 0);
}

#[test]
fn test_eq() {
let field = Arc::new(PgUuidColumn::new("id"));
let (sql, params) = field.eq(&1).render_chunk().split();

assert_eq!(sql, "(id = {})");
assert_eq!(params.len(), 1);
assert_eq!(params[0], 1);

let f_age = Arc::new(PgUuidColumn::new("age").with_alias("u"));
let (sql, params) = f_age.add(5).eq(&18).render_chunk().split();

assert_eq!(sql, "((u.age) + ({}) = {})");
assert_eq!(params.len(), 2);
assert_eq!(params[0], 5);
assert_eq!(params[1], 18);
}
}
Loading

0 comments on commit e539119

Please sign in to comment.