one soft delete hook to rule them all

Issue #4004 new
Mike Bayer repo owner created an issue

not really sure how this should look but goals are:

  1. ORM keeps "delete" semantics completely. session.delete(obj), relationships will use cascade='all, delete, delete-orphan' normally, so that everything looks like "DELETE"

  2. the critical part is unitofwork.register_object(). that's where the "thing" happens that determines the "deletedness" of all objects. unlike session.delete(), this is the place where it happens for all the relationship cascades also.

  3. I am loathe to add a plain event inside of unitofwork.register_object(). at this point we are asking too much of users to understand internal flow. before_flush() is right at the top, fine. before_update() / before_delete(), right before the statement, OK. But because of relationship operations that take place in dependency.py, before_flush() doesn't tell us about everything that will happen in the flush and we can't react to everything.

  4. the hook is really something that happens specific to UOW - after all the presorts are done and before the actions proceed.

register_object() hook still seems like the way to go. this could also impact versioning recipes too.

see if a register_object() hook can come up with recipes that handle both soft_delete and versioning. that could help come up with some way to explain it.

Comments (3)

  1. Mike Bayer reporter

    dependency processors can have rich contextual objects set up when they are initialized, that can be passed to each call to register_object() so that the event hook can see how it's being called; the same object can also be subject to register_object() multiple times:

    diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
    index a87ec56..c4591de 100644
    --- a/lib/sqlalchemy/orm/dependency.py
    +++ b/lib/sqlalchemy/orm/dependency.py
    @@ -698,12 +698,15 @@ class ManyToOneDP(DependencyProcessor):
                             if child is None:
                                 continue
                             uowcommit.register_object(
    -                            child, isdelete=True,
    -                            operation="delete", prop=self.prop)
    +                            child,
    +                            self.OPERATION_CASCADE_DELETE_TO_IMMEDIATE_MEMBERS,
    +                            isdelete=True)
                             t = self.mapper.cascade_iterator('delete', child)
                             for c, m, st_, dct_ in t:
                                 uowcommit.register_object(
    -                                st_, isdelete=True)
    +                                st_,
    +                                self.OPERATION_CASCADE_DELETE_TO_DESCENDANT_MEMBERS,
    +                                isdelete=True)
    
         def presort_saves(self, uowcommit, states):
             for state in states:
    

    the OPERATION objects are per-dependency processor so also link to the originating relationship.

    the event hook will include flags for: 1. have we already seen this state before 2. "isdelete" flag 3. existing value of "isdelete".

    problem: calling session.add() here e.g. for versioning will not affect the current flush. What will happen is that flush will run again when it detects new objects. This is OK and may actually be a great way to handle this as it enforces that secondary changes are self-contained vs. the primary changes. I don't want to expose "regiser_object()" itself in this hook.

  2. Mike Bayer reporter
    operation = NamedTuple(
        "relationship",    # the Relationship object handling an operation, may be None
       "cascade", # type of cascade in effect, e.g. "delete", "delete-orphan", "add".
                        # if "delete", then we know our parent is being deleted.  if "delete-orphan", then
                        # we know we were de-associated w/ parent
      "member_type",   # for relationship, "immediate" or "descendant"
      "isdelete", # True or False
    
    )
    
  3. Log in to comment