diff options
author | Federico Caselli <cfederico87@gmail.com> | 2020-01-23 17:51:38 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-01-25 18:03:48 -0500 |
commit | 1de64504d8e68e2c0d14669c7638cf6f6d74973f (patch) | |
tree | bcb2f19fe89efc629408c4576c355f3fe998578b /lib/sqlalchemy/sql/elements.py | |
parent | 411637fbcf679f36448f1b094afef375158df15e (diff) | |
download | sqlalchemy-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.py | 106 |
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_` |