diff options
Diffstat (limited to 'lib/sqlalchemy/sql/expression.py')
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 154 |
1 files changed, 111 insertions, 43 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 46a08379b..37c0ac65c 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1468,6 +1468,10 @@ def _cloned_intersection(a, b): return set(elem for elem in a if all_overlap.intersection(elem._cloned_set)) +def _cloned_difference(a, b): + all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b)) + return set(elem for elem in a + if not all_overlap.intersection(elem._cloned_set)) def _from_objects(*elements): return itertools.chain(*[element._from_objects for element in elements]) @@ -5262,7 +5266,7 @@ class Select(HasPrefixes, SelectBase): _distinct = False _from_cloned = None _correlate = () - _correlate_except = () + _correlate_except = None _memoized_property = SelectBase._memoized_property def __init__(self, @@ -5357,7 +5361,8 @@ class Select(HasPrefixes, SelectBase): return froms - def _get_display_froms(self, existing_froms=None, asfrom=False): + def _get_display_froms(self, explicit_correlate_froms=None, + implicit_correlate_froms=None): """Return the full list of 'from' clauses to be displayed. Takes into account a set of existing froms which may be @@ -5368,7 +5373,9 @@ class Select(HasPrefixes, SelectBase): """ froms = self._froms - toremove = set(itertools.chain(*[f._hide_froms for f in froms])) + toremove = set(itertools.chain(*[ + _expand_cloned(f._hide_froms) + for f in froms])) if toremove: # if we're maintaining clones of froms, # add the copies out to the toremove list. only include @@ -5383,36 +5390,42 @@ class Select(HasPrefixes, SelectBase): # using a list to maintain ordering froms = [f for f in froms if f not in toremove] - if not asfrom: - if self._correlate: + if self._correlate: + to_correlate = self._correlate + if to_correlate: froms = [ f for f in froms if f not in _cloned_intersection( - _cloned_intersection(froms, existing_froms or ()), - self._correlate - ) - ] - if self._correlate_except: - froms = [ - f for f in froms if f in - _cloned_intersection( - froms, - self._correlate_except + _cloned_intersection(froms, explicit_correlate_froms or ()), + to_correlate ) ] - if self._auto_correlate and existing_froms and len(froms) > 1: - froms = [ - f for f in froms if f not in - _cloned_intersection(froms, existing_froms) - ] + if self._correlate_except is not None: + + froms = [ + f for f in froms if f not in + _cloned_difference( + _cloned_intersection(froms, explicit_correlate_froms or ()), + self._correlate_except + ) + ] - if not len(froms): - raise exc.InvalidRequestError("Select statement '%s" - "' returned no FROM clauses due to " - "auto-correlation; specify " - "correlate(<tables>) to control " - "correlation manually." % self) + if self._auto_correlate and \ + implicit_correlate_froms and \ + len(froms) > 1: + + froms = [ + f for f in froms if f not in + _cloned_intersection(froms, implicit_correlate_froms) + ] + + if not len(froms): + raise exc.InvalidRequestError("Select statement '%s" + "' returned no FROM clauses due to " + "auto-correlation; specify " + "correlate(<tables>) to control " + "correlation manually." % self) return froms @@ -5757,19 +5770,52 @@ class Select(HasPrefixes, SelectBase): @_generative def correlate(self, *fromclauses): - """return a new select() construct which will correlate the given FROM - clauses to that of an enclosing select(), if a match is found. - - By "match", the given fromclause must be present in this select's - list of FROM objects and also present in an enclosing select's list of - FROM objects. - - Calling this method turns off the select's default behavior of - "auto-correlation". Normally, select() auto-correlates all of its FROM - clauses to those of an embedded select when compiled. - - If the fromclause is None, correlation is disabled for the returned - select(). + """return a new :class:`.Select` which will correlate the given FROM + clauses to that of an enclosing :class:`.Select`. + + Calling this method turns off the :class:`.Select` object's + default behavior of "auto-correlation". Normally, FROM elements + which appear in a :class:`.Select` that encloses this one via + its :term:`WHERE clause`, ORDER BY, HAVING or + :term:`columns clause` will be omitted from this :class:`.Select` + object's :term:`FROM clause`. + Setting an explicit correlation collection using the + :meth:`.Select.correlate` method provides a fixed list of FROM objects + that can potentially take place in this process. + + When :meth:`.Select.correlate` is used to apply specific FROM clauses + for correlation, the FROM elements become candidates for + correlation regardless of how deeply nested this :class:`.Select` + object is, relative to an enclosing :class:`.Select` which refers to + the same FROM object. This is in contrast to the behavior of + "auto-correlation" which only correlates to an immediate enclosing + :class:`.Select`. Multi-level correlation ensures that the link + between enclosed and enclosing :class:`.Select` is always via + at least one WHERE/ORDER BY/HAVING/columns clause in order for + correlation to take place. + + If ``None`` is passed, the :class:`.Select` object will correlate + none of its FROM entries, and all will render unconditionally + in the local FROM clause. + + :param \*fromclauses: a list of one or more :class:`.FromClause` + constructs, or other compatible constructs (i.e. ORM-mapped + classes) to become part of the correlate collection. + + .. versionchanged:: 0.8.0 ORM-mapped classes are accepted by + :meth:`.Select.correlate`. + + .. versionchanged:: 0.8.0 The :meth:`.Select.correlate` method no + longer unconditionally removes entries from the FROM clause; instead, + the candidate FROM entries must also be matched by a FROM entry + located in an enclosing :class:`.Select`, which ultimately encloses + this one as present in the WHERE clause, ORDER BY clause, HAVING + clause, or columns clause of an enclosing :meth:`.Select`. + + .. versionchanged:: 0.8.2 explicit correlation takes place + via any level of nesting of :class:`.Select` objects; in previous + 0.8 versions, correlation would only occur relative to the immediate + enclosing :class:`.Select` construct. .. seealso:: @@ -5787,9 +5833,30 @@ class Select(HasPrefixes, SelectBase): @_generative def correlate_except(self, *fromclauses): - """"Return a new select() construct which will auto-correlate - on FROM clauses of enclosing selectables, except for those FROM - clauses specified here. + """return a new :class:`.Select` which will omit the given FROM + clauses from the auto-correlation process. + + Calling :meth:`.Select.correlate_except` turns off the + :class:`.Select` object's default behavior of + "auto-correlation" for the given FROM elements. An element + specified here will unconditionally appear in the FROM list, while + all other FROM elements remain subject to normal auto-correlation + behaviors. + + .. versionchanged:: 0.8.2 The :meth:`.Select.correlate_except` + method was improved to fully prevent FROM clauses specified here + from being omitted from the immediate FROM clause of this + :class:`.Select`. + + If ``None`` is passed, the :class:`.Select` object will correlate + all of its FROM entries. + + .. versionchanged:: 0.8.2 calling ``correlate_except(None)`` will + correctly auto-correlate all FROM clauses. + + :param \*fromclauses: a list of one or more :class:`.FromClause` + constructs, or other compatible constructs (i.e. ORM-mapped + classes) to become part of the correlate-exception collection. .. seealso:: @@ -5798,11 +5865,12 @@ class Select(HasPrefixes, SelectBase): :ref:`correlated_subqueries` """ + self._auto_correlate = False if fromclauses and fromclauses[0] is None: self._correlate_except = () else: - self._correlate_except = set(self._correlate_except).union( + self._correlate_except = set(self._correlate_except or ()).union( _interpret_as_from(f) for f in fromclauses) def append_correlation(self, fromclause): |