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
608 stars 148 forks source link

Expose arguments of relation directive to schema introspection #216

Open calendardays opened 5 years ago

calendardays commented 5 years ago

The @relation directive is useful for taking relations in an underlying Neo4j database and encoding them in a GraphQL schema. I'd like to be able to derive the type names (labels) of relations in the database from a schema object, as well as the relation direction. This seems like a natural thing to do, because the type names and directions are passed as arguments to the @relation directive.

For example, part of a GraphQL schema might be:

type Person {
    name: String!
}

type Book {
    title: String! 
    Editor: [Person] @relation(name: "EDITS", direction: "IN")
    Author: [Person] @relation(name: "AUTHORS", direction: "IN")
}

I can obtain a schema object by doing something like this:

import { introspectionQuery } from 'graphql'

async getSchema(client) {
    const schema = await client.query({ query: introspectionQuery })
    return schema
}

But currently, neither "EDITS" nor "AUTHORS" (in any capitalization pattern) is to be found in the returned schema. Correct me if I'm wrong, but I've confirmed that those strings don't appear in a JSON.stringify printout of the schema object.

My use case:

I'm working on a web-based interface for importing new data, in spreadsheet form, to an underlying Neo4j database, using GraphQL. When a user provides a spreadsheet, I can use introspection on the schema to generate a list of all node types (represented as identically-named GraphQL types) and their properties (fields), so that the user can assign which properties correspond to which columns of data in the sheet. I would also like to be able to automatically generate a list of all the relations in the database so that the user can check boxes for which ones should be created based on the new data. Currently, I can at least generate a list of pairs such as "Book-Person", but I cannot derive the relation names ("Edits" and "Authors") from schema introspection.

If any given pair of node types corresponded to at most one relation type (e.g., every Book-Person relation has type AUTHORS), then this wouldn't be an issue, so I've selected the "Edits" vs. "Authors" example to highlight the problem. A user could come with Column A = titles and Column B = names, and I don't have a good way to help them choose which of these two relation types should be created during data import, because schema introspection doesn't give me access to the terms "Edits" and "Authors".

I've been thinking about this in terms of the @relation field directive. Similar logic might apply to the @relation type directive, or maybe clever naming patterns or extra fields in the schema allow for an easier workaround in that case.

johnymontana commented 5 years ago

Hey @calendardays -

You are correct that GraphQL schema directives are not exposed via introspection, this is part of the GraphQL specification. There is a discussion currently about whether this should be the case.

I wonder though if for your use case inspecting the Neo4j database is what you want, instead of the GraphQL API. You can accomplish this via the call db.schema.nodeTypeProperties() and call db.schema.relTypeProperties() procedures.

calendardays commented 5 years ago

That's an interesting discussion. Even if a spec extension that allows explicitly exposing chosen directives to introspection doesn't materialize, I think I was originally imagining something similar to what @deprecated does, exposing the arguments of the @relation directive by adding properties. For example, the query...

{
    __type (name: "Book") {
        fields {
            name
            type {
                kind
                name
                ofType {
                    kind
                    name
                }
            }
            _isRelation
            _relationName
            _relationDirection
        }
    }
}

...could return...

{
    "data": {
        "__type": {
            "fields": [
                {
                    "name": "title",
                    "type": {
                        "kind": "SCALAR",
                        "name": "String",
                        "ofType": null
                    }
                    "_isRelation": false,
                    "_relationName": null,
                    "_relationDirection": null
                },
                {
                    "name": "Editor",
                    "type": {
                        "kind": "LIST",
                        "name": null,
                        "ofType": {
                            "kind": "OBJECT",
                            "name": "Person",
                        }
                    }
                    "_isRelation": true,
                    "_relationName": "EDITS",
                    "_relationDirection": "IN"
                },
                {
                    "name": "Author",
                    "type": {
                        "kind": "LIST",
                        "name": null,
                        "ofType": {
                            "kind": "OBJECT",
                            "name": "Person",
                        }
                    }
                    "_isRelation": true,
                    "_relationName": "AUTHORS",
                    "_relationDirection": "IN"
                }
            ]
        }
    }
}

That may be a bad idea, but that's what I was picturing.

The operation I was hoping to perform was "given a list of node types, produce a list of all relations in the database that exist among those node types." The information I think I need, then, is a set of correspondences {NodeTypeA, RelationType, NodeTypeB, Direction}.

It looks like those correspondences can be derived from call db.schema.visualization(), which could be put into a @cypher directive... I'm not sure exactly how this would go, but it's an interesting avenue to try.

Thanks!

harshil4076 commented 3 years ago

Does graphql^15.x.x introspection has directives on field level now? @calendardays I have a similar use case for identifying relationships on a type in client. Just wondering if you were able to solve it.

calendardays commented 3 years ago

Hi @harshil4076, I haven't touched this in a long enough that I'd forgotten my own solution. Looking back, it appears I settled on making a very comprehensive (perhaps redundant) schema, with type names chosen to make everything very explicit to the point where I could do parsing and string comparisons on type names. This way, I could get everything I needed by analyzing the schema that pops out of an introspectionQuery.

Partial schema to demonstrate:

type Person {
    # Node Properties
    name: String!

    # Related Nodes
    ThisAuthorsBookNode: [Book] @relation(name: "AUTHORS", direction: "OUT")
    ThisEditsBookNode: [Book] @relation(name: "EDITS", direction: "OUT")

    # Relations
    ThisAuthorsBookRelation: [PersonAuthorsBook]
    ThisEditsBookRelation: [PersonEditsBook]
}

type Book implements Publication & Product {
    # Node Properties
    title: String!

    # Related Nodes
    PersonAuthorsThisNode: [Person] @relation(name: "AUTHORS", direction: "IN")
    PersonEditsThisNode: [Person] @relation(name: "EDITS", direction: "IN")

    # Relations
    PersonAuthorsThisRelation: [PersonAuthorsBook]
    PersonEditsThisRelation: [PersonEditsBook]
}

type PersonAuthorsBook @relation(name: "AUTHORS") {
    from: Person
    to: Book
}

type PersonEditsBook @relation(name: "EDITS") {
    from: Person
    to: Book
}
michaeldgraham commented 3 years ago

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