repair result type for is_, is_not_

Issue #3873 resolved
Christoph-Rauch created an issue

I've been working on some system which type-checks variables and ran across this behaviour where sometimes the type attribute of a BinaryExpression would be NullType(). This is confusion because I can't build something which checks for NULL via a boolean operation.

For example: table.c.col.isnot(None) == True would work, though the types are incompatible, so I can't check beforehand. I hope this makes sense?

From the code I can't deduce if this is intentional or not, though there is special casing going on in sqlalchemy.sql.default_comparator._boolean_compare.

The most current versions of 1.1 and 1.0 are both affected.

A script to illustrate the behaviour is attached.

Even if this behaviour is intentional, I'd be happy to have an explanation as to why this is.

Comments (7)

  1. Mike Bayer repo owner

    this is a bug as the result type is missing on line 54 of lib/sqlalchemy/sql/default_comparator.py.

  2. Mike Bayer repo owner

    that is, the majority are repaired by:

    diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py
    index 827492f..68459c1 100644
    --- a/lib/sqlalchemy/sql/default_comparator.py
    +++ b/lib/sqlalchemy/sql/default_comparator.py
    @@ -50,11 +50,13 @@ 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()', "
    

    for the custom_op thing, honoring is_comparison is something new.

  3. Mike Bayer repo owner

    the custom op issue is:

    diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
    index 215c09e..f244e99 100644
    --- a/lib/sqlalchemy/sql/sqltypes.py
    +++ b/lib/sqlalchemy/sql/sqltypes.py
    @@ -2471,7 +2471,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:
    

    however that has the potential to impact other situations as well; it probably impacts them correctly but any behavioral change has to be considered for backwards-compatible impact if we wanted this in 1.1.x.

  4. Mike Bayer repo owner

    Enforce boolean result type for all eq_, is_, isnot, comparison

    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-Id: Ib810ff686de500d8db26ae35a51005fab29603b6 Fixes: #3873

    → <<cset 433d2ee9f14a>>

  5. Log in to comment