Deleting node with required inverse edges

Hi,

Suppose we have the following schema

type Parent {
    children: [Child] @hasInverse(field: parent)
}

type Child {
   parent: Parent! 
}

If we delete an instance of a previously created parent, we’ll leave all its children with no parent while its required in the schema.

In this case, wouldn’t it be the role of Dgraph to also remove the children to make data comply with the user’s schema ?

I guess that this would required the query to be somehow recursive which doesn’t seem to be the current behavior. But still, the documentation is quite sparse about the details and make it hard to understand the behavior of data deletion.

2 Likes

Best would be to have control over this behaviour by the developer in the schema similar in nature to MySQL use of “ON DELETE” which has options to either set null, restrict, no action, cascade, and set default.

That would be interesting to see of that logic could be worked in as a schema directive on an edge.

When delete Parent, “set null” is current behaviour on child edge.

  • restrict would block delete if there were any children
  • no action would leave the inverse edge to the uid that is now empty
  • cascade delete children
  • set default would set children Parent’s to a staticly declared uid.
2 Likes

Thanks for your insight.

What I do for now, is implementing a custom “deepDeleteParent” mutation.
I catch the deleteParent mutation in a Business Logic Layer (build with gqlgen) and I do the deletion with a DQL request instead, trying to delete its orphan like this

upsert {
  query {
    id as var(func: uid({{.id}})) {                                                                                                                                                                            
          a as Parent.children          
          b as Parent.extra_node_to_delete                                                                                                                                                      
        }                                                                                                                                                                                                          
        all(func: uid(id,a,b)) {                                                                                                                                                                                 
            all_ids as uid                                                                                                                                                                                         
        }  
  }
  
  mutation {
    delete {
      uid(all_ids) * * .
    }
  }
}

I guess that I could also do that using a custom dql directive in the schema…

1 Like

This is a fragile solution requiring the developer to be aware of the details schema and to be careful to include any dependent node in this delete lest nodes be left in a broken state.

It also requires the team maintaining the schema to be diligent in ensuring that any changes to the schema are considered carefully, and to update the delete query with every schema change that introduces a new, even transitive, dependency on the type during delete.

I was under the impression that Dgraph validates schema on mutate to ensure no constraints are broken, i.e, you can’t create a node with a required field missing; it seems to me that Dgraph should do the same for updates/deletes, to ensure that not only the individual node has its relationships kept valid, but also those transitively connected.

If I were to delete parent, I would expect the delete to give an error that it would leave a child in a broken state, requiring you to fall back to a dql query for a transactional upsert, or, even better, provide some kind of cascade flag which tells dgraph to delete the node, and also cascade the delete to remove any dependent entity which would be left in an invalid (per the schema) state.

3 Likes

It can be argued that preserving the overall consistency of the database is not the responsibility of the database alone. This is particularly true for a graph database as transitive dependencies can end up covering the whole database. Thinking of graph structures as a tree might not be the most optimal abstraction here; perhaps a mesh is a better candidate for abstraction (thinking about federated graph structures here as well)
My suggestion is to use lambdas for mutations that can affect consistency and switch off delete options for individual types using the @generate directive. This brings more domain-centric flavor to the GraphQL API, and does not leave any loose ends for the developer to clean up.

If the answer to every Dgraph problem is to use a lambda then what is the purpose of even having a GraphQL endpoint to use as a public backend. It is close to the point again where a community member might need to write another/better GraphQL to DQL abstraction layer that does what clients need if Dgraph is only going to focus on their service as a database and not their service as a complete backend API solution. Too much to piece together to make a lambda for every use case where it should be built into the graphql endpoint directly IMO. Not liking where I am seeing Dgraph headed. :frowning:

Yes, you are right. This can be argued, but users of Dgraph don’t want to have to argue for every feature request they make to improve the usability of the API.

This is saddening, because what users take as basic database concepts found in alternative rdbms databases that we are accustomed to having, we have to handle ourselves. This would he similar to MySQL saying that they are ripping out cascade effects because it is not the job of the database. And this is coming from a comparison of a rdbms database that is solely a database compared to a database that is not only the database but also the API. There is the expectation to have more control over things like this baked into the core one way or another instead of relying on custom lambda functions to do everything uneficiently. Just think with over 100 types with a huge factor of connectivity between those types. How many custom mutations is a dev expected to write before he just gives up and instead looks for another alternative?

