diff options
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 |