]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
the "tack on the leftover tasks at the end" step of the "circular dependency sort"
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Mar 2007 15:27:34 +0000 (15:27 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Mar 2007 15:27:34 +0000 (15:27 +0000)
makes a copy of those tasks with the circular_parent marked.  this way the tasks
do not iterate through their child items polymorphically, which is necessary because
the "circular sort" stores individual subclass tasks separately (i.e. saving/deleting
should not traverse polymorhically for those tasks)

CHANGES
lib/sqlalchemy/orm/unitofwork.py
test/orm/cycles.py

diff --git a/CHANGES b/CHANGES
index 0f95dbab63c445403746924c956d765e53274381..2f0f72d5d8a6fb5d0cfe8e823e9ddeaf7dc6bbf1 100644 (file)
--- a/CHANGES
+++ b/CHANGES
       targeting of columns that belong to the polymorphic union vs. those
       that dont.
       
-    - some fixes to relationship calcs when using "view_only=True" to pull
+    - some fixes to relationship calcs when using "viewonly=True" to pull
       in other tables into the join condition which arent parent of the
       relationship's parent/child mappings
 
-    - flush fixes on self-referential relationships that contain references
-      to other instances outside of the cyclical chain, when the initial
-      self-referential objects are not actually part of the flush
+    - flush fixes on cyclical-referential relationships that contain references
+      to other instances outside of the cyclical chain, when some of the 
+      objects in the cycle are not actually part of the flush
       
     - put an aggressive check for "flushing object A with a collection
       of B's, but you put a C in the collection" error condition - 
index 705dce1126496f0bd0d4604690a485578352fc73..7fdcaeba252714bf0cecaef21d0c3d78a91365d6 100644 (file)
@@ -458,8 +458,10 @@ class UOWTask(object):
         # dependency processing, and before pre-delete processing and deletes
         self.childtasks = []
 
-        # whether this UOWTask is circular, meaning it holds a second
-        # UOWTask that contains a special row-based dependency structure.
+        # holds a second UOWTask that contains a special row-based dependency 
+        # structure. this is generated when cycles are detected between mapper-
+        # level UOWTasks, and breaks up the mapper-level UOWTasks into individual
+        # object-based tasks.  
         self.circular = None
 
         # for a task thats part of that row-based dependency structure, points
@@ -546,7 +548,8 @@ class UOWTask(object):
         # first us
         yield self
 
-        # "circular dependency" tasks aren't polymorphic
+        # "circular dependency" tasks aren't polymorphic, since they break down into many 
+        # sub-tasks which encompass a subset of the objects that their "non-circular" parent task would.
         if self.circular_parent is not None:
             return
 
@@ -778,8 +781,16 @@ class UOWTask(object):
             # will have no "save" or "delete" members, but may have dependency
             # processors that operate upon other tasks outside of the cycle.
             if t2 not in used_tasks and t2 is not self:
-                t.childtasks.insert(0, t2)
-                
+                # the task must be copied into a "circular" task, so that polymorphic
+                # rules dont fire off.  this ensures that the task will have no "save"
+                # or "delete" members due to inheriting mappers which contain tasks
+                localtask = UOWTask(self.uowtransaction, t2.mapper, circular_parent=self)
+                for obj in t2.get_elements(polymorphic=False):
+                    localtask.append(obj, t2.listonly, isdelete=t2.objects[obj].isdelete)
+                for dep in t2.dependencies:
+                    localtask.dependencies.add(dep)
+                t.childtasks.insert(0, localtask)
+
         return t
 
     def dump(self):
index 69ecbc042629aeb56ddc5b8c204e1ceb8bf5e8ac..d02a8c8d2df3cf59ca201235f0b59780684ae54d 100644 (file)
@@ -231,6 +231,50 @@ class InheritTestOne(AssertMixin):
         # attached to a task corresponding to c1, since "child1_id" is not nullable
         session.flush()
 
+class InheritTestTwo(ORMTest):
+    """the fix in BiDirectionalManyToOneTest raised this issue, regarding
+    the 'circular sort' containing UOWTasks that were still polymorphic, which could
+    create duplicate entries in the final sort"""
+    def define_tables(self, metadata):
+        global a, b, c
+        a = Table('a', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('data', String(30)),
+            Column('cid', Integer, ForeignKey('c.id')),
+            )
+
+        b = Table('b', metadata, 
+            Column('id', Integer, ForeignKey("a.id"), primary_key=True),
+            Column('data', String(30)),
+            )
+
+        c = Table('c', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('data', String(30)),
+            Column('aid', Integer, ForeignKey('a.id', use_alter=True, name="foo")),
+            )
+    def test_flush(self):
+        class A(object):pass
+        class B(A):pass
+        class C(object):pass
+
+        mapper(A, a, properties={
+            'cs':relation(C, primaryjoin=a.c.cid==c.c.id)
+        })
+
+        mapper(B, b, inherits=A, inherit_condition=b.c.id==a.c.id, properties={
+        })
+        mapper(C, c, properties={
+            'arel':relation(A, primaryjoin=a.c.id==c.c.aid)
+        })
+
+        sess = create_session()
+        bobj = B()
+        sess.save(bobj)
+        cobj = C()
+        sess.save(cobj)
+        sess.flush()
+        
 
 class BiDirectionalManyToOneTest(ORMTest):
     def define_tables(self, metadata):