Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL - how to filter a hierarchy? ("customers who ordered gizmos last month")

Tags:

graphql

Let's assume a type hierarchy of Customer -(hasMany)-> Orders -(hasMany)-> OrderLines

Something like this:

Customer {
    Name
    Orders [
        {
            OrderId
            Date
            OrderLines [
                { 
                    ItemCount
                    ItemName
                }
            ]
        }
    ]
}

I want to query for this whole tree, and filter on properties at any level in the tree.

For instance: Get all customers who ordered 'gizmos'.

This is what I tried: at each level of the hierarchy, I specify optional arguments that would filter based on the properties available at that level:

Customer (Name) {
    Name
    Orders (OrderId, Date) [
        {
            OrderId
            Date
            OrderLines (ItemCount, ItemName) [
                { 
                    ItemCount
                    ItemName
                }
            ]
        }
    ]
}

GraphQL needs me to define how to resolve each type in the hierarchy, so when resolving, I filter based on the arguments in the query.

But what if I only specify a filter at a deep level? e.g. ItemName : 'gizmo'

Assuming there's only one order line in the system containing a gizmo, I would expect to get a response like this:

[{
    Name: "cust12",
    Orders [{
        OrderId: "ade32f",
        OrderLines: [{
            ItemCount: 50000, //customer really likes gizmos
            ItemName: "gizmo"
        }]
    }]
}]

But what I actually get is all customers (no filter there), all their orders (no filter there) and all order items, mostly empty (the items inside are filtered).

[{
    Name: "cust12",
    Orders [
    {
        OrderId: "aaaaaa",
        OrderLines: [ ]
    },
    {
        OrderId: "ade32f",
        OrderLines: [{
            ItemCount: 50000,
            ItemName: "gizmo"
        }]
    },
    {
        OrderId: "bbbbbb",
        OrderLines: [ ]
    },
    {
        OrderId: "cccccc",
        OrderLines: [ ]
    }
    ]
},
{
    Name: "cust345",
    Orders [
    {
        OrderId: "eeeeee",
        OrderLines: [ ]
    },
    {
        OrderId: "ffffff",
        OrderLines: [ ]
    }
    ]
}]

GraphQL calls the resolvers top-down: - get all (filtered) clients - for each of these get all (filtered) orders - for each of those get all (filtered) order lines

Because of the top-down nature of calling the resolvers, I get a lot more data than I bargained for.

How should I approach this?

like image 964
Cristian Diaconescu Avatar asked Jan 25 '17 22:01

Cristian Diaconescu


1 Answers

Relation filters

This is actually a more complex topic than it first seems. The problem is that your current filter condition expresses

get all customers, but only include items named 'gizmo'

but what you really want is

get all customers that are related to at least one item named 'gizmo'

get all customers that are related to at least one item named 'gizmo'

An elegant solution for this problem is the addition of relation filters to the schema. In your case, it could look like this:

query {
  Customer(filter: {
    orders_some: {
      orderLines_some: {
        item: {
          itemName: "gizmo"
        }
      }
    }
  }) {
    Name
    Orders {
      OrderId
      Date
      OrderLines { 
        ItemCount
        ItemName
      }
    }
  }
}

Using

orders_some: {
  orderLines_some: {
    item: {
      itemName: "gizmo"
    }
  }
}

we only fetch customers that are indirectly related to an item named 'gizmo', exactly what we wanted.

Two more examples:

get all customers that are not related to any item named 'gizmo'

query {
  Customer(filter: {
    orders_none: {
      orderLines_some: {
        item: {
          itemName: "gizmo"
        }
      }
    }
  }) {
    Name
    Orders {
      OrderId
      Date
      OrderLines { 
        ItemCount
        ItemName
      }
    }
  }
}

get all customers where all their orders contain some order line with an item named 'gizmo'

query {
  Customer(filter: {
    orders_every: {
      orderLines_some: {
        item: {
          itemName: "gizmo"
        }
      }
    }
  }) {
    Name
    Orders {
      OrderId
      Date
      OrderLines { 
        ItemCount
        ItemName
      }
    }
  }
}

The every, some and none relation filters are an essential part of the Graphcool APIs - you can read more here.

like image 91
marktani Avatar answered Sep 21 '22 23:09

marktani