ElasticGraph Query API: Filter Conjunctions

ElasticGraph supports two conjunction predicates:

allOf
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.

Note: multiple filters are automatically ANDed together. This is only needed when you have multiple filters that can’t be provided on a single filter input because of collisions between key names. For example, if you want to provide multiple anySatisfy: ... filters, you could do allOf: [{anySatisfy: ...}, {anySatisfy: ...}].

Will be ignored when null or an empty list is passed.

anyOf
Matches records where any of the provided sub-filters evaluate to true. This works just like an OR operator in SQL.

Will be ignored when null is passed. When an empty list is passed, will cause this part of the filter to match no documents.

By default, multiple filters are ANDed together. For example, this query finds artists formed after the year 2000 with “accordion” in their bio:

query FindRecentAccordionArtists {
  artists(filter: {
    bio: {
      yearFormed: {gt: 2000}
      description: {matchesQuery: {query: "accordion"}}
    }
  }) {
    nodes {
      name
      bio {
        yearFormed
        description
      }
    }
  }
}

ORing subfilters with anyOf

To instead find artists formed after the year 2000 OR with “accordion” in their bio, you can wrap the sub-filters in an anyOf:

query FindRecentOrAccordionArtists {
  artists(filter: {
    bio: {
      anyOf: [
        {yearFormed: {gt: 2000}}
        {description: {matchesQuery: {query: "accordion"}}}
      ]
    }
  }) {
    nodes {
      name
      bio {
        yearFormed
        description
      }
    }
  }
}

anyOf is available at all levels of the filtering structure so that you can OR sub-filters anywhere you like.

ANDing subfilters with allOf

allOf is rarely needed since multiple filters are ANDed together by default. But it can come in handy when you’d otherwise have a duplicate key collision on a filter input. One case where this comes in handy is when using anySatisfy to filter on a list. Consider this query:

query ArtistsWithPlatinum90sAlbum {
  artists(filter: {
    albums: {
      anySatisfy: {
        soldUnits: {gte: 1000000}
        releasedOn: {gte: "1990-01-01", lt: "2000-01-01"}
      }
    }
  }) {
    nodes {
      name
      albums {
        name
        releasedOn
        soldUnits
      }
    }
  }
}

This query finds artists who released an album in the 90’s that sold more than million copies. If you wanted to broaden the query to find artists with at least one 90’s album and at least one platinum-selling album–without requiring it to be the same album–you could do this:

query ArtistsWith90sAlbumAndPlatinumAlbum {
  artists(filter: {
    albums: {
      allOf: [
        {anySatisfy: {soldUnits: {gte: 1000000}}}
        {anySatisfy: {releasedOn: {gte: "1990-01-01", lt: "2000-01-01"}}}
      ]
    }
  }) {
    nodes {
      name
      albums {
        name
        releasedOn
        soldUnits
      }
    }
  }
}

GraphQL input objects don’t allow duplicate keys, so albums: {anySatisfy: {...}, anySatisfy: {...}} isn’t supported, but allOf enables this use case.

Warning: Always Pass a List

When using allOf or anyOf, be sure to pass the sub-filters as a list. If you instead pass them as an object, it won’t work as expected. Consider this query:

query AnyOfGotcha {
  artists(filter: {
    bio: {
      anyOf: {
        yearFormed: {gt: 2000}
        description: {matchesQuery: {query: "accordion"}}
      }
    }
  }) {
    nodes {
      name
      bio {
        yearFormed
        description
      }
    }
  }
}

While this query will return results, it doesn’t behave as it appears. The GraphQL spec mandates that list inputs coerce non-list values into a list of one value. In this case, that means that the anyOf expression is coerced into this:

query AnyOfGotcha {
  artists(filter: {
    bio: {
      anyOf: [{
        yearFormed: {gt: 2000}
        description: {matchesQuery: {query: "accordion"}}
      }]
    }
  }) {
    # ...
  }
}

Using anyOf with only a single sub-expression, as we have here, doesn’t do anything; the query is equivalent to:

query AnyOfGotcha {
  artists(filter: {
    bio: {
      yearFormed: {gt: 2000}
      description: {matchesQuery: {query: "accordion"}}
    }
  }) {
    # ...
  }
}