use std::collections::HashMap;
use std::hash::Hash;
use std::sync::Arc;
use keelson_core::Mod;
use keelson_exec::{ExecError, Executor};
use crate::select::{Loader, ModelSelect};
use crate::{ModelTable, View};
pub const KEY_BATCH: usize = 900;
type Shape<C> = Arc<dyn Fn(&mut ModelSelect<C>) + Send + Sync>;
pub trait IntoLoader<T> {
fn into_loader(self) -> Loader<T>;
}
impl<T> IntoLoader<T> for Loader<T> {
fn into_loader(self) -> Loader<T> {
self
}
}
pub struct ThenLoad<P: View, C: View, K> {
keys: fn(&[P::Row]) -> Vec<K>,
key_filter: fn(Vec<K>, &mut ModelSelect<C>),
attach: fn(&mut [P::Row], Vec<C::Row>),
shape: Vec<Shape<C>>,
nested: Vec<Loader<C::Row>>,
batch: usize,
}
impl<P: View, C: View, K> std::fmt::Debug for ThenLoad<P, C, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ThenLoad")
.field("shape", &self.shape.len())
.field("nested", &self.nested.len())
.field("batch", &self.batch)
.finish()
}
}
impl<P, C, K> ThenLoad<P, C, K>
where
P: View,
C: View,
K: Ord + Clone + Send + Sync + 'static,
{
pub fn new(
keys: fn(&[P::Row]) -> Vec<K>,
key_filter: fn(Vec<K>, &mut ModelSelect<C>),
attach: fn(&mut [P::Row], Vec<C::Row>),
) -> Self {
ThenLoad {
keys,
key_filter,
attach,
shape: Vec::new(),
nested: Vec::new(),
batch: KEY_BATCH,
}
}
#[must_use]
pub fn then(mut self, deeper: impl IntoLoader<C::Row>) -> Self {
self.nested.push(deeper.into_loader());
self
}
#[must_use]
pub fn with(mut self, shape: impl Fn(&mut ModelSelect<C>) + Send + Sync + 'static) -> Self {
self.shape.push(Arc::new(shape));
self
}
#[must_use]
#[track_caller]
pub fn batch(mut self, keys: usize) -> Self {
assert!(keys > 0, "then-load batch size must be at least 1");
self.batch = keys;
self
}
async fn run(&self, db: &dyn Executor, parents: &mut [P::Row]) -> Result<(), ExecError> {
let keys = distinct((self.keys)(parents));
if keys.is_empty() {
return Ok(());
}
let mut children: Vec<C::Row> = Vec::new();
for chunk in keys.chunks(self.batch) {
let mut q = ModelTable::<C>::new().query(());
(self.key_filter)(chunk.to_vec(), &mut q);
for shape in &self.shape {
shape(&mut q);
}
children.extend(q.all(db).await?);
}
for deeper in &self.nested {
deeper(db, &mut children).await?;
}
(self.attach)(parents, children);
Ok(())
}
}
fn distinct<K: Ord>(mut keys: Vec<K>) -> Vec<K> {
keys.sort_unstable();
keys.dedup();
keys
}
impl<P, C, K> IntoLoader<P::Row> for ThenLoad<P, C, K>
where
P: View,
C: View,
K: Ord + Clone + Send + Sync + 'static,
{
fn into_loader(self) -> Loader<P::Row> {
let level = Arc::new(self);
Arc::new(move |db, rows: &mut Vec<P::Row>| {
let level = Arc::clone(&level);
Box::pin(async move { level.run(db, rows).await })
})
}
}
impl<P, C, K> Mod<ModelSelect<P>> for ThenLoad<P, C, K>
where
P: View,
C: View,
K: Ord + Clone + Send + Sync + 'static,
{
fn apply(self, q: &mut ModelSelect<P>) {
q.add_loader(self.into_loader());
}
}
pub fn attach_to_one<P, C, K>(
parents: &mut [P],
children: Vec<C>,
parent_key: impl Fn(&P) -> K,
child_key: impl Fn(&C) -> K,
mut attach: impl FnMut(&mut P, Option<C>),
) where
K: Eq + Hash,
C: Clone,
{
let by_key: HashMap<K, C> = children.into_iter().map(|c| (child_key(&c), c)).collect();
for p in parents {
let child = by_key.get(&parent_key(p)).cloned();
attach(p, child);
}
}
pub fn attach_to_many<P, C, K>(
parents: &mut [P],
children: Vec<C>,
parent_key: impl Fn(&P) -> K,
child_key: impl Fn(&C) -> K,
mut attach: impl FnMut(&mut P, Vec<C>),
) where
K: Eq + Hash,
{
let mut by_key: HashMap<K, Vec<C>> = HashMap::new();
for c in children {
by_key.entry(child_key(&c)).or_default().push(c);
}
for p in parents {
let own = by_key.remove(&parent_key(p)).unwrap_or_default();
attach(p, own);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Parent {
id: i32,
children: Vec<i32>,
one: Option<i32>,
}
fn parents() -> Vec<Parent> {
(1..=3)
.map(|id| Parent {
id,
children: Vec::new(),
one: None,
})
.collect()
}
#[test]
fn to_many_groups_by_key_and_leaves_misses_empty() {
let mut ps = parents();
let children = vec![(10, 1), (11, 1), (20, 2)];
attach_to_many(
&mut ps,
children,
|p| p.id,
|c| c.1,
|p, cs| p.children = cs.into_iter().map(|c| c.0).collect(),
);
assert_eq!(ps[0].children, vec![10, 11]);
assert_eq!(ps[1].children, vec![20]);
assert_eq!(ps[2].children, Vec::<i32>::new());
}
#[test]
fn to_one_attaches_a_match_or_none_and_shares_children() {
let mut ps = parents();
ps[1].id = 1; let children = vec![(100, 1)];
attach_to_one(
&mut ps,
children,
|p| p.id,
|c| c.1,
|p, c| p.one = c.map(|c| c.0),
);
assert_eq!(ps[0].one, Some(100));
assert_eq!(ps[1].one, Some(100), "a shared child is cloned, not stolen");
assert_eq!(ps[2].one, None);
}
#[test]
fn keys_are_deduplicated_and_ordered() {
assert_eq!(distinct(vec![3, 1, 3, 2, 1, 1]), vec![1, 2, 3]);
assert_eq!(distinct(Vec::<i32>::new()), Vec::<i32>::new());
}
#[test]
fn keys_batch_at_the_boundary() {
let queries = |n: usize, batch: usize| {
distinct((0..n as i32).collect::<Vec<_>>())
.chunks(batch)
.count()
};
assert_eq!(queries(KEY_BATCH - 1, KEY_BATCH), 1);
assert_eq!(
queries(KEY_BATCH, KEY_BATCH),
1,
"the cap itself is one query"
);
assert_eq!(
queries(KEY_BATCH + 1, KEY_BATCH),
2,
"one over the cap is two"
);
assert_eq!(queries(2 * KEY_BATCH, KEY_BATCH), 2);
assert_eq!(queries(2 * KEY_BATCH + 1, KEY_BATCH), 3);
assert_eq!(queries(4, 2), 2);
assert_eq!(queries(5, 2), 3);
let mut dup = vec![1; KEY_BATCH * 2];
dup.extend([2, 3]);
assert_eq!(distinct(dup).chunks(KEY_BATCH).count(), 1);
}
}