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)
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 -
# 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
# 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
# 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):
# 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):