Introduction
@cascade directive when used in a query with pagination gives wrong results. For eg, for the given GraphQL
schema:-
type School {
name: String! @id
affln: String
principal: String
classes: [Class]
}
type Class {
std: Int! @id
teacher: String
students: [Student]
}
type Student {
name: String! @id
age: Int
}
the following GraphQL
query:-
query{
querySchool(first: 2, offset: 5) @cascade{
name
affln
classes(first: 5, offset: 3){
std
teacher
students(first: 10, offset: 10){
name
age
}
}
}
}
which is equivalent to the corresponding DQL
query gives wrong results.
querySchool(func: type(School), first: 2, offset: 5) @cascade {
School.name : School.name
School.affln : School.affln
School.classes : School.classes (first: 5, offset: 3) {
Class.std : Class.std
Class.teacher : Class.teacher
Class.students : Class.students (first: 10, offset: 10) {
Student.name : Student.name
Student.age : Student.age
dgraph.uid : uid
}
dgraph.uid : uid
}
dgraph.uid : uid
}
Underlying Problem
The issue arises due to the fact that @cascade is a post-processing step. Suppose for the above query, Dgraph first fetches the first 2 entries of School
with the offset of 5
and then 5
entries of classes at the offset of 3
and so on. It then removes null
entries in a bottom-up fashion. So first any Student
for which the age
or name
is null
is removed and then classes
and School
are removed which leads to a result different from what is expected.
The semantics of the query should be that, Give me the first 2
schools at an offset of 5
from a list of schools having non null
attributes and so on.
Possible Solutions
Based on my understanding, there are two possible solutions to fix this issue:
Query Rewriting
In order to get the desired result, the query can be written alternatively using inverse
edges as:
stdnts as var(func: type(Student)) @filter(has(Student.age) AND has(Student.name)){
cls as ~Class.students @filter(has(Class.teacher) AND has(Class.std)){
sch as ~School.classes
}
}
query(func: uid(sch), first:2, offset: 5) {
School.name
School.affln
School.classes(first: 5, offset: 3) @filter(uid(cls)){
Class.std
Class.teacher
Class.students(first: 10, offset: 10) @filter(uid(stdnts)){
Student.name
Student.age
}
}
}
Fixing pagination while applying @cascade.
In order to fetch the correct result with the DQL
/GraphQL
query presented above in the problem statement, we need to remove pagination from all the deep levels from query processing when the @cascade is applied at any level. The query will fetch all the data without pagination and then the @cascade filter should be applied and the result should be paginated after that.