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
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):
if history:
for child in history.added:
self._synchronize(state, child, None, False, uowcommit)
+
self._conditional_post_update(state, uowcommit, history.sum())
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().
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
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):
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
class RudimentaryFlushTest(UOWTest):
- def test_one_to_many(self):
+ def test_one_to_many_save(self):
mapper(User, users, properties={
'addresses':relationship(Address),
})
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)
{'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={
),
)
+ 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)
{'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}
+ ),
+ )
+