use chrono::{DateTime, Utc}; use ext_model_migration::{Alias, Expr, IntoIden, Quote, SelectExpr, SelectStatement, TableRef}; use magnetar_sdk::types::SpanFilter; use sea_orm::{ ColumnTrait, Condition, ConnectionTrait, Cursor, DbErr, DynIden, EntityTrait, FromQueryResult, Iden, IntoIdentity, Iterable, JoinType, QueryTrait, RelationDef, RelationTrait, Select, SelectModel, SelectorTrait, }; use serde::{Deserialize, Serialize}; use std::fmt::Write; #[derive(Clone)] pub enum MagIden { DynIden(DynIden), Alias(Alias), } impl MagIden { pub fn alias(alias: &str) -> MagIden { Self::Alias(Alias::new(alias)) } } impl From for MagIden { fn from(value: DynIden) -> Self { Self::DynIden(value) } } impl Iden for MagIden { fn prepare(&self, s: &mut dyn Write, q: Quote) { match self { MagIden::DynIden(di) => di.prepare(s, q), MagIden::Alias(a) => a.prepare(s, q), } } fn quoted(&self, q: Quote) -> String { match self { MagIden::DynIden(di) => di.quoted(q), MagIden::Alias(a) => a.quoted(q), } } fn to_string(&self) -> String { match self { MagIden::DynIden(di) => di.to_string(), MagIden::Alias(a) => a.to_string(), } } fn unquoted(&self, s: &mut dyn Write) { match self { MagIden::DynIden(di) => di.unquoted(s), MagIden::Alias(a) => a.unquoted(s), } } } pub(crate) trait SelectColumnsExt { fn add_aliased_columns(&mut self, alias: &MagIden) -> &mut Self; fn join_columns(&mut self, join: JoinType, rel: RelationDef, alias: &MagIden) -> &mut Self; fn join_columns_on( &mut self, join: JoinType, rel: RelationDef, alias: &MagIden, condition: Condition, ) -> &mut Self; } pub(crate) fn join_columns_default(rel: RelationDef) -> Condition { let tbl_id = |tbl: &TableRef| { match tbl { TableRef::Table(id) => id, TableRef::TableAlias(_, id) => id, _ => unreachable!(), } .clone() }; let mut cond = Condition::all(); for (owner_key, foreign_key) in rel.from_col.into_iter().zip(rel.to_col.into_iter()) { cond = cond.add( Expr::col((tbl_id(&rel.from_tbl), owner_key)) .equals((tbl_id(&rel.to_tbl), foreign_key)), ); } cond } impl SelectColumnsExt for SelectStatement { fn add_aliased_columns(&mut self, iden: &MagIden) -> &mut Self { for col in T::Column::iter() { let column: &T::Column = &col; let alias = iden.join(&col); let column_ref = iden.col(column.as_column_ref().1); self.expr(SelectExpr { expr: col.select_as(column_ref), alias: Some(alias.into_iden()), window: None, }); } self } fn join_columns(&mut self, join: JoinType, mut rel: RelationDef, alias: &MagIden) -> &mut Self { rel.to_tbl = rel.to_tbl.alias(alias.clone().into_iden()); self.join(join, rel.to_tbl.clone(), join_columns_default(rel)); self } fn join_columns_on( &mut self, join: JoinType, mut rel: RelationDef, alias: &MagIden, condition: Condition, ) -> &mut Self { rel.to_tbl = rel.to_tbl.alias(alias.clone().into_iden()); self.join(join, rel.to_tbl.clone(), condition); self } } pub trait AliasColumnExt { fn col_tuple(&self, col: impl IntoIden) -> (MagIden, MagIden); fn col(&self, col: impl IntoIden) -> Expr; } impl AliasColumnExt for T { fn col_tuple(&self, col: impl IntoIden) -> (MagIden, MagIden) { ( MagIden::alias(&self.to_string()), MagIden::DynIden(col.into_iden()), ) } fn col(&self, col: impl IntoIden) -> Expr { Expr::col(self.col_tuple(col)) } } pub trait AliasSuffixExt { fn join(&self, suffix: &dyn Iden) -> MagIden; fn join_str(&self, suffix: &str) -> MagIden; fn join_as_str(&self, suffix: &dyn Iden) -> String; fn join_str_as_str(&self, suffix: &str) -> String; } impl AliasSuffixExt for T { fn join(&self, suffix: &dyn Iden) -> MagIden { MagIden::alias(&self.join_as_str(suffix)) } fn join_str(&self, suffix: &str) -> MagIden { MagIden::alias(&self.join_str_as_str(suffix)) } fn join_as_str(&self, suffix: &dyn Iden) -> String { format!("{}{}", self.to_string(), suffix.to_string()) } fn join_str_as_str(&self, suffix: &str) -> String { format!("{}{}", self.to_string(), suffix) } } pub(crate) trait EntityPrefixExt { fn base_prefix_str(&self) -> String; fn base_prefix(&self) -> MagIden; } impl EntityPrefixExt for T { fn base_prefix_str(&self) -> String { format!("{}.", self.table_name()) } fn base_prefix(&self) -> MagIden { MagIden::alias(&self.base_prefix_str()) } } pub trait AliasSourceExt { fn with_from_alias(&self, alias: &MagIden) -> RelationDef; fn with_to_alias(&self, alias: &MagIden) -> RelationDef; fn with_alias(&self, from: &MagIden, to: &MagIden) -> RelationDef; } impl AliasSourceExt for T { fn with_from_alias(&self, alias: &MagIden) -> RelationDef { let mut def = self.def(); def.from_tbl = def.from_tbl.alias(Alias::new(alias.clone().to_string())); def } fn with_to_alias(&self, alias: &MagIden) -> RelationDef { let mut def = self.def(); def.to_tbl = def.to_tbl.alias(Alias::new(alias.clone().to_string())); def } fn with_alias(&self, from: &MagIden, to: &MagIden) -> RelationDef { let mut def = self.def(); def.from_tbl = def.from_tbl.alias(from.clone().into_iden()); def.to_tbl = def.to_tbl.alias(to.clone().into_iden()); def } } pub trait CursorPaginationExt { type Selector: SelectorTrait + Send + Sync; fn cursor_by_columns_and_span( self, cursor_prefix_alias: Option, order_columns: C, pagination: &SpanFilter, limit: Option, ) -> Cursor where C: IntoIdentity; async fn get_paginated_model( self, db: &T, cursor_prefix_alias: Option, columns: C, curr: &SpanFilter, prev: &mut Option, next: &mut Option, limit: u64, ) -> Result, DbErr> where M: FromQueryResult + ModelPagination, C: IntoIdentity, T: ConnectionTrait; } impl CursorPaginationExt for Select where E: EntityTrait, M: FromQueryResult + Sized + Send + Sync, { type Selector = SelectModel; fn cursor_by_columns_and_span( self, cursor_prefix_alias: Option, order_columns: C, pagination: &SpanFilter, limit: Option, ) -> Cursor where C: IntoIdentity, { let mut cursor = Cursor::new( self.into_query(), cursor_prefix_alias.map_or_else(|| E::default().into_iden(), MagIden::into_iden), order_columns, ); if let Some(start) = pagination.start() { cursor.after(start); } if let Some(end) = pagination.end() { cursor.before(end); } if let Some(lim) = limit { if pagination.is_desc() { cursor.last(lim); } else { cursor.first(lim); } } cursor } async fn get_paginated_model( self, db: &T, cursor_prefix_alias: Option, columns: C, curr: &SpanFilter, prev: &mut Option, next: &mut Option, limit: u64, ) -> Result, DbErr> where Q: FromQueryResult + ModelPagination, C: IntoIdentity, T: ConnectionTrait, { let mut result = self .cursor_by_columns_and_span(cursor_prefix_alias, columns, curr, Some(limit + 1)) .into_model::() .all(db) .await?; if curr.is_desc() { result.reverse(); } if result.len() > limit as usize { result.pop(); let last = result.last(); *next = last.and_then(|c| curr.next(c.time(), c.id())); } let first = result.first(); *prev = first.and_then(|c| curr.prev(c.time(), c.id())); Ok(result) } } pub trait ModelPagination { fn id(&self) -> &str; fn time(&self) -> DateTime; } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IdShape { pub id: String, }