diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-05-17 11:20:10 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-05-17 12:26:48 -0400 |
| commit | a49b2c3dbb9bff1d004eb2c53a752999e27ff769 (patch) | |
| tree | 3190734880c30a52c3fbc7ec92ab4dbd164fe17b /lib/sqlalchemy/sql | |
| parent | c379f80b4e6fb1b65983958116bac202c91a210a (diff) | |
| download | sqlalchemy-a49b2c3dbb9bff1d004eb2c53a752999e27ff769.tar.gz | |
Run SelectState from obj normalize ahead of calcing ORM joins
Fixed regression where the full combination of joined inheritance, global
with_polymorphic, self-referential relationship and joined loading would
fail to be able to produce a query with the scope of lazy loads and object
refresh operations that also attempted to render the joined loader.
Fixes: #6495
Change-Id: If74a744c237069e3a89617498096c18b9b6e5dde
Diffstat (limited to 'lib/sqlalchemy/sql')
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 61 |
1 files changed, 40 insertions, 21 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 7007bb430..997c3588e 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -4209,38 +4209,57 @@ class SelectState(util.MemoizedSlots, CompileState): return go def _get_froms(self, statement): + return self._normalize_froms( + itertools.chain( + itertools.chain.from_iterable( + [ + element._from_objects + for element in statement._raw_columns + ] + ), + itertools.chain.from_iterable( + [ + element._from_objects + for element in statement._where_criteria + ] + ), + self.from_clauses, + ), + check_statement=statement, + ) + + def _normalize_froms(self, iterable_of_froms, check_statement=None): + """given an iterable of things to select FROM, reduce them to what + would actually render in the FROM clause of a SELECT. + + This does the job of checking for JOINs, tables, etc. that are in fact + overlapping due to cloning, adaption, present in overlapping joins, + etc. + + """ seen = set() froms = [] - for item in itertools.chain( - itertools.chain.from_iterable( - [element._from_objects for element in statement._raw_columns] - ), - itertools.chain.from_iterable( - [ - element._from_objects - for element in statement._where_criteria - ] - ), - self.from_clauses, - ): - if item._is_subquery and item.element is statement: + for item in iterable_of_froms: + if item._is_subquery and item.element is check_statement: raise exc.InvalidRequestError( "select() construct refers to itself as a FROM" ) + if not seen.intersection(item._cloned_set): froms.append(item) seen.update(item._cloned_set) - toremove = set( - itertools.chain.from_iterable( - [_expand_cloned(f._hide_froms) for f in froms] + if froms: + toremove = set( + itertools.chain.from_iterable( + [_expand_cloned(f._hide_froms) for f in froms] + ) ) - ) - if toremove: - # filter out to FROM clauses not in the list, - # using a list to maintain ordering - froms = [f for f in froms if f not in toremove] + if toremove: + # filter out to FROM clauses not in the list, + # using a list to maintain ordering + froms = [f for f in froms if f not in toremove] return froms |
