use std::collections::HashMap;
use crate::error::{QueryError, QueryResult};
use crate::filter::{Filter, FilterValue};
use crate::relations::RelationMeta;
use crate::row::FromRow;
use crate::traits::{Model, ModelWithPk, QueryEngine};
pub async fn load_has_many<E, P, C, R>(
engine: &E,
parents: &[P],
) -> QueryResult<HashMap<String, Vec<C>>>
where
E: QueryEngine,
P: Model + ModelWithPk,
C: Model + ModelWithPk + FromRow + Send + 'static,
R: RelationMeta<Owner = P, Target = C>,
{
let pk_values: Vec<FilterValue> = parents.iter().map(|p| p.pk_value()).collect();
if pk_values.is_empty() {
return Ok(HashMap::new());
}
let filter = Filter::In(R::FOREIGN_KEY.into(), pk_values);
let dialect = engine.dialect();
let (where_sql, params) = filter.to_sql(0, dialect);
let sql = format!("SELECT * FROM {} WHERE {}", C::TABLE_NAME, where_sql);
let children: Vec<C> = engine.query_many::<C>(&sql, params).await?;
let mut out: HashMap<String, Vec<C>> = HashMap::new();
for child in children {
let fk = child.get_column_value(R::FOREIGN_KEY).ok_or_else(|| {
QueryError::internal(format!(
"relation {}: child model missing FK column {}",
R::NAME,
R::FOREIGN_KEY
))
})?;
let key = filter_value_key(&fk);
out.entry(key).or_default().push(child);
}
Ok(out)
}
#[doc(hidden)]
pub fn filter_value_key_public(v: &FilterValue) -> String {
filter_value_key(v)
}
pub(crate) fn filter_value_key(v: &FilterValue) -> String {
match v {
FilterValue::Int(i) => i.to_string(),
FilterValue::String(s) => s.clone(),
FilterValue::Bool(b) => b.to_string(),
FilterValue::Float(f) => f.to_string(),
FilterValue::Null => "<null>".into(),
FilterValue::Json(v) => v.to_string(),
FilterValue::List(_) => {
panic!("relation executor does not support composite keys yet (FilterValue::List)")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filter_value_key_int() {
assert_eq!(filter_value_key(&FilterValue::Int(42)), "42");
}
#[test]
fn filter_value_key_string() {
assert_eq!(filter_value_key(&FilterValue::String("abc".into())), "abc");
}
#[test]
fn filter_value_key_bool() {
assert_eq!(filter_value_key(&FilterValue::Bool(true)), "true");
}
#[test]
fn filter_value_key_null() {
assert_eq!(filter_value_key(&FilterValue::Null), "<null>");
}
#[test]
#[should_panic(expected = "composite keys")]
fn filter_value_key_list_panics() {
let _ = filter_value_key(&FilterValue::List(vec![FilterValue::Int(1)]));
}
}