use turso_sql::{Expr, ForeignKey, ForeignKeyAction, JoinType};
use super::base_entity::EntityTrait;
use super::column::ColumnTrait;
use super::iden::{IdenStatic, Iterable};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RelationType {
HasOne,
HasMany,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RelationDef {
pub rel_type: RelationType,
pub from_tbl: &'static str,
pub to_tbl: &'static str,
pub from_col: Vec<&'static str>,
pub to_col: Vec<&'static str>,
pub is_owner: bool,
pub on_delete: Option<ForeignKeyAction>,
pub on_update: Option<ForeignKeyAction>,
pub skip_fk: bool,
}
impl RelationDef {
#[must_use]
pub fn rev(self) -> Self {
Self {
rel_type: self.rel_type,
from_tbl: self.to_tbl,
to_tbl: self.from_tbl,
from_col: self.to_col,
to_col: self.from_col,
is_owner: !self.is_owner,
on_delete: self.on_delete,
on_update: self.on_update,
skip_fk: self.skip_fk,
}
}
pub fn join_condition(&self) -> Expr {
self.join_condition_refs(self.from_tbl, self.to_tbl)
}
pub fn join_condition_refs(&self, from_ref: &str, to_ref: &str) -> Expr {
let mut cond: Option<Expr> = None;
for (f, t) in self.from_col.iter().zip(&self.to_col) {
let e = Expr::col((from_ref.to_owned(), *f)).eq(Expr::col((to_ref.to_owned(), *t)));
cond = Some(match cond {
Some(c) => c.and(e),
None => e,
});
}
cond.unwrap_or_else(|| Expr::val(true))
}
pub fn foreign_key(&self) -> Option<ForeignKey> {
if !self.is_owner || self.skip_fk {
return None;
}
let mut fk = ForeignKey::new(
self.from_col.iter().copied(),
self.to_tbl,
self.to_col.iter().copied(),
);
if let Some(a) = self.on_delete {
fk = fk.on_delete(a);
}
if let Some(a) = self.on_update {
fk = fk.on_update(a);
}
Some(fk)
}
pub fn default_join(&self) -> JoinType {
JoinType::Left
}
}
pub trait RelationTrait: Iterable + std::fmt::Debug {
fn def(&self) -> RelationDef;
}
pub trait Related<R: EntityTrait>: EntityTrait {
fn to() -> RelationDef;
fn via() -> Option<RelationDef> {
None
}
}
pub trait Linked {
type FromEntity: EntityTrait;
type ToEntity: EntityTrait;
fn link(&self) -> Vec<RelationDef>;
}
pub(crate) fn column_of<E: EntityTrait>(name: &str) -> Option<E::Column> {
E::Column::iter().find(|c| c.as_str() == name)
}
#[derive(Debug)]
pub struct RelationBuilder<E: EntityTrait, R: EntityTrait> {
def: RelationDef,
_e: std::marker::PhantomData<(E, R)>,
}
impl<E: EntityTrait, R: EntityTrait> RelationBuilder<E, R> {
pub(crate) fn new(rel_type: RelationType, is_owner: bool) -> Self {
Self {
def: RelationDef {
rel_type,
from_tbl: E::TABLE_NAME,
to_tbl: R::TABLE_NAME,
from_col: Vec::new(),
to_col: Vec::new(),
is_owner,
on_delete: None,
on_update: None,
skip_fk: false,
},
_e: std::marker::PhantomData,
}
}
#[must_use]
pub fn from<C: ColumnTrait>(mut self, column: C) -> Self {
self.def.from_col.push(column.as_str());
self
}
#[must_use]
pub fn to<C: ColumnTrait>(mut self, column: C) -> Self {
self.def.to_col.push(column.as_str());
self
}
#[must_use]
pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
self.def.on_delete = Some(action);
self
}
#[must_use]
pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
self.def.on_update = Some(action);
self
}
#[must_use]
pub fn skip_fk(mut self) -> Self {
self.def.skip_fk = true;
self
}
}
impl<E: EntityTrait, R: EntityTrait> From<RelationBuilder<E, R>> for RelationDef {
fn from(b: RelationBuilder<E, R>) -> Self {
b.def
}
}