diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-22 16:36:29 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-22 16:36:29 -0400 |
commit | faa9b2c8da63aa116579fc6c43a30ce479b92ac2 (patch) | |
tree | 30a75f94f18a9f51197658e5b0a8d5ae1b09753d /lib/sqlalchemy/sql/operators.py | |
parent | 2bee05098e09dcdf09f7c8ff1c7efeba0c2fc9f2 (diff) | |
download | sqlalchemy-faa9b2c8da63aa116579fc6c43a30ce479b92ac2.tar.gz |
- [feature] Revised the rules used to determine
the operator precedence for the user-defined
operator, i.e. that granted using the ``op()``
method. Previously, the smallest precedence
was applied in all cases, now the default
precedence is zero, lower than all operators
except "comma" (such as, used in the argument
list of a ``func`` call) and "AS", and is
also customizable via the "precedence" argument
on the ``op()`` method. [ticket:2537]
Diffstat (limited to 'lib/sqlalchemy/sql/operators.py')
-rw-r--r-- | lib/sqlalchemy/sql/operators.py | 230 |
1 files changed, 129 insertions, 101 deletions
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 866cc8f06..9adab2acf 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -19,27 +19,28 @@ from operator import (div,) from ..util import symbol + class Operators(object): """Base of comparison and logical operators. - + Implements base methods :meth:`operate` and :meth:`reverse_operate`, as well as :meth:`__and__`, :meth:`__or__`, :meth:`__invert__`. - + Usually is used via its most common subclass :class:`.ColumnOperators`. - + """ def __and__(self, other): """Implement the ``&`` operator. - + When used with SQL expressions, results in an AND operation, equivalent to :func:`~.expression.and_`, that is:: - + a & b - + is equivalent to:: - + from sqlalchemy import and_ and_(a, b) @@ -47,7 +48,7 @@ class Operators(object): operator precedence; the ``&`` operator has the highest precedence. The operands should be enclosed in parenthesis if they contain further sub expressions:: - + (a == 2) & (b == 4) """ @@ -55,15 +56,15 @@ class Operators(object): def __or__(self, other): """Implement the ``|`` operator. - + When used with SQL expressions, results in an OR operation, equivalent to :func:`~.expression.or_`, that is:: - + a | b - + is equivalent to:: - + from sqlalchemy import or_ or_(a, b) @@ -71,7 +72,7 @@ class Operators(object): operator precedence; the ``|`` operator has the highest precedence. The operands should be enclosed in parenthesis if they contain further sub expressions:: - + (a == 2) | (b == 4) """ @@ -79,22 +80,22 @@ class Operators(object): def __invert__(self): """Implement the ``~`` operator. - - When used with SQL expressions, results in a - NOT operation, equivalent to + + When used with SQL expressions, results in a + NOT operation, equivalent to :func:`~.expression.not_`, that is:: - + ~a - + is equivalent to:: - + from sqlalchemy import not_ not_(a) """ return self.operate(inv) - def op(self, opstring): + def op(self, opstring, precedence=0): """produce a generic operator function. e.g.:: @@ -105,34 +106,46 @@ class Operators(object): somecolumn * 5 - :param operator: a string which will be output as the infix operator - between this :class:`.ClauseElement` and the expression passed to the - generated function. - This function can also be used to make bitwise operators explicit. For example:: somecolumn.op('&')(0xff) - is a bitwise AND of the value in somecolumn. + is a bitwise AND of the value in ``somecolumn``. + + :param operator: a string which will be output as the infix operator + between this :class:`.ClauseElement` and the expression passed to the + generated function. + + :param precedence: precedence to apply to the operator, when + parenthesizing expressions. A lower number will cause the expression + to be parenthesized when applied against another operator with + higher precedence. The default value of ``0`` is lower than all + operators except for the comma (``,``) and ``AS`` operators. + A value of 100 will be higher or equal to all operators, and -100 + will be lower than or equal to all operators. + + .. versionadded:: 0.8 - added the 'precedence' argument. """ - def _op(b): - return self.operate(op, opstring, b) - return _op + operator = custom_op(opstring, precedence) + + def against(other): + return operator(self, other) + return against def operate(self, op, *other, **kwargs): """Operate on an argument. - + This is the lowest level of operation, raises :class:`NotImplementedError` by default. - - Overriding this on a subclass can allow common - behavior to be applied to all operations. + + Overriding this on a subclass can allow common + behavior to be applied to all operations. For example, overriding :class:`.ColumnOperators` - to apply ``func.lower()`` to the left and right + to apply ``func.lower()`` to the left and right side:: - + class MyComparator(ColumnOperators): def operate(self, op, other): return op(func.lower(self), func.lower(other)) @@ -142,48 +155,60 @@ class Operators(object): be a single scalar for most operations. :param \**kwargs: modifiers. These may be passed by special operators such as :meth:`ColumnOperators.contains`. - - + + """ raise NotImplementedError(str(op)) def reverse_operate(self, op, other, **kwargs): """Reverse operate on an argument. - + Usage is the same as :meth:`operate`. - + """ raise NotImplementedError(str(op)) + +class custom_op(object): + __name__ = 'custom_op' + + def __init__(self, opstring, precedence=0): + self.opstring = opstring + self.precedence = precedence + + def __call__(self, left, right, **kw): + return left.operate(self, right, **kw) + + class ColumnOperators(Operators): """Defines comparison and math operations. - + By default all methods call down to :meth:`Operators.operate` or :meth:`Operators.reverse_operate` - passing in the appropriate operator function from the + passing in the appropriate operator function from the Python builtin ``operator`` module or - a SQLAlchemy-specific operator function from + a SQLAlchemy-specific operator function from :mod:`sqlalchemy.expression.operators`. For example the ``__eq__`` function:: - + def __eq__(self, other): return self.operate(operators.eq, other) Where ``operators.eq`` is essentially:: - + def eq(a, b): return a == b - + A SQLAlchemy construct like :class:`.ColumnElement` ultimately overrides :meth:`.Operators.operate` and others - to return further :class:`.ClauseElement` constructs, + to return further :class:`.ClauseElement` constructs, so that the ``==`` operation above is replaced by a clause construct. - + The docstrings here will describe column-oriented behavior of each operator. For ORM-based operators on related objects and collections, see :class:`.RelationshipProperty.Comparator`. - + """ timetuple = None @@ -191,17 +216,17 @@ class ColumnOperators(Operators): def __lt__(self, other): """Implement the ``<`` operator. - + In a column context, produces the clause ``a < b``. - + """ return self.operate(lt, other) def __le__(self, other): """Implement the ``<=`` operator. - + In a column context, produces the clause ``a <= b``. - + """ return self.operate(le, other) @@ -209,7 +234,7 @@ class ColumnOperators(Operators): def __eq__(self, other): """Implement the ``==`` operator. - + In a column context, produces the clause ``a = b``. If the target is ``None``, produces ``a IS NULL``. @@ -221,66 +246,66 @@ class ColumnOperators(Operators): In a column context, produces the clause ``a != b``. If the target is ``None``, produces ``a IS NOT NULL``. - + """ return self.operate(ne, other) def __gt__(self, other): """Implement the ``>`` operator. - + In a column context, produces the clause ``a > b``. - + """ return self.operate(gt, other) def __ge__(self, other): """Implement the ``>=`` operator. - + In a column context, produces the clause ``a >= b``. - + """ return self.operate(ge, other) def __neg__(self): """Implement the ``-`` operator. - + In a column context, produces the clause ``-a``. - + """ return self.operate(neg) def concat(self, other): """Implement the 'concat' operator. - + In a column context, produces the clause ``a || b``, or uses the ``concat()`` operator on MySQL. - + """ return self.operate(concat_op, other) def like(self, other, escape=None): """Implement the ``like`` operator. - + In a column context, produces the clause ``a LIKE other``. - + """ return self.operate(like_op, other, escape=escape) def ilike(self, other, escape=None): """Implement the ``ilike`` operator. - + In a column context, produces the clause ``a ILIKE other``. - + """ return self.operate(ilike_op, other, escape=escape) def in_(self, other): """Implement the ``in`` operator. - + In a column context, produces the clause ``a IN other``. "other" may be a tuple/list of column expressions, or a :func:`~.expression.select` construct. - + """ return self.operate(in_op, other) @@ -288,31 +313,31 @@ class ColumnOperators(Operators): """Implement the ``startwith`` operator. In a column context, produces the clause ``LIKE '<other>%'`` - + """ return self.operate(startswith_op, other, **kwargs) def endswith(self, other, **kwargs): """Implement the 'endswith' operator. - + In a column context, produces the clause ``LIKE '%<other>'`` - + """ return self.operate(endswith_op, other, **kwargs) def contains(self, other, **kwargs): """Implement the 'contains' operator. - + In a column context, produces the clause ``LIKE '%<other>%'`` - + """ return self.operate(contains_op, other, **kwargs) def match(self, other, **kwargs): """Implements the 'match' operator. - - In a column context, this produces a MATCH clause, i.e. - ``MATCH '<other>'``. The allowed contents of ``other`` + + In a column context, this produces a MATCH clause, i.e. + ``MATCH '<other>'``. The allowed contents of ``other`` are database backend specific. """ @@ -347,7 +372,7 @@ class ColumnOperators(Operators): """Implement the ``+`` operator in reverse. See :meth:`__add__`. - + """ return self.reverse_operate(add, other) @@ -355,7 +380,7 @@ class ColumnOperators(Operators): """Implement the ``-`` operator in reverse. See :meth:`__sub__`. - + """ return self.reverse_operate(sub, other) @@ -363,7 +388,7 @@ class ColumnOperators(Operators): """Implement the ``*`` operator in reverse. See :meth:`__mul__`. - + """ return self.reverse_operate(mul, other) @@ -371,7 +396,7 @@ class ColumnOperators(Operators): """Implement the ``/`` operator in reverse. See :meth:`__div__`. - + """ return self.reverse_operate(div, other) @@ -386,61 +411,61 @@ class ColumnOperators(Operators): def __add__(self, other): """Implement the ``+`` operator. - + In a column context, produces the clause ``a + b`` if the parent object has non-string affinity. - If the parent object has a string affinity, + If the parent object has a string affinity, produces the concatenation operator, ``a || b`` - see :meth:`concat`. - + """ return self.operate(add, other) def __sub__(self, other): """Implement the ``-`` operator. - + In a column context, produces the clause ``a - b``. - + """ return self.operate(sub, other) def __mul__(self, other): """Implement the ``*`` operator. - + In a column context, produces the clause ``a * b``. - + """ return self.operate(mul, other) def __div__(self, other): """Implement the ``/`` operator. - + In a column context, produces the clause ``a / b``. - + """ return self.operate(div, other) def __mod__(self, other): """Implement the ``%`` operator. - + In a column context, produces the clause ``a % b``. - + """ return self.operate(mod, other) def __truediv__(self, other): """Implement the ``//`` operator. - + In a column context, produces the clause ``a / b``. - + """ return self.operate(truediv, other) def __rtruediv__(self, other): """Implement the ``//`` operator in reverse. - + See :meth:`__truediv__`. - + """ return self.reverse_operate(truediv, other) @@ -530,14 +555,14 @@ def is_commutative(op): return op in _commutative def is_ordering_modifier(op): - return op in (asc_op, desc_op, + return op in (asc_op, desc_op, nullsfirst_op, nullslast_op) _associative = _commutative.union([concat_op, and_, or_]) -_smallest = symbol('_smallest') -_largest = symbol('_largest') +_smallest = symbol('_smallest', canonical=-100) +_largest = symbol('_largest', canonical=100) _PRECEDENCE = { from_: 15, @@ -575,13 +600,16 @@ _PRECEDENCE = { collate: 7, as_: -1, exists: 0, - _smallest: -1000, - _largest: 1000 + _smallest: _smallest, + _largest: _largest } + def is_precedent(operator, against): if operator is against and operator in _associative: return False else: - return (_PRECEDENCE.get(operator, _PRECEDENCE[_smallest]) <= - _PRECEDENCE.get(against, _PRECEDENCE[_largest])) + return (_PRECEDENCE.get(operator, + getattr(operator, 'precedence', _smallest)) <= + _PRECEDENCE.get(against, + getattr(against, 'precedence', _largest))) |