From ddecef10715d9cdd05c568f81ade052c12330488 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 19 Jan 2007 03:18:46 +0000 Subject: [PATCH] - fixed bug where delete-orphan basically didn't work with many-to-many relationships [ticket:427], backref presence generally hid the symptom --- CHANGES | 2 ++ lib/sqlalchemy/orm/dependency.py | 11 ++++++++++- test/orm/cascade.py | 29 +++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index c85fef9676..fe16a2acc5 100644 --- a/CHANGES +++ b/CHANGES @@ -52,6 +52,8 @@ - some deeper error checking when compiling relations, to detect an ambiguous "primaryjoin" in the case that both sides of the relationship have foreign key references in the primary join condition + - fixed bug where delete-orphan basically didn't work with many-to-many relationships [ticket:427], + backref presence generally hid the symptom - added a mutex to the mapper compilation step. ive been reluctant to add any kind of threading anything to SA but this is one spot that its its really needed since mappers are typically "global", and while their state does not change during normal operation, the diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index e4e351d268..9887025a6c 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -320,7 +320,16 @@ class ManyToManyDP(DependencyProcessor): connection.execute(statement, secondary_insert) def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): - pass + #print self.mapper.mapped_table.name + " " + self.key + " " + repr(len(deplist)) + " preprocess_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if not delete: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is not None: + for child in childlist.deleted_items(): + if self.cascade.delete_orphan and childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) def _synchronize(self, obj, child, associationrow, clearkeys): dest = associationrow source = None diff --git a/test/orm/cascade.py b/test/orm/cascade.py index e770a4ec42..16453974b7 100644 --- a/test/orm/cascade.py +++ b/test/orm/cascade.py @@ -190,7 +190,7 @@ class M2MCascadeTest(testbase.AssertMixin): def tearDownAll(self): metadata.drop_all() - @testbase.supported('') + def testdeleteorphan(self): class A(object): def __init__(self, data): @@ -200,7 +200,7 @@ class M2MCascadeTest(testbase.AssertMixin): self.data = data mapper(A, a, properties={ - # if no backref here, delete-orphan fails + # if no backref here, delete-orphan failed until [ticket:427] was fixed 'bs':relation(B, secondary=atob, cascade="all, delete-orphan") }) mapper(B, b) @@ -217,7 +217,32 @@ class M2MCascadeTest(testbase.AssertMixin): assert atob.count().scalar() ==0 assert b.count().scalar() == 0 assert a.count().scalar() == 1 + + def testcascadedelete(self): + class A(object): + def __init__(self, data): + self.data = data + class B(object): + def __init__(self, data): + self.data = data + mapper(A, a, properties={ + 'bs':relation(B, secondary=atob, cascade="all, delete-orphan") + }) + mapper(B, b) + + sess = create_session() + a1 = A('a1') + b1 = B('b1') + a1.bs.append(b1) + sess.save(a1) + sess.flush() + + sess.delete(a1) + sess.flush() + assert atob.count().scalar() ==0 + assert b.count().scalar() == 0 + assert a.count().scalar() == 0 if __name__ == "__main__": testbase.main() -- 2.47.2