Has many through queries? i.e. How to filter for nodes based on nested relationships/edges?

This is my failed attempt at a query that performs a ‘has many through’ lookup:

{
  var(func: type(Truck)) @filter(eq(Truck.name, "abc-truck")) {
    truck as uid
  }
    
  var(func: type(WheelType)) @filter(eq(WheelType.truck, uid(truck))) {
    wheel as uid
  }
    
  query(func: type(BoltType)) @filter(eq(BoltType.wheelType, uid(wheel))) {
    uid
    make
    diameter
    price
  }
}

What would be the right way to find BoltType nodes if I’m starting with a Truck?

Schema:

type Truck {
  id: ID!
  name: String!
  wheelTypes: [WheelType] @hasInverse(field: truck)
}

type WheelType {
  id: ID!
  truck: Truck!
  boltTypes: [BoltType] @hasInverse(field: wheelType)
}

type BoltType {
  id: ID!
  wheelType: WheelType
  make: String!
  diameter: Float!
  price: Int!
}

Since it is just a straight traversal no matter the depth, then you only need a single var block. This could be split into two var blocks but would make no difference.

Also it will provide better performance to put the eq and uid functions at the root of the blocks and move the type functions to the filter directive.

Your query was almost there and would work if you returned the wheels as the Truck.wheelTypes from the first var block and used them as the filter in the second block then returned bolts as WheelType.boltTypes from the second block and used them in the filter on the third block instead.

{
  var(func: eq(Truck.name, "abc-truck")) @filter(type(Truck)) {
    Truck.wheelTypes {
      bolts as WheelType.boltTypes
    }
  }
    
  query(func: uid(bolts)) @filter(type(BoltType)) {
    uid
    make
    diameter
    price
  }
}
1 Like

Thanks Anthony!

Realised there were two blindspots I had. First, I didn’t realise that you could assign a relationship to a variable, for some reason it didn’t occur to me to try that:

The other thing that wasn’t working in my code was that I had specified that the query should return BoltType and not [BoltType], Dgraph provided a helpful error making it obvious though:

"errors": [
    {
      "message": "A list was returned, but GraphQL was expecting just one item. This indicates an internal error - probably a mismatch between the GraphQL and Dgraph/remote schemas. The value was resolved as null (which may trigger GraphQL error propagation) and as much other data as possible returned.",

Finished query:

  paginateBoltTypes(truckName: String!, first: Int!, offset: Int!): [BoltType] @custom(dql: """
    query q($truckName: string, $first: int, $offset: int) {
      var(func: type(Truck)) @filter(eq(Truck.name, $truckName)) @cascade {
        Truck.wheelTypes {
          bolts as WaveFile.boltTypes
        }
      }

      paginateBoltTypes(func: uid(bolts), orderasc: BoltType.make, first: $first, offset: $offset) @cascade {
        id: uid
        make: BoltType.make
        diameter: BoltType.diameter
      }
    }
  """)
1 Like