From: Mike Bayer Date: Wed, 31 Mar 2010 21:31:34 +0000 (-0400) Subject: beginning to address cycles but its not worked out yet X-Git-Tag: rel_0_6_0~77 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=89afcfdda4d714ccc4e361e2fb32328f816d06c9;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git beginning to address cycles but its not worked out yet --- diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 8b90fbb7fd..5729f553d7 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -191,10 +191,73 @@ class OneToManyDP(DependencyProcessor): uow.dependencies.update([ (parent_saves, after_save), (after_save, child_saves), - (child_deletes, before_delete), - (before_delete, parent_deletes) + + (child_saves, parent_deletes), + (before_delete, child_saves), + + (child_deletes, parent_deletes) ]) + def per_saved_state_flush_actions(self, uow, state): + if True: + parent_saves = unitofwork.SaveUpdateAll(uow, self.parent) + child_saves = unitofwork.SaveUpdateAll(uow, self.mapper) + assert parent_saves in uow.cycles + assert child_saves in uow.cycles + + added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True) + if not added and not unchanged and not deleted: + return + + save_parent = unitofwork.SaveUpdateState(state) + after_save = unitofwork.ProcessState(uow, self, False, state) + + for child_state in added + unchanged + deleted: + if child_state is None: + continue + + (deleted, listonly) = uow.states[child_state] + if deleted: + child_action = unitofwork.DeleteState(child_state) + else: + child_action = unitofwork.SaveUpdateState(child_state) + + uow.dependencies.update([ + (save_parent, after_save), + (after_save, child_action), + ]) + + def per_deleted_state_flush_actions(self, uow, state): + if True: + parent_deletes = unitofwork.DeleteAll(uow, self.parent) + child_deletes = unitofwork.DeleteAll(uow, self.mapper) + assert parent_deletes in uow.cycles + assert child_deletes in uow.cycles + + added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True) + if not added and not unchanged and not deleted: + return + + delete_parent = unitofwork.DeleteState(state) + after_delete = unitofwork.ProcessState(uow, self, True, state) + + for child_state in added + unchanged + deleted: + if child_state is None: + continue + + (deleted, listonly) = uow.states[child_state] + if deleted: + child_action = unitofwork.DeleteState(child_state) + else: + child_action = unitofwork.SaveUpdateState(child_state) + + uow.dependencies.update([ + (child_action, ) + (save_parent, after_save), + (after_save, child_action), + ]) + + def presort_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of child objects # the child objects have to have their foreign key to the parent set to NULL @@ -318,12 +381,11 @@ class ManyToOneDP(DependencyProcessor): else: unitofwork.GetDependentObjects(uow, self, False, True) unitofwork.GetDependentObjects(uow, self, True, True) - + uow.dependencies.update([ - (after_save, parent_saves), (child_saves, after_save), - (parent_deletes, before_delete), - (before_delete, child_deletes) + (after_save, parent_saves), + (parent_saves, child_deletes) ]) def presort_deletes(self, uowcommit, states): @@ -375,6 +437,7 @@ class ManyToOneDP(DependencyProcessor): if history: for child in history.added: self._synchronize(state, child, None, False, uowcommit) + self._conditional_post_update(state, uowcommit, history.sum()) diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 6c53586e7d..65d85a37b0 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -166,16 +166,23 @@ class UOWTransaction(object): if not ret: break + self.cycles = cycles = topological.find_cycles(self.dependencies, self.postsort_actions.values()) + assert not cycles + for rec in cycles: + rec.per_state_flush_actions(self) + + for edge in list(self.dependencies): + # both nodes in this edge were part of a cycle. + # remove that from our deps as it has replaced + # itself with per-state actions + if cycles.issuperset(edge): + self.dependencies.remove(edge) + sort = topological.sort(self.dependencies, self.postsort_actions.values()) print sort for rec in sort: rec.execute(self) -# if cycles: -# break up actions into finer grained actions along those cycles - -# for rec in topological.sort(self.dependencies, self.actions): -# rec.execute() def finalize_flush_changes(self): """mark processed objects as clean / deleted after a successful flush(). @@ -276,6 +283,13 @@ class ProcessAll(PropertyRecMixin, PostSortRec): else: self.dependency_processor.process_saves(uow, states) + def per_state_flush_actions(self, uow): + for state in self._elements(uow): + if self.delete: + self.dependency_processor.per_deleted_state_flush_actions(uow, self.dependency_processor, state) + else: + self.dependency_processor.per_saved_state_flush_actions(uow, self.dependency_processor, state) + class SaveUpdateAll(PostSortRec): def __init__(self, uow, mapper): self.mapper = mapper @@ -285,6 +299,10 @@ class SaveUpdateAll(PostSortRec): uow.states_for_mapper_hierarchy(self.mapper, False, False), uow ) + + def per_state_flush_actions(self, uow): + for state in uow.states_for_mapper_hierarchy(self.mapper, False, False): + SaveUpdateState(uow, state) class DeleteAll(PostSortRec): def __init__(self, uow, mapper): @@ -296,6 +314,10 @@ class DeleteAll(PostSortRec): uow ) + def per_state_flush_actions(self, uow): + for state in uow.states_for_mapper_hierarchy(self.mapper, True, False): + DeleteState(uow, state) + class ProcessState(PostSortRec): def __init__(self, uow, dependency_processor, delete, state): self.dependency_processor = dependency_processor diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py index 268e83121c..15b481898e 100644 --- a/test/orm/test_unitofworkv2.py +++ b/test/orm/test_unitofworkv2.py @@ -15,7 +15,7 @@ class UOWTest(_fixtures.FixtureTest, testing.AssertsExecutionResults): class RudimentaryFlushTest(UOWTest): - def test_one_to_many(self): + def test_one_to_many_save(self): mapper(User, users, properties={ 'addresses':relationship(Address), }) @@ -44,6 +44,17 @@ class RudimentaryFlushTest(UOWTest): lambda ctx: {'email_address': 'a2', 'user_id':u1.id} ), ) + + def test_one_to_many_delete_all(self): + mapper(User, users, properties={ + 'addresses':relationship(Address), + }) + mapper(Address, addresses) + sess = create_session() + a1, a2 = Address(email_address='a1'), Address(email_address='a2') + u1 = User(name='u1', addresses=[a1, a2]) + sess.add(u1) + sess.flush() sess.delete(u1) sess.delete(a1) @@ -60,8 +71,37 @@ class RudimentaryFlushTest(UOWTest): {'id':u1.id} ), ) + + def test_one_to_many_delete_parent(self): + mapper(User, users, properties={ + 'addresses':relationship(Address), + }) + mapper(Address, addresses) + sess = create_session() + a1, a2 = Address(email_address='a1'), Address(email_address='a2') + u1 = User(name='u1', addresses=[a1, a2]) + sess.add(u1) + sess.flush() + + sess.delete(u1) + self.assert_sql_execution( + testing.db, + sess.flush, + CompiledSQL( + "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", + [{u'addresses_id': 1, 'user_id': None}] + ), + CompiledSQL( + "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", + [{u'addresses_id': 2, 'user_id': None}] + ), + CompiledSQL( + "DELETE FROM users WHERE users.id = :id", + {'id':u1.id} + ), + ) - def test_many_to_one(self): + def test_many_to_one_save(self): mapper(User, users) mapper(Address, addresses, properties={ @@ -93,6 +133,19 @@ class RudimentaryFlushTest(UOWTest): ), ) + def test_many_to_one_delete_all(self): + mapper(User, users) + mapper(Address, addresses, properties={ + 'user':relationship(User) + }) + sess = create_session() + + u1 = User(name='u1') + a1, a2 = Address(email_address='a1', user=u1), \ + Address(email_address='a2', user=u1) + sess.add_all([a1, a2]) + sess.flush() + sess.delete(u1) sess.delete(a1) sess.delete(a2) @@ -108,4 +161,84 @@ class RudimentaryFlushTest(UOWTest): {'id':u1.id} ), ) - \ No newline at end of file + + def test_many_to_one_delete_target(self): + mapper(User, users) + mapper(Address, addresses, properties={ + 'user':relationship(User) + }) + sess = create_session() + + u1 = User(name='u1') + a1, a2 = Address(email_address='a1', user=u1), \ + Address(email_address='a2', user=u1) + sess.add_all([a1, a2]) + sess.flush() + + sess.delete(u1) + a1.user = a2.user = None + self.assert_sql_execution( + testing.db, + sess.flush, + CompiledSQL( + "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", + [{u'addresses_id': 1, 'user_id': None}] + ), + CompiledSQL( + "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", + [{u'addresses_id': 2, 'user_id': None}] + ), + CompiledSQL( + "DELETE FROM users WHERE users.id = :id", + {'id':u1.id} + ), + ) + +class SingleCycleTest(UOWTest): + def test_one_to_many(self): + mapper(Node, nodes, properties={ + 'children':relationship(Node) + }) + sess = create_session() + + n2, n3 = Node(data='n2'), Node(data='n3') + n1 = Node(data='n1', children=[n2, n3]) + + sess.add(n1) + + self.assert_sql_execution( + testing.db, + sess.flush, + CompiledSQL( + "INSERT INTO nodes (data) VALUES (:data)", + {'data': 'n1'} + ), + CompiledSQL( + "INSERT INTO addresses (user_id, email_address) " + "VALUES (:user_id, :email_address)", + lambda ctx: {'email_address': 'a1', 'user_id':u1.id} + ), + CompiledSQL( + "INSERT INTO addresses (user_id, email_address) " + "VALUES (:user_id, :email_address)", + lambda ctx: {'email_address': 'a2', 'user_id':u1.id} + ), + ) + + return + sess.delete(n1) + sess.delete(n2) + sess.delete(n3) + self.assert_sql_execution( + testing.db, + sess.flush, + CompiledSQL( + "DELETE FROM addresses WHERE addresses.id = :id", + [{'id':a1.id},{'id':a2.id}] + ), + CompiledSQL( + "DELETE FROM users WHERE users.id = :id", + {'id':u1.id} + ), + ) +