neo4j / graphql

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations.
https://neo4j.com/docs/graphql-manual/current/
Apache License 2.0
508 stars 149 forks source link

Variable-length relationship pattern matching #596

Open AccsoSG opened 2 years ago

AccsoSG commented 2 years ago

Use case: I have nodes of the same type (label) that can have an "IS_EQUIVALENT_TO" relationship with each other. For this I would like to model an "equivalent" field in the GraphQL schema. When creating such "equivalents" a unidirectional relationship is created. So far, so good. However, only this one direction is taken into account in a query.

Here is my example:

Extract from the scheme

type IdentifierType {
  id: ID! @id
  name: String
  equivalents: [IdentifierType] @relationship(type: "IS_EQUIVALENT_TO", direction: OUT)
}

Mutation to create the nodes:

mutation {
  createIdentifierTypes(input: {
    name: "Test1", 
    equivalents: {
      create: {
        node: {
          name: "Test2"
        }
      }
    }
  }) {
    identifierTypes {
      id
      equivalents {
        id
        equivalents {
          id
        }
      }
    }
  }
}

Created nodes: image

Mutation response:

{
  "data": {
    "createIdentifierTypes": {
      "identifierTypes": [
        {
          "id": "8ab86b3b-7eec-49eb-9682-9fc22d37fb52",
          "equivalents": [
            {
              "id": "58d3ca1d-6395-40cb-ab8d-76d9d4127410",
              "equivalents": [
              ]
            }
          ]
        }
      ]
    }
  }
}

Describe the solution you'd like A way to ignore the direction of the relationship to get the following result:

{
  "data": {
    "createIdentifierTypes": {
      "identifierTypes": [
        {
          "id": "8ab86b3b-7eec-49eb-9682-9fc22d37fb52",
          "equivalents": [
            {
              "id": "58d3ca1d-6395-40cb-ab8d-76d9d4127410",
              "equivalents": [
                {
                  "id": "8ab86b3b-7eec-49eb-9682-9fc22d37fb52"
                }
              ]
            }
          ]
        }
      ]
    }
  }
}

Further considerations: It could also be a use case to query all "equivalents" recursively. For example, the following nodes are in the database: image Now I want to recursively get all connected nodes (in both directions). Maybe circular relationships are a challenge here?

darrellwarde commented 2 years ago

Hey @AccsoSG, thanks for raising! Although in slightly different contexts, we already have this tracked in #142 and #353, but the principle remains the same.

Further considerations: It could also be a use case to query all "equivalents" recursively. For example, the following nodes are in the database: image Now I want to recursively get all connected nodes (in both directions). Maybe circular relationships are a challenge here?

This, however, we have eyes on internally, but I don't believe is tracked in a GitHub issue. Really this is describing variable length relationship matching, so if you don't mind, I will re-title your issue just to focus on that given that the other feature request is already tracked elsewhere? 🙂

AccsoSG commented 2 years ago

Hi @darrellwarde , Thanks for the feedback. Feel free to rename the issue.

nielsdejong commented 2 years ago

Just to chime in, this would be a great feature to have! Spoke to several users that are looking for this functionality.

barbinbrad commented 1 year ago

Just wanted to upvote this idea. From my perspective, a variable length GraphQL query would make this library extremely valuable.

Assuming that we can't represent a variably recursive mutation in GraphQL notation, are there any other ways to get a tree-like data structure from the database?

type Item = {
  id: string;
  children?: Item[]
}
jrsperry commented 1 year ago

I would also add that a variable length relationship matching would be incredibly valuable. In my graphs currently I've had to define "variable" length relationships within my graph in a strict manner.

For example: A relationship (IS_FRIEND_WITH3) which represents within 3 hops of the (IS_FRIEND_WITH), adding many relationships to a graph as each person would have many relationships connecting it to all of their friends, their friends of friends (2 hops), and their friends of friends of friends (3 hops).

