]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
have token load flatten aliases unconditionally
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 20 Jul 2023 17:06:22 +0000 (13:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 25 Jul 2023 14:54:16 +0000 (10:54 -0400)
Fixed issue where chaining :func:`_orm.load_only` or other wildcard use of
:func:`_orm.defer` from another eager loader using a :func:`_orm.aliased`
against a joined inheritance subclass would fail to take effect for columns
local to the superclass.

Fixes: #10125
Change-Id: I15e4b8be5a6d2ba49d2354fcd1daa099d8c3498f

doc/build/changelog/unreleased_20/10125.rst [new file with mode: 0644]
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/path_registry.py
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_deferred.py

diff --git a/doc/build/changelog/unreleased_20/10125.rst b/doc/build/changelog/unreleased_20/10125.rst
new file mode 100644 (file)
index 0000000..95336f3
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 10125
+
+    Fixed issue where chaining :func:`_orm.load_only` or other wildcard use of
+    :func:`_orm.defer` from another eager loader using a :func:`_orm.aliased`
+    against a joined inheritance subclass would fail to take effect for columns
+    local to the superclass.
+
index aca6fcc088c32e6ee340395784afe9e57dcf56e6..e7a0d3ebffdcc92a514da76076013288f5f8eb53 100644 (file)
@@ -745,7 +745,6 @@ def _setup_entity_query(
     for value in poly_properties:
         if only_load_props and value.key not in only_load_props:
             continue
-
         value.setup(
             compile_state,
             query_entity,
index 2269fb26b51727e782dc279ff60b1b5f2c0e4920..2cd8a1412c4d2e1b91b913970088b8e839b0e702 100644 (file)
@@ -449,6 +449,7 @@ class TokenRegistry(PathRegistry):
     is_token = True
 
     def generate_for_superclasses(self) -> Iterator[PathRegistry]:
+        # NOTE: this method is no longer used.  consider removal
         parent = self.parent
         if is_root(parent):
             yield self
@@ -474,6 +475,35 @@ class TokenRegistry(PathRegistry):
         else:
             yield self
 
+    def _generate_natural_for_superclasses(
+        self,
+    ) -> Iterator[_PathRepresentation]:
+        parent = self.parent
+        if is_root(parent):
+            yield self.natural_path
+            return
+
+        if TYPE_CHECKING:
+            assert isinstance(parent, AbstractEntityRegistry)
+        for mp_ent in parent.mapper.iterate_to_root():
+            yield TokenRegistry(parent.parent[mp_ent], self.token).natural_path
+        if (
+            parent.is_aliased_class
+            and cast(
+                "AliasedInsp[Any]",
+                parent.entity,
+            )._is_with_polymorphic
+        ):
+            yield self.natural_path
+            for ent in cast(
+                "AliasedInsp[Any]", parent.entity
+            )._with_polymorphic_entities:
+                yield (
+                    TokenRegistry(parent.parent[ent], self.token).natural_path
+                )
+        else:
+            yield self.natural_path
+
     def _getitem(self, entity: Any) -> Any:
         try:
             return self.path[entity]
@@ -589,7 +619,7 @@ class PropRegistry(PathRegistry):
 
         self._wildcard_path_loader_key = (
             "loader",
-            parent.path + self.prop._wildcard_token,  # type: ignore
+            parent.natural_path + self.prop._wildcard_token,  # type: ignore
         )
         self._default_path_loader_key = self.prop._default_path_loader_key
         self._loader_key = ("loader", self.natural_path)
index d93ec0a273151c98d921a9f42146f2a6adf43ce0..d59fbb7693f534f1c688dcb9657926b084c4e517 100644 (file)
@@ -2129,10 +2129,12 @@ class _TokenStrategyLoad(_LoadElement):
         # is set.
 
         return [
-            ("loader", _path.natural_path)
-            for _path in cast(
-                TokenRegistry, effective_path
-            ).generate_for_superclasses()
+            ("loader", natural_path)
+            for natural_path in (
+                cast(
+                    TokenRegistry, effective_path
+                )._generate_natural_for_superclasses()
+            )
         ]
 
 
index fa044d033c99edd60293181e35d4a041ef43d830..66e3104a95dbb64f7a55c18c6eaf55095243a9cc 100644 (file)
@@ -1735,6 +1735,69 @@ class InheritanceTest(_Polymorphic):
             "ORDER BY managers.person_id",
         )
 
+    @testing.variation("load", ["contains_eager", "joinedload"])
+    def test_issue_10125(self, load):
+        s = fixture_session()
+
+        employee_alias = aliased(Manager, flat=True)
+        company_alias = aliased(Company)
+
+        if load.contains_eager:
+            q = (
+                s.query(company_alias)
+                .outerjoin(
+                    employee_alias,
+                    company_alias.employees.of_type(employee_alias),
+                )
+                .options(
+                    contains_eager(
+                        company_alias.employees.of_type(employee_alias)
+                    ).load_only(
+                        employee_alias.person_id,
+                    )
+                )
+            )
+        elif load.joinedload:
+            q = s.query(company_alias).options(
+                joinedload(
+                    company_alias.employees.of_type(employee_alias)
+                ).load_only(
+                    employee_alias.person_id,
+                )
+            )
+        else:
+            load.fail()
+
+        if load.contains_eager:
+            self.assert_compile(
+                q,
+                "SELECT people_1.person_id AS people_1_person_id, "
+                "people_1.type AS people_1_type, "
+                "managers_1.person_id AS managers_1_person_id, "
+                "companies_1.company_id AS companies_1_company_id, "
+                "companies_1.name AS companies_1_name "
+                "FROM companies AS companies_1 LEFT OUTER JOIN "
+                "(people AS people_1 JOIN managers AS managers_1 "
+                "ON people_1.person_id = managers_1.person_id) "
+                "ON companies_1.company_id = people_1.company_id",
+            )
+        elif load.joinedload:
+            self.assert_compile(
+                q,
+                "SELECT companies_1.company_id AS companies_1_company_id, "
+                "companies_1.name AS companies_1_name, "
+                "people_1.person_id AS people_1_person_id, "
+                "people_1.type AS people_1_type, "
+                "managers_1.person_id AS managers_1_person_id "
+                "FROM companies AS companies_1 LEFT OUTER JOIN "
+                "(people AS people_1 JOIN managers AS managers_1 "
+                "ON people_1.person_id = managers_1.person_id) "
+                "ON companies_1.company_id = people_1.company_id "
+                "ORDER BY people_1.person_id",
+            )
+        else:
+            load.fail()
+
     def test_load_only_subclass_bound(self):
         s = fixture_session()
         q = (