Fixed issue that is closely related to :ticket:`3639` where an expression
rendered in a boolean context on a non-native boolean backend would
be compared to 1/0 even though it is already an implcitly boolean
expression, when :meth:`.ColumnElement.self_group` were used. While this
does not affect the user-friendly backends (MySQL, SQLite) it was not
handled by Oracle (and possibly SQL Server). Whether or not the
expression is implicitly boolean on any database is now determined
up front as an additional check to not generate the integer comparison
within the compliation of the statement.
Fixes: #4320
Change-Id: Iae0a65e5c01bd576e64733c3651e1e1a1a1b240c
(cherry picked from commit
462ccd9ff18d2e428b20ec3f596391a275472140)
--- /dev/null
+.. change::
+ :tags: bug, sql
+ :tickets: 4320
+
+ Fixed issue that is closely related to :ticket:`3639` where an expression
+ rendered in a boolean context on a non-native boolean backend would
+ be compared to 1/0 even though it is already an implcitly boolean
+ expression, when :meth:`.ColumnElement.self_group` were used. While this
+ does not affect the user-friendly backends (MySQL, SQLite) it was not
+ handled by Oracle (and possibly SQL Server). Whether or not the
+ expression is implicitly boolean on any database is now determined
+ up front as an additional check to not generate the integer comparison
+ within the compliation of the statement.
"Unary expression has no operator or modifier")
def visit_istrue_unary_operator(self, element, operator, **kw):
- if self.dialect.supports_native_boolean:
+ if element._is_implicitly_boolean or \
+ self.dialect.supports_native_boolean:
return self.process(element.element, **kw)
else:
return "%s = 1" % self.process(element.element, **kw)
def visit_isfalse_unary_operator(self, element, operator, **kw):
- if self.dialect.supports_native_boolean:
+ if element._is_implicitly_boolean or \
+ self.dialect.supports_native_boolean:
return "NOT %s" % self.process(element.element, **kw)
else:
return "%s = 0" % self.process(element.element, **kw)
"""A flag that can be flipped to prevent a column from being resolvable
by string label name."""
+ _is_implicitly_boolean = False
+
_alt_names = ()
def self_group(self, against=None):
_execution_options = \
Executable._execution_options.union(
{'autocommit': PARSE_AUTOCOMMIT})
+ _is_implicitly_boolean = False
@property
def _select_iterable(self):
self.clauses = [
text_converter(clause)
for clause in clauses]
+ self._is_implicitly_boolean = operators.is_boolean(self.operator)
def __iter__(self):
return iter(self.clauses)
self.operator = operator
self.group_contents = True
self.type = type_api.BOOLEANTYPE
+ self._is_implicitly_boolean = True
return self
@classmethod
self.negate = negate
self.modifier = None
self.wraps_column_expression = True
+ self._is_implicitly_boolean = element._is_implicitly_boolean
def self_group(self, against=None):
return self
__visit_name__ = 'binary'
+ _is_implicitly_boolean = True
+ """Indicates that any database will know this is a boolean expression
+ even if the database does not have an explicit boolean datatype.
+
+ """
+
def __init__(self, left, right, operator, type_=None,
negate=None, modifiers=None):
# allow compatibility with libraries that
self.operator = operator
self.type = type_api.to_instance(type_)
self.negate = negate
+ self._is_implicitly_boolean = operators.is_boolean(operator)
if modifiers is None:
self.modifiers = {}
def self_group(self, against=None):
return self
+ @util.memoized_property
+ def _is_implicitly_boolean(self):
+ return self.element._is_implicitly_boolean
+
@property
def _key_label(self):
return self._label
def __reduce__(self):
return self.__class__, (self.name, self._element, self._type)
+ @util.memoized_property
+ def _is_implicitly_boolean(self):
+ return self.element._is_implicitly_boolean
+
@util.memoized_property
def _allow_label_resolve(self):
return self.element._allow_label_resolve
return op in _natural_self_precedent or \
isinstance(op, custom_op) and op.natural_self_precedent
+_booleans = (inv, istrue, isfalse, and_, or_)
+
+
+def is_boolean(op):
+ return is_comparison(op) or op in _booleans
+
_mirror = {
gt: lt,
ge: le,
class ScalarSelect(Generative, Grouping):
_from_objects = []
_is_from_container = True
+ _is_implicitly_boolean = False
def __init__(self, element):
self.element = element
self.assert_compile(
expr,
- "NOT (mytable.myid = :myid_1 OR mytable.myid = :myid_2)"
+ "NOT (mytable.myid = :myid_1 OR mytable.myid = :myid_2)",
+ dialect=default.DefaultDialect(supports_native_boolean=False)
)
+ def test_negate_operator_self_group(self):
+ orig_expr = or_(
+ self.table1.c.myid == 1, self.table1.c.myid == 2).self_group()
+ expr = not_(orig_expr)
+ is_not_(expr, orig_expr)
+
+ self.assert_compile(
+ expr,
+ "NOT (mytable.myid = :myid_1 OR mytable.myid = :myid_2)",
+ dialect=default.DefaultDialect(supports_native_boolean=False)
+ )
+
+ def test_implicitly_boolean(self):
+ # test for expressions that the database always considers as boolean
+ # even if there is no boolean datatype.
+ assert not self.table1.c.myid._is_implicitly_boolean
+ assert (self.table1.c.myid == 5)._is_implicitly_boolean
+ assert (self.table1.c.myid == 5).self_group()._is_implicitly_boolean
+ assert (self.table1.c.myid == 5).label('x')._is_implicitly_boolean
+ assert not_(self.table1.c.myid == 5)._is_implicitly_boolean
+ assert or_(
+ self.table1.c.myid == 5, self.table1.c.myid == 7
+ )._is_implicitly_boolean
+ assert not column('x', Boolean)._is_implicitly_boolean
+ assert not (self.table1.c.myid + 5)._is_implicitly_boolean
+ assert not not_(column('x', Boolean))._is_implicitly_boolean
+ assert not select([self.table1.c.myid]).\
+ as_scalar()._is_implicitly_boolean
+ assert not text("x = y")._is_implicitly_boolean
+ assert not literal_column("x = y")._is_implicitly_boolean
+
class LikeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = 'default'