2 Likes

@anand Agreed it is not the responsibility of the database for everything. However, if the user does not have the tools to properly model and USE the database efficiently to keep the database consistent, that is on the database developers!

Please read my two articles, as I have solved the problem for you if you guys listen:


This is 100% the problem!

You guys made a great product, then you put GraphQLl on it. This is awesome! However, that extra layer has no security except basic JWT validation, and the Dgraph team seems to just ignore the MOST IMPORTANT user needs. We are stuck with Dgraph’s GraphQL layer, which is VERY VERY LIMITED, and more importantly, with NO HOPE of fixing the problems.

Start thinking about what MOST users will need

If all of your developers are database developers and not frontend developers, you may not understand this list. However, what you are selling is NOT a DbaaS, but a Database platform! The platform needs to come first, and it seems like it is at the bottom of the list. Yes, we realize there are money issues with getting things done, but PRIORITIZE your paying cloud users, or lose them!

Here is where we are:

  • Basic backend security
    • You have to create a lambda mutation for EVERY mutation
    • post-hooks are worthless because you have no access to original data
    • There are no pre-hooks, as promised, so you have to create that extra-coded custom mutation every time
  • Foreign Keys
    • As I spoke about in my first article, in some cases, it is IMPOSSIBLE to keep the database consistent without using a lambda mutation, again, only a custom lambda mutation.
    • This is NOT the job of the user, but the job of the database!
  • User / Roles
    • While I don’t think having a user / roles system is a must for this Dgraph GraphQL, allowing GraphQL to save a role in the database, and check the user’s role is a must
    • Imagine a front-end developer having to change the JWT in order to change any RBAC information every time. This is insane!
  • Nested Filters / Facets
    • I believe you guys are aware of the problems not having nested filters. Was supposed to be fixed in 21.07, lol. Nhost.io is a good example of a DbaaS (postgres) that has GraphQL nested filters built in. I would argue that facets should come last, but still important.

How to fix this GraphQL (in this order)

Obviously all bugs come first, then documentation, then…

  1. after-update-bug
  2. @reference directive
  3. nested filters
  4. database role validation
  • pre-hooks
  • facets

I would state that the first 4 are simply a must. Put all cloud and data studio upgrades to the side.

To get back to this post, imagine a MYSQL database having to write a stored procedure for every sql foreign key query transaction. Imagine noSQL having to write a custom function or lambda for every add / update / set for security.

People go away from mysql because of speed and scaling. People found noSQL. People go away from noSQL because of a lack of relations and writing custom functions to fix it. People found GraphDB. People stick with GraphDB because it quick, scales, secure, solves relationship problems clearly and automatically.

Dgraph is almost there, but the Dgraph team keeps arguing about the usefulness of these things, focusing on everything but the front-end user. Start looking at their needs! They are your users!

We have drawn you a map! --the regular users here with their real struggles

Then again, maybe you guys are not ignoring GraphQL problems, but all we see is no communication at all on what you are working on, or arguments about what you believe is important… neither is promising.

I would not be taking 30 minutes of my time to write this post if I did not believe in this product. Please understand the real needs of your real users.

J

2 Likes

Hi @jdgamble555 @amaster507 thank you for all the feedback. There is a lot to digest here. Will read thru. thx.

I agree it’s not the responsibility of the database alone, the developer is responsible for giving the database the information it needs to be able to preserve that consistency correctly, however, via the database offered GraphQL schema, I am explicitly marking edges as required, yet Dgraph is not honoring this when deleting nodes in the graph, leaving required edges broken. This is akin to a relational database allowing the caller to delete in-use foreign keys without warning or error by default.

I would actually rather Dgraph throw an error on delete in this situation than allow the data model to left broken.

I understand that lambdas can be used to implement custom cascading delete logic and I’m glad there’s a workaround available, but this default behavior of the Dgraph GraphQL interface is likely to leave people in trouble if they don’t read up on this sort of issue ahead of time.

I am actively evaluating graph databases to consider adopting on behalf of my company, and the biggest initial draw of Dgraph was the native GraphQL interface, and that it could potentially replace X% of our existing backend code and infrastructure. Issues like this, among others such as update after auth, lack of system timestamps, etc, throw spanners in the works. Whilst the problems aren’t insurmountable, but they definitely lessen some of the wow I felt playing with Dgraph GraphQL initially.

1 Like