.. include:: changelog_07.rst
:start-line: 5
+.. changelog::
+ :version: 0.9.5
+
+ .. change::
+ :tags: feature, sql
+ :tickets: 2990
+
+ Added new flag :paramref:`.expression.between.symmetric`, when set to True
+ renders "BETWEEN SYMMETRIC". Also added a new negation operator
+ "notbetween_op", which now allows an expression like ``~col.between(x, y)``
+ to render as "col NOT BETWEEN x AND y", rather than a parentheiszed NOT
+ string.
+
.. changelog::
:version: 0.9.4
:released: March 28, 2014
from .inspection import inspect
from .engine import create_engine, engine_from_config
-__version__ = '0.9.4'
+__version__ = '0.9.5'
def __go(lcls):
global __all__
operators.ge: ' >= ',
operators.eq: ' = ',
operators.concat_op: ' || ',
- operators.between_op: ' BETWEEN ',
operators.match_op: ' MATCH ',
operators.in_op: ' IN ',
operators.notin_op: ' NOT IN ',
if escape else ''
)
+ def visit_between_op_binary(self, binary, operator, **kw):
+ symmetric = binary.modifiers.get("symmetric", False)
+ return self._generate_generic_binary(
+ binary, " BETWEEN SYMMETRIC "
+ if symmetric else " BETWEEN ", **kw)
+
+ def visit_notbetween_op_binary(self, binary, operator, **kw):
+ symmetric = binary.modifiers.get("symmetric", False)
+ return self._generate_generic_binary(
+ binary, " NOT BETWEEN SYMMETRIC "
+ if symmetric else " NOT BETWEEN ", **kw)
+
def visit_bindparam(self, bindparam, within_columns_clause=False,
literal_binds=False,
skip_bind_expression=False,
self._check_literal(expr, operators.and_, cright),
operator=operators.and_,
group=False, group_contents=False),
- operators.between_op)
+ op,
+ negate=operators.notbetween_op
+ if op is operators.between_op
+ else operators.between_op,
+ modifiers=kw)
def _collate_impl(self, expr, op, other, **kw):
return collate(expr, other)
"match_op": (_match_impl,),
"distinct_op": (_distinct_impl,),
"between_op": (_between_impl, ),
+ "notbetween_op": (_between_impl, ),
"neg": (_neg_impl,),
"getitem": (_unsupported_impl,),
"lshift": (_unsupported_impl,),
_literal_as_text(collation),
operators.collate, type_=expr.type)
-def between(expr, lower_bound, upper_bound):
+def between(expr, lower_bound, upper_bound, symmetric=False):
"""Produce a ``BETWEEN`` predicate clause.
E.g.::
:param upper_bound: a column or Python scalar expression serving as the
upper bound of the right side of the ``BETWEEN`` expression.
+ :param symmetric: if True, will render " BETWEEN SYMMETRIC ". Note
+ that not all databases support this syntax.
+
+ .. versionadded:: 0.9.5
+
.. seealso::
:meth:`.ColumnElement.between`
"""
expr = _literal_as_binds(expr)
- return expr.between(lower_bound, upper_bound)
+ return expr.between(lower_bound, upper_bound, symmetric=symmetric)
def literal(value, type_=None):
"""Return a literal clause, bound to a bind parameter.
"""
return self.reverse_operate(div, other)
- def between(self, cleft, cright):
+ def between(self, cleft, cright, symmetric=False):
"""Produce a :func:`~.expression.between` clause against
- the parent object, given the lower and upper range."""
- return self.operate(between_op, cleft, cright)
+ the parent object, given the lower and upper range.
+
+ """
+ return self.operate(between_op, cleft, cright, symmetric=symmetric)
def distinct(self):
"""Produce a :func:`~.expression.distinct` clause against the
return a.notilike(b, escape=escape)
-def between_op(a, b, c):
- return a.between(b, c)
+def between_op(a, b, c, symmetric=False):
+ return a.between(b, c, symmetric=symmetric)
+
+def notbetween_op(a, b, c, symmetric=False):
+ return a.notbetween(b, c, symmetric=symmetric)
def in_op(a, b):
le: 5,
between_op: 5,
+ notbetween_op: 5,
distinct_op: 5,
inv: 5,
istrue: 5,
def test_operator_precedence_9(self):
self.assert_compile(self.table2.select(
not_(self.table2.c.field.between(5, 6))),
- "SELECT op.field FROM op WHERE NOT "
- "(op.field BETWEEN :field_1 AND :field_2)")
+ "SELECT op.field FROM op WHERE "
+ "op.field NOT BETWEEN :field_1 AND :field_2")
def test_operator_precedence_10(self):
self.assert_compile(self.table2.select(not_(self.table2.c.field) == 5),
~(self.table1.c.name.between('jack', 'john'))),
"SELECT mytable.myid, mytable.name FROM "
"mytable WHERE mytable.myid != :myid_1 AND "\
- "NOT (mytable.name BETWEEN :name_1 AND :name_2)"
+ "mytable.name NOT BETWEEN :name_1 AND :name_2"
)
def test_negate_operators_4(self):
"mytable.name NOT ILIKE %(name_1)s",
dialect=postgresql.dialect())
+class BetweenTest(fixtures.TestBase, testing.AssertsCompiledSQL):
+ __dialect__ = 'default'
+
+ table1 = table('mytable',
+ column('myid', Integer),
+ column('name', String),
+ )
+
+ def test_between_1(self):
+ self.assert_compile(
+ self.table1.c.myid.between(1, 2),
+ "mytable.myid BETWEEN :myid_1 AND :myid_2")
+
+ def test_between_2(self):
+ self.assert_compile(
+ ~self.table1.c.myid.between(1, 2),
+ "mytable.myid NOT BETWEEN :myid_1 AND :myid_2")
+
+ def test_between_3(self):
+ self.assert_compile(
+ self.table1.c.myid.between(1, 2, symmetric=True),
+ "mytable.myid BETWEEN SYMMETRIC :myid_1 AND :myid_2")
+
+ def test_between_4(self):
+ self.assert_compile(
+ ~self.table1.c.myid.between(1, 2, symmetric=True),
+ "mytable.myid NOT BETWEEN SYMMETRIC :myid_1 AND :myid_2")
+
+ def test_between_5(self):
+ self.assert_compile(
+ between(self.table1.c.myid, 1, 2, symmetric=True),
+ "mytable.myid BETWEEN SYMMETRIC :myid_1 AND :myid_2")
+
+ def test_between_6(self):
+ self.assert_compile(
+ ~between(self.table1.c.myid, 1, 2, symmetric=True),
+ "mytable.myid NOT BETWEEN SYMMETRIC :myid_1 AND :myid_2")
+
+
+
class MatchTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = 'default'