Neo4j-graphql-js: [BUG] Nested ordering errors

Created on 7 Jul 2019  路  11Comments  路  Source: neo4j-graphql/neo4j-graphql-js

When i try to order the values of a related entity it throws an error:

query {
  Chat {
    id
    messages(orderBy: [sentAt_desc]) {
      text
      sentAt {
        year
        month
        day
      }
      sender {
        id
        name
      }
    }
  }
}

Get this error:

Failed to invoke function `apoc.coll.sortMulti`: Caused by: java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.Comparable

We are using:

  • apollo-server: 2.6.4
  • neo4j-driver: 1.7.4
  • neo4j-graphql-js: 2.6.3
Priority Bug Fix

Most helpful comment

Thanks for pinging this @g3rd - will try to address it this week.

I suppose a temporary workaround might be to store an additional property that is the epoch milliseconds version of the timestamp as a Long and use that for ordering instead of the DateTime field?

All 11 comments

Could you share your GraphQL type definitions so I can try to reproduce? (or at least the Chat and message types).

I'm assuming sentAt is a Date type?

sentAt is a DateTime type, here are the types definitios for Chat and Message:

type Chat {
  id: String!
  checkIn: DateTime
  checkOut: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!
  roomNumber: Int!
  owner: User! @relation(name: "OWNS", direction: "IN")
  messages: [Message] @relation(name: "BELONGS_TO", direction: "IN")
  hotel: Hotel @relation(name: "BELONGS_TO", direction: "OUT")
}

type Message {
  id: String!
  text: String!
  sentAt: DateTime!
  sender: User! @relation(name: "SENT", direction: "IN")
  chat: Chat! @relation(name: "BELONGS_TO", direction: "OUT")
}

I'm facing the same problem with a DateTime field. Any news on this?

Hey, we are also dealing with this issue. We would like to refactor our code base to use Temporal values like DateTime, but ran into an issue when we try to use orderBy: createdAt_asc for nested attributes like comments on a Post

Post(id: "p15") {
  comments(orderBy: createdAt_asc) {
    content
    createdAt {
      formatted
    }
  }
}

In our case, the neo4j-graphql-js generated query look like this

