summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/util.py')
-rw-r--r--lib/sqlalchemy/sql/util.py115
1 files changed, 14 insertions, 101 deletions
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 9954811d6..d4163b73b 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -147,102 +147,7 @@ class ColumnsInClause(visitors.ClauseVisitor):
if self.selectable.c.get(column.key) is column:
self.result = True
-class AbstractClauseProcessor(object):
- """Traverse and copy a ClauseElement, replacing selected elements based on rules.
-
- This class implements its own visit-and-copy strategy but maintains the
- same public interface as visitors.ClauseVisitor.
-
- The convert_element() method receives the *un-copied* version of each element.
- It can return a new element or None for no change. If None, the element
- will be cloned afterwards and added to the new structure. Note this is the
- opposite behavior of visitors.traverse(clone=True), where visitors receive
- the cloned element so that it can be mutated.
- """
-
- __traverse_options__ = {'column_collections':False}
-
- def __init__(self, stop_on=None):
- self.stop_on = stop_on
-
- def convert_element(self, elem):
- """Define the *conversion* method for this ``AbstractClauseProcessor``."""
-
- raise NotImplementedError()
-
- def chain(self, visitor):
- # chaining AbstractClauseProcessor and other ClauseVisitor
- # objects separately. All the ACP objects are chained on
- # their convert_element() method whereas regular visitors
- # chain on their visit_XXX methods.
- if isinstance(visitor, AbstractClauseProcessor):
- attr = '_next_acp'
- else:
- attr = '_next'
-
- tail = self
- while getattr(tail, attr, None) is not None:
- tail = getattr(tail, attr)
- setattr(tail, attr, visitor)
- return self
-
- def copy_and_process(self, list_):
- """Copy the given list to a new list, with each element traversed individually."""
-
- list_ = list(list_)
- stop_on = util.Set(self.stop_on or [])
- cloned = {}
- for i in range(0, len(list_)):
- list_[i] = self._traverse(list_[i], stop_on, cloned, _clone_toplevel=True)
- return list_
-
- def _convert_element(self, elem, stop_on, cloned):
- v = self
- while v is not None:
- newelem = v.convert_element(elem)
- if newelem:
- stop_on.add(newelem)
- return newelem
- v = getattr(v, '_next_acp', None)
-
- if elem not in cloned:
- # the full traversal will only make a clone of a particular element
- # once.
- cloned[elem] = elem._clone()
- return cloned[elem]
-
- def traverse(self, elem, clone=True):
- if not clone:
- raise exceptions.ArgumentError("AbstractClauseProcessor 'clone' argument must be True")
-
- return self._traverse(elem, util.Set(self.stop_on or []), {}, _clone_toplevel=True)
-
- def _traverse(self, elem, stop_on, cloned, _clone_toplevel=False):
- if elem in stop_on:
- return elem
-
- if _clone_toplevel:
- elem = self._convert_element(elem, stop_on, cloned)
- if elem in stop_on:
- return elem
-
- def clone(element):
- return self._convert_element(element, stop_on, cloned)
- elem._copy_internals(clone=clone)
-
- v = getattr(self, '_next', None)
- while v is not None:
- meth = getattr(v, "visit_%s" % elem.__visit_name__, None)
- if meth:
- meth(elem)
- v = getattr(v, '_next', None)
-
- for e in elem.get_children(**self.__traverse_options__):
- if e not in stop_on:
- self._traverse(e, stop_on, cloned)
- return elem
-
-class ClauseAdapter(AbstractClauseProcessor):
+class ClauseAdapter(visitors.ClauseVisitor):
"""Given a clause (like as in a WHERE criterion), locate columns
which are embedded within a given selectable, and changes those
columns to be that of the selectable.
@@ -270,13 +175,21 @@ class ClauseAdapter(AbstractClauseProcessor):
s.c.col1 == table2.c.col1
"""
+ __traverse_options__ = {'column_collections':False}
+
def __init__(self, selectable, include=None, exclude=None, equivalents=None):
- AbstractClauseProcessor.__init__(self, [selectable])
+ self.__traverse_options__ = self.__traverse_options__.copy()
+ self.__traverse_options__['stop_on'] = [selectable]
self.selectable = selectable
self.include = include
self.exclude = exclude
self.equivalents = equivalents
-
+
+ def traverse(self, obj, clone=True):
+ if not clone:
+ raise exceptions.ArgumentError("ClauseAdapter 'clone' argument must be True")
+ return visitors.ClauseVisitor.traverse(self, obj, clone=True)
+
def copy_and_chain(self, adapter):
"""create a copy of this adapter and chain to the given adapter.
@@ -289,14 +202,14 @@ class ClauseAdapter(AbstractClauseProcessor):
if adapter is None:
return self
- if hasattr(self, '_next_acp') or hasattr(self, '_next'):
+ if hasattr(self, '_next'):
raise NotImplementedError("Can't chain_to on an already chained ClauseAdapter (yet)")
ca = ClauseAdapter(self.selectable, self.include, self.exclude, self.equivalents)
- ca._next_acp = adapter
+ ca._next = adapter
return ca
- def convert_element(self, col):
+ def before_clone(self, col):
if isinstance(col, expression.FromClause):
if self.selectable.is_derived_from(col):
return self.selectable