use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use super::pool::DbPool;
use super::query::QueryBuilder;
use super::repository::placeholder;
use super::{DbError, Model, ModelRow, Value};
use crate::pagination::Page;
pub const IN_BATCH: usize = 500;
const OWNER_KEY: &str = "rws_owner_key";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RelationKind {
HasMany,
HasOne,
BelongsTo,
BelongsToMany,
}
pub struct Relation<O, R> {
kind: RelationKind,
foreign_key: &'static str,
join: Option<(&'static str, &'static str)>,
_models: PhantomData<fn() -> (O, R)>,
}
impl<O, R> Clone for Relation<O, R> {
fn clone(&self) -> Self {
*self
}
}
impl<O, R> Copy for Relation<O, R> {}
impl<O, R> std::fmt::Debug for Relation<O, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Relation")
.field("kind", &self.kind)
.field("foreign_key", &self.foreign_key)
.field("join", &self.join)
.finish()
}
}
impl<O: Model, R: Model> Relation<O, R> {
pub fn has_many(foreign_key: &'static str) -> Self {
Relation { kind: RelationKind::HasMany, foreign_key, join: None, _models: PhantomData }
}
pub fn has_one(foreign_key: &'static str) -> Self {
Relation { kind: RelationKind::HasOne, foreign_key, join: None, _models: PhantomData }
}
pub fn belongs_to(foreign_key: &'static str) -> Self {
Relation { kind: RelationKind::BelongsTo, foreign_key, join: None, _models: PhantomData }
}
pub fn belongs_to_many(join_table: &'static str, owner_key: &'static str, related_key: &'static str) -> Self {
Relation { kind: RelationKind::BelongsToMany, foreign_key: owner_key, join: Some((join_table, related_key)), _models: PhantomData }
}
pub fn kind(&self) -> RelationKind {
self.kind
}
pub fn foreign_key(&self) -> &'static str {
self.foreign_key
}
pub fn join_table(&self) -> Option<(&'static str, &'static str)> {
self.join
}
fn key(&self) -> String {
format!("{:?}:{}:{}:{:?}", self.kind, R::table_name(), self.foreign_key, self.join)
}
pub async fn load(&self, pool: &DbPool, owner: &O) -> Result<Vec<R>, DbError>
where
R: Clone,
{
Ok(self.load_for(pool, std::slice::from_ref(owner)).await?.pop().unwrap_or_default())
}
pub async fn load_for(&self, pool: &DbPool, owners: &[O]) -> Result<Vec<Vec<R>>, DbError>
where
R: Clone,
{
let owner_keys: Vec<Value> = match self.kind {
RelationKind::BelongsTo => {
let mut keys = Vec::with_capacity(owners.len());
for owner in owners {
let value = owner.to_values().into_iter().find(|(c, _)| c.eq_ignore_ascii_case(self.foreign_key)).map(|(_, v)| v);
keys.push(value.ok_or_else(|| {
DbError::new(format!("belongs_to: {} has no column {}", O::table_name(), self.foreign_key))
})?);
}
keys
}
_ => owners.iter().map(|o| o.primary_key_value()).collect(),
};
let distinct = distinct_keys(&owner_keys);
let pk = R::primary_key_name();
let (rows, match_column) = match self.kind {
RelationKind::HasMany | RelationKind::HasOne => {
let live = live_and::<R>("");
let rows = fetch_in(pool, &distinct, |list| {
format!("SELECT * FROM {} WHERE {} IN ({list}){live} ORDER BY {pk}", R::table_name(), self.foreign_key)
})
.await?;
(rows, self.foreign_key)
}
RelationKind::BelongsTo => {
let live = live_and::<R>("");
let rows = fetch_in(pool, &distinct, |list| {
format!("SELECT * FROM {} WHERE {pk} IN ({list}){live} ORDER BY {pk}", R::table_name())
})
.await?;
(rows, pk)
}
RelationKind::BelongsToMany => {
let (join, related_key) = self.join.unwrap_or(("", ""));
let live = live_and::<R>("r.");
let rows = fetch_in(pool, &distinct, |list| {
format!(
"SELECT r.*, j.{owner} AS {OWNER_KEY} FROM {} r INNER JOIN {join} j ON j.{related_key} = r.{pk} \
WHERE j.{owner} IN ({list}){live} ORDER BY r.{pk}",
R::table_name(),
owner = self.foreign_key,
)
})
.await?;
(rows, OWNER_KEY)
}
};
let mut by_key: HashMap<String, Vec<R>> = HashMap::new();
for row in &rows {
let Some(key) = column(row, match_column).and_then(key_of) else { continue };
by_key.entry(key).or_default().push(R::from_row(row)?);
}
let one = matches!(self.kind, RelationKind::HasOne | RelationKind::BelongsTo);
Ok(owner_keys
.iter()
.map(|k| {
let mut related = key_of(k.clone()).and_then(|k| by_key.get(&k)).cloned().unwrap_or_default();
if one {
related.truncate(1);
}
related
})
.collect())
}
}
fn live_and<T: Model>(prefix: &str) -> String {
T::soft_delete_column().map(|c| format!(" AND {prefix}{c} IS NULL")).unwrap_or_default()
}
fn column(row: &ModelRow, name: &str) -> Option<Value> {
row.columns.iter().find(|(c, _)| c.eq_ignore_ascii_case(name)).map(|(_, v)| v.clone())
}
pub(crate) fn key_of(value: Value) -> Option<String> {
match value {
Value::Null => None,
Value::Bool(b) => Some(format!("i{}", b as i64)),
Value::Int(n) => Some(format!("i{n}")),
Value::Float(f) => Some(format!("f{f}")),
Value::Text(s) => Some(format!("t{s}")),
Value::Bytes(b) => Some(format!("b{}", b.iter().map(|x| format!("{x:02x}")).collect::<String>())),
}
}
pub(crate) fn distinct_keys(values: &[Value]) -> Vec<Value> {
let mut seen = std::collections::HashSet::new();
values.iter().filter(|v| key_of((*v).clone()).is_some_and(|k| seen.insert(k))).cloned().collect()
}
async fn fetch_in(pool: &DbPool, keys: &[Value], sql: impl Fn(String) -> String) -> Result<Vec<ModelRow>, DbError> {
let backend = pool.backend();
let mut rows = Vec::new();
for batch in keys.chunks(IN_BATCH) {
let list = (1..=batch.len()).map(|i| placeholder(backend, i)).collect::<Vec<_>>().join(", ");
rows.extend(pool.query_rows(&sql(list), batch).await?);
}
Ok(rows)
}
pub struct Loaded<T> {
model: T,
relations: Vec<(String, Box<dyn Any + Send + Sync>)>,
}
impl<T: Model> Loaded<T> {
pub fn model(&self) -> &T {
&self.model
}
pub fn into_inner(self) -> T {
self.model
}
pub fn related<R: Model + 'static>(&self, relation: &Relation<T, R>) -> &[R] {
self.get(relation).map(Vec::as_slice).unwrap_or(&[])
}
pub fn one<R: Model + 'static>(&self, relation: &Relation<T, R>) -> Option<&R> {
self.related(relation).first()
}
pub fn is_loaded<R: Model + 'static>(&self, relation: &Relation<T, R>) -> bool {
self.get(relation).is_some()
}
pub fn take<R: Model + 'static>(&mut self, relation: &Relation<T, R>) -> Vec<R> {
let key = relation.key();
match self.relations.iter().position(|(k, _)| *k == key) {
Some(i) => self.relations.remove(i).1.downcast::<Vec<R>>().map(|b| *b).unwrap_or_default(),
None => Vec::new(),
}
}
fn get<R: Model + 'static>(&self, relation: &Relation<T, R>) -> Option<&Vec<R>> {
let key = relation.key();
self.relations.iter().find(|(k, _)| *k == key).and_then(|(_, v)| v.downcast_ref::<Vec<R>>())
}
}
impl<T> std::ops::Deref for Loaded<T> {
type Target = T;
fn deref(&self) -> &T {
&self.model
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for Loaded<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Loaded")
.field("model", &self.model)
.field("relations", &self.relations.iter().map(|(k, _)| k.as_str()).collect::<Vec<_>>())
.finish()
}
}
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
type Erased = Box<dyn Any + Send + Sync>;
trait Loader<O>: Send + Sync {
fn key(&self) -> String;
fn load<'a>(&'a self, pool: &'a DbPool, owners: &'a [O]) -> BoxFuture<'a, Result<Vec<Erased>, DbError>>;
}
impl<O: Model + 'static, R: Model + Clone + 'static> Loader<O> for Relation<O, R> {
fn key(&self) -> String {
Relation::key(self)
}
fn load<'a>(&'a self, pool: &'a DbPool, owners: &'a [O]) -> BoxFuture<'a, Result<Vec<Erased>, DbError>> {
Box::pin(async move {
let related = self.load_for(pool, owners).await?;
Ok(related.into_iter().map(|r| Box::new(r) as Erased).collect())
})
}
}
pub struct EagerQuery<'a, T: Model> {
query: QueryBuilder<'a, T>,
loaders: Vec<Box<dyn Loader<T>>>,
}
impl<'a, T: Model + 'static> EagerQuery<'a, T> {
pub(crate) fn new(query: QueryBuilder<'a, T>) -> Self {
EagerQuery { query, loaders: Vec::new() }
}
pub fn with<R: Model + Clone + 'static>(mut self, relation: Relation<T, R>) -> Self {
let key = relation.key();
if !self.loaders.iter().any(|l| l.key() == key) {
self.loaders.push(Box::new(relation));
}
self
}
pub async fn fetch_all(self) -> Result<Vec<Loaded<T>>, DbError> {
let pool = self.query.pool();
let models = self.query.fetch_all().await?;
attach(pool, models, &self.loaders).await
}
pub async fn fetch_one(self) -> Result<Option<Loaded<T>>, DbError> {
let pool = self.query.pool();
let model = self.query.fetch_one().await?;
Ok(attach(pool, model.into_iter().collect(), &self.loaders).await?.pop())
}
pub async fn paginate(self, page: u64, per_page: u64) -> Result<Page<Loaded<T>>, DbError> {
let pool = self.query.pool();
let found = self.query.paginate(page, per_page).await?;
let (page, per_page, total) = (found.page, found.per_page, found.total_items);
let items = attach(pool, found.items, &self.loaders).await?;
Ok(Page::new(items, page, per_page, total))
}
}
async fn attach<T: Model>(pool: &DbPool, models: Vec<T>, loaders: &[Box<dyn Loader<T>>]) -> Result<Vec<Loaded<T>>, DbError> {
let mut relations: Vec<Vec<(String, Erased)>> = models.iter().map(|_| Vec::with_capacity(loaders.len())).collect();
if !models.is_empty() {
for loader in loaders {
let key = loader.key();
for (slot, related) in relations.iter_mut().zip(loader.load(pool, &models).await?) {
slot.push((key.clone(), related));
}
}
}
Ok(models.into_iter().zip(relations).map(|(model, relations)| Loaded { model, relations }).collect())
}