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_0_13~34 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dfe49c7b7f0c83ced11fdbceef14d89c82647f0b;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 (cherry picked from commit 07a4b6cbcda6e6ee6e67893c5a5d2fd01e5f125f) --- 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 9f54004552..496844d964 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1572,7 +1572,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 0470edfbae..124dbdb98c 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -705,6 +705,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() @@ -2677,6 +2680,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) @@ -2701,6 +2711,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 8b161686c2..24428beefd 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -637,6 +637,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 3390f4a771..9d714164b9 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -2193,6 +2193,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()