Feature Request: Cascade Delete & Deep Mutations By Reference Directive

Experience Report for Feature Request

Update: 7/1/21 - to comply with Feature Request Template:

What you wanted to do

1.) Update nested nodes from the parent Update Mutation.
2.) Delete nested nodes from the parent Delete Mutation
3.) Choose which nodes allow this Cascade Delete and Cascade Update, as not all parent / children relationships should have this ability

What you actually did

1.) This can only be done by creating a new update mutation for EVERY SINGLE child individually. If I am updating 10 children nodes, I need 11 mutations when including the parent.

2.) This is currently impossible, even with multiple mutations, unless you query every single ID.

3.) Obviously I don’t want to delete some nested nodes like country, language, etc. Currently, all nested updates just update the connection, not the data.

Why that wasn’t great, with examples

1.) How to Update a Book’s Chapters

mutation {
    updateChapter0: updateChapter(input: { 
        filter: { id: "0xfffd8d6aa985abef" }, set: { ... changed info here } }
    ) {
        numUids
    }
    updateChapter1: updateChapter(input: { 
        filter: { id: "0xfffd8d6aa985abf1" }, set: { ... changed info here } }
    ) {
        numUids
    }
    updateBook(input: { 
        filter: { id: "0xfffd8d6aa985abee" }, set: { ... changed info here } }
    ) {
        chapter {
            id
            name
            slug
            description
        }
        numUids
    }
}

For every single chapter you’re updating, you need to manually query the id, and create a separate mutation with a separate namespace. The should be done in one mutation like on add.

2.) How to Delete a Book (with its chapters):

  • First Get all Chapter Ids:
query {
    queryBook(filter: { id: "0xfffd8d6aa985abdf" }) {
        chapters {
            id
            ...
        }
    }
}
  • Use the returned Ids to delete the chapters one by one…, then delete the book (no other way without querying first)
mutation {
    deleteChapter(filter: { id: ["0xfffd8d6aa985abde", "0xfffd8d6aa985abe0"] }) {
        chapter {
            id
            ...
        }
        numUids
        msg
    }
    deleteBook(filter: { id: "0xfffd8d6aa985abdf" }) {
        book {
            id
            ...
        }
        numUids
        msg
    }
}

This isn’t even one step, but several steps, and several mutations. Nested Mutations would make this one step, but it will still be several mutations.

Any external references to support your case


Original Post

There needs to be a way update deep fields, and delete deep fields.

While it is clear why this is not default behavior, not having it as an option is also a grave problem.

I realize there are many many posts on this, but in summary:

Deleting

Right now it is 100% impossible to delete nested fields without using DQL. I theoretically can query to get every single ID to do this manually, but it is not even advisable if I could, since there could be thousands. I also can’t flip the node, since it would be a nested field as well.

So, I end up with ghost nodes. Again, it is currently IMPOSSIBLE to avoid this in graphql. I understand nested filters are on the way eventually:

But, the best we can hope for if you are a cloud user like myself is November at the earliest. This again, does not guarantee (or almost guarantee since dgraph graphql is not perfect) a lack of ghost nodes.

So we need to start thinking about this now.

Updating

If I want to update one record with nested fields (say an array of data), I currently have to create a mutation for that node, plus a mutation for every single node in the array I need to update. If that array (nested field) is 10 items, I need to create 11 different mutations. Part of that problem is the lack of multiple sets in update mutations, but I can save that for a different post.

Solving the Problem

The best way to solve this problem is what @amaster507 said:

While this is way too complicated IMHO:

So we do something like this:

Type Student {
  id: ID!
  name: String
  classes: Class @hasInverse(field: students)
  ...
}
Type Book {
  id: ID!
  name: String
  ...
}
Type Class {
  id: ID!
  ...
  students: [Student] @reference(onUpdate: null, onDelete: restrict)
  books: [Book] @reference(onUpdate: cascade: onDelete: cascade);
}

And just like mySQL there are four options and two paramenters (onUpdate and onDelete):

options: [restrict, cascade, null, nothing]
  • nothing, short for doNothing or noAction would be the default behavior for onUpdate to be backwards compatible
  • cascade - delete or update
  • restrict - throws error if trying to delete or update
  • null - removes connection, does not delete, would be the default behavior for onDelete to be backwards compatible

(Note: Default should be a fifth option when Dgraph implements Default values)

We should be able to do this, it is simple, makes sense, and keeps Dgraph consistent.

J

5 Likes

Yes, please!

I have updated this to be in an official Feature Request format.

J

1 Like