use crate::error::Result;
use crate::handle::SqlContext;
pub trait RelationView: Sized {
type Parent: crate::row::FromRow;
fn load<C: SqlContext>(cx: &C, parents: Vec<Self::Parent>) -> Result<Vec<Self>>;
}
#[doc(hidden)]
pub fn in_placeholders(n: usize) -> String {
(1..=n)
.map(|i| format!("?{i}"))
.collect::<Vec<_>>()
.join(", ")
}
use rusqlite::ToSql;
use std::collections::HashMap;
use std::hash::Hash;
const IN_CHUNK_SIZE: usize = 999;
#[doc(hidden)]
pub fn load_children<PK, C, CX, F>(
cx: &CX,
keys: &[PK],
child_table: &str,
key_col: &str,
key_of: F,
) -> Result<HashMap<PK, Vec<C>>>
where
PK: ToSql + Eq + Hash + Clone,
C: crate::row::FromRow,
CX: SqlContext,
F: Fn(&C) -> PK,
{
let mut map: HashMap<PK, Vec<C>> = HashMap::new();
if keys.is_empty() {
return Ok(map);
}
for chunk in keys.chunks(IN_CHUNK_SIZE) {
let sql = format!(
"SELECT * FROM \"{child_table}\" WHERE \"{key_col}\" IN ({})",
in_placeholders(chunk.len())
);
let refs: Vec<&dyn ToSql> = chunk.iter().map(|k| k as &dyn ToSql).collect();
let children: Vec<C> = cx.ctx_query_all(&sql, &refs[..])?;
for c in children {
map.entry(key_of(&c)).or_default().push(c);
}
}
Ok(map)
}
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn load_junction<PK, EK, C, CX, F>(
cx: &CX,
keys: &[PK],
junction_table: &str,
j_parent_col: &str,
j_entity_col: &str,
child_table: &str,
child_key_col: &str,
key_of: F,
) -> Result<HashMap<PK, Vec<C>>>
where
PK: ToSql + rusqlite::types::FromSql + Eq + Hash + Clone,
EK: ToSql + rusqlite::types::FromSql + Eq + Hash + Clone + Ord,
C: crate::row::FromRow + Clone,
CX: SqlContext,
F: Fn(&C) -> EK,
{
let mut map: HashMap<PK, Vec<C>> = HashMap::new();
if keys.is_empty() {
return Ok(map);
}
let mut pairs: Vec<(PK, EK)> = Vec::new();
for chunk in keys.chunks(IN_CHUNK_SIZE) {
let jsql = format!(
"SELECT \"{j_parent_col}\", \"{j_entity_col}\" FROM \"{junction_table}\" \
WHERE \"{j_parent_col}\" IN ({})",
in_placeholders(chunk.len())
);
let refs: Vec<&dyn ToSql> = chunk.iter().map(|k| k as &dyn ToSql).collect();
pairs.extend(cx.ctx_query_all::<(PK, EK), _>(&jsql, &refs[..])?);
}
let mut child_keys: Vec<EK> = pairs.iter().map(|(_, e)| e.clone()).collect();
child_keys.sort();
child_keys.dedup();
let mut by_key: HashMap<EK, C> = HashMap::new();
if !child_keys.is_empty() {
for chunk in child_keys.chunks(IN_CHUNK_SIZE) {
let csql = format!(
"SELECT * FROM \"{child_table}\" WHERE \"{child_key_col}\" IN ({})",
in_placeholders(chunk.len())
);
let crefs: Vec<&dyn ToSql> = chunk.iter().map(|k| k as &dyn ToSql).collect();
let children: Vec<C> = cx.ctx_query_all(&csql, &crefs[..])?;
for c in children {
by_key.insert(key_of(&c), c);
}
}
}
for (p, e) in pairs {
if let Some(c) = by_key.get(&e) {
map.entry(p).or_default().push(c.clone());
}
}
Ok(map)
}