neo4j-graphql / neo4j-graphql-js

NOTE: This project is no longer actively maintained. Please consider using the official Neo4j GraphQL Library (linked in README).
Other
609 stars 148 forks source link

Wrong generated query when using interfaces #548

Open nardo7 opened 3 years ago

nardo7 commented 3 years ago

Bug

Hi guys, I want to report what I think it's a bug which is not letting us go forward with our graphql API using your framework.

Our project uses the same version as in the starter project: neo4j-graphql-js 2.17.0

I'm experiencing some troubles with your framework, receiving wrong data in some queries which were working well until I started to use interfaces. After watching the generated cypher queries I figured out that the interface label is generated as variable name instead of generating the variable name of the type which implements the interface.

I tried to reproduce the problem with the grandstack starter project.

Schema

interface Entity {
  name: String!
  descriptions:[DescriptionRel]
}

type Description {
  DescriptionID:ID! 
  description:String
  created_at:Date
  entity: DescriptionRel
}

type DescriptionRel @relation(name: "DESCRIPTION") {
  lang:String
  from: Entity
  to: Description
}

type Business implements Entity {
  businessId: ID!
  address: String
  name: String!
  descriptions:[DescriptionRel]
  city: String
  state: String
  location: Point
  avgStars: Float
    @cypher(
      statement: "MATCH (this)<-[:REVIEWS]-(r:Review) RETURN coalesce(avg(r.stars),0.0)"
    )
  reviews: [Review] @relation(name: "REVIEWS", direction: "IN")
  categories: [Category] @relation(name: "IN_CATEGORY", direction: "OUT")
}

type User {
  userId: ID!
  name: String
  reviews: [Review] @relation(name: "WROTE", direction: "OUT")
  avgStars: Float
    @cypher(
      statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN toFloat(avg(r.stars))"
    )
  numReviews: Int
    @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
  recommendations(first: Int = 3): [Business]
    @cypher(
      statement: "MATCH (this)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Business)<-[:REVIEWS]-(:Review)<-[:WROTE]-(:User)-[:WROTE]->(:Review)-[:REVIEWS]->(rec:Business) WHERE NOT EXISTS( (this)-[:WROTE]->(:Review)-[:REVIEWS]->(rec) ) WITH rec, COUNT(*) AS num ORDER BY num DESC LIMIT $first RETURN rec"
    )
}

type Review {
  reviewId: ID!
  stars: Float
  text: String
  date: Date
  business: Business @relation(name: "REVIEWS", direction: "OUT")
  user: User @relation(name: "WROTE", direction: "IN")
}

type Category {
  name: ID!
  businesses: [Business] @relation(name: "IN_CATEGORY", direction: "IN")
}

type RatingCount {
  stars: Float!
  count: Int!
}

type Mutation {
  mergeBusinessCategory(categories: [String!]!, businessId: ID!): Business
    @cypher(
      statement: "MATCH (b:Business {businessId: $businessId}) UNWIND $categories AS cat MERGE (c:Category {name: cat}) MERGE (b)-[:IN_CATEGORY]->(c) RETURN b"
    )
}

type Query {
  userCount: Int! @cypher(statement: "MATCH (u:User) RETURN COUNT(u)")
  ratingsCount: [RatingCount]
    @cypher(
      statement: "MATCH (r:Review) WITH r.stars AS stars, COUNT(*) AS count ORDER BY stars RETURN {stars: stars, count: count}"
    )
}

Graphql Query

After creating this dumm elements

mutation create{
  CreateBusiness(businessId:999999,name:"test"){
    businessId
    name
  }
  CreateDescription(DescriptionID:1,description:"hello world"){
    DescriptionID
    description
  }
  AddDescriptionEntity(from:{name:"test"},to:{DescriptionID:1},data:{lang:"en"}){
    from{
      name
    }
    to{
      DescriptionID
      DescriptionID
    }
    lang
  }
}

This is the query I need to work

query bussiness{
  Business(businessId:999999){
    businessId
    descriptions(filter:{
      OR:[{lang:"de"},
      {Description:{description_not:""}}
      ]
      }){
      lang
      Description{
        description
      }
    }
  }
}

The generated Query

MATCH (`business`:`Business` {businessId:$businessId}) RETURN `business` 
{ .businessId ,

descriptions: [(`business`)-[`business_descriptions_relation`:`DESCRIPTION`]->(:`Description`) WHERE 
(ANY(_OR IN $1_filter.OR WHERE 
(_OR.lang IS NULL OR `business_descriptions_relation`.lang = _OR.lang) AND 
(_OR.Description IS NULL OR ALL(`description` IN 
**[(`entity`)-[`entity_filter_description`]->(`_description`:Description) | `_description`]** WHERE 
(_OR.Description.description_not IS NULL OR NOT `description`.description =  _OR.Description.description_not))))) | 
business_descriptions_relation { 
    .lang ,Description: head([(:`Entity`)-[`business_descriptions_relation`]->(`business_descriptions_Description`:`Description`) | 
    business_descriptions_Description { .DescriptionID , .description }]) }] } AS `business`

I think the part [(`entity`)-[`entity_filter_description`]->(`_description`:Description) | `_description`] is wrong, because:

  1. It retrieves every node from the database which has a connection with every Description
  2. In my real case, I have thousands of nodes which fill this part of the query query, therefore this query doesn't returns what I want
  3. In the part of the query (`entity`) should be generated (`business`) instead.

I tested it myself replacing the entity for the real label and it worked as I expected.

What can I do? Am I missing something here?

Thank for your work and for your help

gabriellovric commented 3 years ago

Hey I experienced similar problems and wrote a fix for this in #527.

I would be happy to hear if this works for you too.

nardo7 commented 3 years ago

Hi @gabriellovric Thanks for your help. I’ll look at it for sure! I’ll let you know

nardo7 commented 3 years ago

Hi @gabriellovric It doesn't resolve my issue. I think your fix isn't related with this, since the function you modified isn't called at all when I run the query example. Thanks for your help

michaeldgraham commented 3 years ago

https://github.com/neo4j-graphql/neo4j-graphql-js/issues/608