From: Mike Bayer Date: Mon, 21 Mar 2016 14:57:40 +0000 (-0400) Subject: - Fixed bug where the negation of an EXISTS expression would not X-Git-Tag: rel_1_1_0b1~93 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=07a4b6cbcda6e6ee6e67893c5a5d2fd01e5f125f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. fixes #3682 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index defaf452b1..07188b7711 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -18,6 +18,15 @@ .. 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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 8600dbaebd..8d5f585ce5 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 [ diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 8256900f96..00c2c37bac 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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() diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index 8e75638a24..66612eb338 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -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') diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 7203cc5a38..94e4ac024c 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -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()