summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/mapping/topological.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/mapping/topological.py')
-rw-r--r--lib/sqlalchemy/mapping/topological.py349
1 files changed, 0 insertions, 349 deletions
diff --git a/lib/sqlalchemy/mapping/topological.py b/lib/sqlalchemy/mapping/topological.py
deleted file mode 100644
index 495eec8ce..000000000
--- a/lib/sqlalchemy/mapping/topological.py
+++ /dev/null
@@ -1,349 +0,0 @@
-# topological.py
-# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-"""topological sorting algorithms. the key to the unit of work is to assemble a list
-of dependencies amongst all the different mappers that have been defined for classes.
-Related tables with foreign key constraints have a definite insert order, deletion order,
-objects need dependent properties from parent objects set up before saved, etc.
-These are all encoded as dependencies, in the form "mapper X is dependent on mapper Y",
-meaning mapper Y's objects must be saved before those of mapper X, and mapper X's objects
-must be deleted before those of mapper Y.
-
-The topological sort is an algorithm that receives this list of dependencies as a "partial
-ordering", that is a list of pairs which might say, "X is dependent on Y", "Q is dependent
-on Z", but does not necessarily tell you anything about Q being dependent on X. Therefore,
-its not a straight sort where every element can be compared to another...only some of the
-elements have any sorting preference, and then only towards just some of the other elements.
-For a particular partial ordering, there can be many possible sorts that satisfy the
-conditions.
-
-An intrinsic "gotcha" to this algorithm is that since there are many possible outcomes
-to sorting a partial ordering, the algorithm can return any number of different results for the
-same input; just running it on a different machine architecture, or just random differences
-in the ordering of dictionaries, can change the result that is returned. While this result
-is guaranteed to be true to the incoming partial ordering, if the partial ordering itself
-does not properly represent the dependencies, code that works fine will suddenly break, then
-work again, then break, etc. Most of the bugs I've chased down while developing the "unit of work"
-have been of this nature - very tricky to reproduce and track down, particularly before I
-realized this characteristic of the algorithm.
-"""
-import string, StringIO
-from sets import *
-import sqlalchemy.util as util
-from sqlalchemy.exceptions import *
-
-class QueueDependencySorter(object):
- """this is a topological sort from wikipedia. its very stable. it creates a straight-line
- list of elements, then a second pass groups non-dependent actions together to build
- more of a tree structure with siblings."""
- class Node:
- """represents a node in a tree. stores an 'item' which represents the
- dependent thing we are talking about. if node 'a' is an ancestor node of
- node 'b', it means 'a's item is *not* dependent on that of 'b'."""
- def __init__(self, item):
- self.item = item
- self.edges = {}
- self.dependencies = {}
- self.children = []
- self.cycles = None
- def __str__(self):
- return self.safestr()
- def safestr(self, indent=0):
- return (' ' * indent) + "%s (idself=%s)" % (str(self.item), repr(id(self))) + repr(self.cycles) + "\n" + string.join([n.safestr(indent + 1) for n in self.children], '')
- def describe(self):
- return "%s (idself=%s)" % (str(self.item), repr(id(self)))
- def __repr__(self):
- return self.describe()
- def is_dependent(self, child):
- if self.cycles is not None:
- for c in self.cycles:
- if c.dependencies.has_key(child):
- return True
- if child.cycles is not None:
- for c in child.cycles:
- if self.dependencies.has_key(c):
- return True
- return self.dependencies.has_key(child)
-
- def __init__(self, tuples, allitems):
- self.tuples = tuples
- self.allitems = allitems
-
- def _dump_edges(self, edges):
- s = StringIO.StringIO()
- for key, value in edges.iteritems():
- for c in value.keys():
- s.write("%s->%s\n" % (repr(key), repr(c)))
- return s.getvalue()
-
- def sort(self, allow_self_cycles=True, allow_all_cycles=False):
- (tuples, allitems) = (self.tuples, self.allitems)
-
- #print "\n---------------------------------\n"
- #print repr([t for t in tuples])
- #print repr([a for a in allitems])
- #print "\n---------------------------------\n"
-
- nodes = {}
- edges = {}
- for item in allitems + [t[0] for t in tuples] + [t[1] for t in tuples]:
- if not nodes.has_key(item):
- node = QueueDependencySorter.Node(item)
- nodes[item] = node
- edges[node] = {}
-
- for t in tuples:
- if t[0] is t[1]:
- if allow_self_cycles:
- n = nodes[t[0]]
- n.cycles = Set([n])
- continue
- else:
- raise CommitError("Self-referential dependency detected " + repr(t))
- childnode = nodes[t[1]]
- parentnode = nodes[t[0]]
- self._add_edge(edges, (parentnode, childnode))
-
- queue = []
- for n in nodes.values():
- if len(n.edges) == 0:
- queue.append(n)
- cycles = {}
- output = []
- while len(edges) > 0:
- #print self._dump_edges(edges)
- if len(queue) == 0:
- # edges remain but no edgeless nodes to remove; this indicates
- # a cycle
- if allow_all_cycles:
- cycle = self._find_cycle(edges)
- lead = cycle[0][0]
- lead.cycles = Set()
- for edge in cycle:
- n = self._remove_edge(edges, edge)
- lead.cycles.add(edge[0])
- lead.cycles.add(edge[1])
- if n is not None:
- queue.append(n)
- if n is not lead:
- n._cyclical = True
- # loop through cycle
- # remove edges from the edge dictionary
- # install the cycled nodes in the "cycle" list of one of the nodes
- continue
- else:
- # long cycles not allowed
- raise CommitError("Circular dependency detected " + repr(edges) + repr(queue))
- node = queue.pop()
- if not hasattr(node, '_cyclical'):
- output.append(node)
- nodeedges = edges.pop(node, None)
- if nodeedges is None:
- continue
- for childnode in nodeedges.keys():
- del childnode.edges[node]
- if len(childnode.edges) == 0:
- queue.append(childnode)
-
- return self._create_batched_tree(output)
-
-
- def _create_batched_tree(self, nodes):
- """given a list of nodes from a topological sort, organizes the nodes into a tree structure,
- with as many non-dependent nodes set as silbings to each other as possible."""
- def sort(index=None, l=None):
- if index is None:
- index = 0
-
- if index >= len(nodes):
- return None
-
- node = nodes[index]
- l2 = []
- sort(index + 1, l2)
- for n in l2:
- if l is None or search_dep(node, n):
- node.children.append(n)
- else:
- l.append(n)
- if l is not None:
- l.append(node)
- return node
-
- def search_dep(parent, child):
- if child is None:
- return False
- elif parent.is_dependent(child):
- return True
- else:
- for c in child.children:
- x = search_dep(parent, c)
- if x is True:
- return True
- else:
- return False
- return sort()
-
-
- def _add_edge(self, edges, edge):
- (parentnode, childnode) = edge
- edges[parentnode][childnode] = True
- parentnode.dependencies[childnode] = True
- childnode.edges[parentnode] = True
-
- def _remove_edge(self, edges, edge):
- (parentnode, childnode) = edge
- del edges[parentnode][childnode]
- del childnode.edges[parentnode]
- del parentnode.dependencies[childnode]
- if len(childnode.edges) == 0:
- return childnode
-
- def _find_cycle(self, edges):
- """given a structure of edges, locates a cycle in the strucure and returns
- as a list of tuples representing edges involved in the cycle."""
- seen = Set()
- cycled_edges = []
- def traverse(d, parent=None):
- for key in d.keys():
- if not edges.has_key(key):
- continue
- if key in seen:
- if parent is not None:
- cycled_edges.append((parent, key))
- return key
- seen.add(key)
- x = traverse(edges[key], parent=key)
- if x is None:
- seen.remove(key)
- else:
- if parent is not None:
- cycled_edges.append((parent, key))
- return x
- else:
- return None
- s = traverse(edges)
- if s is None:
- return None
- else:
- return cycled_edges
-
-class TreeDependencySorter(object):
- """
- this is my first topological sorting algorithm. its crazy, but matched my thinking
- at the time. it also creates the kind of structure I want. but, I am not 100% sure
- it works in all cases since I always did really poorly in linear algebra. anyway,
- I got the other one above to produce a tree structure too so we should be OK.
- """
- class Node:
- """represents a node in a tree. stores an 'item' which represents the
- dependent thing we are talking about. if node 'a' is an ancestor node of
- node 'b', it means 'a's item is *not* dependent on that of 'b'."""
- def __init__(self, item):
- #print "new node on " + str(item)
- self.item = item
- self.children = HashSet()
- self.parent = None
- def append(self, node):
- """appends the given node as a child on this node. removes the node from
- its preexisting parent."""
- if node.parent is not None:
- del node.parent.children[node]
- self.children.append(node)
- node.parent = self
- def is_descendant_of(self, node):
- """returns true if this node is a descendant of the given node"""
- n = self
- while n is not None:
- if n is node:
- return True
- else:
- n = n.parent
- return False
- def get_root(self):
- """returns the highest ancestor node of this node, i.e. which has no parent"""
- n = self
- while n.parent is not None:
- n = n.parent
- return n
- def get_sibling_ancestor(self, node):
- """returns the node which is:
- - an ancestor of this node
- - is a sibling of the given node
- - not an ancestor of the given node
-
- - else returns this node's root node."""
- n = self
- while n.parent is not None and n.parent is not node.parent and not node.is_descendant_of(n.parent):
- n = n.parent
- return n
- def __str__(self):
- return self.safestr({})
- def safestr(self, hash, indent = 0):
- if hash.has_key(self):
- return (' ' * indent) + "RECURSIVE:%s(%s, %s)" % (str(self.item), repr(id(self)), self.parent and repr(id(self.parent)) or 'None')
- hash[self] = True
- return (' ' * indent) + "%s (idself=%s, idparent=%s)" % (str(self.item), repr(id(self)), self.parent and repr(id(self.parent)) or "None") + "\n" + string.join([n.safestr(hash, indent + 1) for n in self.children], '')
- def describe(self):
- return "%s (idself=%s)" % (str(self.item), repr(id(self)))
-
- def __init__(self, tuples, allitems):
- self.tuples = tuples
- self.allitems = allitems
-
- def sort(self):
- (tuples, allitems) = (self.tuples, self.allitems)
-
- nodes = {}
- # make nodes for all the items and store in the hash
- for item in allitems + [t[0] for t in tuples] + [t[1] for t in tuples]:
- if not nodes.has_key(item):
- nodes[item] = TreeDependencySorter.Node(item)
-
- # loop through tuples
- for tup in tuples:
- (parent, child) = (tup[0], tup[1])
- # get parent node
- parentnode = nodes[parent]
-
- # if parent is child, mark "circular" attribute on the node
- if parent is child:
- parentnode.circular = True
- # and just continue
- continue
-
- # get child node
- childnode = nodes[child]
-
- if parentnode.parent is childnode:
- # check for "a switch"
- t = parentnode.item
- parentnode.item = childnode.item
- childnode.item = t
- nodes[parentnode.item] = parentnode
- nodes[childnode.item] = childnode
- elif parentnode.is_descendant_of(childnode):
- # check for a line thats backwards with nodes in between, this is a
- # circular dependency (although confirmation on this would be helpful)
- raise CommitError("Circular dependency detected")
- elif not childnode.is_descendant_of(parentnode):
- # if relationship doesnt exist, connect nodes together
- root = childnode.get_sibling_ancestor(parentnode)
- parentnode.append(root)
-
-
- # now we have a collection of subtrees which represent dependencies.
- # go through the collection root nodes wire them together into one tree
- head = None
- for node in nodes.values():
- if node.parent is None:
- if head is not None:
- head.append(node)
- else:
- head = node
- #print str(head)
- return head
- \ No newline at end of file