Non-nullable field was not present in result from Dgraph

Report a GraphQL Bug

What edition and version of Dgraph are you using?

Edition:

  • SlashGraphQL
  • Dgraph (community edition/Dgraph Cloud)

If you are using the community edition or enterprise edition of Dgraph, please list the version:

Dgraph Version
$ dgraph version

[Decoder]: Using assembly version of decoder
Page Size: 4096

Dgraph version   : v20.11.1
Dgraph codename  : tchalla-1
Dgraph SHA-256   : cefdcc880c0607a92a1d8d3ba0beb015459ebe216e79fdad613eb0d00d09f134
Commit SHA-1     : 7153d13fe
Commit timestamp : 2021-01-28 15:59:35 +0530
Branch           : HEAD
Go version       : go1.15.5
jemalloc enabled : true

For Dgraph official documentation, visit https://dgraph.io/docs/.
For discussions about Dgraph     , visit http://discuss.dgraph.io.

Licensed variously under the Apache Public License 2.0 and Dgraph Community License.
Copyright 2015-2020 Dgraph Labs, Inc.

Have you tried reproducing the issue with the latest release?

I believe that I am on the latest release, so, yes.

Steps to reproduce the issue (paste the query/schema if possible)

Given the following schema:

directive @webhook on OBJECT | INTERFACE

# ---- INTERFACES ---- #

interface IBase {
    id: ID!
    dateCreatedUTC: DateTime! @search
}

interface ITestInterface {
    id: ID!

    name: String! @id
    notes: [Note] @hasInverse(field: belongsTo)
}

type Note implements IBase {
    note: String!
    belongsTo: ITestInterface @hasInverse(field: notes)
}

type Year implements IBase @secret(field: "fakePassword") {
    year: Int!
    dates: [Date] @hasInverse(field: year)
    weeks: [Week] @hasInverse(field: year)
}

type Season implements IBase & ITestInterface {
    months: [Month] @hasInverse(field: season)
}

type Month implements IBase & ITestInterface {
    name: String! @id
    number: Int! @search
    season: Season! @hasInverse(field: months)
    weeks: [Week] @hasInverse(field: month)
    dates: [Date] @hasInverse(field: month)
}

type Week implements IBase {
    numberInYear: Int!
    year: Year! @hasInverse(field: weeks)
    month: Month! @hasInverse(field: weeks)
    dates: [Date] @hasInverse(field: week)
}

type Date implements IBase & ITestInterface @webhook(type: "com.someone.something.date", source: "https://github.com/someone/something/v1/type/date") {
    year: Year @hasInverse(field: dates)
    month: Month @hasInverse(field: dates)
    week: Week @hasInverse(field: dates)
}

Start with a completely clean Dgraph instance (docker standalone), then add a single ‘note’ using the following mutation:

mutation {
  addNote(input: {note: "test note", dateCreatedUTC: "2020-01-01T00:00:00"}) {
    note {
      id
      note
    }
  }
}

Dgraph will give you a result similar to the following (id may be different):

{
  "data": {
    "addNote": {
      "note": [
        {
          "id": "0x4",
          "note": "test note"
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 8
  }
}

Then, try to update this ‘note’ using the following mutation:

mutation{
  update_2: updateNote(input: {
    filter: {
      id: "0x5"
    },
    remove: {},
    set: {
      note: "updated note"
    }
  }) {
    numUids
    note {
      id
      note
    }
  }
}

The result is always:

{
  "errors": [
    {
      "message": "Non-nullable field 'note' (type String!) was not present in result from Dgraph.  GraphQL error propagation triggered.",
      "locations": [
        {
          "line": 6,
          "column": 7
        }
      ],
      "path": [
        "update_2",
        "note",
        0,
        "note"
      ]
    }
  ],
  "data": {
    "update_2": {
      "numUids": 1,
      "note": [
        null
      ]
    }
  },
  "extensions": {
    "touched_uids": 12
  }
}

Not only does Dgraph not update the note that was passed in, it deletes it, which is obviously not desirable.

It appears that this is due to the empty remove part of the variables, which if removed, does not end up having a problem and the result instead becomes:

{
  "data": {
    "update_2": {
      "numUids": 1,
      "note": [
        {
          "id": "0x6",
          "note": "updated note"
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 9
  }
}

Expected behaviour and actual result.

Providing an empty variable section in an update request should not have the side effect of deleting any matching object. I can confirm that if you leave the filter section blank, it will delete all notes and respond with an array of similar errors for each matched note node.

I imagine that the code is simply taking the empty remove variable section to mean that anything matching, regardless of the field, should be removed.

This does seem counter-intuitive. The correct way to delete a node should be to use the deleteType mutation. A node shouldn’t be deleted if the user supplies an empty remove object. We should ignore the mutation in this case. This has also been reported by another user Update patch with blank `remove` section removes selected nodes? and I think we should change the behavior to have no effect.

2 Likes