summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py17
-rw-r--r--lib/sqlalchemy/topological.py47
-rw-r--r--test/orm/test_unitofworkv2.py24
3 files changed, 85 insertions, 3 deletions
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index 432e2e238..9f888f867 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -219,8 +219,21 @@ class UOWTransaction(object):
print "------------------------"
print self.dependencies
print sort
- for rec in sort:
- rec.execute(self)
+
+ if cycles:
+ # organize into a tree so that groups of nodes can be
+ # merged together, allowing better maintenance of insert
+ # ordering and other things
+ (head, children) = topological.organize_as_tree(self.dependencies, sort)
+ stack = [(head, children)]
+
+ while stack:
+ node, children = stack.pop()
+ node.execute(self)
+ stack += children
+ else:
+ for rec in sort:
+ rec.execute(self)
def finalize_flush_changes(self):
diff --git a/lib/sqlalchemy/topological.py b/lib/sqlalchemy/topological.py
index bcf47bd64..7253cdc4d 100644
--- a/lib/sqlalchemy/topological.py
+++ b/lib/sqlalchemy/topological.py
@@ -114,3 +114,50 @@ def find_cycles(tuples, allitems):
else:
node = stack.pop()
return output
+
+
+def organize_as_tree(tuples, allitems):
+ """Given a list of sorted nodes from a topological sort, organize the
+ nodes into a tree structure, with as many non-dependent nodes
+ set as siblings to each other as possible.
+
+ returns nodes as tuples (item, children).
+ """
+
+ nodes = allitems
+ edges = _EdgeCollection(tuples)
+ children = util.defaultdict(list)
+
+ if not nodes:
+ return None
+ # a list of all currently independent subtrees as a tuple of
+ # (root_node, set_of_all_tree_nodes, set_of_all_cycle_nodes_in_tree)
+ # order of the list has no semantics for the algorithmic
+ independents = []
+ # in reverse topological order
+ for node in reversed(nodes):
+ # nodes subtree and cycles contain the node itself
+ subtree = set([node])
+ # get a set of dependent nodes of node and its cycles
+ nodealldeps = edges.outgoing(node)
+ if nodealldeps:
+ # iterate over independent node indexes in reverse order so we can efficiently remove them
+ for index in xrange(len(independents) - 1, -1, -1):
+ child, childsubtree = independents[index]
+ # if there is a dependency between this node and an independent node
+ if (childsubtree.intersection(nodealldeps)):
+ # prepend child to nodes children
+ # (append should be fine, but previous implemetation used prepend)
+ children[node][0:0] = [(child, children[child])]
+ # merge childs subtree and cycles
+ subtree.update(childsubtree)
+ # remove the child from list of independent subtrees
+ independents[index:index+1] = []
+ # add node as a new independent subtree
+ independents.append((node, subtree))
+ # choose an arbitrary node from list of all independent subtrees
+ head = independents.pop()[0]
+ # add all other independent subtrees as a child of the chosen root
+ # used prepend [0:0] instead of extend to maintain exact behaviour of previous implementation
+ children[head][0:0] = [(i[0], children[i[0]]) for i in independents]
+ return (head, children[head])
diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py
index 5dd334d6e..e532b14fe 100644
--- a/test/orm/test_unitofworkv2.py
+++ b/test/orm/test_unitofworkv2.py
@@ -371,4 +371,26 @@ class SingleCycleTest(UOWTest):
sess.delete(n1)
sess.delete(n3)
- sess.flush() \ No newline at end of file
+ sess.flush()
+
+ def test_bidirectional_multilevel_save(self):
+ mapper(Node, nodes, properties={
+ 'children':relationship(Node,
+ backref=backref('parent', remote_side=nodes.c.id)
+ )
+ })
+ sess = create_session()
+ n1 = Node(data='n1')
+ n1.children.append(Node(data='n11'))
+ n1.children.append(Node(data='n12'))
+ n1.children.append(Node(data='n13'))
+ n1.children[1].children.append(Node(data='n121'))
+ n1.children[1].children.append(Node(data='n122'))
+ n1.children[1].children.append(Node(data='n123'))
+ sess.add(n1)
+ sess.flush()
+# self.assert_sql_execution(
+# testing.db,
+ # sess.flush,
+ # )
+ \ No newline at end of file