Remove Elixir library
Update SQLAlchemy
This commit is contained in:
+200
-387
@@ -1,41 +1,33 @@
|
||||
# sql/util.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy import exc, schema, util, sql, types as sqltypes
|
||||
from sqlalchemy.util import topological
|
||||
from sqlalchemy.sql import expression, operators, visitors
|
||||
"""High level utilities which build upon other modules here.
|
||||
|
||||
"""
|
||||
|
||||
from .. import exc, util
|
||||
from .base import _from_objects, ColumnSet
|
||||
from . import operators, visitors
|
||||
from itertools import chain
|
||||
from collections import deque
|
||||
|
||||
"""Utility functions that build upon SQL and Schema constructs."""
|
||||
from .elements import BindParameter, ColumnClause, ColumnElement, \
|
||||
Null, UnaryExpression, literal_column, Label
|
||||
from .selectable import ScalarSelect, Join, FromClause, FromGrouping
|
||||
from .schema import Column
|
||||
|
||||
def sort_tables(tables):
|
||||
"""sort a collection of Table objects in order of their foreign-key dependency."""
|
||||
join_condition = util.langhelpers.public_factory(
|
||||
Join._join_condition,
|
||||
".sql.util.join_condition")
|
||||
|
||||
tables = list(tables)
|
||||
tuples = []
|
||||
def visit_foreign_key(fkey):
|
||||
if fkey.use_alter:
|
||||
return
|
||||
parent_table = fkey.column.table
|
||||
if parent_table in tables:
|
||||
child_table = fkey.parent.table
|
||||
if parent_table is not child_table:
|
||||
tuples.append((parent_table, child_table))
|
||||
# names that are still being imported from the outside
|
||||
from .annotation import _shallow_annotate, _deep_annotate, _deep_deannotate
|
||||
from .elements import _find_columns
|
||||
from .ddl import sort_tables
|
||||
|
||||
for table in tables:
|
||||
visitors.traverse(table,
|
||||
{'schema_visitor':True},
|
||||
{'foreign_key':visit_foreign_key})
|
||||
|
||||
tuples.extend(
|
||||
[parent, table] for parent in table._extra_dependencies
|
||||
)
|
||||
|
||||
return list(topological.sort(tuples, tables))
|
||||
|
||||
def find_join_source(clauses, join_to):
|
||||
"""Given a list of FROM clauses and a selectable,
|
||||
@@ -54,7 +46,7 @@ def find_join_source(clauses, join_to):
|
||||
|
||||
"""
|
||||
|
||||
selectables = list(expression._from_objects(join_to))
|
||||
selectables = list(_from_objects(join_to))
|
||||
for i, f in enumerate(clauses):
|
||||
for s in selectables:
|
||||
if f.is_derived_from(s):
|
||||
@@ -62,6 +54,67 @@ def find_join_source(clauses, join_to):
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def visit_binary_product(fn, expr):
|
||||
"""Produce a traversal of the given expression, delivering
|
||||
column comparisons to the given function.
|
||||
|
||||
The function is of the form::
|
||||
|
||||
def my_fn(binary, left, right)
|
||||
|
||||
For each binary expression located which has a
|
||||
comparison operator, the product of "left" and
|
||||
"right" will be delivered to that function,
|
||||
in terms of that binary.
|
||||
|
||||
Hence an expression like::
|
||||
|
||||
and_(
|
||||
(a + b) == q + func.sum(e + f),
|
||||
j == r
|
||||
)
|
||||
|
||||
would have the traversal::
|
||||
|
||||
a <eq> q
|
||||
a <eq> e
|
||||
a <eq> f
|
||||
b <eq> q
|
||||
b <eq> e
|
||||
b <eq> f
|
||||
j <eq> r
|
||||
|
||||
That is, every combination of "left" and
|
||||
"right" that doesn't further contain
|
||||
a binary comparison is passed as pairs.
|
||||
|
||||
"""
|
||||
stack = []
|
||||
|
||||
def visit(element):
|
||||
if isinstance(element, ScalarSelect):
|
||||
# we dont want to dig into correlated subqueries,
|
||||
# those are just column elements by themselves
|
||||
yield element
|
||||
elif element.__visit_name__ == 'binary' and \
|
||||
operators.is_comparison(element.operator):
|
||||
stack.insert(0, element)
|
||||
for l in visit(element.left):
|
||||
for r in visit(element.right):
|
||||
fn(stack[0], l, r)
|
||||
stack.pop(0)
|
||||
for elem in element.get_children():
|
||||
visit(elem)
|
||||
else:
|
||||
if isinstance(element, ColumnClause):
|
||||
yield element
|
||||
for elem in element.get_children():
|
||||
for e in visit(elem):
|
||||
yield e
|
||||
list(visit(expr))
|
||||
|
||||
|
||||
def find_tables(clause, check_columns=False,
|
||||
include_aliases=False, include_joins=False,
|
||||
include_selects=False, include_crud=False):
|
||||
@@ -77,7 +130,7 @@ def find_tables(clause, check_columns=False,
|
||||
_visitors['join'] = tables.append
|
||||
|
||||
if include_aliases:
|
||||
_visitors['alias'] = tables.append
|
||||
_visitors['alias'] = tables.append
|
||||
|
||||
if include_crud:
|
||||
_visitors['insert'] = _visitors['update'] = \
|
||||
@@ -90,15 +143,10 @@ def find_tables(clause, check_columns=False,
|
||||
|
||||
_visitors['table'] = tables.append
|
||||
|
||||
visitors.traverse(clause, {'column_collections':False}, _visitors)
|
||||
visitors.traverse(clause, {'column_collections': False}, _visitors)
|
||||
return tables
|
||||
|
||||
def find_columns(clause):
|
||||
"""locate Column objects within the given expression."""
|
||||
|
||||
cols = util.column_set()
|
||||
visitors.traverse(clause, {}, {'column':cols.add})
|
||||
return cols
|
||||
|
||||
def unwrap_order_by(clause):
|
||||
"""Break up an 'order by' expression into individual column-expressions,
|
||||
@@ -108,9 +156,9 @@ def unwrap_order_by(clause):
|
||||
stack = deque([clause])
|
||||
while stack:
|
||||
t = stack.popleft()
|
||||
if isinstance(t, expression.ColumnElement) and \
|
||||
if isinstance(t, ColumnElement) and \
|
||||
(
|
||||
not isinstance(t, expression._UnaryExpression) or \
|
||||
not isinstance(t, UnaryExpression) or \
|
||||
not operators.is_ordering_modifier(t.modifier)
|
||||
):
|
||||
cols.add(t)
|
||||
@@ -119,6 +167,7 @@ def unwrap_order_by(clause):
|
||||
stack.append(c)
|
||||
return cols
|
||||
|
||||
|
||||
def clause_is_present(clause, search):
|
||||
"""Given a target clause and a second to search within, return True
|
||||
if the target is plainly present in the search without any
|
||||
@@ -128,15 +177,30 @@ def clause_is_present(clause, search):
|
||||
|
||||
"""
|
||||
|
||||
stack = [search]
|
||||
for elem in surface_selectables(search):
|
||||
if clause == elem: # use == here so that Annotated's compare
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def surface_selectables(clause):
|
||||
stack = [clause]
|
||||
while stack:
|
||||
elem = stack.pop()
|
||||
if clause is elem:
|
||||
return True
|
||||
elif isinstance(elem, expression.Join):
|
||||
yield elem
|
||||
if isinstance(elem, Join):
|
||||
stack.extend((elem.left, elem.right))
|
||||
return False
|
||||
elif isinstance(elem, FromGrouping):
|
||||
stack.append(elem.element)
|
||||
|
||||
def selectables_overlap(left, right):
|
||||
"""Return True if left/right have some overlapping selectable"""
|
||||
|
||||
return bool(
|
||||
set(surface_selectables(left)).intersection(
|
||||
surface_selectables(right)
|
||||
)
|
||||
)
|
||||
|
||||
def bind_values(clause):
|
||||
"""Return an ordered list of "bound" values in the given clause.
|
||||
@@ -151,19 +215,22 @@ def bind_values(clause):
|
||||
"""
|
||||
|
||||
v = []
|
||||
|
||||
def visit_bindparam(bind):
|
||||
v.append(bind.effective_value)
|
||||
|
||||
visitors.traverse(clause, {}, {'bindparam':visit_bindparam})
|
||||
visitors.traverse(clause, {}, {'bindparam': visit_bindparam})
|
||||
return v
|
||||
|
||||
|
||||
def _quote_ddl_expr(element):
|
||||
if isinstance(element, basestring):
|
||||
if isinstance(element, util.string_types):
|
||||
element = element.replace("'", "''")
|
||||
return "'%s'" % element
|
||||
else:
|
||||
return repr(element)
|
||||
|
||||
|
||||
class _repr_params(object):
|
||||
"""A string view of bound parameters, truncating
|
||||
display to the given number of 'multi' parameter sets.
|
||||
@@ -177,289 +244,40 @@ class _repr_params(object):
|
||||
if isinstance(self.params, (list, tuple)) and \
|
||||
len(self.params) > self.batches and \
|
||||
isinstance(self.params[0], (list, dict, tuple)):
|
||||
msg = " ... displaying %i of %i total bound parameter sets ... "
|
||||
return ' '.join((
|
||||
repr(self.params[:self.batches - 2])[0:-1],
|
||||
" ... displaying %i of %i total bound parameter sets ... " % (self.batches, len(self.params)),
|
||||
msg % (self.batches, len(self.params)),
|
||||
repr(self.params[-2:])[1:]
|
||||
))
|
||||
else:
|
||||
return repr(self.params)
|
||||
|
||||
|
||||
def expression_as_ddl(clause):
|
||||
"""Given a SQL expression, convert for usage in DDL, such as
|
||||
CREATE INDEX and CHECK CONSTRAINT.
|
||||
|
||||
Converts bind params into quoted literals, column identifiers
|
||||
into detached column constructs so that the parent table
|
||||
identifier is not included.
|
||||
|
||||
"""
|
||||
def repl(element):
|
||||
if isinstance(element, expression._BindParamClause):
|
||||
return expression.literal_column(_quote_ddl_expr(element.value))
|
||||
elif isinstance(element, expression.ColumnClause) and \
|
||||
element.table is not None:
|
||||
return expression.column(element.name)
|
||||
else:
|
||||
return None
|
||||
|
||||
return visitors.replacement_traverse(clause, {}, repl)
|
||||
|
||||
def adapt_criterion_to_null(crit, nulls):
|
||||
"""given criterion containing bind params, convert selected elements to IS NULL."""
|
||||
"""given criterion containing bind params, convert selected elements
|
||||
to IS NULL.
|
||||
|
||||
"""
|
||||
|
||||
def visit_binary(binary):
|
||||
if isinstance(binary.left, expression._BindParamClause) \
|
||||
if isinstance(binary.left, BindParameter) \
|
||||
and binary.left._identifying_key in nulls:
|
||||
# reverse order if the NULL is on the left side
|
||||
binary.left = binary.right
|
||||
binary.right = expression.null()
|
||||
binary.right = Null()
|
||||
binary.operator = operators.is_
|
||||
binary.negate = operators.isnot
|
||||
elif isinstance(binary.right, expression._BindParamClause) \
|
||||
elif isinstance(binary.right, BindParameter) \
|
||||
and binary.right._identifying_key in nulls:
|
||||
binary.right = expression.null()
|
||||
binary.right = Null()
|
||||
binary.operator = operators.is_
|
||||
binary.negate = operators.isnot
|
||||
|
||||
return visitors.cloned_traverse(crit, {}, {'binary':visit_binary})
|
||||
return visitors.cloned_traverse(crit, {}, {'binary': visit_binary})
|
||||
|
||||
def join_condition(a, b, ignore_nonexistent_tables=False, a_subset=None):
|
||||
"""create a join condition between two tables or selectables.
|
||||
|
||||
e.g.::
|
||||
|
||||
join_condition(tablea, tableb)
|
||||
|
||||
would produce an expression along the lines of::
|
||||
|
||||
tablea.c.id==tableb.c.tablea_id
|
||||
|
||||
The join is determined based on the foreign key relationships
|
||||
between the two selectables. If there are multiple ways
|
||||
to join, or no way to join, an error is raised.
|
||||
|
||||
:param ignore_nonexistent_tables: Deprecated - this
|
||||
flag is no longer used. Only resolution errors regarding
|
||||
the two given tables are propagated.
|
||||
|
||||
:param a_subset: An optional expression that is a sub-component
|
||||
of ``a``. An attempt will be made to join to just this sub-component
|
||||
first before looking at the full ``a`` construct, and if found
|
||||
will be successful even if there are other ways to join to ``a``.
|
||||
This allows the "right side" of a join to be passed thereby
|
||||
providing a "natural join".
|
||||
|
||||
"""
|
||||
crit = []
|
||||
constraints = set()
|
||||
|
||||
for left in (a_subset, a):
|
||||
if left is None:
|
||||
continue
|
||||
for fk in sorted(
|
||||
b.foreign_keys,
|
||||
key=lambda fk:fk.parent._creation_order):
|
||||
try:
|
||||
col = fk.get_referent(left)
|
||||
except exc.NoReferenceError, nrte:
|
||||
if nrte.table_name == left.name:
|
||||
raise
|
||||
else:
|
||||
continue
|
||||
|
||||
if col is not None:
|
||||
crit.append(col == fk.parent)
|
||||
constraints.add(fk.constraint)
|
||||
if left is not b:
|
||||
for fk in sorted(
|
||||
left.foreign_keys,
|
||||
key=lambda fk:fk.parent._creation_order):
|
||||
try:
|
||||
col = fk.get_referent(b)
|
||||
except exc.NoReferenceError, nrte:
|
||||
if nrte.table_name == b.name:
|
||||
raise
|
||||
else:
|
||||
# this is totally covered. can't get
|
||||
# coverage to mark it.
|
||||
continue
|
||||
|
||||
if col is not None:
|
||||
crit.append(col == fk.parent)
|
||||
constraints.add(fk.constraint)
|
||||
if crit:
|
||||
break
|
||||
|
||||
if len(crit) == 0:
|
||||
if isinstance(b, expression._FromGrouping):
|
||||
hint = " Perhaps you meant to convert the right side to a "\
|
||||
"subquery using alias()?"
|
||||
else:
|
||||
hint = ""
|
||||
raise exc.ArgumentError(
|
||||
"Can't find any foreign key relationships "
|
||||
"between '%s' and '%s'.%s" % (a.description, b.description, hint))
|
||||
elif len(constraints) > 1:
|
||||
raise exc.ArgumentError(
|
||||
"Can't determine join between '%s' and '%s'; "
|
||||
"tables have more than one foreign key "
|
||||
"constraint relationship between them. "
|
||||
"Please specify the 'onclause' of this "
|
||||
"join explicitly." % (a.description, b.description))
|
||||
elif len(crit) == 1:
|
||||
return (crit[0])
|
||||
else:
|
||||
return sql.and_(*crit)
|
||||
|
||||
|
||||
class Annotated(object):
|
||||
"""clones a ClauseElement and applies an 'annotations' dictionary.
|
||||
|
||||
Unlike regular clones, this clone also mimics __hash__() and
|
||||
__cmp__() of the original element so that it takes its place
|
||||
in hashed collections.
|
||||
|
||||
A reference to the original element is maintained, for the important
|
||||
reason of keeping its hash value current. When GC'ed, the
|
||||
hash value may be reused, causing conflicts.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
if not args:
|
||||
# clone constructor
|
||||
return object.__new__(cls)
|
||||
else:
|
||||
element, values = args
|
||||
# pull appropriate subclass from registry of annotated
|
||||
# classes
|
||||
try:
|
||||
cls = annotated_classes[element.__class__]
|
||||
except KeyError:
|
||||
cls = annotated_classes[element.__class__] = type.__new__(type,
|
||||
"Annotated%s" % element.__class__.__name__,
|
||||
(Annotated, element.__class__), {})
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, element, values):
|
||||
# force FromClause to generate their internal
|
||||
# collections into __dict__
|
||||
if isinstance(element, expression.FromClause):
|
||||
element.c
|
||||
|
||||
self.__dict__ = element.__dict__.copy()
|
||||
self.__element = element
|
||||
self._annotations = values
|
||||
|
||||
def _annotate(self, values):
|
||||
_values = self._annotations.copy()
|
||||
_values.update(values)
|
||||
clone = self.__class__.__new__(self.__class__)
|
||||
clone.__dict__ = self.__dict__.copy()
|
||||
clone._annotations = _values
|
||||
return clone
|
||||
|
||||
def _deannotate(self):
|
||||
return self.__element
|
||||
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return self.__element.__class__._compiler_dispatch(self, visitor, **kw)
|
||||
|
||||
@property
|
||||
def _constructor(self):
|
||||
return self.__element._constructor
|
||||
|
||||
def _clone(self):
|
||||
clone = self.__element._clone()
|
||||
if clone is self.__element:
|
||||
# detect immutable, don't change anything
|
||||
return self
|
||||
else:
|
||||
# update the clone with any changes that have occurred
|
||||
# to this object's __dict__.
|
||||
clone.__dict__.update(self.__dict__)
|
||||
return Annotated(clone, self._annotations)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__element)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(self.__element, expression.ColumnOperators):
|
||||
return self.__element.__class__.__eq__(self, other)
|
||||
else:
|
||||
return hash(other) == hash(self)
|
||||
|
||||
|
||||
# hard-generate Annotated subclasses. this technique
|
||||
# is used instead of on-the-fly types (i.e. type.__new__())
|
||||
# so that the resulting objects are pickleable.
|
||||
annotated_classes = {}
|
||||
|
||||
for cls in expression.__dict__.values() + [schema.Column, schema.Table]:
|
||||
if isinstance(cls, type) and issubclass(cls, expression.ClauseElement):
|
||||
exec "class Annotated%s(Annotated, cls):\n" \
|
||||
" pass" % (cls.__name__, ) in locals()
|
||||
exec "annotated_classes[cls] = Annotated%s" % (cls.__name__)
|
||||
|
||||
def _deep_annotate(element, annotations, exclude=None):
|
||||
"""Deep copy the given ClauseElement, annotating each element
|
||||
with the given annotations dictionary.
|
||||
|
||||
Elements within the exclude collection will be cloned but not annotated.
|
||||
|
||||
"""
|
||||
cloned = util.column_dict()
|
||||
|
||||
def clone(elem):
|
||||
# check if element is present in the exclude list.
|
||||
# take into account proxying relationships.
|
||||
if elem in cloned:
|
||||
return cloned[elem]
|
||||
elif exclude and \
|
||||
hasattr(elem, 'proxy_set') and \
|
||||
elem.proxy_set.intersection(exclude):
|
||||
newelem = elem._clone()
|
||||
elif annotations != elem._annotations:
|
||||
newelem = elem._annotate(annotations)
|
||||
else:
|
||||
newelem = elem
|
||||
newelem._copy_internals(clone=clone)
|
||||
cloned[elem] = newelem
|
||||
return newelem
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
def _deep_deannotate(element):
|
||||
"""Deep copy the given element, removing all annotations."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
|
||||
def clone(elem):
|
||||
if elem not in cloned:
|
||||
newelem = elem._deannotate()
|
||||
newelem._copy_internals(clone=clone)
|
||||
cloned[elem] = newelem
|
||||
return cloned[elem]
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
def _shallow_annotate(element, annotations):
|
||||
"""Annotate the given ClauseElement and copy its internals so that
|
||||
internal objects refer to the new annotated object.
|
||||
|
||||
Basically used to apply a "dont traverse" annotation to a
|
||||
selectable, without digging throughout the whole
|
||||
structure wasting time.
|
||||
"""
|
||||
element = element._annotate(annotations)
|
||||
element._copy_internals()
|
||||
return element
|
||||
|
||||
def splice_joins(left, right, stop_on=None):
|
||||
if left is None:
|
||||
@@ -471,7 +289,7 @@ def splice_joins(left, right, stop_on=None):
|
||||
ret = None
|
||||
while stack:
|
||||
(right, prevright) = stack.pop()
|
||||
if isinstance(right, expression.Join) and right is not stop_on:
|
||||
if isinstance(right, Join) and right is not stop_on:
|
||||
right = right._clone()
|
||||
right._reset_exported()
|
||||
right.onclause = adapter.traverse(right.onclause)
|
||||
@@ -485,18 +303,21 @@ def splice_joins(left, right, stop_on=None):
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def reduce_columns(columns, *clauses, **kw):
|
||||
"""given a list of columns, return a 'reduced' set based on natural equivalents.
|
||||
"""given a list of columns, return a 'reduced' set based on natural
|
||||
equivalents.
|
||||
|
||||
the set is reduced to the smallest list of columns which have no natural
|
||||
equivalent present in the list. A "natural equivalent" means that two columns
|
||||
will ultimately represent the same value because they are related by a foreign key.
|
||||
equivalent present in the list. A "natural equivalent" means that two
|
||||
columns will ultimately represent the same value because they are related
|
||||
by a foreign key.
|
||||
|
||||
\*clauses is an optional list of join clauses which will be traversed
|
||||
to further identify columns that are "equivalent".
|
||||
|
||||
\**kw may specify 'ignore_nonexistent_tables' to ignore foreign keys
|
||||
whose tables are not yet configured.
|
||||
whose tables are not yet configured, or columns that aren't yet present.
|
||||
|
||||
This function is primarily used to determine the most minimal "primary key"
|
||||
from a selectable, by reducing the set of primary key columns present
|
||||
@@ -504,6 +325,7 @@ def reduce_columns(columns, *clauses, **kw):
|
||||
|
||||
"""
|
||||
ignore_nonexistent_tables = kw.pop('ignore_nonexistent_tables', False)
|
||||
only_synonyms = kw.pop('only_synonyms', False)
|
||||
|
||||
columns = util.ordered_column_set(columns)
|
||||
|
||||
@@ -515,28 +337,44 @@ def reduce_columns(columns, *clauses, **kw):
|
||||
continue
|
||||
try:
|
||||
fk_col = fk.column
|
||||
except exc.NoReferencedTableError:
|
||||
except exc.NoReferencedColumnError:
|
||||
# TODO: add specific coverage here
|
||||
# to test/sql/test_selectable ReduceTest
|
||||
if ignore_nonexistent_tables:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
if fk_col.shares_lineage(c):
|
||||
except exc.NoReferencedTableError:
|
||||
# TODO: add specific coverage here
|
||||
# to test/sql/test_selectable ReduceTest
|
||||
if ignore_nonexistent_tables:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
if fk_col.shares_lineage(c) and \
|
||||
(not only_synonyms or \
|
||||
c.name == col.name):
|
||||
omit.add(col)
|
||||
break
|
||||
|
||||
if clauses:
|
||||
def visit_binary(binary):
|
||||
if binary.operator == operators.eq:
|
||||
cols = util.column_set(chain(*[c.proxy_set for c in columns.difference(omit)]))
|
||||
cols = util.column_set(chain(*[c.proxy_set
|
||||
for c in columns.difference(omit)]))
|
||||
if binary.left in cols and binary.right in cols:
|
||||
for c in columns:
|
||||
if c.shares_lineage(binary.right):
|
||||
for c in reversed(columns):
|
||||
if c.shares_lineage(binary.right) and \
|
||||
(not only_synonyms or \
|
||||
c.name == binary.left.name):
|
||||
omit.add(c)
|
||||
break
|
||||
for clause in clauses:
|
||||
visitors.traverse(clause, {}, {'binary':visit_binary})
|
||||
if clause is not None:
|
||||
visitors.traverse(clause, {}, {'binary': visit_binary})
|
||||
|
||||
return ColumnSet(columns.difference(omit))
|
||||
|
||||
return expression.ColumnSet(columns.difference(omit))
|
||||
|
||||
def criterion_as_pairs(expression, consider_as_foreign_keys=None,
|
||||
consider_as_referenced_keys=None, any_operator=False):
|
||||
@@ -547,84 +385,47 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None,
|
||||
"'consider_as_foreign_keys' or "
|
||||
"'consider_as_referenced_keys'")
|
||||
|
||||
def col_is(a, b):
|
||||
#return a is b
|
||||
return a.compare(b)
|
||||
|
||||
def visit_binary(binary):
|
||||
if not any_operator and binary.operator is not operators.eq:
|
||||
return
|
||||
if not isinstance(binary.left, sql.ColumnElement) or \
|
||||
not isinstance(binary.right, sql.ColumnElement):
|
||||
if not isinstance(binary.left, ColumnElement) or \
|
||||
not isinstance(binary.right, ColumnElement):
|
||||
return
|
||||
|
||||
if consider_as_foreign_keys:
|
||||
if binary.left in consider_as_foreign_keys and \
|
||||
(binary.right is binary.left or
|
||||
(col_is(binary.right, binary.left) or
|
||||
binary.right not in consider_as_foreign_keys):
|
||||
pairs.append((binary.right, binary.left))
|
||||
elif binary.right in consider_as_foreign_keys and \
|
||||
(binary.left is binary.right or
|
||||
(col_is(binary.left, binary.right) or
|
||||
binary.left not in consider_as_foreign_keys):
|
||||
pairs.append((binary.left, binary.right))
|
||||
elif consider_as_referenced_keys:
|
||||
if binary.left in consider_as_referenced_keys and \
|
||||
(binary.right is binary.left or
|
||||
(col_is(binary.right, binary.left) or
|
||||
binary.right not in consider_as_referenced_keys):
|
||||
pairs.append((binary.left, binary.right))
|
||||
elif binary.right in consider_as_referenced_keys and \
|
||||
(binary.left is binary.right or
|
||||
(col_is(binary.left, binary.right) or
|
||||
binary.left not in consider_as_referenced_keys):
|
||||
pairs.append((binary.right, binary.left))
|
||||
else:
|
||||
if isinstance(binary.left, schema.Column) and \
|
||||
isinstance(binary.right, schema.Column):
|
||||
if isinstance(binary.left, Column) and \
|
||||
isinstance(binary.right, Column):
|
||||
if binary.left.references(binary.right):
|
||||
pairs.append((binary.right, binary.left))
|
||||
elif binary.right.references(binary.left):
|
||||
pairs.append((binary.left, binary.right))
|
||||
pairs = []
|
||||
visitors.traverse(expression, {}, {'binary':visit_binary})
|
||||
visitors.traverse(expression, {}, {'binary': visit_binary})
|
||||
return pairs
|
||||
|
||||
def folded_equivalents(join, equivs=None):
|
||||
"""Return a list of uniquely named columns.
|
||||
|
||||
The column list of the given Join will be narrowed
|
||||
down to a list of all equivalently-named,
|
||||
equated columns folded into one column, where 'equated' means they are
|
||||
equated to each other in the ON clause of this join.
|
||||
|
||||
This function is used by Join.select(fold_equivalents=True).
|
||||
|
||||
Deprecated. This function is used for a certain kind of
|
||||
"polymorphic_union" which is designed to achieve joined
|
||||
table inheritance where the base table has no "discriminator"
|
||||
column; [ticket:1131] will provide a better way to
|
||||
achieve this.
|
||||
|
||||
"""
|
||||
if equivs is None:
|
||||
equivs = set()
|
||||
def visit_binary(binary):
|
||||
if binary.operator == operators.eq and binary.left.name == binary.right.name:
|
||||
equivs.add(binary.right)
|
||||
equivs.add(binary.left)
|
||||
visitors.traverse(join.onclause, {}, {'binary':visit_binary})
|
||||
collist = []
|
||||
if isinstance(join.left, expression.Join):
|
||||
left = folded_equivalents(join.left, equivs)
|
||||
else:
|
||||
left = list(join.left.columns)
|
||||
if isinstance(join.right, expression.Join):
|
||||
right = folded_equivalents(join.right, equivs)
|
||||
else:
|
||||
right = list(join.right.columns)
|
||||
used = set()
|
||||
for c in left + right:
|
||||
if c in equivs:
|
||||
if c.name not in used:
|
||||
collist.append(c)
|
||||
used.add(c.name)
|
||||
else:
|
||||
collist.append(c)
|
||||
return collist
|
||||
|
||||
class AliasedRow(object):
|
||||
"""Wrap a RowProxy with a translation map.
|
||||
@@ -681,15 +482,27 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
||||
s.c.col1 == table2.c.col1
|
||||
|
||||
"""
|
||||
def __init__(self, selectable, equivalents=None, include=None, exclude=None, adapt_on_names=False):
|
||||
self.__traverse_options__ = {'stop_on':[selectable]}
|
||||
def __init__(self, selectable, equivalents=None,
|
||||
include=None, exclude=None,
|
||||
include_fn=None, exclude_fn=None,
|
||||
adapt_on_names=False):
|
||||
self.__traverse_options__ = {'stop_on': [selectable]}
|
||||
self.selectable = selectable
|
||||
self.include = include
|
||||
self.exclude = exclude
|
||||
if include:
|
||||
assert not include_fn
|
||||
self.include_fn = lambda e: e in include
|
||||
else:
|
||||
self.include_fn = include_fn
|
||||
if exclude:
|
||||
assert not exclude_fn
|
||||
self.exclude_fn = lambda e: e in exclude
|
||||
else:
|
||||
self.exclude_fn = exclude_fn
|
||||
self.equivalents = util.column_dict(equivalents or {})
|
||||
self.adapt_on_names = adapt_on_names
|
||||
|
||||
def _corresponding_column(self, col, require_embedded, _seen=util.EMPTY_SET):
|
||||
def _corresponding_column(self, col, require_embedded,
|
||||
_seen=util.EMPTY_SET):
|
||||
newcol = self.selectable.corresponding_column(
|
||||
col,
|
||||
require_embedded=require_embedded)
|
||||
@@ -704,20 +517,20 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
||||
newcol = self.selectable.c.get(col.name)
|
||||
return newcol
|
||||
|
||||
magic_flag = False
|
||||
def replace(self, col):
|
||||
if isinstance(col, expression.FromClause):
|
||||
if self.selectable.is_derived_from(col):
|
||||
return self.selectable
|
||||
|
||||
if not isinstance(col, expression.ColumnElement):
|
||||
if not self.magic_flag and isinstance(col, FromClause) and \
|
||||
self.selectable.is_derived_from(col):
|
||||
return self.selectable
|
||||
elif not isinstance(col, ColumnElement):
|
||||
return None
|
||||
|
||||
if self.include and col not in self.include:
|
||||
elif self.include_fn and not self.include_fn(col):
|
||||
return None
|
||||
elif self.exclude and col in self.exclude:
|
||||
elif self.exclude_fn and self.exclude_fn(col):
|
||||
return None
|
||||
else:
|
||||
return self._corresponding_column(col, True)
|
||||
|
||||
return self._corresponding_column(col, True)
|
||||
|
||||
class ColumnAdapter(ClauseAdapter):
|
||||
"""Extends ClauseAdapter with extra utility functions.
|
||||
@@ -761,15 +574,14 @@ class ColumnAdapter(ClauseAdapter):
|
||||
c = self.adapt_clause(col)
|
||||
|
||||
# anonymize labels in case they have a hardcoded name
|
||||
if isinstance(c, expression._Label):
|
||||
if isinstance(c, Label):
|
||||
c = c.label(None)
|
||||
|
||||
# adapt_required indicates that if we got the same column
|
||||
# back which we put in (i.e. it passed through),
|
||||
# it's not correct. this is used by eagerloading which
|
||||
# knows that all columns and expressions need to be adapted
|
||||
# to a result row, and a "passthrough" is definitely targeting
|
||||
# the wrong column.
|
||||
# adapt_required used by eager loading to indicate that
|
||||
# we don't trust a result row column that is not translated.
|
||||
# this is to prevent a column from being interpreted as that
|
||||
# of the child row in a self-referential scenario, see
|
||||
# inheritance/test_basic.py->EagerTargetingTest.test_adapt_stringency
|
||||
if self.adapt_required and c is col:
|
||||
return None
|
||||
|
||||
@@ -786,3 +598,4 @@ class ColumnAdapter(ClauseAdapter):
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
self.columns = util.PopulateDict(self._locate_col)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user