diff options
| -rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 17 | ||||
| -rw-r--r-- | lib/sqlalchemy/topological.py | 47 | ||||
| -rw-r--r-- | test/orm/test_unitofworkv2.py | 24 |
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 |
