diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-04-29 23:26:36 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-05-18 17:46:10 -0400 |
commit | f07e050c9ce4afdeb9c0c136dbcc547f7e5ac7b8 (patch) | |
tree | 1b3cd7409ae2eddef635960126551d74f469acc1 /lib/sqlalchemy/sql/elements.py | |
parent | 614dfb5f5b5a2427d5d6ce0bc5f34bf0581bf698 (diff) | |
download | sqlalchemy-f07e050c9ce4afdeb9c0c136dbcc547f7e5ac7b8.tar.gz |
Implement new ClauseElement role and coercion system
A major refactoring of all the functions handle all detection of
Core argument types as well as perform coercions into a new class hierarchy
based on "roles", each of which identify a syntactical location within a
SQL statement. In contrast to the ClauseElement hierarchy that identifies
"what" each object is syntactically, the SQLRole hierarchy identifies
the "where does it go" of each object syntactically. From this we define
a consistent type checking and coercion system that establishes well
defined behviors.
This is a breakout of the patch that is reorganizing select()
constructs to no longer be in the FromClause hierarchy.
Also includes a rename of as_scalar() into scalar_subquery(); deprecates
automatic coercion to scalar_subquery().
Partially-fixes: #4617
Change-Id: I26f1e78898693c6b99ef7ea2f4e7dfd0e8e1a1bd
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 678 |
1 files changed, 244 insertions, 434 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index e634e5a36..a333303ec 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -13,12 +13,13 @@ from __future__ import unicode_literals import itertools -import numbers import operator import re from . import clause_compare +from . import coercions from . import operators +from . import roles from . import type_api from .annotation import Annotated from .base import _generative @@ -26,6 +27,7 @@ from .base import Executable from .base import Immutable from .base import NO_ARG from .base import PARSE_AUTOCOMMIT +from .coercions import _document_text_coercion from .visitors import cloned_traverse from .visitors import traverse from .visitors import Visitable @@ -38,20 +40,6 @@ def _clone(element, **kw): return element._clone() -def _document_text_coercion(paramname, meth_rst, param_rst): - return util.add_parameter_text( - paramname, - ( - ".. warning:: " - "The %s argument to %s can be passed as a Python string argument, " - "which will be treated " - "as **trusted SQL text** and rendered as given. **DO NOT PASS " - "UNTRUSTED INPUT TO THIS PARAMETER**." - ) - % (param_rst, meth_rst), - ) - - def collate(expression, collation): """Return the clause ``expression COLLATE collation``. @@ -71,7 +59,7 @@ def collate(expression, collation): """ - expr = _literal_as_binds(expression) + expr = coercions.expect(roles.ExpressionElementRole, expression) return BinaryExpression( expr, CollationClause(collation), operators.collate, type_=expr.type ) @@ -127,7 +115,7 @@ def between(expr, lower_bound, upper_bound, symmetric=False): :meth:`.ColumnElement.between` """ - expr = _literal_as_binds(expr) + expr = coercions.expect(roles.ExpressionElementRole, expr) return expr.between(lower_bound, upper_bound, symmetric=symmetric) @@ -172,11 +160,11 @@ def not_(clause): same result. """ - return operators.inv(_literal_as_binds(clause)) + return operators.inv(coercions.expect(roles.ExpressionElementRole, clause)) @inspection._self_inspects -class ClauseElement(Visitable): +class ClauseElement(roles.SQLRole, Visitable): """Base class for elements of a programmatically constructed SQL expression. @@ -188,13 +176,20 @@ class ClauseElement(Visitable): supports_execution = False _from_objects = [] bind = None + description = None _is_clone_of = None - is_selectable = False + is_clause_element = True + is_selectable = False - description = None - _order_by_label_element = None + _is_textual = False + _is_from_clause = False + _is_returns_rows = False + _is_text_clause = False _is_from_container = False + _is_select_statement = False + + _order_by_label_element = None def _clone(self): """Create a shallow copy of this ClauseElement. @@ -238,7 +233,7 @@ class ClauseElement(Visitable): """ - raise NotImplementedError(self.__class__) + raise NotImplementedError() @property def _constructor(self): @@ -394,6 +389,7 @@ class ClauseElement(Visitable): return [] def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement """Apply a 'grouping' to this :class:`.ClauseElement`. This method is overridden by subclasses to return a @@ -553,7 +549,20 @@ class ClauseElement(Visitable): ) -class ColumnElement(operators.ColumnOperators, ClauseElement): +class ColumnElement( + roles.ColumnArgumentOrKeyRole, + roles.StatementOptionRole, + roles.WhereHavingRole, + roles.BinaryElementRole, + roles.OrderByRole, + roles.ColumnsClauseRole, + roles.LimitOffsetRole, + roles.DMLColumnRole, + roles.DDLConstraintColumnRole, + roles.DDLExpressionRole, + operators.ColumnOperators, + ClauseElement, +): """Represent a column-oriented SQL expression suitable for usage in the "columns" clause, WHERE clause etc. of a statement. @@ -586,17 +595,13 @@ class ColumnElement(operators.ColumnOperators, ClauseElement): :class:`.TypeEngine` objects) are applied to the value. * any special object value, typically ORM-level constructs, which - feature a method called ``__clause_element__()``. The Core + feature an accessor called ``__clause_element__()``. The Core expression system looks for this method when an object of otherwise unknown type is passed to a function that is looking to coerce the - argument into a :class:`.ColumnElement` expression. The - ``__clause_element__()`` method, if present, should return a - :class:`.ColumnElement` instance. The primary use of - ``__clause_element__()`` within SQLAlchemy is that of class-bound - attributes on ORM-mapped classes; a ``User`` class which contains a - mapped attribute named ``.name`` will have a method - ``User.name.__clause_element__()`` which when invoked returns the - :class:`.Column` called ``name`` associated with the mapped table. + argument into a :class:`.ColumnElement` and sometimes a + :class:`.SelectBase` expression. It is used within the ORM to + convert from ORM-specific objects like mapped classes and + mapped attributes into Core expression objects. * The Python ``None`` value is typically interpreted as ``NULL``, which in SQLAlchemy Core produces an instance of :func:`.null`. @@ -702,6 +707,7 @@ class ColumnElement(operators.ColumnOperators, ClauseElement): _alt_names = () def self_group(self, against=None): + # type: (Module, Module, Optional[Any]) -> ClauseEleent if ( against in (operators.and_, operators.or_, operators._asbool) and self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity @@ -826,7 +832,9 @@ class ColumnElement(operators.ColumnOperators, ClauseElement): else: key = name co = ColumnClause( - _as_truncated(name) if name_is_truncatable else name, + coercions.expect(roles.TruncatedLabelRole, name) + if name_is_truncatable + else name, type_=getattr(self, "type", None), _selectable=selectable, ) @@ -878,7 +886,7 @@ class ColumnElement(operators.ColumnOperators, ClauseElement): ) -class BindParameter(ColumnElement): +class BindParameter(roles.InElementRole, ColumnElement): r"""Represent a "bound expression". :class:`.BindParameter` is invoked explicitly using the @@ -1235,7 +1243,8 @@ class BindParameter(ColumnElement): "bindparams collection argument required for _cache_key " "implementation. Bound parameter cache keys are not safe " "to use without accommodating for the value or callable " - "within the parameter itself.") + "within the parameter itself." + ) else: bindparams.append(self) return (BindParameter, self.type._cache_key, self._orig_key) @@ -1282,7 +1291,20 @@ class TypeClause(ClauseElement): return (TypeClause, self.type._cache_key) -class TextClause(Executable, ClauseElement): +class TextClause( + roles.DDLConstraintColumnRole, + roles.DDLExpressionRole, + roles.StatementOptionRole, + roles.WhereHavingRole, + roles.OrderByRole, + roles.FromClauseRole, + roles.SelectStatementRole, + roles.CoerceTextStatementRole, + roles.BinaryElementRole, + roles.InElementRole, + Executable, + ClauseElement, +): """Represent a literal SQL text fragment. E.g.:: @@ -1304,6 +1326,10 @@ class TextClause(Executable, ClauseElement): __visit_name__ = "textclause" + _is_text_clause = True + + _is_textual = True + _bind_params_regex = re.compile(r"(?<![:\w\x5c]):(\w+)(?!:)", re.UNICODE) _execution_options = Executable._execution_options.union( {"autocommit": PARSE_AUTOCOMMIT} @@ -1318,20 +1344,16 @@ class TextClause(Executable, ClauseElement): def _select_iterable(self): return (self,) - @property - def selectable(self): - # allows text() to be considered by - # _interpret_as_from - return self - - _hide_froms = [] - # help in those cases where text() is # interpreted in a column expression situation key = _label = _resolve_label = None _allow_label_resolve = False + @property + def _hide_froms(self): + return [] + def __init__(self, text, bind=None): self._bind = bind self._bindparams = {} @@ -1670,7 +1692,6 @@ class TextClause(Executable, ClauseElement): """ - positional_input_cols = [ ColumnClause(col.key, types.pop(col.key)) if col.key in types @@ -1696,6 +1717,7 @@ class TextClause(Executable, ClauseElement): return self.type.comparator_factory(self) def self_group(self, against=None): + # type: (Optional[Any]) -> Union[Grouping, TextClause] if against is operators.in_op: return Grouping(self) else: @@ -1715,7 +1737,7 @@ class TextClause(Executable, ClauseElement): ) -class Null(ColumnElement): +class Null(roles.ConstExprRole, ColumnElement): """Represent the NULL keyword in a SQL statement. :class:`.Null` is accessed as a constant via the @@ -1739,7 +1761,7 @@ class Null(ColumnElement): return (Null,) -class False_(ColumnElement): +class False_(roles.ConstExprRole, ColumnElement): """Represent the ``false`` keyword, or equivalent, in a SQL statement. :class:`.False_` is accessed as a constant via the @@ -1798,7 +1820,7 @@ class False_(ColumnElement): return (False_,) -class True_(ColumnElement): +class True_(roles.ConstExprRole, ColumnElement): """Represent the ``true`` keyword, or equivalent, in a SQL statement. :class:`.True_` is accessed as a constant via the @@ -1864,7 +1886,12 @@ class True_(ColumnElement): return (True_,) -class ClauseList(ClauseElement): +class ClauseList( + roles.InElementRole, + roles.OrderByRole, + roles.ColumnsClauseRole, + ClauseElement, +): """Describe a list of clauses, separated by an operator. By default, is comma-separated, such as a column listing. @@ -1877,16 +1904,22 @@ class ClauseList(ClauseElement): self.operator = kwargs.pop("operator", operators.comma_op) self.group = kwargs.pop("group", True) self.group_contents = kwargs.pop("group_contents", True) - text_converter = kwargs.pop( - "_literal_as_text", _expression_literal_as_text + + self._text_converter_role = text_converter_role = kwargs.pop( + "_literal_as_text_role", roles.WhereHavingRole ) if self.group_contents: self.clauses = [ - text_converter(clause).self_group(against=self.operator) + coercions.expect(text_converter_role, clause).self_group( + against=self.operator + ) for clause in clauses ] else: - self.clauses = [text_converter(clause) for clause in clauses] + self.clauses = [ + coercions.expect(text_converter_role, clause) + for clause in clauses + ] self._is_implicitly_boolean = operators.is_boolean(self.operator) def __iter__(self): @@ -1902,10 +1935,14 @@ class ClauseList(ClauseElement): def append(self, clause): if self.group_contents: self.clauses.append( - _literal_as_text(clause).self_group(against=self.operator) + coercions.expect(self._text_converter_role, clause).self_group( + against=self.operator + ) ) else: - self.clauses.append(_literal_as_text(clause)) + self.clauses.append( + coercions.expect(self._text_converter_role, clause) + ) def _copy_internals(self, clone=_clone, **kw): self.clauses = [clone(clause, **kw) for clause in self.clauses] @@ -1923,6 +1960,7 @@ class ClauseList(ClauseElement): return list(itertools.chain(*[c._from_objects for c in self.clauses])) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement if self.group and operators.is_precedent(self.operator, against): return Grouping(self) else: @@ -1947,7 +1985,7 @@ class BooleanClauseList(ClauseList, ColumnElement): convert_clauses = [] clauses = [ - _expression_literal_as_text(clause) + coercions.expect(roles.WhereHavingRole, clause) for clause in util.coerce_generator_arg(clauses) ] for clause in clauses: @@ -2055,6 +2093,7 @@ class BooleanClauseList(ClauseList, ColumnElement): return (self,) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement if not self.clauses: return self else: @@ -2092,7 +2131,9 @@ class Tuple(ClauseList, ColumnElement): """ - clauses = [_literal_as_binds(c) for c in clauses] + clauses = [ + coercions.expect(roles.ExpressionElementRole, c) for c in clauses + ] self._type_tuple = [arg.type for arg in clauses] self.type = kw.pop( "type_", @@ -2283,12 +2324,20 @@ class Case(ColumnElement): if value is not None: whenlist = [ - (_literal_as_binds(c).self_group(), _literal_as_binds(r)) + ( + coercions.expect( + roles.ExpressionElementRole, c + ).self_group(), + coercions.expect(roles.ExpressionElementRole, r), + ) for (c, r) in whens ] else: whenlist = [ - (_no_literals(c).self_group(), _literal_as_binds(r)) + ( + coercions.expect(roles.ColumnArgumentRole, c).self_group(), + coercions.expect(roles.ExpressionElementRole, r), + ) for (c, r) in whens ] @@ -2300,12 +2349,12 @@ class Case(ColumnElement): if value is None: self.value = None else: - self.value = _literal_as_binds(value) + self.value = coercions.expect(roles.ExpressionElementRole, value) self.type = type_ self.whens = whenlist if else_ is not None: - self.else_ = _literal_as_binds(else_) + self.else_ = coercions.expect(roles.ExpressionElementRole, else_) else: self.else_ = None @@ -2455,7 +2504,9 @@ class Cast(ColumnElement): """ self.type = type_api.to_instance(type_) - self.clause = _literal_as_binds(expression, type_=self.type) + self.clause = coercions.expect( + roles.ExpressionElementRole, expression, type_=self.type + ) self.typeclause = TypeClause(self.type) def _copy_internals(self, clone=_clone, **kw): @@ -2557,7 +2608,9 @@ class TypeCoerce(ColumnElement): """ self.type = type_api.to_instance(type_) - self.clause = _literal_as_binds(expression, type_=self.type) + self.clause = coercions.expect( + roles.ExpressionElementRole, expression, type_=self.type + ) def _copy_internals(self, clone=_clone, **kw): self.clause = clone(self.clause, **kw) @@ -2598,7 +2651,7 @@ class Extract(ColumnElement): """ self.type = type_api.INTEGERTYPE self.field = field - self.expr = _literal_as_binds(expr, None) + self.expr = coercions.expect(roles.ExpressionElementRole, expr) def _copy_internals(self, clone=_clone, **kw): self.expr = clone(self.expr, **kw) @@ -2733,7 +2786,7 @@ class UnaryExpression(ColumnElement): """ return UnaryExpression( - _literal_as_label_reference(column), + coercions.expect(roles.ByOfRole, column), modifier=operators.nullsfirst_op, wraps_column_expression=False, ) @@ -2776,7 +2829,7 @@ class UnaryExpression(ColumnElement): """ return UnaryExpression( - _literal_as_label_reference(column), + coercions.expect(roles.ByOfRole, column), modifier=operators.nullslast_op, wraps_column_expression=False, ) @@ -2817,7 +2870,7 @@ class UnaryExpression(ColumnElement): """ return UnaryExpression( - _literal_as_label_reference(column), + coercions.expect(roles.ByOfRole, column), modifier=operators.desc_op, wraps_column_expression=False, ) @@ -2857,7 +2910,7 @@ class UnaryExpression(ColumnElement): """ return UnaryExpression( - _literal_as_label_reference(column), + coercions.expect(roles.ByOfRole, column), modifier=operators.asc_op, wraps_column_expression=False, ) @@ -2898,7 +2951,7 @@ class UnaryExpression(ColumnElement): :data:`.func` """ - expr = _literal_as_binds(expr) + expr = coercions.expect(roles.ExpressionElementRole, expr) return UnaryExpression( expr, operator=operators.distinct_op, @@ -2953,6 +3006,7 @@ class UnaryExpression(ColumnElement): return ClauseElement._negate(self) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement if self.operator and operators.is_precedent(self.operator, against): return Grouping(self) else: @@ -2990,10 +3044,8 @@ class CollectionAggregate(UnaryExpression): """ - expr = _literal_as_binds(expr) + expr = coercions.expect(roles.ExpressionElementRole, expr) - if expr.is_selectable and hasattr(expr, "as_scalar"): - expr = expr.as_scalar() expr = expr.self_group() return CollectionAggregate( expr, @@ -3023,9 +3075,7 @@ class CollectionAggregate(UnaryExpression): """ - expr = _literal_as_binds(expr) - if expr.is_selectable and hasattr(expr, "as_scalar"): - expr = expr.as_scalar() + expr = coercions.expect(roles.ExpressionElementRole, expr) expr = expr.self_group() return CollectionAggregate( expr, @@ -3064,6 +3114,7 @@ class AsBoolean(UnaryExpression): self._is_implicitly_boolean = element._is_implicitly_boolean def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement return self def _cache_key(self, **kw): @@ -3155,6 +3206,8 @@ class BinaryExpression(ColumnElement): ) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement + if operators.is_precedent(self.operator, against): return Grouping(self) else: @@ -3191,6 +3244,7 @@ class Slice(ColumnElement): self.type = type_api.NULLTYPE def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement assert against is operator.getitem return self @@ -3215,6 +3269,7 @@ class Grouping(ColumnElement): self.type = getattr(element, "type", type_api.NULLTYPE) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement return self @util.memoized_property @@ -3363,13 +3418,12 @@ class Over(ColumnElement): self.element = element if order_by is not None: self.order_by = ClauseList( - *util.to_list(order_by), - _literal_as_text=_literal_as_label_reference + *util.to_list(order_by), _literal_as_text_role=roles.ByOfRole ) if partition_by is not None: self.partition_by = ClauseList( *util.to_list(partition_by), - _literal_as_text=_literal_as_label_reference + _literal_as_text_role=roles.ByOfRole ) if range_: @@ -3534,8 +3588,7 @@ class WithinGroup(ColumnElement): self.element = element if order_by is not None: self.order_by = ClauseList( - *util.to_list(order_by), - _literal_as_text=_literal_as_label_reference + *util.to_list(order_by), _literal_as_text_role=roles.ByOfRole ) def over(self, partition_by=None, order_by=None, range_=None, rows=None): @@ -3658,7 +3711,7 @@ class FunctionFilter(ColumnElement): """ for criterion in list(criterion): - criterion = _expression_literal_as_text(criterion) + criterion = coercions.expect(roles.WhereHavingRole, criterion) if self.criterion is not None: self.criterion = self.criterion & criterion @@ -3727,7 +3780,7 @@ class FunctionFilter(ColumnElement): ) -class Label(ColumnElement): +class Label(roles.LabeledColumnExprRole, ColumnElement): """Represents a column label (AS). Represent a label, as typically applied to any column-level @@ -3801,6 +3854,7 @@ class Label(ColumnElement): return self._element.self_group(against=operators.as_) def self_group(self, against=None): + # type: (Optional[Any]) -> ClauseElement return self._apply_to_inner(self._element.self_group, against=against) def _negate(self): @@ -3849,7 +3903,7 @@ class Label(ColumnElement): return e -class ColumnClause(Immutable, ColumnElement): +class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement): """Represents a column expression from any textual string. The :class:`.ColumnClause`, a lightweight analogue to the @@ -3985,14 +4039,14 @@ class ColumnClause(Immutable, ColumnElement): if ( self.is_literal or self.table is None - or self.table._textual + or self.table._is_textual or not hasattr(other, "proxy_set") or ( isinstance(other, ColumnClause) and ( other.is_literal or other.table is None - or other.table._textual + or other.table._is_textual ) ) ): @@ -4083,7 +4137,7 @@ class ColumnClause(Immutable, ColumnElement): counter += 1 label = _label - return _as_truncated(label) + return coercions.expect(roles.TruncatedLabelRole, label) else: return name @@ -4110,7 +4164,7 @@ class ColumnClause(Immutable, ColumnElement): # otherwise its considered to be a label is_literal = self.is_literal and (name is None or name == self.name) c = self._constructor( - _as_truncated(name or self.name) + coercions.expect(roles.TruncatedLabelRole, name or self.name) if name_is_truncatable else (name or self.name), type_=self.type, @@ -4250,6 +4304,108 @@ class quoted_name(util.MemoizedSlots, util.text_type): return "'%s'" % backslashed +def _expand_cloned(elements): + """expand the given set of ClauseElements to be the set of all 'cloned' + predecessors. + + """ + return itertools.chain(*[x._cloned_set for x in elements]) + + +def _select_iterables(elements): + """expand tables into individual columns in the + given list of column expressions. + + """ + return itertools.chain(*[c._select_iterable for c in elements]) + + +def _cloned_intersection(a, b): + """return the intersection of sets a and b, counting + any overlap between 'cloned' predecessors. + + The returned set is in terms of the entities present within 'a'. + + """ + all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) + return set( + elem for elem in a if all_overlap.intersection(elem._cloned_set) + ) + + +def _cloned_difference(a, b): + all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) + return set( + elem for elem in a if not all_overlap.intersection(elem._cloned_set) + ) + + +def _find_columns(clause): + """locate Column objects within the given expression.""" + + cols = util.column_set() + traverse(clause, {}, {"column": cols.add}) + return cols + + +def _type_from_args(args): + for a in args: + if not a.type._isnull: + return a.type + else: + return type_api.NULLTYPE + + +def _corresponding_column_or_error(fromclause, column, require_embedded=False): + c = fromclause.corresponding_column( + column, require_embedded=require_embedded + ) + if c is None: + raise exc.InvalidRequestError( + "Given column '%s', attached to table '%s', " + "failed to locate a corresponding column from table '%s'" + % (column, getattr(column, "table", None), fromclause.description) + ) + return c + + +class AnnotatedColumnElement(Annotated): + def __init__(self, element, values): + Annotated.__init__(self, element, values) + ColumnElement.comparator._reset(self) + for attr in ("name", "key", "table"): + if self.__dict__.get(attr, False) is None: + self.__dict__.pop(attr) + + def _with_annotations(self, values): + clone = super(AnnotatedColumnElement, self)._with_annotations(values) + ColumnElement.comparator._reset(clone) + return clone + + @util.memoized_property + def name(self): + """pull 'name' from parent, if not present""" + return self._Annotated__element.name + + @util.memoized_property + def table(self): + """pull 'table' from parent, if not present""" + return self._Annotated__element.table + + @util.memoized_property + def key(self): + """pull 'key' from parent, if not present""" + return self._Annotated__element.key + + @util.memoized_property + def info(self): + return self._Annotated__element.info + + @util.memoized_property + def anon_label(self): + return self._Annotated__element.anon_label + + class _truncated_label(quoted_name): """A unicode subclass used to identify symbolic " "names that may require truncation.""" @@ -4378,349 +4534,3 @@ class _anonymous_label(_truncated_label): else: # else skip the constructor call return self % map_ - - -def _as_truncated(value): - """coerce the given value to :class:`._truncated_label`. - - Existing :class:`._truncated_label` and - :class:`._anonymous_label` objects are passed - unchanged. - """ - - if isinstance(value, _truncated_label): - return value - else: - return _truncated_label(value) - - -def _string_or_unprintable(element): - if isinstance(element, util.string_types): - return element - else: - try: - return str(element) - except Exception: - return "unprintable element %r" % element - - -def _expand_cloned(elements): - """expand the given set of ClauseElements to be the set of all 'cloned' - predecessors. - - """ - return itertools.chain(*[x._cloned_set for x in elements]) - - -def _select_iterables(elements): - """expand tables into individual columns in the - given list of column expressions. - - """ - return itertools.chain(*[c._select_iterable for c in elements]) - - -def _cloned_intersection(a, b): - """return the intersection of sets a and b, counting - any overlap between 'cloned' predecessors. - - The returned set is in terms of the entities present within 'a'. - - """ - all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) - return set( - elem for elem in a if all_overlap.intersection(elem._cloned_set) - ) - - -def _cloned_difference(a, b): - all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) - return set( - elem for elem in a if not all_overlap.intersection(elem._cloned_set) - ) - - -@util.dependencies("sqlalchemy.sql.functions") -def _labeled(functions, element): - if not hasattr(element, "name") or isinstance( - element, functions.FunctionElement - ): - return element.label(None) - else: - return element - - -def _is_column(col): - """True if ``col`` is an instance of :class:`.ColumnElement`.""" - - return isinstance(col, ColumnElement) - - -def _find_columns(clause): - """locate Column objects within the given expression.""" - - cols = util.column_set() - traverse(clause, {}, {"column": cols.add}) - return cols - - -# there is some inconsistency here between the usage of -# inspect() vs. checking for Visitable and __clause_element__. -# Ideally all functions here would derive from inspect(), -# however the inspect() versions add significant callcount -# overhead for critical functions like _interpret_as_column_or_from(). -# Generally, the column-based functions are more performance critical -# and are fine just checking for __clause_element__(). It is only -# _interpret_as_from() where we'd like to be able to receive ORM entities -# that have no defined namespace, hence inspect() is needed there. - - -def _column_as_key(element): - if isinstance(element, util.string_types): - return element - if hasattr(element, "__clause_element__"): - element = element.__clause_element__() - try: - return element.key - except AttributeError: - return None - - -def _clause_element_as_expr(element): - if hasattr(element, "__clause_element__"): - return element.__clause_element__() - else: - return element - - -def _literal_as_label_reference(element): - if isinstance(element, util.string_types): - return _textual_label_reference(element) - - elif hasattr(element, "__clause_element__"): - element = element.__clause_element__() - - return _literal_as_text(element) - - -def _literal_and_labels_as_label_reference(element): - if isinstance(element, util.string_types): - return _textual_label_reference(element) - - elif hasattr(element, "__clause_element__"): - element = element.__clause_element__() - - if ( - isinstance(element, ColumnElement) - and element._order_by_label_element is not None - ): - return _label_reference(element) - else: - return _literal_as_text(element) - - -def _expression_literal_as_text(element): - return _literal_as_text(element) - - -def _literal_as(element, text_fallback): - if isinstance(element, Visitable): - return element - elif hasattr(element, "__clause_element__"): - return element.__clause_element__() - elif isinstance(element, util.string_types): - return text_fallback(element) - elif isinstance(element, (util.NoneType, bool)): - return _const_expr(element) - else: - raise exc.ArgumentError( - "SQL expression object expected, got object of type %r " - "instead" % type(element) - ) - - -def _literal_as_text(element, allow_coercion_to_text=False): - if allow_coercion_to_text: - return _literal_as(element, TextClause) - else: - return _literal_as(element, _no_text_coercion) - - -def _literal_as_column(element): - return _literal_as(element, ColumnClause) - - -def _no_column_coercion(element): - element = str(element) - guess_is_literal = not _guess_straight_column.match(element) - raise exc.ArgumentError( - "Textual column expression %(column)r should be " - "explicitly declared with text(%(column)r), " - "or use %(literal_column)s(%(column)r) " - "for more specificity" - % { - "column": util.ellipses_string(element), - "literal_column": "literal_column" - if guess_is_literal - else "column", - } - ) - - -def _no_text_coercion(element, exc_cls=exc.ArgumentError, extra=None): - raise exc_cls( - "%(extra)sTextual SQL expression %(expr)r should be " - "explicitly declared as text(%(expr)r)" - % { - "expr": util.ellipses_string(element), - "extra": "%s " % extra if extra else "", - } - ) - - -def _no_literals(element): - if hasattr(element, "__clause_element__"): - return element.__clause_element__() - elif not isinstance(element, Visitable): - raise exc.ArgumentError( - "Ambiguous literal: %r. Use the 'text()' " - "function to indicate a SQL expression " - "literal, or 'literal()' to indicate a " - "bound value." % (element,) - ) - else: - return element - - -def _is_literal(element): - return not isinstance(element, Visitable) and not hasattr( - element, "__clause_element__" - ) - - -def _only_column_elements_or_none(element, name): - if element is None: - return None - else: - return _only_column_elements(element, name) - - -def _only_column_elements(element, name): - if hasattr(element, "__clause_element__"): - element = element.__clause_element__() - if not isinstance(element, ColumnElement): - raise exc.ArgumentError( - "Column-based expression object expected for argument " - "'%s'; got: '%s', type %s" % (name, element, type(element)) - ) - return element - - -def _literal_as_binds(element, name=None, type_=None): - if hasattr(element, "__clause_element__"): - return element.__clause_element__() - elif not isinstance(element, Visitable): - if element is None: - return Null() - else: - return BindParameter(name, element, type_=type_, unique=True) - else: - return element - - -_guess_straight_column = re.compile(r"^\w\S*$", re.I) - - -def _interpret_as_column_or_from(element): - if isinstance(element, Visitable): - return element - elif hasattr(element, "__clause_element__"): - return element.__clause_element__() - - insp = inspection.inspect(element, raiseerr=False) - if insp is None: - if isinstance(element, (util.NoneType, bool)): - return _const_expr(element) - elif hasattr(insp, "selectable"): - return insp.selectable - - # be forgiving as this is an extremely common - # and known expression - if element == "*": - guess_is_literal = True - elif isinstance(element, (numbers.Number)): - return ColumnClause(str(element), is_literal=True) - else: - _no_column_coercion(element) - return ColumnClause(element, is_literal=guess_is_literal) - - -def _const_expr(element): - if isinstance(element, (Null, False_, True_)): - return element - elif element is None: - return Null() - elif element is False: - return False_() - elif element is True: - return True_() - else: - raise exc.ArgumentError("Expected None, False, or True") - - -def _type_from_args(args): - for a in args: - if not a.type._isnull: - return a.type - else: - return type_api.NULLTYPE - - -def _corresponding_column_or_error(fromclause, column, require_embedded=False): - c = fromclause.corresponding_column( - column, require_embedded=require_embedded - ) - if c is None: - raise exc.InvalidRequestError( - "Given column '%s', attached to table '%s', " - "failed to locate a corresponding column from table '%s'" - % (column, getattr(column, "table", None), fromclause.description) - ) - return c - - -class AnnotatedColumnElement(Annotated): - def __init__(self, element, values): - Annotated.__init__(self, element, values) - ColumnElement.comparator._reset(self) - for attr in ("name", "key", "table"): - if self.__dict__.get(attr, False) is None: - self.__dict__.pop(attr) - - def _with_annotations(self, values): - clone = super(AnnotatedColumnElement, self)._with_annotations(values) - ColumnElement.comparator._reset(clone) - return clone - - @util.memoized_property - def name(self): - """pull 'name' from parent, if not present""" - return self._Annotated__element.name - - @util.memoized_property - def table(self): - """pull 'table' from parent, if not present""" - return self._Annotated__element.table - - @util.memoized_property - def key(self): - """pull 'key' from parent, if not present""" - return self._Annotated__element.key - - @util.memoized_property - def info(self): - return self._Annotated__element.info - - @util.memoized_property - def anon_label(self): - return self._Annotated__element.anon_label |