From 3d3095497b872696e8860763f54217f425c7d35b Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 3 Apr 2010 19:41:56 -0400 Subject: [PATCH] row switch works for post-cycle sorts too...just needed more data in the deps. the pattern here is more data needed for each tweak. --- lib/sqlalchemy/orm/dependency.py | 14 +++++++++++--- lib/sqlalchemy/orm/mapper.py | 8 ++++++-- lib/sqlalchemy/orm/unitofwork.py | 27 +++++++++------------------ test/orm/test_unitofworkv2.py | 16 ++++++++++++++++ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 86cac2d875..398bdd91cd 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -79,6 +79,8 @@ class DependencyProcessor(object): before_delete) def per_state_flush_actions(self, uow, state, isdelete): + # locate and disable the aggregate processors + # for this dependency after_save = unitofwork.ProcessAll(uow, self, False, True) before_delete = unitofwork.ProcessAll(uow, self, True, True) after_save.disabled = True @@ -88,14 +90,20 @@ class DependencyProcessor(object): child_saves = unitofwork.SaveUpdateAll(uow, self.mapper.base_mapper) child_deletes = unitofwork.DeleteAll(uow, self.mapper.base_mapper) if child_saves not in uow.cycles: + # based on the current dependencies we use, the saves/ + # deletes should always be in the 'cycles' collection + # together. if this changes, we will have to break up + # this method a bit more. assert child_deletes not in uow.cycles - # its not, so we will link per-state + + # child side is not part of the cycle, so we will link per-state # actions to the aggregate "saves", "deletes" actions child_actions = [ (child_saves, False), (child_deletes, True) ] else: - # it is. see if there's any objects. + # child side is part of the cycle. create dependencies for + # each related object. sum_ = uow.get_attribute_history(state, self.key, passive=True).sum() if not sum_: return @@ -114,7 +122,7 @@ class DependencyProcessor(object): child_actions.append(child_action) # check if the "parent" side is part of the cycle, - # if so break up parent_saves + # if so break up parent_saves or parent_deletes if not isdelete: parent_saves = unitofwork.SaveUpdateAll(uow, self.parent.base_mapper) if parent_saves in uow.cycles: diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 319b061586..ac35ce49b7 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1263,11 +1263,15 @@ class Mapper(object): prop.per_property_flush_actions(uow) def per_state_flush_actions(self, uow, state, isdelete): + # keep saves before deletes - + # this ensures 'row switch' operations work if isdelete: action = unitofwork.DeleteState(uow, state) + uow.dependencies.add((unitofwork.SaveUpdateAll(uow, self.base_mapper), action)) else: action = unitofwork.SaveUpdateState(uow, state) - + uow.dependencies.add((action, unitofwork.DeleteAll(uow, self.base_mapper))) + yield action mapper = state.manager.mapper for prop in mapper._props.values(): @@ -1354,7 +1358,7 @@ class Mapper(object): self._log_debug( "detected row switch for identity %s. will update %s, remove %s from " "transaction", instance_key, state_str(state), state_str(existing)) - + # remove the "delete" flag from the existing element uowtransaction.remove_state_actions(existing) row_switches[state] = existing diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index a4eb00f70a..c8eca67eb7 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -105,15 +105,7 @@ class UOWTransaction(object): def remove_state_actions(self, state): """remove pending actions for a state from the uowtransaction.""" - if state in self.states: - isdelete, listonly = self.states[state] - self.states[state] = (False, True) - if isdelete: - self.postsort_actions.pop((DeleteState, state), None) - else: - self.postsort_actions.pop((SaveUpdateState, state), None) - - + self.states[state] = (False, True) def get_attribute_history(self, state, key, passive=True): hashkey = ("history", state, key) @@ -223,11 +215,9 @@ class UOWTransaction(object): # execute actions sort = topological.sort(self.dependencies, self.postsort_actions.values()) - print "------------------------" -# import pdb -# pdb.set_trace() - print self.dependencies - print sort +# print "------------------------" +# print self.dependencies +# print sort for rec in sort: rec.execute(self) @@ -411,10 +401,11 @@ class DeleteState(PostSortRec): def execute(self, uow): mapper = self.state.manager.mapper.base_mapper - mapper._delete_obj( - [self.state], - uow - ) + if uow.states[self.state][0]: + mapper._delete_obj( + [self.state], + uow + ) def __repr__(self): return "%s(%s)" % ( diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py index adc7dc79a4..5dd334d6e8 100644 --- a/test/orm/test_unitofworkv2.py +++ b/test/orm/test_unitofworkv2.py @@ -338,7 +338,23 @@ class SingleCycleTest(UOWTest): CompiledSQL("DELETE FROM nodes WHERE nodes.id = :id", lambda ctx: {'id':n1.id}) ) + + def test_cycle_rowswitch(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]) + sess.add(n1) + sess.flush() + sess.delete(n2) + n3.id = n2.id + n1.children.append(n3) + sess.flush() + def test_bidirectional_mutations_one(self): mapper(Node, nodes, properties={ 'children':relationship(Node, backref=backref('parent', remote_side=nodes.c.id)) -- 2.47.3