MATCH (`post`:`Post` {id:$id, deleted:$deleted, disabled:$disabled}) WITH `post` ORDER BY post.createdAt DESC RETURN `post` { .deleted , .disabled , .id , .id , .title , .content , .contentExcerpt ,createdAt: { formatted: toString(`post`.createdAt) }, .disabled , .deleted , .slug , .image ,author: head([(`post`)<-[:`WROTE`]-(`post_author`:`User`) | post_author { .id , .slug , .name , .avatar , .disabled , .deleted ,shoutedCount: apoc.cypher.runFirstColumn("MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)", {this: post_author, cypherParams: $cypherParams}, false),contributionsCount: apoc.cypher.runFirstColumn("MATCH (this)-[: WROTE]->(r: Post)
WHERE NOT r.deleted = true AND NOT r.disabled = true
RETURN COUNT(r)", {this: post_author, cypherParams: $cypherParams}, false),commentedCount: apoc.cypher.runFirstColumn("MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))", {this: post_author, cypherParams: $cypherParams}, false),followedByCount: apoc.cypher.runFirstColumn("MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)", {this: post_author, cypherParams: $cypherParams}, false),followedByCurrentUser: apoc.cypher.runFirstColumn("MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1", {this: post_author, cypherParams: $cypherParams}, false),location: head([ post_author_location IN apoc.cypher.runFirstColumn("MATCH (this)-[: IS_IN]->(l: Location) RETURN l", {this: post_author, cypherParams: $cypherParams}, true) | post_author_location { .nameEN }]) ,badges: [(`post_author`)<-[:`REWARDED`]-(`post_author_badges`:`Badge`) | post_author_badges { .id , .icon }] }]) ,tags: [(`post`)-[:`TAGGED`]->(`post_tags`:`Tag`) | post_tags { .id }] ,categories: [(`post`)-[:`CATEGORIZED`]->(`post_categories`:`Category`) | post_categories { .id , .name , .icon }] ,commentsCount: apoc.cypher.runFirstColumn("MATCH (this)<-[: COMMENTS]-(r: Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)", {this: post, cypherParams: $cypherParams}, false),shoutedCount: apoc.cypher.runFirstColumn("MATCH (this)<-[: SHOUTED]-(r: User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)", {this: post, cypherParams: $cypherParams}, false),shoutedByCurrentUser: apoc.cypher.runFirstColumn("MATCH (this)<-[: SHOUTED]-(u: User { id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1", {this: post, cypherParams: $cypherParams}, false),emotionsCount: apoc.cypher.runFirstColumn("MATCH (this)<-[emoted: EMOTED]-(: User) RETURN COUNT(DISTINCT emoted)", {this: post, cypherParams: $cypherParams}, false),comments: [sortedElement IN apoc.coll.sortMulti([(`post`)<-[:`COMMENTS`]-(`post_comments`:`Comment`) | post_comments { .deleted , .disabled , .id , .id ,createdAt: `post_comments`.createdAt, .disabled , .deleted , .content , .contentExcerpt ,author: head([(`post_comments`)<-[:`WROTE`]-(`post_comments_author`:`User`) | post_comments_author { .id , .slug , .name , .avatar , .disabled , .deleted ,shoutedCount: apoc.cypher.runFirstColumn("MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)", {this: post_comments_author, cypherParams: $cypherParams}, false),contributionsCount: apoc.cypher.runFirstColumn("MATCH (this)-[: WROTE]->(r: Post)
WHERE NOT r.deleted = true AND NOT r.disabled = true
RETURN COUNT(r)", {this: post_comments_author, cypherParams: $cypherParams}, false),commentedCount: apoc.cypher.runFirstColumn("MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))", {this: post_comments_author, cypherParams: $cypherParams}, false),followedByCount: apoc.cypher.runFirstColumn("MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)", {this: post_comments_author, cypherParams: $cypherParams}, false),followedByCurrentUser: apoc.cypher.runFirstColumn("MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1", {this: post_comments_author, cypherParams: $cypherParams}, false),location: head([ post_comments_author_location IN apoc.cypher.runFirstColumn("MATCH (this)-[: IS_IN]->(l: Location) RETURN l", {this: post_comments_author, cypherParams: $cypherParams}, true) | post_comments_author_location { .nameEN }]) ,badges: [(`post_comments_author`)<-[:`REWARDED`]-(`post_comments_author_badges`:`Badge`) | post_comments_author_badges { .id , .icon }] }]) }], ['^createdAt']) | sortedElement { .*,  }] } AS `post`

and the error thrown in the console is

Error GraphQL error: Invalid input '}': expected whitespace, comment, literal entry, property selector, variable selector or all properties selector (line 7, column 512 (offset: 4643))
"RETURN COUNT(u) >= 1", {this: post_comments_author, cypherParams: $cypherParams}, false),location: head([ post_comments_author_location IN apoc.cypher.runFirstColumn("MATCH (this)-[: IS_IN]->(l: Location) RETURN l", {this: post_comments_author, cypherParams: $cypherParams}, true) | post_comments_author_location { .nameEN }]) ,badges: [(`post_comments_author`)<-[:`REWARDED`]-(`post_comments_author_badges`:`Badge`) | post_comments_author_badges { .id , .icon }] }]) }], ['^createdAt']) | 
sortedElement { .*,  }] } AS `post`"
                     ^

Here is our Post and Comment schema in case it's helpful for anyone

