summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-05-17 11:20:10 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-05-17 12:26:48 -0400
commita49b2c3dbb9bff1d004eb2c53a752999e27ff769 (patch)
tree3190734880c30a52c3fbc7ec92ab4dbd164fe17b /lib/sqlalchemy/sql
parentc379f80b4e6fb1b65983958116bac202c91a210a (diff)
downloadsqlalchemy-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.py61
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