diff options
-rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 23 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 52 | ||||
-rw-r--r-- | test/orm/test_unitofworkv2.py | 43 |
3 files changed, 78 insertions, 40 deletions
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index b7c62a487..1c6473375 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -205,11 +205,11 @@ class OneToManyDP(DependencyProcessor): assert parent_saves in uow.cycles assert child_saves in uow.cycles - added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True) + added, unchanged, 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) + save_parent = unitofwork.SaveUpdateState(uow, state) after_save = unitofwork.ProcessState(uow, self, False, state) for child_state in added + unchanged + deleted: @@ -218,9 +218,9 @@ class OneToManyDP(DependencyProcessor): (deleted, listonly) = uow.states[child_state] if deleted: - child_action = unitofwork.DeleteState(child_state) + child_action = unitofwork.DeleteState(uow, child_state) else: - child_action = unitofwork.SaveUpdateState(child_state) + child_action = unitofwork.SaveUpdateState(uow, child_state) uow.dependencies.update([ (save_parent, after_save), @@ -234,12 +234,12 @@ class OneToManyDP(DependencyProcessor): assert parent_deletes in uow.cycles assert child_deletes in uow.cycles - added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True) + added, unchanged, 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) + delete_parent = unitofwork.DeleteState(uow, state) + before_delete = unitofwork.ProcessState(uow, self, True, state) for child_state in added + unchanged + deleted: if child_state is None: @@ -247,14 +247,13 @@ class OneToManyDP(DependencyProcessor): (deleted, listonly) = uow.states[child_state] if deleted: - child_action = unitofwork.DeleteState(child_state) + child_action = unitofwork.DeleteState(uow, child_state) else: - child_action = unitofwork.SaveUpdateState(child_state) + child_action = unitofwork.SaveUpdateState(uow, child_state) uow.dependencies.update([ - (child_action, ) - (save_parent, after_save), - (after_save, child_action), + (child_action, before_delete), + (before_delete, delete_parent), ]) diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 65d85a37b..d2f5946df 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -167,20 +167,30 @@ class UOWTransaction(object): 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) + + if cycles: + convert = {} + for rec in cycles: + convert[rec] = set(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 + # remove old dependencies between two cycle nodes, + # splice dependencies for dependencies from/to cycle + # nodes from non-cycle nodes if cycles.issuperset(edge): self.dependencies.remove(edge) + elif edge[0] in cycles: + for dep in convert[edge[0]]: + self.dependencies.add((dep, edge[1])) + elif edge[1] in cycles: + for dep in convert[edge[1]]: + self.dependencies.add((edge[0], dep)) sort = topological.sort(self.dependencies, self.postsort_actions.values()) print sort for rec in sort: + if rec in cycles: + continue rec.execute(self) @@ -286,10 +296,11 @@ class ProcessAll(PropertyRecMixin, PostSortRec): 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) + self.dependency_processor.per_deleted_state_flush_actions(uow, state) else: - self.dependency_processor.per_saved_state_flush_actions(uow, self.dependency_processor, state) - + self.dependency_processor.per_saved_state_flush_actions(uow, state) + return iter([]) + class SaveUpdateAll(PostSortRec): def __init__(self, uow, mapper): self.mapper = mapper @@ -302,7 +313,7 @@ class SaveUpdateAll(PostSortRec): def per_state_flush_actions(self, uow): for state in uow.states_for_mapper_hierarchy(self.mapper, False, False): - SaveUpdateState(uow, state) + yield SaveUpdateState(uow, state) class DeleteAll(PostSortRec): def __init__(self, uow, mapper): @@ -316,20 +327,39 @@ class DeleteAll(PostSortRec): def per_state_flush_actions(self, uow): for state in uow.states_for_mapper_hierarchy(self.mapper, True, False): - DeleteState(uow, state) + yield DeleteState(uow, state) class ProcessState(PostSortRec): def __init__(self, uow, dependency_processor, delete, state): self.dependency_processor = dependency_processor self.delete = delete self.state = state + + def execute(self, uow): + if self.delete: + self.dependency_processor.process_deletes(uow, [self.state]) + else: + self.dependency_processor.process_saves(uow, [self.state]) class SaveUpdateState(PostSortRec): def __init__(self, uow, state): self.state = state + def execute(self, uow): + mapper = self.state.manager.mapper.base_mapper + mapper._save_obj( + [self.state], + uow + ) + class DeleteState(PostSortRec): def __init__(self, uow, state): self.state = state + def execute(self, uow): + mapper = self.state.manager.mapper.base_mapper + mapper._delete_obj( + [self.state], + uow + ) diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py index 15b481898..8a39e77ee 100644 --- a/test/orm/test_unitofworkv2.py +++ b/test/orm/test_unitofworkv2.py @@ -195,7 +195,7 @@ class RudimentaryFlushTest(UOWTest): ) class SingleCycleTest(UOWTest): - def test_one_to_many(self): + def test_one_to_many_save(self): mapper(Node, nodes, properties={ 'children':relationship(Node) }) @@ -209,36 +209,45 @@ class SingleCycleTest(UOWTest): self.assert_sql_execution( testing.db, sess.flush, + CompiledSQL( - "INSERT INTO nodes (data) VALUES (:data)", - {'data': 'n1'} + "INSERT INTO nodes (parent_id, data) VALUES (:parent_id, :data)", + {'parent_id': None, 'data': 'n1'} ), + AllOf( CompiledSQL( - "INSERT INTO addresses (user_id, email_address) " - "VALUES (:user_id, :email_address)", - lambda ctx: {'email_address': 'a1', 'user_id':u1.id} + "INSERT INTO nodes (parent_id, data) VALUES (:parent_id, :data)", + lambda ctx: {'parent_id': n1.id, 'data': 'n2'} ), CompiledSQL( - "INSERT INTO addresses (user_id, email_address) " - "VALUES (:user_id, :email_address)", - lambda ctx: {'email_address': 'a2', 'user_id':u1.id} + "INSERT INTO nodes (parent_id, data) VALUES (:parent_id, :data)", + lambda ctx: {'parent_id': n1.id, 'data': 'n3'} ), + ) ) + + def test_one_to_many_delete_all(self): + mapper(Node, nodes, properties={ + 'children':relationship(Node) + }) + sess = create_session() + + n2, n3 = Node(data='n2', children=[]), Node(data='n3', children=[]) + n1 = Node(data='n1', children=[n2, n3]) + + sess.add(n1) + sess.flush() - 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} + AllOf( + CompiledSQL("DELETE FROM nodes WHERE nodes.id = :id", {'id':3}), + CompiledSQL("DELETE FROM nodes WHERE nodes.id = :id", {'id':2}), ), + CompiledSQL("DELETE FROM nodes WHERE nodes.id = :id", {'id':1}) ) |