From: Mike Bayer Date: Fri, 18 Jun 2010 15:36:35 +0000 (-0400) Subject: this approach seems to allow many-to-one post updates to occur as a single action... X-Git-Tag: rel_0_6_2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46e0138405778379848e6c6f2ab424388724e555;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git this approach seems to allow many-to-one post updates to occur as a single action. for one-to-many, we have it working the old way still. as usual the huge job is the tests. also I am not yet certain that post updates can always be a "full mapper" operation, i.e. are never involved in per-state dependencies, or even if the old approach supports that. --- diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 7a8c4cf702..4d7a038943 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -83,7 +83,8 @@ class DependencyProcessor(object): parent_deletes, child_deletes, after_save, - before_delete) + before_delete + ) def per_state_flush_actions(self, uow, states, isdelete): @@ -95,6 +96,11 @@ class DependencyProcessor(object): """ + parent_base_mapper = self.parent.primary_base_mapper + child_base_mapper = self.mapper.primary_base_mapper + child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper) + child_deletes = unitofwork.DeleteAll(uow, child_base_mapper) + # locate and disable the aggregate processors # for this dependency @@ -107,11 +113,6 @@ class DependencyProcessor(object): # check if the "child" side is part of the cycle - parent_base_mapper = self.parent.primary_base_mapper - child_base_mapper = self.mapper.primary_base_mapper - child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper) - child_deletes = unitofwork.DeleteAll(uow, child_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 @@ -196,7 +197,7 @@ class DependencyProcessor(object): # establish dependencies between our possibly per-state # parent action and our possibly per-state child action. - for (child_action, childisdelete) in child_actions: + for child_action, childisdelete in child_actions: self.per_state_dependencies(uow, parent_saves, parent_deletes, child_action, @@ -265,14 +266,15 @@ class DependencyProcessor(object): set ) - def _post_update(self, state, uowcommit, related, processed): + def _post_update(self, state, uowcommit, related, processed, immediate): if processed is not None and state in processed: return for x in related: if x is not None: uowcommit.issue_post_update( state, - [r for l, r in self.prop.synchronize_pairs] + [r for l, r in self.prop.synchronize_pairs], + immediate ) if processed is not None: processed.add(state) @@ -292,13 +294,16 @@ class OneToManyDP(DependencyProcessor): parent_deletes, child_deletes, after_save, - before_delete): + before_delete, + ): if self.post_update: + uow.dependencies.update([ (child_saves, after_save), (parent_saves, after_save), (before_delete, parent_deletes), (before_delete, child_deletes), + ]) else: uow.dependencies.update([ @@ -321,29 +326,30 @@ class OneToManyDP(DependencyProcessor): isdelete, childisdelete): if self.post_update: + # TODO: this whole block is not covered # by any tests if not isdelete: if childisdelete: uow.dependencies.update([ (save_parent, after_save), - (after_save, child_action) + (after_save, child_action), ]) else: uow.dependencies.update([ (save_parent, after_save), - (child_action, after_save) + (child_action, after_save), ]) else: if childisdelete: uow.dependencies.update([ (before_delete, delete_parent), - (before_delete, child_action) + (before_delete, child_action), ]) else: uow.dependencies.update([ - (before_delete, delete_parent), - (child_action, before_delete) + (before_delete, delete_parent), + (child_action, before_delete), ]) elif not isdelete: uow.dependencies.update([ @@ -449,7 +455,7 @@ class OneToManyDP(DependencyProcessor): self._post_update( child, uowcommit, - [state], processed) + [state], processed, True) if self.post_update or not self.cascade.delete: for child in set(history.unchanged).\ difference(children_added): @@ -462,7 +468,7 @@ class OneToManyDP(DependencyProcessor): self._post_update( child, uowcommit, - [state], processed) + [state], processed, True) # technically, we can even remove each child from the # collection here too. but this would be a somewhat # inconsistent behavior since it wouldn't happen if the old @@ -482,7 +488,8 @@ class OneToManyDP(DependencyProcessor): child, uowcommit, [state], - processed + processed, + True ) for child in history.deleted: @@ -530,11 +537,19 @@ class ManyToOneDP(DependencyProcessor): before_delete): if self.post_update: + child_post_updates = unitofwork.PostUpdateThing( + uow, self.parent.primary_base_mapper, False) + child_pre_updates = unitofwork.PostUpdateThing( + uow, self.parent.primary_base_mapper, True) + uow.dependencies.update([ (child_saves, after_save), (parent_saves, after_save), (before_delete, parent_deletes), (before_delete, child_deletes), + (after_save, child_post_updates), + (before_delete, child_pre_updates), + (child_pre_updates, child_deletes), ]) else: uow.dependencies.update([ @@ -552,21 +567,36 @@ class ManyToOneDP(DependencyProcessor): isdelete, childisdelete): if self.post_update: + if not isdelete: + child_post_updates = unitofwork.PostUpdateThing( + uow, self.parent.primary_base_mapper, False) if childisdelete: uow.dependencies.update([ (save_parent, after_save), - (after_save, child_action) + (after_save, child_action), # can remove + + (after_save, child_post_updates), + (child_post_updates, child_action) ]) else: uow.dependencies.update([ (save_parent, after_save), - (child_action, after_save) + (child_action, after_save), + + (after_save, child_post_updates) ]) else: + child_pre_updates = unitofwork.PostUpdateThing( + uow, self.parent.primary_base_mapper, True) + uow.dependencies.update([ - (before_delete, delete_parent), - (before_delete, child_action) + (before_delete, delete_parent), # can remove + (before_delete, child_action), # can remove + + (before_delete, child_pre_updates), + (child_pre_updates, delete_parent), + (child_pre_updates, child_action) ]) elif not isdelete: @@ -647,7 +677,7 @@ class ManyToOneDP(DependencyProcessor): self._post_update( state, uowcommit, - history.sum(), processed) + history.sum(), processed, False) def process_saves(self, uowcommit, states): if self.post_update: @@ -662,7 +692,7 @@ class ManyToOneDP(DependencyProcessor): if self.post_update: self._post_update( state, - uowcommit, history.sum(), processed) + uowcommit, history.sum(), processed, False) def _synchronize(self, state, child, associationrow, clearkeys, uowcommit): if state is None or (not self.post_update and uowcommit.is_deleted(state)): @@ -789,7 +819,8 @@ class ManyToManyDP(DependencyProcessor): parent_deletes, child_deletes, after_save, - before_delete): + before_delete + ): uow.dependencies.update([ (parent_saves, after_save), diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index cbf45039e1..aed50f6499 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -110,6 +110,8 @@ class UOWTransaction(object): # or insert/updated, or just refreshed self.states = {} + self.post_update_states = util.defaultdict(set) + @property def has_work(self): return bool(self.states) @@ -183,11 +185,14 @@ class UOWTransaction(object): if not listonly and (isdelete or cancel_delete): self.states[state] = (isdelete, False) - def issue_post_update(self, state, post_update_cols): - mapper = state.manager.mapper.base_mapper - mapper._save_obj([state], self, \ - postupdate=True, \ - post_update_cols=set(post_update_cols)) + def issue_post_update(self, state, post_update_cols, immediate): + if immediate: + mapper = state.manager.mapper.base_mapper + mapper._save_obj([state], self, \ + postupdate=True, \ + post_update_cols=set(post_update_cols)) + else: + self.post_update_states[state].update(post_update_cols) @util.memoized_property def _mapper_for_dep(self): @@ -272,10 +277,10 @@ class UOWTransaction(object): def execute(self): postsort_actions = self._generate_actions() - #sort = topological.sort(self.dependencies, postsort_actions) + sort = topological.sort(self.dependencies, postsort_actions) #print "--------------" #print self.dependencies - #print list(sort) + print list(sort) #print "COUNT OF POSTSORT ACTIONS", len(postsort_actions) # execute @@ -414,7 +419,26 @@ class ProcessAll(IterateMappersMixin, PostSortRec): (isdelete, listonly) = uow.states[state] if isdelete == self.delete and not listonly: yield state - + +class PostUpdateThing(PostSortRec): + def __init__(self, uow, mapper, isdelete): + self.mapper = mapper + self.isdelete = isdelete + + def execute(self, uow): + states = [] + update_cols = set() + + for state in uow.states_for_mapper_hierarchy(self.mapper, self.isdelete, False): + if state not in uow.post_update_states: + continue + states.append(state) + update_cols.update(uow.post_update_states[state]) + + self.mapper._save_obj(states, uow, \ + postupdate=True, \ + post_update_cols=update_cols) + class SaveUpdateAll(PostSortRec): def __init__(self, uow, mapper): self.mapper = mapper