]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added new flag :paramref:`.expression.between.symmetric`, when set to True
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 31 Mar 2014 01:20:20 +0000 (21:20 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 31 Mar 2014 01:20:20 +0000 (21:20 -0400)
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.  fixes #2990

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/__init__.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/default_comparator.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/operators.py
test/sql/test_operators.py

index 05e0f51b3ddb704330856a7d914945e366a1b235..6674a59a0d9da7a1a2337de45166fb9cb498fbba 100644 (file)
     .. 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
index 67155e0f2b502e31d868e6d06eb050e561889672..8d77acc279f690d9dcb3aa59d638873ee51c7424 100644 (file)
@@ -116,7 +116,7 @@ from .schema import (
 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__
index 5165ee78f160706932ce35da80904b84773102e6..af40b25372bb04ede0db661be06c2f840a151093 100644 (file)
@@ -93,7 +93,6 @@ OPERATORS = {
     operators.ge: ' >= ',
     operators.eq: ' = ',
     operators.concat_op: ' || ',
-    operators.between_op: ' BETWEEN ',
     operators.match_op: ' MATCH ',
     operators.in_op: ' IN ',
     operators.notin_op: ' NOT IN ',
@@ -956,6 +955,18 @@ class SQLCompiler(Compiled):
                 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,
index 6d595450be3d7415cb13d8bea17046dc4bce1155..6bc7fb5807ba348975320ae5a92945619b98075d 100644 (file)
@@ -214,7 +214,11 @@ class _DefaultColumnComparator(operators.ColumnOperators):
                     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)
@@ -255,6 +259,7 @@ class _DefaultColumnComparator(operators.ColumnOperators):
         "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,),
index c230fb0d3289c3886ce6970eefa9fa21f2c445cd..5ebc7478a65a6abbe2f0fb4c77002732e0b11b6c 100644 (file)
@@ -45,7 +45,7 @@ def collate(expression, collation):
         _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.::
@@ -85,13 +85,18 @@ def between(expr, lower_bound, upper_bound):
     :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.
index 91301c78cfec2eee171682e225e83540c4a78df0..bafe009797c78d78341b0bee7ae93a3714d7aad9 100644 (file)
@@ -584,10 +584,12 @@ class ColumnOperators(Operators):
         """
         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
@@ -707,8 +709,11 @@ def notilike_op(a, b, escape=None):
     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):
@@ -837,6 +842,7 @@ _PRECEDENCE = {
     le: 5,
 
     between_op: 5,
+    notbetween_op: 5,
     distinct_op: 5,
     inv: 5,
     istrue: 5,
index 1001e598c51ec4fa28ad3bf7d517ffda0ebc8563..31af7d2730dea4916a4a1589971612be160acb25 100644 (file)
@@ -679,8 +679,8 @@ class OperatorPrecedenceTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     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),
@@ -1248,7 +1248,7 @@ class NegationTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                 ~(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):
@@ -1344,6 +1344,46 @@ class LikeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                 "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'