From: Mike Bayer Date: Mon, 31 Mar 2014 01:20:20 +0000 (-0400) Subject: - Added new flag :paramref:`.expression.between.symmetric`, when set to True X-Git-Tag: rel_0_9_5~91 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e16ede8cae00fd5cbbd5fb33d63c14df0153c2bc;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. fixes #2990 --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 05e0f51b3d..6674a59a0d 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -11,6 +11,19 @@ .. 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 diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 67155e0f2b..8d77acc279 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -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__ diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 5165ee78f1..af40b25372 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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, diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py index 6d595450be..6bc7fb5807 100644 --- a/lib/sqlalchemy/sql/default_comparator.py +++ b/lib/sqlalchemy/sql/default_comparator.py @@ -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,), diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index c230fb0d32..5ebc7478a6 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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. diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 91301c78cf..bafe009797 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -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, diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index 1001e598c5..31af7d2730 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -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'