summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-08-15 17:11:14 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2018-08-15 17:15:24 -0400
commit462ccd9ff18d2e428b20ec3f596391a275472140 (patch)
tree45367cbdecf3f3d7928d43dcf556a9703bc55a4a /lib/sqlalchemy/sql
parentdfa47b454a1d873b5746263f638d757c70edd3e1 (diff)
downloadsqlalchemy-462ccd9ff18d2e428b20ec3f596391a275472140.tar.gz
Add concept of "implicit boolean", treat as native
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
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/compiler.py6
-rw-r--r--lib/sqlalchemy/sql/elements.py21
-rw-r--r--lib/sqlalchemy/sql/operators.py6
-rw-r--r--lib/sqlalchemy/sql/selectable.py1
4 files changed, 32 insertions, 2 deletions
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 5fbc7d87f..e45db428a 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -1019,13 +1019,15 @@ class SQLCompiler(Compiled):
"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)
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 2e8c39f3b..2a6fa323c 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -641,6 +641,8 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
"""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):
@@ -1229,6 +1231,7 @@ class TextClause(Executable, ClauseElement):
_execution_options = \
Executable._execution_options.union(
{'autocommit': PARSE_AUTOCOMMIT})
+ _is_implicitly_boolean = False
@property
def _select_iterable(self):
@@ -1813,6 +1816,7 @@ class ClauseList(ClauseElement):
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)
@@ -1915,6 +1919,7 @@ class BooleanClauseList(ClauseList, ColumnElement):
self.operator = operator
self.group_contents = True
self.type = type_api.BOOLEANTYPE
+ self._is_implicitly_boolean = True
return self
@classmethod
@@ -2923,6 +2928,7 @@ class AsBoolean(UnaryExpression):
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
@@ -2950,6 +2956,12 @@ class BinaryExpression(ColumnElement):
__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
@@ -2962,6 +2974,7 @@ class BinaryExpression(ColumnElement):
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 = {}
@@ -3066,6 +3079,10 @@ class Grouping(ColumnElement):
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
@@ -3551,6 +3568,10 @@ class Label(ColumnElement):
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
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index fd65979c5..a9f4e3e3e 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -1315,6 +1315,12 @@ def is_natural_self_precedent(op):
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,
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 04f6c086d..5b665829b 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -3576,6 +3576,7 @@ class Select(HasPrefixes, HasSuffixes, GenerativeSelect):
class ScalarSelect(Generative, Grouping):
_from_objects = []
_is_from_container = True
+ _is_implicitly_boolean = False
def __init__(self, element):
self.element = element