--- /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'