From 920f77e3c17ec43382e60a992dc4dcec2ba173b8 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 21 Apr 2017 13:35:38 -0400 Subject: [PATCH] Add _negate() to Label to negate inner element Fixed the negation of a :class:`.Label` construct so that the inner element is negated correctly, when the :func:`.not_` modifier is applied to the labeled expression. Change-Id: Ia99917b2959bdfbff28689c962b4203911c57b85 Fixes: #3969 --- doc/build/changelog/changelog_12.rst | 8 ++++++++ lib/sqlalchemy/sql/elements.py | 8 +++++++- test/sql/test_operators.py | 16 +++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst index 57262ef9b5..1f49281edd 100644 --- a/doc/build/changelog/changelog_12.rst +++ b/doc/build/changelog/changelog_12.rst @@ -13,6 +13,14 @@ .. changelog:: :version: 1.2.0b1 + .. change:: 3969 + :tags: bug, sql + :tickets: 3969 + + Fixed the negation of a :class:`.Label` construct so that the + inner element is negated correctly, when the :func:`.not_` modifier + is applied to the labeled expression. + .. change:: :tags: bug, orm :tickets: 3967 diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 414e3f4778..1ce03e66a3 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -3577,7 +3577,13 @@ class Label(ColumnElement): return self._element.self_group(against=operators.as_) def self_group(self, against=None): - sub_element = self._element.self_group(against=against) + return self._apply_to_inner(self._element.self_group, against=against) + + def _negate(self): + return self._apply_to_inner(self._element._negate) + + def _apply_to_inner(self, fn, *arg, **kw): + sub_element = fn(*arg, **kw) if sub_element is not self._element: return Label(self.name, sub_element, diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index ac05d3a811..c0637d2257 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -11,7 +11,7 @@ import operator from sqlalchemy import String, Integer, LargeBinary from sqlalchemy import exc from sqlalchemy.engine import default -from sqlalchemy.sql.elements import _literal_as_text +from sqlalchemy.sql.elements import _literal_as_text, Label from sqlalchemy.schema import Column, Table, MetaData from sqlalchemy.sql import compiler from sqlalchemy.types import TypeEngine, TypeDecorator, UserDefinedType, \ @@ -2079,6 +2079,20 @@ class NegationTest(fixtures.TestBase, testing.AssertsCompiledSQL): self.table1.c.myid.type, ) + def test_negate_operator_label(self): + orig_expr = or_( + self.table1.c.myid == 1, self.table1.c.myid == 2).label('foo') + expr = not_(orig_expr) + isinstance(expr, Label) + eq_(expr.name, 'foo') + is_not_(expr, orig_expr) + is_(expr._element.operator, operator.inv) # e.g. and not false_ + + self.assert_compile( + expr, + "NOT (mytable.myid = :myid_1 OR mytable.myid = :myid_2)" + ) + class LikeTest(fixtures.TestBase, testing.AssertsCompiledSQL): __dialect__ = 'default' -- 2.47.2