use std::collections::HashMap;
#[cfg(all(feature = "active", feature = "postgres"))]
use std::collections::HashSet;
#[cfg(all(feature = "active", feature = "postgres"))]
use crate::core::condition::SqlValue;
#[cfg(all(feature = "active", feature = "postgres"))]
use crate::core::model::Model;
#[derive(Debug)]
pub struct WithMany<P, C> {
pub model: P,
pub related: Vec<C>,
}
#[derive(Debug)]
pub struct WithOne<P, C> {
pub model: P,
pub related: Option<C>,
}
pub fn group_has_many<P, C, K>(
parents: Vec<P>,
children: Vec<C>,
fk_getter: impl Fn(&C) -> K,
pk_getter: impl Fn(&P) -> K,
) -> Vec<WithMany<P, C>>
where
K: Eq + std::hash::Hash,
{
let mut result: Vec<WithMany<P, C>> = parents
.into_iter()
.map(|m| WithMany {
model: m,
related: Vec::new(),
})
.collect();
let pk_to_idx: HashMap<K, usize> = result
.iter()
.enumerate()
.map(|(i, r)| (pk_getter(&r.model), i))
.collect();
for child in children {
let fk = fk_getter(&child);
if let Some(&idx) = pk_to_idx.get(&fk) {
result[idx].related.push(child);
}
}
result
}
pub fn group_has_one<P, C, K>(
parents: Vec<P>,
children: Vec<C>,
fk_getter: impl Fn(&C) -> K,
pk_getter: impl Fn(&P) -> K,
) -> Vec<WithOne<P, C>>
where
K: Eq + std::hash::Hash,
{
let mut result: Vec<WithOne<P, C>> = parents
.into_iter()
.map(|m| WithOne {
model: m,
related: None,
})
.collect();
let pk_to_idx: HashMap<K, usize> = result
.iter()
.enumerate()
.map(|(i, r)| (pk_getter(&r.model), i))
.collect();
for child in children {
let fk = fk_getter(&child);
if let Some(&idx) = pk_to_idx.get(&fk) {
if result[idx].related.is_none() {
result[idx].related = Some(child);
}
}
}
result
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub async fn with_has_many<P, C, K>(
parents: Vec<P>,
fk_col: &str,
fk_getter: impl Fn(&C) -> K,
pk_getter: impl Fn(&P) -> K,
) -> Result<Vec<WithMany<P, C>>, sqlx::Error>
where
P: Model + Send + Sync + 'static,
C: Model + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Sync + Unpin + 'static,
K: Eq + std::hash::Hash + Into<SqlValue> + Clone,
{
if parents.is_empty() {
return Ok(Vec::new());
}
let pk_vals: Vec<K> = parents.iter().map(&pk_getter).collect();
let children = crate::orm::model_query::ModelQuery::<C>::new(C::query())
.and_where_in(fk_col, pk_vals)
.get()
.await?;
Ok(group_has_many(parents, children, fk_getter, pk_getter))
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub async fn with_has_one<P, C, K>(
parents: Vec<P>,
fk_col: &str,
fk_getter: impl Fn(&C) -> K,
pk_getter: impl Fn(&P) -> K,
) -> Result<Vec<WithOne<P, C>>, sqlx::Error>
where
P: Model + Send + Sync + 'static,
C: Model + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Sync + Unpin + 'static,
K: Eq + std::hash::Hash + Into<SqlValue> + Clone,
{
if parents.is_empty() {
return Ok(Vec::new());
}
let pk_vals: Vec<K> = parents.iter().map(&pk_getter).collect();
let children = crate::orm::model_query::ModelQuery::<C>::new(C::query())
.and_where_in(fk_col, pk_vals)
.get()
.await?;
Ok(group_has_one(parents, children, fk_getter, pk_getter))
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub async fn with_belongs_to<C, P, K>(
children: Vec<C>,
parent_pk_col: &str,
fk_getter: impl Fn(&C) -> K,
pk_getter: impl Fn(&P) -> K,
) -> Result<Vec<(C, Option<P>)>, sqlx::Error>
where
C: Send + 'static,
P: Model
+ for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>
+ Clone
+ Send
+ Sync
+ Unpin
+ 'static,
K: Eq + std::hash::Hash + Into<SqlValue> + Clone,
{
if children.is_empty() {
return Ok(Vec::new());
}
let mut seen: HashSet<K> = HashSet::new();
let mut fk_vals: Vec<K> = Vec::new();
for c in &children {
let k = fk_getter(c);
if seen.insert(k.clone()) {
fk_vals.push(k);
}
}
let parents = crate::orm::model_query::ModelQuery::<P>::new(P::query())
.and_where_in(parent_pk_col, fk_vals)
.get()
.await?;
let parent_map: HashMap<K, P> = parents.into_iter().map(|p| (pk_getter(&p), p)).collect();
Ok(children
.into_iter()
.map(|c| {
let fk = fk_getter(&c);
let parent = parent_map.get(&fk).cloned();
(c, parent)
})
.collect())
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub trait EagerLoadable: Sized + Send + Sync + 'static {
fn load_eager(
relation: &str,
parents: Vec<Self>,
) -> impl std::future::Future<Output = Result<Vec<Self>, sqlx::Error>> + Send;
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub async fn eager_get<M>(
query: crate::orm::model_query::ModelQuery<M>,
relations: &[&str],
) -> Result<Vec<M>, sqlx::Error>
where
M: EagerLoadable
+ crate::core::model::Model
+ for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>
+ Send
+ Sync
+ Unpin
+ 'static,
{
let mut results = query.get().await?;
for rel in relations {
results = M::load_eager(rel, results).await?;
}
Ok(results)
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub trait LazyLoadable: Sized {
fn load(
self,
relation: &str,
) -> impl std::future::Future<Output = Result<Self, sqlx::Error>> + Send;
}
#[cfg(all(feature = "active", feature = "postgres"))]
impl<M> LazyLoadable for Vec<M>
where
M: EagerLoadable
+ crate::core::model::Model
+ for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>
+ Send
+ Sync
+ Unpin
+ 'static,
{
fn load(
self,
relation: &str,
) -> impl std::future::Future<Output = Result<Self, sqlx::Error>> + Send {
M::load_eager(relation, self)
}
}
#[cfg(all(feature = "active", feature = "postgres"))]
pub struct EagerModelQuery<M> {
pub(crate) query: crate::orm::model_query::ModelQuery<M>,
pub(crate) relations: Vec<String>,
}
#[cfg(all(feature = "active", feature = "postgres"))]
impl<M> EagerModelQuery<M>
where
M: EagerLoadable
+ crate::core::model::Model
+ for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>
+ Send
+ Sync
+ Unpin
+ 'static,
{
#[must_use]
pub fn with(mut self, relation: impl Into<String>) -> Self {
self.relations.push(relation.into());
self
}
pub async fn get(self) -> Result<Vec<M>, sqlx::Error> {
let refs: Vec<&str> = self.relations.iter().map(String::as_str).collect();
eager_get(self.query, &refs).await
}
}