.. changelog::
:version: 0.9.5
+ .. change::
+ :tags: bug, sql
+ :tickets: 3012
+ :versions: 1.0.0
+
+ Fixed bug where the :meth:`.Operators.__and__`,
+ :meth:`.Operators.__or__` and :meth:`.Operators.__invert__`
+ operator overload methods could not be overridden within a custom
+ :class:`.TypeEngine.Comparator` implementation.
+
.. change::
:tags: feature, postgresql
:tickets: 2785
from .elements import BindParameter, True_, False_, BinaryExpression, \
Null, _const_expr, _clause_element_as_expr, \
ClauseList, ColumnElement, TextClause, UnaryExpression, \
- collate, _is_literal, _literal_as_text, ClauseElement
+ collate, _is_literal, _literal_as_text, ClauseElement, and_, or_
from .selectable import SelectBase, Alias, Selectable, ScalarSelect
class _DefaultColumnComparator(operators.ColumnOperators):
return BinaryExpression(left, right, op, type_=result_type)
+ def _conjunction_operate(self, expr, op, other, **kw):
+ if op is operators.and_:
+ return and_(expr, other)
+ elif op is operators.or_:
+ return or_(expr, other)
+ else:
+ raise NotImplementedError()
+
def _scalar(self, expr, op, fn, **kw):
return fn(expr)
raise NotImplementedError("Operator '%s' is not supported on "
"this expression" % op.__name__)
+ def _inv_impl(self, expr, op, **kw):
+ """See :meth:`.ColumnOperators.__inv__`."""
+ if hasattr(expr, 'negation_clause'):
+ return expr.negation_clause
+ else:
+ return expr._negate()
+
def _neg_impl(self, expr, op, **kw):
"""See :meth:`.ColumnOperators.__neg__`."""
return UnaryExpression(expr, operator=operators.neg)
# a mapping of operators with the method they use, along with
# their negated operator for comparison operators
operators = {
+ "and_": (_conjunction_operate,),
+ "or_": (_conjunction_operate,),
+ "inv": (_inv_impl,),
"add": (_binary_operate,),
"mul": (_binary_operate,),
"sub": (_binary_operate,),
return unicode(self.compile()).encode('ascii', 'backslashreplace')
def __and__(self, other):
+ """'and' at the ClauseElement level.
+
+ .. deprecated:: 0.9.5 - conjunctions are intended to be
+ at the :class:`.ColumnElement`. level
+
+ """
return and_(self, other)
def __or__(self, other):
+ """'or' at the ClauseElement level.
+
+ .. deprecated:: 0.9.5 - conjunctions are intended to be
+ at the :class:`.ColumnElement`. level
+
+ """
return or_(self, other)
def __invert__(self):
else:
return self._negate()
- def __bool__(self):
- raise TypeError("Boolean value of this clause is not defined")
-
- __nonzero__ = __bool__
-
def _negate(self):
return UnaryExpression(
self.self_group(against=operators.inv),
operator=operators.inv,
negate=None)
+ def __bool__(self):
+ raise TypeError("Boolean value of this clause is not defined")
+
+ __nonzero__ = __bool__
+
+
def __repr__(self):
friendly = getattr(self, 'description', None)
if friendly is None:
self.__module__, self.__class__.__name__, id(self), friendly)
-
-class ColumnElement(ClauseElement, operators.ColumnOperators):
+class ColumnElement(operators.ColumnOperators, ClauseElement):
"""Represent a column-oriented SQL expression suitable for usage in the
"columns" clause, WHERE clause etc. of a statement.
def get_children(self, **kwargs):
return list(self._bindparams.values())
+ def compare(self, other):
+ return isinstance(other, TextClause) and other.text == self.text
class Null(ColumnElement):
"""Represent the NULL keyword in a SQL statement.
operator=operators.custom_op("!!"),
type_=MyInteger)
+ def __invert__(self):
+ return UnaryExpression(self.expr,
+ operator=operators.custom_op("!!!"),
+ type_=MyInteger)
+
return MyInteger
def test_factorial(self):
"!! somecol"
)
+ def test_factorial_invert(self):
+ col = column('somecol', self._factorial_fixture())
+ self.assert_compile(
+ ~col,
+ "!!! somecol"
+ )
+
+ def test_double_factorial_invert(self):
+ col = column('somecol', self._factorial_fixture())
+ self.assert_compile(
+ ~(~col),
+ "!!! (!!! somecol)"
+ )
+
def test_unary_no_ops(self):
assert_raises_message(
exc.CompileError,
)
proxied = t.select().c.foo
self._assert_add_override(proxied)
+ self._assert_and_override(proxied)
def test_alias_proxy(self):
t = Table('t', MetaData(),
)
proxied = t.alias().c.foo
self._assert_add_override(proxied)
+ self._assert_and_override(proxied)
def test_binary_propagate(self):
c1 = Column('foo', self._add_override_factory())
self._assert_add_override(c1 - 6)
+ self._assert_and_override(c1 - 6)
def test_reverse_binary_propagate(self):
c1 = Column('foo', self._add_override_factory())
self._assert_add_override(6 - c1)
+ self._assert_and_override(6 - c1)
def test_binary_multi_propagate(self):
c1 = Column('foo', self._add_override_factory())
self._assert_add_override((c1 - 6) + 5)
+ self._assert_and_override((c1 - 6) + 5)
def test_no_boolean_propagate(self):
c1 = Column('foo', self._add_override_factory())
self._assert_not_add_override(c1 == 56)
+ self._assert_not_and_override(c1 == 56)
+
+ def _assert_and_override(self, expr):
+ assert (expr & text("5")).compare(
+ expr.op("goofy_and")(text("5"))
+ )
def _assert_add_override(self, expr):
assert (expr + 5).compare(
expr.op("goofy")(5)
)
+ def _assert_not_and_override(self, expr):
+ assert not (expr & text("5")).compare(
+ expr.op("goofy_and")(text("5"))
+ )
+
class CustomComparatorTest(_CustomComparatorTests, fixtures.TestBase):
def _add_override_factory(self):
def __add__(self, other):
return self.expr.op("goofy")(other)
+ def __and__(self, other):
+ return self.expr.op("goofy_and")(other)
return MyInteger
def __add__(self, other):
return self.expr.op("goofy")(other)
+ def __and__(self, other):
+ return self.expr.op("goofy_and")(other)
+
return MyInteger
def __add__(self, other):
return self.expr.op("goofy")(other)
+ def __and__(self, other):
+ return self.expr.op("goofy_and")(other)
+
class MyDecInteger(TypeDecorator):
impl = MyInteger
def _assert_not_add_override(self, expr):
assert not hasattr(expr, "foob")
+ def _assert_and_override(self, expr):
+ pass
+
+ def _assert_not_and_override(self, expr):
+ pass
+
class ExtensionOperatorTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = 'default'