use chrono::{DateTime, Utc}; use lsor_core::{ column::{col, ColumnName}, driver::{Driver, PushPrql}, filter::Filterable, row::{upsert_into, IsPk, Row}, sort::{Order, Sorting}, table::table, }; use sqlx::{postgres::PgRow, FromRow, Type}; use uuid::Uuid; pub struct Metadata { pub name: Option, pub description: Option, pub tags: Vec, } pub enum MetadataFilter { Name( as Filterable>::Filter), Description( as Filterable>::Filter), Tags( as Filterable>::Filter), } pub enum MetadataSort { Name(Order), Description(Order), } impl PushPrql for MetadataSort { fn push_to_driver(&self, driver: &mut Driver) { match self { Self::Name(_) => { col("name").push_to_driver(driver); } Self::Description(_) => { col("description").push_to_driver(driver); } } } } impl Sorting for MetadataSort { fn order(&self) -> Order { match self { Self::Name(order) => *order, Self::Description(order) => *order, } } fn flip(&self) -> impl Sorting { match self { Self::Name(order) => Self::Name(order.flip()), Self::Description(order) => Self::Description(order.flip()), } } fn push_to_driver_with_order(&self, driver: &mut Driver) { match self { Self::Name(order) => { order.push_to_driver(driver); col("name").push_to_driver(driver); } Self::Description(order) => { order.push_to_driver(driver); col("description").push_to_driver(driver); } } } } impl Row for Metadata { fn column_names() -> impl Iterator { (Some((col(stringify!(name)), false)).into_iter()) .chain(Some((col(stringify!(description)), false)).into_iter()) .chain(Some((col(stringify!(tags)), false)).into_iter()) } fn push_column_values(&self, driver: &mut Driver) { self.name.push_to_driver(driver); driver.push(", "); self.description.push_to_driver(driver); driver.push(", "); self.tags.push_to_driver(driver); } } impl<'r> FromRow<'r, PgRow> for Metadata { fn from_row(row: &'r PgRow) -> Result { use sqlx::Row; Ok(Self { name: row.try_get("name")?, description: row.try_get("description")?, tags: row.try_get("tags")?, }) } } impl Filterable for Metadata { type Filter = MetadataFilter; } #[derive(Type)] pub enum AccountTier { Free, Pro, Startup, Enterprise, } impl PushPrql for AccountTier { fn push_to_driver(&self, driver: &mut Driver) { driver.push_bind(self); } } pub enum AccountTierFilter { Eq(AccountTier), Ne(AccountTier), } impl Filterable for AccountTier { type Filter = AccountTierFilter; } pub struct Account { pub id: Uuid, pub created_at: DateTime, pub updated_at: DateTime, pub deleted_at: Option>, pub tier: AccountTier, pub metadata: Metadata, } pub struct AccountFilter { pub id: ::Filter, pub created_at: as Filterable>::Filter, pub updated_at: as Filterable>::Filter, pub deleted_at: > as Filterable>::Filter, pub tier: ::Filter, pub metadata: ::Filter, } impl Row for Account { fn column_names() -> impl Iterator { (Some((col(stringify!(id)), true)).into_iter()) .chain(Some((col(stringify!(created_at)), false)).into_iter()) .chain(Some((col(stringify!(updated_at)), false)).into_iter()) .chain(Some((col(stringify!(deleted_at)), false)).into_iter()) .chain(Some((col(stringify!(tier)), false)).into_iter()) .chain(Metadata::column_names()) } fn push_column_values(&self, driver: &mut Driver) { self.id.push_to_driver(driver); driver.push(", "); self.created_at.push_to_driver(driver); driver.push(", "); self.updated_at.push_to_driver(driver); driver.push(", "); self.deleted_at.push_to_driver(driver); driver.push(", "); self.tier.push_to_driver(driver); driver.push(", "); self.metadata.push_column_values(driver); } } impl Row for &Account { fn column_names() -> impl Iterator { Account::column_names() } fn push_column_values(&self, driver: &mut Driver) { (*self).push_column_values(driver); } } impl<'r> FromRow<'r, PgRow> for Account { fn from_row(row: &'r PgRow) -> Result { use sqlx::Row; Ok(Self { id: row.try_get("id")?, created_at: row.try_get("created_at")?, updated_at: row.try_get("updated_at")?, deleted_at: row.try_get("deleted_at")?, tier: row.try_get("tier")?, metadata: Metadata::from_row(row)?, }) } } impl Filterable for Account { type Filter = AccountFilter; } #[test] fn test_column_names() { let column_names = Account::column_names().collect::>(); assert_eq!( column_names, vec![ (col("id"), true), (col("created_at"), false), (col("updated_at"), false), (col("deleted_at"), false), (col("tier"), false), (col("name"), false), (col("description"), false), (col("tags"), false), ] ); } #[test] fn test_column_values() { let account = Account { id: Uuid::new_v4(), created_at: Utc::now(), updated_at: Utc::now(), deleted_at: None, tier: AccountTier::Free, metadata: Metadata { name: Some("name".to_string()), description: Some("description".to_string()), tags: vec!["tag".to_string()], }, }; let mut driver = Driver::new(); account.push_column_values(&mut driver); assert_eq!(driver.prql(), "$1, $2, $3, $4, $5, $6, $7, $8"); } #[test] fn test_upsert() { let account = Account { id: Uuid::new_v4(), created_at: Utc::now(), updated_at: Utc::now(), deleted_at: None, tier: AccountTier::Free, metadata: Metadata { name: Some("name".to_string()), description: Some("description".to_string()), tags: vec!["tag".to_string()], }, }; let mut driver = Driver::new(); upsert_into(table("accounts"), &account).push_to_driver(&mut driver); assert_eq!(driver.prql(), "INSERT INTO accounts (id, created_at, updated_at, deleted_at, tier, name, description, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (id) DO UPDATE SET (created_at, updated_at, deleted_at, tier, name, description, tags) = ($2, $3, $4, $5, $6, $7, $8)"); }