Graphql-engine: GraphQL schema generation creates Array Relationships as NON_NULL

Created on 13 Nov 2020  路  3Comments  路  Source: hasura/graphql-engine

I'll preface this by acknowledging this may be "by design."

Please consider the following scenario with two tables that have an array relationship based on a foreign key.

Table 1: author (id uuid, name text)
Table 2: article (id uuid, title text, author_id uuid); Foriegn Key (author_id -> author.id)

Table 1 (author) Relationships-> Array Relationships -> articles: article.author_id -> author.id

In this case, the author may have articles, or may not. If I were writing the schema by hand, I'd probably write something like this:

type author: {
    id: uuid!
    name: String!
    articles: [article]
}   

type article: {
    id: uuid!
    title: String!
    author_id: uuid!
}

However, generated schema is (essentially):

type author: {
    id: uuid!
    name: String!
    articles: [article!]! //<-- NON_NULL
    article_aggregate: article_aggregate!  //<-- NON_NULL
}
//... etc

This creates a challenge when using a tool like graphql-codegen, because the resulting typescript types will include these additional fields as mandatory and non-null on the type:

export type Author = {
  __typename?: 'author';
  articles: Array<Article>;
  article_aggregate: Article_Aggregate;
  id: Scalars['uuid'];
  name: Scalars['String'];
};

When in practice this may be more appropriate:

export type Article = {
  __typename?: 'article';
  articles?: Maybe<Array<articles>>; //<-- Optional
  article_aggregate?: Maybe<Article_Aggregate>; //<-- Optional
  id: Scalars['uuid'];
  name: Scalars['String'];
};

As a result, in order to use the generated type, the gql statements used to generate the operations must include the array relationship (and aggregate nodes). If it does not, then it will not be possible to pass the results of an operation back to a variable or parameter with the type.

query InvalidAuthorQuery {
    article {
        id
        name
    } 
}  //<-- An operation generated from this query couldn't be passed to a variable typed as "Author"

query ValidAuthorQuery {
    article {
        id
        name
        articleContributors {
            article_id
            contributor
        }
        articleContributors_aggregate {
            nodes {
                article_id
                contributor             
            }
        }
    }
} //<-- This one works!

This feels a bit wrong, as:

  • Some authors may not articles
  • You may be trying to create a query that doesn't care about articles, resulting in extra data fetching

There are of course some work arounds:

  • Modify the types created by graphql-codgen after each run to make them optional
  • Write gql statements that include all of the NON_NULL fields, even if it is extra verbose and fetches extra data

However, I have to wonder:

  • Is there something I'm missing in the metadata that would turn this into an optional array?
  • If not, would it make more sense for this to be generated allowing the array relationships and aggregates to be null / undefined?
question

Most helpful comment

@0x777 - Thank you for your response. You showed me a gap in my understanding of the GraphQL Spec, and I agree [T] would not have been correct, because an empty list [] would be more appropriate than a null.

Looking at "Combining List and Non-Null", is see:

For example if the inner item type of a List is Non鈥怤ull (e.g. [T!]), then that List may not contain any null items. However if the inner type of a Non鈥怤ull is a List (e.g. [T]!), then null is not accepted however an empty list is accepted.

I infer that [T!]! does not accept an empty list, because it explicitly states [T]! accepts an empty list. Unfortunately, the case of an empty list is not in the examples in the spec, so I'll assume I'm coming to an incorrect conclusion on [T!]!

@GavinRay97 - Thanks for this tip! I'll investigate these libraries as well.

It makes the rest of this academic / educational only, so I'll close this issue as it's probably not a good use of both of your time to discuss further.

Again, thank you both for your responses - they were very helpful.

All 3 comments

the author may have articles, or may not

This contract is correctly captured by [article!]!. An author not having any articles should be an empty list [] rather than null and [] is a valid value for [article!]!. If it were [article] both null and [] are valid values for the type. What do they represent? Do they both represent 'an author not having articles'? When does the server return null and when does it return []?

As a side-note, one of the challenges you face with graphql-codegen is that there are a theoretically near-infinite number of permutations of query filters + selection sets you could write as individual operations.

There are other libraries which do dynamic type inference based on selection-sets in the expression and have <typename>_input argument types (which you can use for these nullable fields).

https://github.com/graphql-editor/graphql-zeus
https://github.com/remorses/genql

What I mean by this is, here I have a subscription and you can see the return type is dynamically inferences from the operation and my selection set:

dynamic-gql-types

This approach lets you skip manually writing individual query operations and includes the input types needed for this usecase.

@0x777 - Thank you for your response. You showed me a gap in my understanding of the GraphQL Spec, and I agree [T] would not have been correct, because an empty list [] would be more appropriate than a null.

Looking at "Combining List and Non-Null", is see:

For example if the inner item type of a List is Non鈥怤ull (e.g. [T!]), then that List may not contain any null items. However if the inner type of a Non鈥怤ull is a List (e.g. [T]!), then null is not accepted however an empty list is accepted.

I infer that [T!]! does not accept an empty list, because it explicitly states [T]! accepts an empty list. Unfortunately, the case of an empty list is not in the examples in the spec, so I'll assume I'm coming to an incorrect conclusion on [T!]!

@GavinRay97 - Thanks for this tip! I'll investigate these libraries as well.

It makes the rest of this academic / educational only, so I'll close this issue as it's probably not a good use of both of your time to discuss further.

Again, thank you both for your responses - they were very helpful.

Was this page helpful?
0 / 5 - 0 ratings