We want to add support for cascading deletion in ent. We are in early discussions and your comments and feedback can help us.
The cascading deletion will be implemented in the framework and won't be configured
in the database (e.g. ON DELETE CASCADE in MySQL), in order to support privacy checks,
logging, tracing, etc.
What about adding support for database-level deletion? See below. I was thinking about adding support for schema DDLs, and this way, users will be able to configure ON DELETE CASCADE actions on their tables. But this is part of a different discussion.
Update: database-level cascading deletion was added in https://github.com/ent/ent/pull/1339
--
The first option is cascade-delete, which indicates that if a user is deleted, the cards
that are associated with it will be deleted as well. The schema declaration is as follows:
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cards", Card.Type).
Cascade(edge.Delete()),
}
}
The second option is delete-orphan, which provides the same functionality as cascade-delete,
but in addition to that, it indicates that nodes without a reference should be deleted.
For example, if a user removes an edge from the card, the card should be deleted - because it's an orphan.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cards", Card.Type).
Cascade(edge.Delete(), edge.DeleteOrphan()),
}
}
However, sometimes we want to move a reference to a "child node" from one parent to the other.
For example, move the card ownership from Ariel to Alex - in this case, we don't want the card
to be deleted (between the ownership transition). Therefore, I'm thinking about 2 options:
Add an option to disable cascade-deletion on delete operation. For example:
client.User.
UpdateOne(a8m).
RemoveCards(card).
WithCascadeDelete(false).
Save(ctx)
Add support for sessions - need more design, but the idea goes as follows:
s := client.Session()
s.User.UpdateOne(a8m).RemoveCards(card).Save(ctx)
s.User.UpdateOne(alex).AddCards(card).Save(ctx)
s.Commit() // Maybe, s.Flush() to not confuse with ent.Tx.
Since adding support for a session is a broader change, maybe we can start with option 1, and add support for sessions after V1.
2 additional notes:
go
client.User.Delete().
WithCards(predicates...).
Exec(ctx)
Please feel free to give feedback and comment. Nothing is set in stone yet!
Could you elaborate more on "sessions"?
Really like all the rest of the design. Many thanks for the hard work and explanation for the design.
This looks great. I do have some performance concerns about doing this in
go and at the connection level, but I'm not sure I'm ready to say anything
without looking at code first.
It would be nice if Cascade could accept a function of some kind, that
could be used to further process the node (I'm thinking about compliance
solutions specifically where data might need to be shoveled off) before
taking any final action. Maybe something like GRPC interceptors for
databases.
Hope this was useful commentary,
-Erik
Hey @aca and @erikh, and thanks for your comments.
Could you elaborate more on "sessions"?
Really like all the rest of the design. Many thanks for the hard work and explanation for the design.
I don't have any design for sessions yet (this was just a random thought of mine), but the idea was to make it possible to perform multiple operations in the application side (like moving edge ownerships between entities), and only then flush to the the database.
Since I wrote this, we came up with another suggestion (by @alexsn) to add a Move<E> operation that "moves" an edge/relation ownership from one entity to the other - with one query to the database and without loading the edge. For example:
client.User.MoveCard(from, to).Exec(ctx)
This looks great. I do have some performance concerns about doing this in go and at the connection level, but I'm not sure I'm ready to say anything without looking at code first.
Users that prefer to use ON DELETE CASCADE clause will be able to use it, but we prefer to avoid using it for a few reasons, for example:
Maybe there are more pros/cons for both sides, but like you said, we need to come up with some POC to be able to compare the 2.
It would be nice if Cascade could accept a function of some kind, that
could be used to further process the node (I'm thinking about compliance
solutions specifically where data might need to be shoveled off) before
taking any final action. Maybe something like GRPC interceptors for
databases.
Did you have a chance to see the hooks API? It provides a middleware-like API for ent operations.
For the sake of generality, you might consider changing from .Cascade to .OnDelete (and .OnUpdate). This would allow for the usual SQL ON DELETE options (RESTRICT, CASCADE, SET NULL, NO ACTION, SET DEFAULT) and naming would align.
For example:
edge.To("cards", Card.Type).
OnDelete(edge.Cascade()),
or:
edge.To("cards", Card.Type).
OnDelete(edge.SetNull()),
Of course the above would work with .Cascade(edge.SetNull()) but I think that would be confusing given the SQL names.
As for the edge.DeleteOrphan option, that could perhaps be passed as an argument to edge.Cascade, but not sure if that's the best way to do it.
Just a thought.
I came across this proposal not for the titled deletion aspects but more so on the move proposal:
add a Move
operation that "moves" an edge/relation ownership...
I think the api is fine, even though semantically it feels like an update. I can assume keeping it distinct from the .Update() methods would imply a lower implementation complexity.
Some quick common use cases I can think of that would benefit from the Move<E> proposal.
Most helpful comment
Hey @aca and @erikh, and thanks for your comments.
I don't have any design for sessions yet (this was just a random thought of mine), but the idea was to make it possible to perform multiple operations in the application side (like moving edge ownerships between entities), and only then flush to the the database.
Since I wrote this, we came up with another suggestion (by @alexsn) to add a
Move<E>operation that "moves" an edge/relation ownership from one entity to the other - with one query to the database and without loading the edge. For example:Users that prefer to use
ON DELETE CASCADEclause will be able to use it, but we prefer to avoid using it for a few reasons, for example:Maybe there are more pros/cons for both sides, but like you said, we need to come up with some POC to be able to compare the 2.
Did you have a chance to see the hooks API? It provides a middleware-like API for ent operations.