graphql icon indicating copy to clipboard operation
graphql copied to clipboard

Incorrect cypher for `_SOME` filters on relationships to unions

Open Liam-Doodson opened this issue 1 year ago • 2 comments

Type definitions

union Production = Movie | Series

type Movie {
    title: String!
    cost: Float
    runtime: Int
    actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Series {
    title: String!
    episodes: Int
    actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Actor {
    name: String!
    actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn")
}

type ActedIn @relationshipProperties {
    screenTime: Int
}

Test data

CREATE(a:Actor{name:"Some Name"})
CREATE(:Movie{title:"The Office"})<-[:ACTED_IN]-(a)
CREATE(:Series{title:"The Office"})<-[:ACTED_IN]-(a)

Steps to reproduce

Run this query:

query actedInWhere {
    actors(
        where: {
            actedIn_SOME: { Movie: { title_CONTAINS: "The Office" }, Series: { title_ENDS_WITH: "Office" } }
        }
    ) {
        name
    }
}

What happened

The actor with name="Some Name" is returned.

Expected behaviour

The query suggests that "some" productions the actor has acted in should be both a Series and a Movie. This is not the case for this actor so it should not have been returned.

The following cypher is generated for this query:

MATCH (this:Actor)
WHERE (EXISTS {
    MATCH (this)-[:ACTED_IN]->(this0:Movie)
    WHERE this0.title CONTAINS $param0
} AND EXISTS {
    MATCH (this)-[:ACTED_IN]->(this1:Series)
    WHERE this1.title ENDS WITH $param1
})
RETURN this { .name } AS this

As you can see, the AND is applied outside the EXISTS.

However, I would have expected cypher along these lines (note this is sudo code - I think the labels syntax is probably wrong):

MATCH (this:Actor)
WHERE (EXISTS {
    MATCH (this)-[:ACTED_IN]->(this0:Movie|Series)
    WHERE (this0.title CONTAINS $param0 AND label(this0) = "Movie") AND (this0.title ENDS WITH $param1 AND label(this0) = "Series")
}
RETURN this { .name } AS this

Version

4.4.5

Database version

5.13

Relevant log output

No response

Liam-Doodson avatar Feb 13 '24 13:02 Liam-Doodson

Many thanks for raising this bug report @Liam-Doodson. :bug: We will now attempt to reproduce the bug based on the steps you have provided.

Please ensure that you've provided the necessary information for a minimal reproduction, including but not limited to:

  • Type definitions
  • Resolvers
  • Query and/or Mutation (or multiple) needed to reproduce

If you have a support agreement with Neo4j, please link this GitHub issue to a new or existing Zendesk ticket.

Thanks again! :pray:

neo4j-team-graphql avatar Feb 13 '24 13:02 neo4j-team-graphql

We've been able to confirm this bug using the steps to reproduce that you provided - many thanks @Liam-Doodson! :pray: We will now prioritise the bug and address it appropriately.

neo4j-team-graphql avatar Feb 13 '24 13:02 neo4j-team-graphql

Because unions are inherently separate entities (not supposed to be shared, that would be interfaces) a query asking an entity to be both items of an union would be incorrect

As such, filters on unions are treated as independent queries by convention

SOME { Movie... , Series...} is treated as SOME Movie AND SOME Series

This can be confusing, as the counterargument you mention is valid, but as this is the current convention, which makes the API more usable, for now I'd keep at as is and close this issue

angrykoala avatar Mar 06 '25 16:03 angrykoala

Actually, after reviewing more carefully, @Liam-Doodson is right, these kind of operations can be confusing and the logic should be changed

I'm going to close this issue in favor of https://github.com/neo4j/graphql/issues/5940 as a duplicate

angrykoala avatar Mar 07 '25 09:03 angrykoala