1

I have three type-graphql objects (and typeorm entities): Entry (dictionary headwords), Meaning (meanings of headwords) and Pronunciation (transcriptions of headwords).

There's a many-to-many relationship between Entry and Meaning, and one-to-many between Entry and Pronunciation.

I have the GraphQL query searchEntries which takes a query and returns matching Entry objects with linked Meaning and Pronunciation objects.

In my Entry.ts file, I defined the relationships of Entry like this (@Field() comes from type-graphql):

  @Field(() => [Pronunciation], { nullable: "itemsAndList" })
  @OneToMany(() => Pronunciation, (pronunciation) => pronunciation.entry, {
    nullable: true,
  })
  pronunciations: Relation<Pronunciation[]> | undefined;

  @Field(() => [Meaning], { nullable: "itemsAndList" })
  @ManyToMany(() => Meaning, (meaning) => meaning.entries, { nullable: true })
  meanings: Relation<Meaning[]> | undefined;

So, as you can see, GraphQL should know that fields pronunciations and meanings of Entry are nullable.

However, I'm getting this error (from graphql):

Cannot return null for non-nullable field Pronunciation.id.

I notice that children elements of pronunciations and meanings are still non-nullable: enter image description here enter image description here

Why doesn't GraphQL infer that if the parent element is nullable, its children are nullable as well?

Additional info: I'm fetching the data using typeorm's raw SQL query, and the printed result looks like this:

[
  {
    id: 86,
    headword: 'lightning',
    createdAt: 2023-02-17T07:12:27.825Z,
    updatedAt: 2023-02-17T07:12:27.825Z,
    meanings: [ [Object], [Object], [Object] ],
    pronunciations: [ [Object], [Object], [Object] ]
  }
]

(I'm using JSON_AGG(JSON_BUILD_OBJECT()) for arrays of meanings and pronunciations, and left joins for joining tables.)

Any help would be greatly appreciated!

3
  • Update: the issue was in my raw SQL query with JSON_BUILD_OBJECT() that returns fields like this: [{ id: null, transcription: null, notes: null, userId: null },...] instead of returning a null object. Searching for a workaround Commented Feb 17, 2023 at 13:22
  • Querying for the id of Pronunciation returns null; where in your schema is specified that it cannot be. Commented Feb 18, 2023 at 10:02
  • You have discovered the issue :) Post the answer to your own question :) Commented Feb 18, 2023 at 10:02

1 Answer 1

1

The issue was not in GraphQL, but in PostgreSQL's JSON_BUILD_OBJECT() (which I used in the raw SQL query). (So really, this is a question about PostgreSQL — I will add it to the question title.)

When used with LEFT JOIN, JSON_BUILD_OBJECT() returns an object like this { id: null, transcription: null, notes: null, userId: null }. JSON_AGG() produces an array of these objects, and GraphQL thinks meanings and pronunciations exist, throwing an error trying to access these null fields.

So we need to modify the query. What really helped me was this answer by Mike Stankavich. Using COALESCE and FILTER, the new aggregation looks like this:

SELECT e.*,
  COALESCE(
    JSON_AGG(m.*)
    FILTER (WHERE m.id IS NOT NULL),
    '[]'
  ) meanings,
  COALESCE(
    JSON_AGG(p.*)
    FILTER (WHERE p.id IS NOT NULL),
    '[]'
  ) pronunciations
...

COALESCE returns the first argument that is not null, so it's either the JSON array or []. For JSON_AGG(), which builds the JSON array, FILTER, as Mike wrote, "prevents the aggregate from processing the rows that are null because the left join condition is not met, so you end up with a database null instead of the json [null]."

The GraphQL response for an entry that has meanings but no pronunciations looks something like this:

"searchEntries": [
  {
    "id": 86,
    "headword": "lightning",
    "pronunciations": [],
    "meanings": [
      {
        "id": 59,
        "definition": "a giant spark of electricity in the atmosphere between clouds, the air, or the ground"
      },
      ...]
  }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.