]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug where the negation of an EXISTS expression would not
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 21 Mar 2016 14:57:40 +0000 (10:57 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 21 Mar 2016 14:57:40 +0000 (10:57 -0400)
be properly typed as boolean in the result, and also would fail to be
anonymously aliased in a SELECT list as is the case with a
non-negated EXISTS construct.
fixes #3682

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/elements.py
test/sql/test_compiler.py
test/sql/test_selectable.py

index defaf452b1f186ab800a60454bb77a26f7e0ba08..07188b7711e97af38c2c0edac99c7b3ed2194739 100644 (file)
 .. changelog::
     :version: 1.0.13
 
+    .. change::
+        :tags: bug, sql
+        :tickets: 3682
+
+        Fixed bug where the negation of an EXISTS expression would not
+        be properly typed as boolean in the result, and also would fail to be
+        anonymously aliased in a SELECT list as is the case with a
+        non-negated EXISTS construct.
+
     .. change::
         :tags: bug, sql
         :tickets: 3666
index 8600dbaebd448abb0ceed2d95af5878e4ad5bc9d..8d5f585ce5c051fc68df348d208577a3eed45bcd 100644 (file)
@@ -1620,7 +1620,6 @@ class SQLCompiler(Compiled):
                 select, select._prefixes, **kwargs)
 
         text += self.get_select_precolumns(select, **kwargs)
-
         # the actual list of columns to print in the SELECT column list.
         inner_columns = [
             c for c in [
index 8256900f960044f7d867baf5ac8e3d4e85c6070d..00c2c37bacf09155bd9d5b8b351e2001c0a9be8b 100644 (file)
@@ -646,6 +646,9 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
 
     def _negate(self):
         if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
+            # TODO: see the note in AsBoolean that it seems to assume
+            # the element is the True_() / False_() constant, so this
+            # is too broad
             return AsBoolean(self, operators.isfalse, operators.istrue)
         else:
             return super(ColumnElement, self)._negate()
@@ -2766,6 +2769,13 @@ class UnaryExpression(ColumnElement):
                 modifier=self.modifier,
                 type_=self.type,
                 wraps_column_expression=self.wraps_column_expression)
+        elif self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
+            return UnaryExpression(
+                self.self_group(against=operators.inv),
+                operator=operators.inv,
+                type_=type_api.BOOLEANTYPE,
+                wraps_column_expression=self.wraps_column_expression,
+                negate=None)
         else:
             return ClauseElement._negate(self)
 
@@ -2875,6 +2885,9 @@ class AsBoolean(UnaryExpression):
         return self
 
     def _negate(self):
+        # TODO: this assumes the element is the True_() or False_()
+        # object, but this assumption isn't enforced and
+        # ColumnElement._negate() can send any number of expressions here
         return self.element._negate()
 
 
index 8e75638a24db02b5e633c775bdc95b575cfdbc48..66612eb338a7de6fd2d1186dc7b32623b4508fbe 100644 (file)
@@ -638,6 +638,21 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
             "myothertable.otherid = :otherid_2)) AS anon_1"
         )
 
+        self.assert_compile(
+            select([exists([1])]),
+            "SELECT EXISTS (SELECT 1) AS anon_1"
+        )
+
+        self.assert_compile(
+            select([~exists([1])]),
+            "SELECT NOT (EXISTS (SELECT 1)) AS anon_1"
+        )
+
+        self.assert_compile(
+            select([~(~exists([1]))]),
+            "SELECT NOT (NOT (EXISTS (SELECT 1))) AS anon_1"
+        )
+
     def test_where_subquery(self):
         s = select([addresses.c.street], addresses.c.user_id
                    == users.c.user_id, correlate=True).alias('s')
index 7203cc5a38446b30b7117d9cb64d08958ddf9d33..94e4ac024c83fae75d538f7d63d3e0ba5b4a9b93 100644 (file)
@@ -2217,6 +2217,33 @@ class ResultMapTest(fixtures.TestBase):
             [Boolean]
         )
 
+    def test_plain_exists(self):
+        expr = exists([1])
+        eq_(type(expr.type), Boolean)
+        eq_(
+            [type(entry[-1]) for
+             entry in select([expr]).compile()._result_columns],
+            [Boolean]
+        )
+
+    def test_plain_exists_negate(self):
+        expr = ~exists([1])
+        eq_(type(expr.type), Boolean)
+        eq_(
+            [type(entry[-1]) for
+             entry in select([expr]).compile()._result_columns],
+            [Boolean]
+        )
+
+    def test_plain_exists_double_negate(self):
+        expr = ~(~exists([1]))
+        eq_(type(expr.type), Boolean)
+        eq_(
+            [type(entry[-1]) for
+             entry in select([expr]).compile()._result_columns],
+            [Boolean]
+        )
+
     def test_column_subquery_plain(self):
         t = self._fixture()
         s1 = select([t.c.x]).where(t.c.x > 5).as_scalar()