summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-03-21 10:57:40 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2016-03-21 10:57:40 -0400
commit07a4b6cbcda6e6ee6e67893c5a5d2fd01e5f125f (patch)
treea28aac49f1f8fd7aef5ae3ed85a2bd9ce98978d1
parent732c613eeb890e7b7cbd04750468dac584151a31 (diff)
downloadsqlalchemy-07a4b6cbcda6e6ee6e67893c5a5d2fd01e5f125f.tar.gz
- 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
-rw-r--r--doc/build/changelog/changelog_10.rst9
-rw-r--r--lib/sqlalchemy/sql/compiler.py1
-rw-r--r--lib/sqlalchemy/sql/elements.py13
-rw-r--r--test/sql/test_compiler.py15
-rw-r--r--test/sql/test_selectable.py27
5 files changed, 64 insertions, 1 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index defaf452b..07188b771 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -20,6 +20,15 @@
.. 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
Fixed bug where "unconsumed column names" exception would fail to
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 8600dbaeb..8d5f585ce 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 8256900f9..00c2c37ba 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 8e75638a2..66612eb33 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 7203cc5a3..94e4ac024 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()