Filters in GraphQL

@mrjn I like this syntax. Does the above implicitly do an and within the same filter and or between two filter clauses? i.e. would i interpret your query as:

(((name is any of john or snow) AND (born between 1990 and 2000)) OR (has more than 2 children)) AND (relatives home is near 10km of the given location)

So to put this syntax in context of the above examples, would my second example (finding all musical dramas where the actor was Angelina Jolie.) be rewritten as below?

{
    film.actor {
        filter {
            type.object.name.en (eq: "Angelina Jolie")
        }
        film.actor.film {
            film.performance.film {
                film.film.genre {
                    filter {
                        type.object.name.en (eq: "Musical Drama")
                    }
                }
                type.object.name.en
            }
        }
    }
}

Or am I misunderstanding the syntax?

This part is correct. Also, we only take the first 10 such results. But, relative is a just an edge out from the results of friends. So, you pick the first 10 results with the above filter, then find their relatives who live within 10km of the given geolocation.

I think yours is a bit tricky, because you aren’t starting with any node, but directly with string matching. It might be something like this:

{
  filter {
    type.object.name.en (eq: "Angelina Jolie")
  }
  film.actor.film {
    film.performance.film {
      film.film.genre {
        filter {
          type.object.name.en (eq: "Musical Drama")
        }
        type.object.name.en
      }
    }
  }
}

Note that casing would be ignored for string matches. Also, I think we can still use the query syntax I proposed above for string matching.

"(angelina & brad) | (jolie & pitt)"

I think this syntax is pretty powerful. This can also be used to do “angelina jolie”, where these two otherwise separate terms would be considered as one term, because they don’t have an operator in between.

Using

filter {
 Some.condition
}

Would violate the graphql spec that the respone should have the same pattern as the query. So that may not work well for us.

1 Like

If that is the case, then we cannot expression the query in example 2 easily (finding all musical dramas where the actor was Angelina Jolie).

Because in that query, film.genre is just an edge, and we just select all edges which have the name “Musical Drama”. But this does not affect the list of films returned (many of them will just not return the genre edge).

We need a way to limit the top level results based on the values in related vertices. Both Cypher and Gremlin support that.

Let’s confirm that. Because, we’re still in the same pattern, same depth as the query, and response is in the same shape. What are you referring to?

Actually, thinking more about this, I feel this is no different than having edges, which don’t produce results. With that understanding, I think this would be within the realms of GraphQL.

We can try and get someone’s advice on this. Someone from GraphQL team.

What I meant was:

{
  me(_uid_: 0x01) {
    friends (first: 10) {
      filter {  // intersection between 2 conditions defined within.
        type.object.name.en (anyof: ["john", "snow"])
        born.on (ge: 1990, lt: 2000)
      }
    }
  }
}

Here filter, though is a keyword for us, is similar to a predicate for someone who just knows GraphQL. And there wont be a field called filter in the JSON response that he gets.

Isn’t that our current behavior, if filter as predicate has no result?

But, if filter predicate has no result, what would the subsequent levels (children of filter) act on? In case of the example, the type.object.name.en and born.on would have no source nodes (query field in subgraph will be empty for evey child of filter predicate).

{
  me(_uid_: 0x01) {
    friends (first: 10) {
      filter {  // intersection between 2 conditions defined within.
        type.object.name.en (anyof: ["john", "snow"])
      }
    }
  }
}

so to access the name of my frineds in the JSON response. I’ll do
resp[“me”][“friends”][“filter”][“type…”], but when we reach the filter level it’ll be empty and we can’t access the type.object.name…

We discussed this. So, just to close the loop on this one, the query that you mentioned there, doesn’t ask for any field inside friends (only filters on that), and hence, it would be empty.

So, something like this might be closer to GraphQL syntax. What do you guys think?

{
  me(_uid_: 0x01) {
    friends (first: 10) @filter(
        anyOf("type.object.name.en", "john", "snow")
        ge("born.on", 1990)
        lt("born.on", 2000) )
        @filter(gt(_count_(child), 2)) {

      relatives @filter (near("home.geolocation", {lat: 12.43, lon: -53.211, rad: 10k}) {
        name
      }
    }
  }
}

Or, we could do something like this:

{
  me(_uid_: 0x01) {
    friends (first: 10) @filter(
        (anyOf("type.object.name.en", "john", "snow") &&
        ge("born.on", 1990) && lt("born.on", 2000)) || gt(_count_(child), 2)) {

      relatives @filter(near("home.geolocation", 12.43, -53.211, 10)) {
        name
      }
    }
  }
}

Adding another one below just for completeness sake, but I find this format dirty because we’re treating predicates like variables (which they are not), but yet can’t fully cover all the cases (like anyOf, or near).

{
  me(_uid_: 0x01) {
    friends (first: 10) @filter(
        (anyOf("type.object.name.en", "john", "snow") &&
        "born.on" >= 1990 && "born.on" < 2000) || _count_(child) > 2) {

      relatives @filter(near("home.geolocation", 12.43, -53.211, 10)) {
        name
      }
    }
  }
}

Alright! Would love some advice now, @minions.

@mrjn Could you update the example so that it also requests the appropriate fields(predicates within friends or relatives). As I understand right now the above queries don’t request anything.

Would it be like this? If yes then the third one looks best(its not as verbose as the first, uses && , || , ge , lt)

{
  me(_uid_: 0x01) {
    friends (first: 10) @filter(
        (anyOf("type.object.name.en", "john", "snow") &&
        ge("born.on", 1990) && lt("born.on", 2000)) || gt(_count_(child), 2)) {

      type.object.name.en
      born.on
      relatives @filter(near("home.geolocation", 12.43, -53.211, 10)) {
        name
      }
    }
  }
}
1 Like

They do request the relatives of the friends, and the names of those relatives. Also, note that you don’t necessarily have to request the same predicates as you use in the filter. You could, but you don’t have to.

I feel that if you have && ||, people might expect = < >?

I think they might initially, but they will get used to it pretty fast. If we have a cleaner way to give them all those special character ops, we could consider them; but I can’t think of any. The one I propose above (the last one) is more on the lines of we shouldn’t do this.

I feel this

{
  me(_uid_: 0x01) {
    friends (first: 10) @filter(
        anyOf("type.object.name.en", "john", "snow")
        ge("born.on", 1990)
        lt("born.on", 2000) )
        @filter(gt(_count_(child), 2)) {

      relatives @filter (near("home.geolocation", {lat: 12.43, lon: -53.211, rad: 10k}) {
        name
      }
    }
  }
}

looks pretty good and also close to GraphQL as we discussed the other day. Just using @filter on the parent level compared to _filter_ in the previous design.

Wonder if we have come to a conclusion on this?

The work on query, string matching is blocked by this. (I will work on tokenizer and other stuff while I wait for this to be resolved.)

To unblock, we don’t have to fully resolve the design. All I need to know is whether how exact matching looks like. Will it look like @filter or filter {}. Something very simple for exact matching would suffice for me, at this point. Thanks.

Let’s go with this one. It ensures there’s only one filter directive, while also providing boolean functions and a way to connect them together via ANDs, ORs and brackets.

In this syntax how would you type the query:

Find all films with genre “Musical drama” where the actor is “Angelina Jolie”

Umm yeah, multi level fiters would be required. One way would be:

{
  debug(_xid_: <angelina-joulie>) {
    film.actor.film {
      film.performance.film @filter( {
      film.film.genre @filter ( equal(type.object.name.en, "Musical drama"  )
    })
      {
        type.object.name.en
      }
    }
  }
}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.