OK, so it actually needs to generalize in a several of ways (I hope this post is concise enough ).
Broadly speaking, it seems possible it seems possible to generalise the postprocessing solution, but it becomes cumbersome, and may be inefficient.
Relationship Complexity
The original query compares an ancestors to a descendant, let’s call them A and B. A and B have a simple recursive child relationship. I’ll express this as though it were one GraphQL+/- block for brevity, even though we know it would have to be divided up into multiple blocks:
// Mock graphql
A {
invented == $inv
recurse child B {
dislikes == $inv
}
}
In the general case A and B could have an arbitrarily complex compound relationship. For a simple example, you may be interested inventors who have a descendant called Bob, where Bob has a descendant who dislikes the invention:
// Mock Gql
A {
invented == $inv
recurse child B {
name == "bob"
recurse child C {
dislikes
}
}
}
If we use the same approach as the original solution we need a query like:
{
inventors as inv(func: eq(occupation, "Inventor")) {
inventions as invented
}
recurse(func: uid(inventors)) {
descendants as child
}
bob(func: uid(descendants)) {
@filter(eq(name, "Bob"))
bobDescendants as child
}
dis(func: uid(bobDescendants)) {
@filter(dislikes, val(inventions))
}
}
I don’t think we can do a final block that links the dislikers to the inventors like in the basic case. However, if we return the uids of all the intermediary nodes there will be enough information to do the post processing even if it is more complex.
The problem is that since the relationship between A and B is any arbitrary relationship that can be expressed in GraphQL+/-, the postprocessing gets arbitrarly complex, and would be better suited to being part of GraphQL+/- itself.
Multiple Use Consistency
Perhaps you are interested in family lines where the sons have done the same job for 4 generations:
// Mock Gql
A {
gender == male
job == $j
recurse child B {
gender == male
job == $j
recurse child C {
gender == male
job == $j
recurse child D {
gender == male
job == $j
}
}
}
}
This will complicate post processing as we have to traverse multiple result sets and check their values.
Cross Branch Matching
The previous examples have been linear. What if we wanted to find someone with name X, whose first child has a descendant of name X, and whose second child has a descendant of name X.
// Mock Gql
A {
name == $X
firstChild B {
recurse child C {
name == $X
}
}
secondChild D {
recurse child E {
name == $X
}
}
}
The actual query in this particular case is relatively simple:
{
ancestor as anc(func: eq(is, "Person")) {
X as name
fc as firstChild
sc as secondChild
}
recurse(func: uid(fc)) {
fDescendants as child
}
recurse(func: uid(sc)) {
sDescendants as child
}
recurse(func: uid(fDescendants)) @filter(eq(name, val(X))) {
~child
name
}
recurse(func: uid(sDescendants)) @filter(eq(name, val(X))) {
~child
name
}
}
This is similar to the original solution, except we’re traversing up and down a tree. In post processing you would have to compare the value name between the ancestor and the two children. And you can imagine a more complex tree where you would compare children at each node in the tree.
Multiple Consistency
You may want to find descendants with the same name and job as their ancestor. Or, you may want someone with a descendant with the same name, and another descendant with the same job:
{
ancestor as anc(func: eq(is, "Person")) {
X as name
J as job
}
recurse(func: uid(ancestor)) {
descendant as child
}
recurse(func: uid(descendants)) @filter(eq(name, val(X))) {
~child
name
}
recurse(func: uid(descendants)) @filter(eq(job, val(J))) {
~child
name
}
}
Alternative Solution
Rather than doing the post processing thing, we could do an initial query to find the possible values we’re dealing with, and run a query for each possible value. This would be expensive, but possibly no more expensive (N+1 queries for N possible values) than the post processing solution.