summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/elements.py
diff options
context:
space:
mode:
authorFederico Caselli <cfederico87@gmail.com>2020-01-23 17:51:38 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-01-25 18:03:48 -0500
commit1de64504d8e68e2c0d14669c7638cf6f6d74973f (patch)
treebcb2f19fe89efc629408c4576c355f3fe998578b /lib/sqlalchemy/sql/elements.py
parent411637fbcf679f36448f1b094afef375158df15e (diff)
downloadsqlalchemy-1de64504d8e68e2c0d14669c7638cf6f6d74973f.tar.gz
Deprecate empty or_() and and_()
Creating an :func:`.and_` or :func:`.or_` construct with no arguments or empty ``*args`` will now emit a deprecation warning, as the SQL produced is a no-op (i.e. it renders as a blank string). This behavior is considered to be non-intuitive, so for empty or possibly empty :func:`.and_` or :func:`.or_` constructs, an appropriate default boolean should be included, such as ``and_(True, *args)`` or ``or_(False, *args)``. As has been the case for many major versions of SQLAlchemy, these particular boolean values will not render if the ``*args`` portion is non-empty. As there are some internal cases where an empty and_() construct is used in order to build an optional WHERE expression, a private utility function is added to suit this use case. Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com> Fixes: #5054 Closes: #5062 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5062 Pull-request-sha: 5ca2f27281977d74e390148c0fb8deaa0e0e4ad9 Change-Id: I599b9c8befa64d9a59a35ad7dd84ff400e3aa647
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r--lib/sqlalchemy/sql/elements.py106
1 files changed, 88 insertions, 18 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 648394ed2..a99c6ca35 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -2131,32 +2131,64 @@ class BooleanClauseList(ClauseList, ColumnElement):
@classmethod
def _construct(cls, operator, continue_on, skip_on, *clauses, **kw):
+
+ has_continue_on = None
+ special_elements = (continue_on, skip_on)
convert_clauses = []
- clauses = [
- coercions.expect(roles.WhereHavingRole, clause)
- for clause in util.coerce_generator_arg(clauses)
- ]
- for clause in clauses:
+ for clause in util.coerce_generator_arg(clauses):
+ clause = coercions.expect(roles.WhereHavingRole, clause)
- if isinstance(clause, continue_on):
- continue
+ # elements that are not the continue/skip are the most
+ # common, try to have only one isinstance() call for that case.
+ if not isinstance(clause, special_elements):
+ convert_clauses.append(clause)
elif isinstance(clause, skip_on):
+ # instance of skip_on, e.g. and_(x, y, False, z), cancels
+ # the rest out
return clause.self_group(against=operators._asbool)
-
- convert_clauses.append(clause)
-
- if len(convert_clauses) == 1:
+ elif has_continue_on is None:
+ # instance of continue_on, like and_(x, y, True, z), store it
+ # if we didn't find one already, we will use it if there
+ # are no other expressions here.
+ has_continue_on = clause
+
+ lcc = len(convert_clauses)
+
+ if lcc > 1:
+ # multiple elements. Return regular BooleanClauseList
+ # which will link elements against the operator.
+ return cls._construct_raw(
+ operator,
+ [c.self_group(against=operator) for c in convert_clauses],
+ )
+ elif lcc == 1:
+ # just one element. return it as a single boolean element,
+ # not a list and discard the operator.
return convert_clauses[0].self_group(against=operators._asbool)
- elif not convert_clauses and clauses:
- return clauses[0].self_group(against=operators._asbool)
-
- convert_clauses = [
- c.self_group(against=operator) for c in convert_clauses
- ]
+ elif not lcc and has_continue_on is not None:
+ # no elements but we had a "continue", just return the continue
+ # as a boolean element, discard the operator.
+ return has_continue_on.self_group(against=operators._asbool)
+ else:
+ # no elements period. deprecated use case. return an empty
+ # ClauseList construct that generates nothing unless it has
+ # elements added to it.
+ util.warn_deprecated(
+ "Invoking %(name)s() without arguments is deprecated, and "
+ "will be disallowed in a future release. For an empty "
+ "%(name)s() construct, use %(name)s(%(continue_on)s, *args)."
+ % {
+ "name": operator.__name__,
+ "continue_on": "True" if continue_on is True_ else "False",
+ }
+ )
+ return cls._construct_raw(operator)
+ @classmethod
+ def _construct_raw(cls, operator, clauses=None):
self = cls.__new__(cls)
- self.clauses = convert_clauses
+ self.clauses = clauses if clauses else []
self.group = True
self.operator = operator
self.group_contents = True
@@ -2198,6 +2230,25 @@ class BooleanClauseList(ClauseList, ColumnElement):
where(users_table.c.name == 'wendy').\
where(users_table.c.enrolled == True)
+ The :func:`.and_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.and_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.and_` expression, from a given list of expressions,
+ a "default" element of ``True`` should be specified::
+
+ criteria = and_(True, *expressions)
+
+ The above expression will compile to SQL as the expression ``true``
+ or ``1 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the ``True`` value is
+ ignored as it does not affect the outcome of an AND expression that
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.and_` element now requires that at
+ least one argument is passed; creating the :func:`.and_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
.. seealso::
:func:`.or_`
@@ -2230,6 +2281,25 @@ class BooleanClauseList(ClauseList, ColumnElement):
(users_table.c.name == 'jack')
)
+ The :func:`.or_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.or_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.or_` expression, from a given list of expressions,
+ a "default" element of ``False`` should be specified::
+
+ or_criteria = or_(False, *expressions)
+
+ The above expression will compile to SQL as the expression ``false``
+ or ``0 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the ``False`` value is
+ ignored as it does not affect the outcome of an OR expression which
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.or_` element now requires that at
+ least one argument is passed; creating the :func:`.or_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
.. seealso::
:func:`.and_`