GraphQL: Connected filter on non-scalar list element fields

Consider the following schema:

type User {
   posts: [Post!] 
}

type Post {
   content: String!
   reactions: [Reaction!]
}

type Reaction {
   type: String!
   user: User!
}

I want to query for a user and filter for his/her posts where the reaction list contains at least a reaction of type “thumbs_up” AND a reaction of type “clap”, such that I receive the full list of reactions for posts that match this nested filter (ie. cascading with a filter on reaction type does not work).

This is likely relate closely to - and might be resolved by - the following proposals, but I’m not positive…(@chewxy?)

There are other list filters that would be immediately useful to me as well…length primarily.

1 Like

I agree with you that this is something that cannot currently be done in a single query with GraphQL. The cascade directive does not solve this use case because you are wanting a AND filter on a child and cascade would only make sure that there is a child not that there are children of each type. A way around this in GraphQL currently would be to use cascade with alias edges. Create an alias for each child that must be included.

However, we currently solve this problem with filtering using deep logic by using DQL (New to DQL?). With DQL, we can create multiple var blocks and then use logic to get resulting nodes by mixing those var blocks.

Isn’t @auth still a problem with DQL?

If by problem you mean that the @auth directive is not supported in DQL and all auth rules are completely ignored when running DQL queries and mutations, then yes.

Our work around for this is to use DQL to get a list of uids that fullfill the filter parameters and then with those uids, get the data using GraphQL.

When there is a will, there is a way :wink: I am all for making my work arounds not needed, but it is just how we learned to cope with some of the limitations in the meantime.

1 Like

Hey @CosmicPangolin1

While we are looking into how could we make this possible, can you share an example of what result do you expect given a dataset? We can possibly look into supporting this in the next quarter once we know what you are looking for?

@pawan Sorry about the delay…I might be able to best illustrate with query examples (which may or may not be accurate in their own right, so correct me if need be).

Query 1

getUser {
   posts @cascade {
      content
      reactions(filter: {
         type: {in: ["thumbs_up", "clap"]}
      }){
         type
         user {name}
      }
   }
}

I think this query gets me only posts that have either thumbs up OR clap reactions. I believe it also sorts out reactions that don’t match the filter.

Query 2

getUser {
   posts @cascade {
      content
      reactions(filter: {type: {eq: "thumbs_up"}}, or: {type: {eq: "clap"}}) {
         type
         user {name}
      }
   }
}

I think this query gets me the same thing as the first.

Query 3

getUser {
   posts @cascade {
      content
      reactions(filter: {type: {eq: "thumbs_up"}}, and: {type: {eq: "clap"}}) {
         type
         user {name}
      }
   }
}

This wouldn’t return anything since the rules are mutually exclusive (as are the values).

So what I want is (I think) is a new input type on connected sets for contains/includes functionality, so I can build a query similar to Query 1 in that it filters for posts with thumbs_up AND/OR clap reactions, but additionally returns all reactions.

The idea is that we often want to have cascading filters on computed properties of a connected set (contains, length, and every are the generic List methods that come to mind), the same way we do for values of singular fields. In my use case, I might have many posts with claps and many posts with thumbs_ups, but only a few with both that I want to retrieve for a client. The functionally equivalent block would be something like:

if (arr.contains((reaction) => reaction.type == "clap") && arr.contains((reaction) => reaction.type == "thumbs_up")) {
   return arr;
}

It might look something like this:

getUser {
   posts @cascade {
      content
      reactions(contains: {type: {eq: "thumbs_up"}}, 
         and/or: {contains: {type: {eq: "clap"}}}
      ) {
         type
         user {name}
      }
   }
}

Hey @CosmicPangolin1

Can you please share a small data set on which I can run this query and the JSON result that you expect this query to run? I think what you are asking for is possible (by Query 3 you mentioned above) but maybe I am missing something.

@pawan I want to make sure you got my private message with the details you’ve requested.

@CosmicPangolin1 see if this does what you are looking for. I have not tested this, and is dependent on cascade working with aliased fields, which I am not confident it does.

query {
  getUser( ... need an id of some sort here ... ) {
     posts @cascade(fields:["thumbs_up","clap"]) {
        content
        thumbs_up: reactions(filter: {
           type: {eq: "thumbs_up" }
        }) @cascade {
           type
        }
        clap: reactions(filter: {
           type: {eq: "clap" }
        }) @cascade {
           type
        }
        reactions {
          type
          user { name }
        }
     }
  }
}

If this works, here is the explanation: We add two additional aliased edges for reactions. These can be ignored by the client, but they are dependent for the top level cascade to work. In these aliased edges, we ask for all of the thumb up reactions in one, and all of the clap reactions in the other. Now all we have to do is use cascade at the post level to filter out the posts that do not have both a thumbs up and a clap. Then we add another edge to reactions to get all of the reactions regardless of the type. The cascade on the aliased edges are to reset the parameterized cascade on these edges so that it is still not filtering out ones reactions that don’t have a thumbs_up and clap fields, which reactions doesn’t have.

If you know that all reactions will have a user and all users will have a name, and you are not getting any more information from the user besides his post at the top level, then you can instead just use a non parameterized cascade at the top level beside getUser.

query {
  getUser( ... need an id of some sort here ... ) @cascade {
    posts {
      content
      thumbs_up: reactions(filter: {
        type: {eq: "thumbs_up" }
      }) {
        type
      }
      clap: reactions(filter: {
        type: {eq: "clap" }
      }) {
        type
      }
      reactions {
        type
        user { name }
      }
    }
  }
}

Oh, and be sure to add in the id filter for getUser. Your initial schema in the OP is invalid with this query though, because there is not any ID or @id field for User, this would not generate the getUser query. Also User does not have a name field. I will assume that it was a partial schema only and not your actual schema.

Hey @CosmicPangolin1 I did and sorry it took long to reply to this. I’ll get this looked at in a couple of days for you.

1 Like

Hi sorry, for thje late reply. I just saw this while going over the old posts related to nested filters.

It’s not possible since Aliases are not allowed inside @cascade, you can only use original field names.