diff options
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 81 |
1 files changed, 57 insertions, 24 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 844293c73..63fa23c15 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1875,7 +1875,7 @@ class Immutable(object): return self -class _DefaultColumnComparator(object): +class _DefaultColumnComparator(operators.ColumnOperators): """Defines comparison and math operations. See :class:`.ColumnOperators` and :class:`.Operators` for descriptions @@ -1883,6 +1883,45 @@ class _DefaultColumnComparator(object): """ + @util.memoized_property + def type(self): + return self.expr.type + + def operate(self, op, *other, **kwargs): + o = self.operators[op.__name__] + return o[0](self, self.expr, op, *(other + o[1:]), **kwargs) + + def reverse_operate(self, op, other, **kwargs): + o = self.operators[op.__name__] + return o[0](self, self.expr, op, other, reverse=True, *o[1:], **kwargs) + + def _adapt_expression(self, op, other_comparator): + """evaluate the return type of <self> <op> <othertype>, + and apply any adaptations to the given operator. + + This method determines the type of a resulting binary expression + given two source types and an operator. For example, two + :class:`.Column` objects, both of the type :class:`.Integer`, will + produce a :class:`.BinaryExpression` that also has the type + :class:`.Integer` when compared via the addition (``+``) operator. + However, using the addition operator with an :class:`.Integer` + and a :class:`.Date` object will produce a :class:`.Date`, assuming + "days delta" behavior by the database (in reality, most databases + other than Postgresql don't accept this particular operation). + + The method returns a tuple of the form <operator>, <type>. + The resulting operator and type will be those applied to the + resulting :class:`.BinaryExpression` as the final operator and the + right-hand side of the expression. + + Note that only a subset of operators make usage of + :meth:`._adapt_expression`, + including math operators and user-defined operators, but not + boolean comparison or special SQL keywords like MATCH or BETWEEN. + + """ + return op, other_comparator.type + def _boolean_compare(self, expr, op, obj, negate=None, reverse=False, **kwargs ): @@ -1912,7 +1951,7 @@ class _DefaultColumnComparator(object): type_=sqltypes.BOOLEANTYPE, negate=negate, modifiers=kwargs) - def _binary_operate(self, expr, op, obj, result_type, reverse=False): + def _binary_operate(self, expr, op, obj, reverse=False): obj = self._check_literal(expr, op, obj) if reverse: @@ -1920,6 +1959,8 @@ class _DefaultColumnComparator(object): else: left, right = expr, obj + op, result_type = left.comparator._adapt_expression(op, right.comparator) + return BinaryExpression(left, right, op, type_=result_type) def _scalar(self, expr, op, fn, **kw): @@ -1986,7 +2027,8 @@ class _DefaultColumnComparator(object): expr, operators.like_op, literal_column("'%'", type_=sqltypes.String).__radd__( - self._check_literal(expr, operators.like_op, other) + self._check_literal(expr, + operators.like_op, other) ), escape=escape) @@ -2068,21 +2110,16 @@ class _DefaultColumnComparator(object): "neg": (_neg_impl,), } - def operate(self, expr, op, *other, **kwargs): - o = self.operators[op.__name__] - return o[0](self, expr, op, *(other + o[1:]), **kwargs) - - def reverse_operate(self, expr, op, other, **kwargs): - o = self.operators[op.__name__] - return o[0](self, expr, op, other, reverse=True, *o[1:], **kwargs) def _check_literal(self, expr, operator, other): - if isinstance(other, BindParameter) and \ - isinstance(other.type, sqltypes.NullType): - # TODO: perhaps we should not mutate the incoming bindparam() - # here and instead make a copy of it. this might - # be the only place that we're mutating an incoming construct. - other.type = expr.type + if isinstance(other, (ColumnElement, TextClause)): + if isinstance(other, BindParameter) and \ + isinstance(other.type, sqltypes.NullType): + # TODO: perhaps we should not mutate the incoming + # bindparam() here and instead make a copy of it. + # this might be the only place that we're mutating + # an incoming construct. + other.type = expr.type return other elif hasattr(other, '__clause_element__'): other = other.__clause_element__() @@ -2096,8 +2133,6 @@ class _DefaultColumnComparator(object): else: return other -_DEFAULT_COMPARATOR = _DefaultColumnComparator() - class ColumnElement(ClauseElement, ColumnOperators): """Represent an element that is usable within the "column clause" portion @@ -2155,11 +2190,7 @@ class ColumnElement(ClauseElement, ColumnOperators): def comparator(self): return self.type.comparator_factory(self) - #def _assert_comparator(self): - # assert self.comparator.expr is self - def __getattr__(self, key): - #self._assert_comparator() try: return getattr(self.comparator, key) except AttributeError: @@ -2171,11 +2202,9 @@ class ColumnElement(ClauseElement, ColumnOperators): ) def operate(self, op, *other, **kwargs): - #self._assert_comparator() return op(self.comparator, *other, **kwargs) def reverse_operate(self, op, other, **kwargs): - #self._assert_comparator() return op(other, self.comparator, **kwargs) def _bind_param(self, operator, obj): @@ -3090,6 +3119,10 @@ class TextClause(Executable, ClauseElement): else: return sqltypes.NULLTYPE + @property + def comparator(self): + return self.type.comparator_factory(self) + def self_group(self, against=None): if against is operators.in_op: return Grouping(self) |