The benefit of this IS_FRIEND_WITH3 relationship is that you could find a Person where any of their "loosely connected" friends (friends, friends of friends, and friends of friends of friends) satisfy a condition. Without this relationship, you'd have to have a large OR statement representing each hop.

Current Example Schema:

type Person {
  name: String
  friends: [Person!]! @relationship(type: "IS_FRIEND_WITH", direction: OUT)
  friends3: [Person!]! @relationship(type: "IS_FRIEND_WITH3", direction: OUT)
}

query with IS_FRIEND_WITH3 relationship

query {
  persons(where: {friends3_SOME: {name: "Sam"} } ) {
  ...

query without the IS_FRIEND_WITH3 relationship

query {
  persons(where: {
  OR: [
   { friends_SOME: {name: "Sam"} }
  { friends_SOME: {friends_SOME: {name: "Sam"} } },
  { friends_SOME: {friends_SOME: {friends_SOME: {name: "Sam"} } } }
]} ) ...

It'd be help out the cleanliness of the graph a lot to be able to just define these number of hops in the schema, for example:

type Person {
  id: ID! @id
  name: String
  friends: [Person!]! @relationship(type: "IS_FRIEND_WITH", direction: OUT)
  friends3: [Person!]! @relationship(type: "IS_FRIEND_WITH",  numberHops: 3, direction: OUT)
}

Where numberHops would represent up to 3 hops (a variable length relationship of IS_FRIEND_WITH*1..3)

It'd be even more amazing if you provide the number of hops in the query such as

query {
  persons(where: {friends*1..3: {name: "Sam"} } ) {
  ...
barbinbrad commented 1 year ago

I would also add that a variable length relationship matching would be incredibly valuable. In my graphs currently I've had to define "variable" length relationships within my graph in a strict manner.

For example: A relationship (IS_FRIEND_WITH3) which represents within 3 hops of the (IS_FRIEND_WITH), adding many relationships to a graph as each person would have many relationships connecting it to all of their friends, their friends of friends (2 hops), and their friends of friends of friends (3 hops).

The benefit of this IS_FRIEND_WITH3 relationship is that you could find a Person where any of their "loosely connected" friends (friends, friends of friends, and friends of friends of friends) satisfy a condition. Without this relationship, you'd have to have a large OR statement representing each hop.

Current Example Schema:

type Person {
  name: String
  friends: [Person!]! @relationship(type: "IS_FRIEND_WITH", direction: OUT)
  friends3: [Person!]! @relationship(type: "IS_FRIEND_WITH3", direction: OUT)
}

query with IS_FRIEND_WITH3 relationship

query {
  persons(where: {friends3_SOME: {name: "Sam"} } ) {
  ...

query without the IS_FRIEND_WITH3 relationship

query {
  persons(where: {
  OR: [
   { friends_SOME: {name: "Sam"} }
  { friends_SOME: {friends_SOME: {name: "Sam"} } },
  { friends_SOME: {friends_SOME: {friends_SOME: {name: "Sam"} } } }
]} ) ...

It'd be help out the cleanliness of the graph a lot to be able to just define these number of hops in the schema, for example:

type Person {
  id: ID! @id
  name: String
  friends: [Person!]! @relationship(type: "IS_FRIEND_WITH", direction: OUT)
  friends3: [Person!]! @relationship(type: "IS_FRIEND_WITH",  numberHops: 3, direction: OUT)
}

Where numberHops would represent up to 3 hops (a variable length relationship of IS_FRIEND_WITH*1..3)

It'd be even more amazing if you provide the number of hops in the query such as

query {
  persons(where: {friends*1..3: {name: "Sam"} } ) {
  ...

100% -- this is what I was getting at. Thanks for the limits and suggestions.

angrykoala commented 1 year ago

A proposal design for this will be discussed in https://github.com/neo4j/graphql/discussions/3182

btroop commented 7 months ago

any update on this?