type Post {
  id: ID!
  activityId: String
  objectId: String
  author: User @relation(name: "WROTE", direction: "IN")
  title: String!
  slug: String
  content: String!
  contentExcerpt: String
  image: String
  imageUpload: Upload
  visibility: Visibility
  deleted: Boolean
  disabled: Boolean
  disabledBy: User @relation(name: "DISABLED", direction: "IN")
  createdAt: DateTime
  updatedAt: String
  language: String
  relatedContributions: [Post]!
  @cypher(
  statement: """
  MATCH (this)-[: TAGGED|CATEGORIZED]->(categoryOrTag)<-[: TAGGED|CATEGORIZED]-(post: 
  Post)
  WHERE NOT post.deleted AND NOT post.disabled
  RETURN DISTINCT post
  LIMIT 10
  """
  )

  tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
  categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")

  comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
  commentsCount: Int!
  @cypher(
  statement: "MATCH (this)<-[: COMMENTS]-(r: Comment) WHERE NOT r.deleted = true AND NOT 
  r.disabled = true RETURN COUNT(DISTINCT r)"
  )

  shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
  shoutedCount: Int!
  @cypher(
  statement: "MATCH (this)<-[: SHOUTED]-(r: User) WHERE NOT r.deleted = true AND NOT 
  r.disabled = true RETURN COUNT(DISTINCT r)"
  )

  # Has the currently logged in user shouted that post?
  shoutedByCurrentUser: Boolean!
  @cypher(
  statement: "MATCH (this)<-[: SHOUTED]-(u: User { id: $cypherParams.currentUserId}) RETURN 
  COUNT(u) >= 1"
  )

  emotions: [EMOTED]
  emotionsCount: Int!
  @cypher(statement: "MATCH (this)<-[emoted: EMOTED]-(: User) RETURN COUNT(DISTINCT 
  emoted)")
}

input _PostInput {
  id: ID!
}

type Mutation {
  CreatePost(
  id: ID
  title: String!
  slug: String
  content: String!
  image: String
  imageUpload: Upload
  visibility: Visibility
  language: String
  categoryIds: [ID]
  contentExcerpt: String
  ): Post
  UpdatePost(
  id: ID!
  title: String!
  slug: String
  content: String!
  contentExcerpt: String
  image: String
  imageUpload: Upload
  visibility: Visibility
  language: String
  categoryIds: [ID]
  ): Post
  DeletePost(id: ID!): Post
  AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
  RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
}
type Query {
  PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
  PostsEmotionsByCurrentUser(postId: ID!): [String]
}
type Comment {
  id: ID!
  activityId: String
  author: User @relation(name: "WROTE", direction: "IN")
  content: String!
  contentExcerpt: String
  post: Post @relation(name: "COMMENTS", direction: "OUT")
  createdAt: DateTime
  updatedAt: String
  deleted: Boolean
  disabled: Boolean
  disabledBy: User @relation(name: "DISABLED", direction: "IN")
}

type Mutation {
  CreateComment(
  id: ID
  postId: ID!
  content: String!
  contentExcerpt: String
  deleted: Boolean
  disabled: Boolean
  ): Comment
  UpdateComment(
  id: ID!
  content: String!
  contentExcerpt: String
  deleted: Boolean
  disabled: Boolean
  ): Comment
  DeleteComment(id: ID!): Comment
}

Wanna say a big thank you to @johnymontana and the other contributors to this library.
Maybe, we can help come to a solution for this issue.

Has anyone found a workaround or fix for this issue?

Thanks for pinging this @g3rd - will try to address it this week.

I suppose a temporary workaround might be to store an additional property that is the epoch milliseconds version of the timestamp as a Long and use that for ordering instead of the DateTime field?

I am facing the same problem on spatial Point as well.

Error-report.pdf

Thanks for pinging this @g3rd - will try to address it this week.

I suppose a temporary workaround might be to store an additional property that is the epoch milliseconds version of the timestamp as a Long and use that for ordering instead of the DateTime field?

The proposed workaround works! Thanks!

This issue still occurs in 2.16.3 with nested requests such as:

query ($id: ID!, $first: Int!, $offset: Int!) {
  User(id: $id) {
    posts(first: $first, offset: $offset, orderBy: createdAt_desc) {
      id
    }
  }
}

This issue still occurs in 2.16.3 with nested requests such as:

query ($id: ID!, $first: Int!, $offset: Int!) {
  User(id: $id) {
    posts(first: $first, offset: $offset, orderBy: createdAt_desc) {
      id
    }
  }
}

Hi
Make sure you have installed the APOC library first

You can follow the instructions here : https://neo4j.com/labs/apoc/4.1/installation/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benpetsch picture benpetsch  路  3Comments

robmurtagh picture robmurtagh  路  3Comments

vijayst picture vijayst  路  5Comments

bebbi picture bebbi  路  3Comments

purplemana picture purplemana  路  3Comments