From e918ea6898266956cc4759fb9340ec8b4f9c3629 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 20 Mar 2007 15:27:34 +0000 Subject: [PATCH] the "tack on the leftover tasks at the end" step of the "circular dependency sort" 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 | 8 +++--- lib/sqlalchemy/orm/unitofwork.py | 21 +++++++++++---- test/orm/cycles.py | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 0f95dbab63..2f0f72d5d8 100644 --- a/CHANGES +++ b/CHANGES @@ -129,13 +129,13 @@ 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 - diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 705dce1126..7fdcaeba25 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -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): diff --git a/test/orm/cycles.py b/test/orm/cycles.py index 69ecbc0426..d02a8c8d2d 100644 --- a/test/orm/cycles.py +++ b/test/orm/cycles.py @@ -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): -- 2.47.2