Skip to content

Commit 634e239

Browse files
committed
graphql: Simplify the join logic and fix a bug
The logic for actually joining parents and children was overly complicated, and for situations where the children were an interface caused repeating the children for each parent as many times as the interface had implementations. Since we have the parents in memory already, and since parent_id's are unique across all the parents that can possibly appear in a list of parents, we can distribute children into their parents simply by looking at the parent_id that the EntityQuery reports for each child already. Fixes #1438
1 parent 4624542 commit 634e239

File tree

1 file changed

+36
-84
lines changed

1 file changed

+36
-84
lines changed

graphql/src/store/prefetch.rs

Lines changed: 36 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,14 @@ impl<'a> Join<'a> {
354354
}
355355

356356
/// Perform the join. The child nodes are distributed into the parent nodes
357-
/// according to the join condition, and are stored in the `response_key`
358-
/// entry in each parent's `children` map.
357+
/// according to the `parent_id` returned by the database in each child as
358+
/// attribute `g$parent_id`, and are stored in the `response_key` entry
359+
/// in each parent's `children` map.
359360
///
360361
/// The `children` must contain the nodes in the correct order for each
361362
/// parent; we simply pick out matching children for each parent but
362363
/// otherwise maintain the order in `children`
363-
fn perform(&self, parents: &mut Vec<Node>, children: Vec<Node>, response_key: &str) {
364+
fn perform(parents: &mut Vec<Node>, children: Vec<Node>, response_key: &str) {
364365
let children: Vec<_> = children.into_iter().map(|child| Rc::new(child)).collect();
365366

366367
if parents.len() == 1 {
@@ -369,92 +370,43 @@ impl<'a> Join<'a> {
369370
return;
370371
}
371372

372-
// Organize the join conditions by child type. We do not precompute that
373-
// in `new`, because `perform` is only called once for each instance
374-
// of a `Join`. For each child type, there might be multiple parent
375-
// types that require different ways of joining to them
376-
let conds_by_child: HashMap<_, Vec<_>> =
377-
self.conds.iter().fold(HashMap::default(), |mut map, cond| {
378-
map.entry(cond.child_type).or_default().push(cond);
379-
map
380-
});
381-
382-
// Build a map (parent_type, child_key) -> Vec<child> for joining by grouping
383-
// children by their child_field
384-
let mut grouped: BTreeMap<(&str, &str), Vec<Rc<Node>>> = BTreeMap::default();
373+
// Build a map parent_id -> Vec<child> that we will use to add
374+
// children to their parent. This relies on the fact that interfaces
375+
// make sure that id's are distinct across all implementations of the
376+
// interface.
377+
let mut grouped: BTreeMap<&str, Vec<Rc<Node>>> = BTreeMap::default();
385378
for child in children.iter() {
386-
for cond in conds_by_child.get(child.typename()).expect(&format!(
387-
"query results only contain known types: {}",
388-
child.typename()
389-
)) {
390-
match child
391-
.get("g$parent_id")
392-
.expect("the query that produces 'child' ensures there is always a g$parent_id")
393-
{
394-
StoreValue::String(key) => grouped
395-
.entry((cond.parent_type, key))
396-
.or_default()
397-
.push(child.clone()),
398-
StoreValue::List(list) => {
399-
for key in list {
400-
match key {
401-
StoreValue::String(key) => grouped
402-
.entry((cond.parent_type, key))
403-
.or_default()
404-
.push(child.clone()),
405-
_ => unreachable!("a list of join keys contains only strings"),
406-
}
407-
}
408-
}
409-
_ => unreachable!("join fields are strings or lists"),
410-
}
379+
match child
380+
.get("g$parent_id")
381+
.expect("the query that produces 'child' ensures there is always a g$parent_id")
382+
{
383+
StoreValue::String(key) => grouped.entry(key).or_default().push(child.clone()),
384+
_ => unreachable!("the parent_id returned by the query is always a string"),
411385
}
412386
}
413387

414-
// Organize the join conditions by parent type.
415-
let conds_by_parent: HashMap<_, Vec<_>> =
416-
self.conds.iter().fold(HashMap::default(), |mut map, cond| {
417-
map.entry(cond.parent_type).or_default().push(cond);
418-
map
419-
});
420-
421388
// Add appropriate children using grouped map
422389
for parent in parents.iter_mut() {
423-
// It is possible that we do not have a join condition for some
424-
// parent types. That can happen, for example, if the parents
425-
// were the result of querying for an interface type, and we
426-
// have to get children for some of the parents, but not others.
427-
// If a parent type is not mentioned in any join conditions,
428-
// we skip it by looping over an empty vector.
429-
//
430-
// See the test interface_inline_fragment_with_subquery in
431-
// core/tests/interfaces.rs for an example.
432-
for cond in conds_by_parent.get(parent.typename()).unwrap_or(&vec![]) {
433-
// Set the `response_key` field in `parent`. Make sure that even
434-
// if `parent` has no matching `children`, the field gets set (to
435-
// an empty `Vec`)
436-
// This is complicated by the fact that, if there was a type
437-
// condition, we should only change parents that meet the type
438-
// condition; we set it for all parents regardless, as that does
439-
// not cause problems in later processing, but make sure that we
440-
// do not clobber an entry under this `response_key` that might
441-
// have been set by a previous join by appending values rather
442-
// than using straight insert into the parent
443-
let mut values = parent
444-
.id()
445-
.ok()
446-
.and_then(|id| {
447-
grouped
448-
.get(&(cond.parent_type, &id))
449-
.map(|values| values.clone())
450-
})
451-
.unwrap_or(vec![]);
452-
parent
453-
.children
454-
.entry(response_key.to_owned())
455-
.or_default()
456-
.append(&mut values);
457-
}
390+
// Set the `response_key` field in `parent`. Make sure that even
391+
// if `parent` has no matching `children`, the field gets set (to
392+
// an empty `Vec`)
393+
// This is complicated by the fact that, if there was a type
394+
// condition, we should only change parents that meet the type
395+
// condition; we set it for all parents regardless, as that does
396+
// not cause problems in later processing, but make sure that we
397+
// do not clobber an entry under this `response_key` that might
398+
// have been set by a previous join by appending values rather
399+
// than using straight insert into the parent
400+
let mut values = parent
401+
.id()
402+
.ok()
403+
.and_then(|id| grouped.get(&*id).map(|values| values.clone()))
404+
.unwrap_or(vec![]);
405+
parent
406+
.children
407+
.entry(response_key.to_owned())
408+
.or_default()
409+
.append(&mut values);
458410
}
459411
}
460412

@@ -668,7 +620,7 @@ where
668620
&child_object_type,
669621
) {
670622
Ok(children) => {
671-
join.perform(&mut parents, children, response_key)
623+
Join::perform(&mut parents, children, response_key)
672624
}
673625
Err(mut e) => errors.append(&mut e),
674626
}

0 commit comments

Comments
 (0)