From: Mike Bayer Date: Thu, 1 Apr 2010 20:00:57 +0000 (-0400) Subject: this version passes one to many tests so far X-Git-Tag: rel_0_6_0~74 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=67481476583d9dadd03f342cfa94628559a4c260;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git this version passes one to many tests so far --- diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 1c6473375e..1823b33996 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -168,6 +168,7 @@ class OneToManyDP(DependencyProcessor): def per_property_flush_actions(self, uow): unitofwork.GetDependentObjects(uow, self, False, True) + unitofwork.GetDependentObjects(uow, self, True, True) after_save = unitofwork.ProcessAll(uow, self, False, True) before_delete = unitofwork.ProcessAll(uow, self, True, True) @@ -186,76 +187,73 @@ class OneToManyDP(DependencyProcessor): (before_delete, child_deletes), ]) else: - unitofwork.GetDependentObjects(uow, self, True, True) - uow.dependencies.update([ (parent_saves, after_save), (after_save, child_saves), - + (after_save, child_deletes), + (child_saves, parent_deletes), + (child_deletes, parent_deletes), + (before_delete, child_saves), + (before_delete, child_deletes), - (child_deletes, parent_deletes) ]) - - def per_saved_state_flush_actions(self, uow, state): + + def per_state_flush_actions(self, uow, state, isdelete): 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 + if not isdelete: + 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 + else: + 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 + + after_save = unitofwork.ProcessAll(uow, self, False, True) + before_delete = unitofwork.ProcessAll(uow, self, True, True) + after_save.disabled = True + before_delete.disabled = True - added, unchanged, deleted = uow.get_attribute_history(state, self.key, passive=True) - if not added and not unchanged and not deleted: + sum_ = uow.get_attribute_history(state, self.key, passive=True).sum() + if not sum_: return - - save_parent = unitofwork.SaveUpdateState(uow, state) - after_save = unitofwork.ProcessState(uow, self, False, state) - for child_state in added + unchanged + deleted: + if not isdelete: + save_parent = unitofwork.SaveUpdateState(uow, state) + after_save = unitofwork.ProcessState(uow, self, False, state) + yield after_save + else: + delete_parent = unitofwork.DeleteState(uow, state) + before_delete = unitofwork.ProcessState(uow, self, True, state) + yield before_delete + + for child_state in sum_: if child_state is None: continue - + (deleted, listonly) = uow.states[child_state] if deleted: child_action = unitofwork.DeleteState(uow, child_state) else: child_action = unitofwork.SaveUpdateState(uow, 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, 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(uow, state) - before_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(uow, child_state) + if not isdelete: + uow.dependencies.update([ + (save_parent, after_save), + (after_save, child_action), + (save_parent, child_action) + ]) else: - child_action = unitofwork.SaveUpdateState(uow, child_state) - - uow.dependencies.update([ - (child_action, before_delete), - (before_delete, delete_parent), - ]) - + uow.dependencies.update([ + (child_action, before_delete), + (before_delete, delete_parent), + (child_action, delete_parent) + ]) + def presort_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of child objects diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 07f6a09aba..5629157056 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -506,6 +506,9 @@ class MapperProperty(object): def per_property_flush_actions(self, uow): pass + + def per_state_flush_actions(self, uow, state, isdelete): + return iter([]) def is_primary(self): """Return True if this ``MapperProperty``'s mapper is the diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index c95bcd4c87..9a18bd4cf6 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1261,6 +1261,18 @@ class Mapper(object): for prop in self._props.values(): prop.per_property_flush_actions(uow) + + def per_state_flush_actions(self, uow, state, isdelete): + if isdelete: + action = unitofwork.DeleteState(uow, state) + else: + action = unitofwork.SaveUpdateState(uow, state) + + yield action + for prop in self._props.values(): + for rec in prop.per_state_flush_actions(uow, state, isdelete): + yield rec + def _save_obj(self, states, uowtransaction, postupdate=False, post_update_cols=None, single=False): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 61dc9eb55f..754cdc118f 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -1207,6 +1207,11 @@ class RelationshipProperty(StrategizedProperty): if not self.viewonly and self._dependency_processor: self._dependency_processor.per_property_flush_actions(uow) + def per_state_flush_actions(self, uow, state, isdelete): + if not self.viewonly and self._dependency_processor: + for rec in self._dependency_processor.per_state_flush_actions(uow, state, isdelete): + yield rec + def _create_joins(self, source_polymorphic=False, source_selectable=None, dest_polymorphic=False, dest_selectable=None, of_type=None): diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index d2f5946df5..7bfac95b63 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -158,6 +158,9 @@ class UOWTransaction(object): def execute(self): + # execute presort_actions, until all states + # have been processed. a presort_action might + # add new states to the uow. while True: ret = False for action in self.presort_actions.values(): @@ -166,31 +169,44 @@ class UOWTransaction(object): if not ret: break + # see if the graph of mapper dependencies has cycles. self.cycles = cycles = topological.find_cycles(self.dependencies, self.postsort_actions.values()) - + if cycles: - convert = {} - for rec in cycles: - convert[rec] = set(rec.per_state_flush_actions(self)) - - for edge in list(self.dependencies): - # 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)) - + # if yes, break the per-mapper actions into + # per-state actions + convert = dict( + (rec, set(rec.per_state_flush_actions(self))) + for rec in cycles + ) + + # rewrite the existing dependencies to point to + # the per-state actions for those per-mapper actions + # that were broken up. + for edge in list(self.dependencies): + if cycles.issuperset(edge): + self.dependencies.remove(edge) + elif edge[0] in cycles: + self.dependencies.remove(edge) + for dep in convert[edge[0]]: + self.dependencies.add((dep, edge[1])) + elif edge[1] in cycles: + self.dependencies.remove(edge) + for dep in convert[edge[1]]: + self.dependencies.add((edge[0], dep)) + + # remove actions that were part of the cycles, + # or have been marked as "disabled" by the "breaking up" + # process + for k, v in list(self.postsort_actions.items()): + if v.disabled or v in cycles: + del self.postsort_actions[k] + + # execute actions sort = topological.sort(self.dependencies, self.postsort_actions.values()) - print sort + #print self.dependencies + #print sort for rec in sort: - if rec in cycles: - continue rec.execute(self) @@ -219,6 +235,8 @@ class PreSortRec(object): return ret class PostSortRec(object): + disabled = False + def __new__(cls, uow, *args): key = (cls, ) + args if key in uow.postsort_actions: @@ -294,11 +312,6 @@ class ProcessAll(PropertyRecMixin, PostSortRec): 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, state) - else: - self.dependency_processor.per_saved_state_flush_actions(uow, state) return iter([]) class SaveUpdateAll(PostSortRec): @@ -313,7 +326,8 @@ class SaveUpdateAll(PostSortRec): def per_state_flush_actions(self, uow): for state in uow.states_for_mapper_hierarchy(self.mapper, False, False): - yield SaveUpdateState(uow, state) + for rec in self.mapper.per_state_flush_actions(uow, state, False): + yield rec class DeleteAll(PostSortRec): def __init__(self, uow, mapper): @@ -327,7 +341,8 @@ class DeleteAll(PostSortRec): def per_state_flush_actions(self, uow): for state in uow.states_for_mapper_hierarchy(self.mapper, True, False): - yield DeleteState(uow, state) + for rec in self.mapper.per_state_flush_actions(uow, state, True): + yield rec class ProcessState(PostSortRec): def __init__(self, uow, dependency_processor, delete, state): @@ -352,6 +367,12 @@ class SaveUpdateState(PostSortRec): uow ) + def __repr__(self): + return "%s(%s)" % ( + self.__class__.__name__, + mapperutil.state_str(self.state) + ) + class DeleteState(PostSortRec): def __init__(self, uow, state): self.state = state @@ -363,3 +384,9 @@ class DeleteState(PostSortRec): uow ) + def __repr__(self): + return "%s(%s)" % ( + self.__class__.__name__, + mapperutil.state_str(self.state) + ) + diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py index 8a39e77ee4..4ccc3df689 100644 --- a/test/orm/test_unitofworkv2.py +++ b/test/orm/test_unitofworkv2.py @@ -89,11 +89,11 @@ class RudimentaryFlushTest(UOWTest): sess.flush, CompiledSQL( "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", - [{u'addresses_id': 1, 'user_id': None}] + lambda ctx: [{u'addresses_id': a1.id, 'user_id': None}] ), CompiledSQL( "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", - [{u'addresses_id': 2, 'user_id': None}] + lambda ctx: [{u'addresses_id': a2.id, 'user_id': None}] ), CompiledSQL( "DELETE FROM users WHERE users.id = :id", @@ -182,11 +182,11 @@ class RudimentaryFlushTest(UOWTest): sess.flush, CompiledSQL( "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", - [{u'addresses_id': 1, 'user_id': None}] + lambda ctx: [{u'addresses_id': a1.id, 'user_id': None}] ), CompiledSQL( "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id", - [{u'addresses_id': 2, 'user_id': None}] + lambda ctx: [{u'addresses_id': a2.id, 'user_id': None}] ), CompiledSQL( "DELETE FROM users WHERE users.id = :id",