339 lines
8.8 KiB
Rust
339 lines
8.8 KiB
Rust
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<DynIden> 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<T: EntityTrait>(&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<T: EntityTrait>(&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<T: Iden + ?Sized> 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<T: Iden + ?Sized> 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<T: EntityTrait> 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<T: RelationTrait> 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<E> {
|
|
type Selector: SelectorTrait + Send + Sync;
|
|
|
|
fn cursor_by_columns_and_span<C>(
|
|
self,
|
|
cursor_prefix_alias: Option<MagIden>,
|
|
order_columns: C,
|
|
pagination: &SpanFilter,
|
|
limit: Option<u64>,
|
|
) -> Cursor<Self::Selector>
|
|
where
|
|
C: IntoIdentity;
|
|
|
|
async fn get_paginated_model<M, C, T>(
|
|
self,
|
|
db: &T,
|
|
cursor_prefix_alias: Option<MagIden>,
|
|
columns: C,
|
|
curr: &SpanFilter,
|
|
prev: &mut Option<SpanFilter>,
|
|
next: &mut Option<SpanFilter>,
|
|
limit: u64,
|
|
) -> Result<Vec<M>, DbErr>
|
|
where
|
|
M: FromQueryResult + ModelPagination,
|
|
C: IntoIdentity,
|
|
T: ConnectionTrait;
|
|
}
|
|
|
|
impl<E, M> CursorPaginationExt<E> for Select<E>
|
|
where
|
|
E: EntityTrait<Model = M>,
|
|
M: FromQueryResult + Sized + Send + Sync,
|
|
{
|
|
type Selector = SelectModel<M>;
|
|
|
|
fn cursor_by_columns_and_span<C>(
|
|
self,
|
|
cursor_prefix_alias: Option<MagIden>,
|
|
order_columns: C,
|
|
pagination: &SpanFilter,
|
|
limit: Option<u64>,
|
|
) -> Cursor<Self::Selector>
|
|
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<Q, C, T>(
|
|
self,
|
|
db: &T,
|
|
cursor_prefix_alias: Option<MagIden>,
|
|
columns: C,
|
|
curr: &SpanFilter,
|
|
prev: &mut Option<SpanFilter>,
|
|
next: &mut Option<SpanFilter>,
|
|
limit: u64,
|
|
) -> Result<Vec<Q>, 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::<Q>()
|
|
.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<Utc>;
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct IdShape {
|
|
pub id: String,
|
|
}
|