summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/orm/dependency.py23
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py52
-rw-r--r--test/orm/test_unitofworkv2.py43
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})
)