]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Related to :ticket:`3060`, an adjustment has been made to the unit
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 30 May 2014 05:39:45 +0000 (01:39 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 30 May 2014 05:39:45 +0000 (01:39 -0400)
    of work such that loading for related many-to-one objects is slightly
    more aggressive, in the case of a graph of self-referential objects
    that are to be deleted; the load of related objects is to help
    determine the correct order for deletion if passive_deletes is
    not set.
    - revert the changes to test_delete_unloaded_m2o, these deletes do in fact
    need to occur in the order of the two child objects first.

merged from master

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/dependency.py
lib/sqlalchemy/orm/unitofwork.py
test/orm/test_unitofworkv2.py

index 512dce091570bfe99c98f1af89138fc4793d9651..98a11a00d3560f738023f9f6bffc26aa3f6144ff 100644 (file)
         of implicitly assuming None isn't really a "change" for a previously
         un-set attribute.  See also :ticket:`3061`.
 
+    .. change::
+        :tags: bug, orm
+        :versions: 1.0.0
+
+        Related to :ticket:`3060`, an adjustment has been made to the unit
+        of work such that loading for related many-to-one objects is slightly
+        more aggressive, in the case of a graph of self-referential objects
+        that are to be deleted; the load of related objects is to help
+        determine the correct order for deletion if passive_deletes is
+        not set.
+
     .. change::
         :tags: bug, orm
         :tickets: 3057
index 09d6e988d4fca6000153c262ca2b5f2f6adee333..bcfd4016d80f51468f19e7146d5851c7335d6627 100644 (file)
@@ -532,7 +532,7 @@ class AttributeImpl(object):
     def get_history(self, state, dict_, passive=PASSIVE_OFF):
         raise NotImplementedError()
 
-    def get_all_pending(self, state, dict_):
+    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
         """Return a list of tuples of (state, obj)
         for all objects in this attribute's current state
         + history.
@@ -737,23 +737,31 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
             else:
                 return History.from_object_attribute(self, state, current)
 
-    def get_all_pending(self, state, dict_):
+    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
         if self.key in dict_:
             current = dict_[self.key]
-            if current is not None:
-                ret = [(instance_state(current), current)]
-            else:
-                ret = [(None, None)]
+        elif passive & CALLABLES_OK:
+            current = self.get(state, dict_, passive=passive)
+        else:
+            return []
+
+        # can't use __hash__(), can't use __eq__() here
+        if current is not None and \
+                current is not PASSIVE_NO_RESULT and \
+                current is not NEVER_SET:
+            ret = [(instance_state(current), current)]
+        else:
+            ret = [(None, None)]
 
-            if self.key in state.committed_state:
-                original = state.committed_state[self.key]
-                if original not in (NEVER_SET, PASSIVE_NO_RESULT, None) and \
+        if self.key in state.committed_state:
+            original = state.committed_state[self.key]
+            if original is not None and \
+                    original is not PASSIVE_NO_RESULT and \
+                    original is not NEVER_SET and \
                     original is not current:
 
-                    ret.append((instance_state(original), original))
-            return ret
-        else:
-            return []
+                ret.append((instance_state(original), original))
+        return ret
 
     def set(self, state, dict_, value, initiator,
                 passive=PASSIVE_OFF, check_old=None, pop=False):
@@ -859,7 +867,9 @@ class CollectionAttributeImpl(AttributeImpl):
         else:
             return History.from_collection(self, state, current)
 
-    def get_all_pending(self, state, dict_):
+    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
+        # NOTE: passive is ignored here at the moment
+
         if self.key not in dict_:
             return []
 
@@ -1087,7 +1097,9 @@ def backref_listeners(attribute, key, uselist):
     def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
         if oldchild is child:
             return child
-        if oldchild is not None and oldchild not in (PASSIVE_NO_RESULT, NEVER_SET):
+        if oldchild is not None and \
+                oldchild is not PASSIVE_NO_RESULT and \
+                oldchild is not NEVER_SET:
             # With lazy=None, there's no guarantee that the full collection is
             # present when updating via a backref.
             old_state, old_dict = instance_state(oldchild),\
index 0d5a4f909ea4d384a397dc4aac74deb8bf67ed6c..bfe7aa0d253bca2d0e339ee04ada019faa525b71 100644 (file)
@@ -154,12 +154,16 @@ class DependencyProcessor(object):
                 parent_in_cycles = True
 
         # now create actions /dependencies for each state.
+
         for state in states:
             # detect if there's anything changed or loaded
-            # by a preprocessor on this state/attribute.  if not,
-            # we should be able to skip it entirely.
+            # by a preprocessor on this state/attribute.   In the
+            # case of deletes we may try to load missing items here as well.
             sum_ = state.manager[self.key].impl.get_all_pending(
-                state, state.dict)
+                state, state.dict,
+                                self._passive_delete_flag
+                                        if isdelete
+                                        else attributes.PASSIVE_NO_INITIALIZE)
 
             if not sum_:
                 continue
index 2964705a2e92d306e6387527a13500d4b280943c..bb6a961b12a733f8ce17d0fc073e6177acc5a27f 100644 (file)
@@ -87,8 +87,9 @@ def track_cascade_events(descriptor, prop):
                     sess._save_or_update_state(newvalue_state)
 
             if oldvalue is not None and \
+                oldvalue is not attributes.NEVER_SET and \
                 oldvalue is not attributes.PASSIVE_NO_RESULT and \
-                prop._cascade.delete_orphan:
+                    prop._cascade.delete_orphan:
                 # possible to reach here with attributes.NEVER_SET ?
                 oldvalue_state = attributes.instance_state(oldvalue)
 
index 7025e087c5f35a3c17ca3c5db0d504897376d7b3..00cc044bfa37f1c4ce9f2d2d3680952d6f43b4c7 100644 (file)
@@ -1008,23 +1008,16 @@ class SingleCycleTest(UOWTest):
                     "WHERE nodes.id = :param_1",
                     lambda ctx: {'param_1': c2id}
                 ),
-                Or(
-                    AllOf(
-                        CompiledSQL(
-                            "DELETE FROM nodes WHERE nodes.id = :id",
-                            lambda ctx: [{'id': c1id}, {'id': c2id}]
-                        ),
-                        CompiledSQL(
-                            "DELETE FROM nodes WHERE nodes.id = :id",
-                            lambda ctx: {'id': pid}
-                        ),
+                AllOf(
+                    CompiledSQL(
+                        "DELETE FROM nodes WHERE nodes.id = :id",
+                        lambda ctx: [{'id': c1id}, {'id': c2id}]
                     ),
                     CompiledSQL(
                         "DELETE FROM nodes WHERE nodes.id = :id",
-                        lambda ctx: [{'id': c1id}, {'id': c2id}, {'id': pid}]
+                        lambda ctx: {'id': pid}
                     ),
-
-                )
+                ),
             ),
         )