summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_12.rst11
-rw-r--r--lib/sqlalchemy/sql/default_comparator.py8
-rw-r--r--lib/sqlalchemy/sql/operators.py3
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py4
-rw-r--r--test/sql/test_operators.py10
5 files changed, 32 insertions, 4 deletions
diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst
index b87682b6d..2b6741494 100644
--- a/doc/build/changelog/changelog_12.rst
+++ b/doc/build/changelog/changelog_12.rst
@@ -13,6 +13,17 @@
.. changelog::
:version: 1.2.0b1
+ .. change:: 3873
+ :tags: bug, sql
+ :tickets: 3873
+
+ Repaired issue where the type of an expression that used
+ :meth:`.ColumnOperators.is_` or similar would not be a "boolean" type,
+ instead the type would be "nulltype", as well as when using custom
+ comparison operators against an untyped expression. This typing can
+ impact how the expression behaves in larger contexts as well as
+ in result-row-handling.
+
.. change:: 3969
:tags: bug, sql
:tickets: 3969
diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py
index 4ba53ef75..4485c661b 100644
--- a/lib/sqlalchemy/sql/default_comparator.py
+++ b/lib/sqlalchemy/sql/default_comparator.py
@@ -50,11 +50,15 @@ def _boolean_compare(expr, op, obj, negate=None, reverse=False,
if op in (operators.eq, operators.is_):
return BinaryExpression(expr, _const_expr(obj),
operators.is_,
- negate=operators.isnot)
+ negate=operators.isnot,
+ type_=result_type
+ )
elif op in (operators.ne, operators.isnot):
return BinaryExpression(expr, _const_expr(obj),
operators.isnot,
- negate=operators.is_)
+ negate=operators.is_,
+ type_=result_type
+ )
else:
raise exc.ArgumentError(
"Only '=', '!=', 'is_()', 'isnot()', "
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index 49642acdd..58f32b3e6 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -1021,7 +1021,8 @@ def json_path_getitem_op(a, b):
_commutative = {eq, ne, add, mul}
-_comparison = {eq, ne, lt, gt, ge, le, between_op, like_op}
+_comparison = {eq, ne, lt, gt, ge, le, between_op, like_op, is_,
+ isnot, is_distinct_from, isnot_distinct_from}
def is_comparison(op):
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index b8117e3ca..b8c8c8116 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -2555,7 +2555,9 @@ class NullType(TypeEngine):
class Comparator(TypeEngine.Comparator):
def _adapt_expression(self, op, other_comparator):
- if isinstance(other_comparator, NullType.Comparator) or \
+ if operators.is_comparison(op):
+ return op, BOOLEANTYPE
+ elif isinstance(other_comparator, NullType.Comparator) or \
not operators.is_commutative(op):
return op, self.expr.type
else:
diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py
index c0637d225..3dd9af5e2 100644
--- a/test/sql/test_operators.py
+++ b/test/sql/test_operators.py
@@ -3,6 +3,7 @@ from sqlalchemy import testing
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.sql import column, desc, asc, literal, collate, null, \
true, false, any_, all_
+from sqlalchemy.sql import sqltypes
from sqlalchemy.sql.expression import BinaryExpression, \
ClauseList, Grouping, \
UnaryExpression, select, union, func, tuple_
@@ -62,6 +63,12 @@ class DefaultColumnComparatorTest(fixtures.TestBase):
self._loop_test(operator, right)
+ if operators.is_comparison(operator):
+ is_(
+ left.comparator.operate(operator, right).type,
+ sqltypes.BOOLEANTYPE
+ )
+
def _loop_test(self, operator, *arg):
loop = LoopOperate()
is_(
@@ -2617,6 +2624,9 @@ class CustomOpTest(fixtures.TestBase):
assert operators.is_comparison(op1)
assert not operators.is_comparison(op2)
+ expr = c.op('$', is_comparison=True)(None)
+ is_(expr.type, sqltypes.BOOLEANTYPE)
+
class TupleTypingTest(fixtures.TestBase):