]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Apply path generation for superclasses to Load._set_path_strategy()
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 30 Nov 2018 19:36:20 +0000 (14:36 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 30 Nov 2018 19:36:20 +0000 (14:36 -0500)
Extended the fix first made as part of :ticket:`3287`, where a loader option
made against a subclass using a wildcard would extend itself to include
application of the wildcard to attributes on the super classes as well, to a
"bound" loader option as well, e.g. in an expression like
``Load(SomeSubClass).load_only('foo')``.  Columns that are part of the
parent class of ``SomeSubClass`` will also be excluded in the same way as if
the unbound option ``load_only('foo')`` were used.

Fixes: #4373
Change-Id: I2eee0e587c34323a77df077b9cb699da370c403d

doc/build/changelog/unreleased_13/4373.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_deferred.py

diff --git a/doc/build/changelog/unreleased_13/4373.rst b/doc/build/changelog/unreleased_13/4373.rst
new file mode 100644 (file)
index 0000000..91afd24
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+   :tags: bug, orm
+   :tickets: 4373
+
+   Extended the fix first made as part of :ticket:`3287`, where a loader option
+   made against a subclass using a wildcard would extend itself to include
+   application of the wildcard to attributes on the super classes as well, to a
+   "bound" loader option as well, e.g. in an expression like
+   ``Load(SomeSubClass).load_only('foo')``.  Columns that are part of the
+   parent class of ``SomeSubClass`` will also be excluded in the same way as if
+   the unbound option ``load_only('foo')`` were used.
index af9bd71b6aa08e9a7739adc3f86e6c430082c797..0ef34b0b9bc0fdda2ffa99e61fba30aa1af0388f 100644 (file)
@@ -357,9 +357,15 @@ class Load(Generative, MapperOption):
         else:
             effective_path = self.path
 
-        self._set_for_path(
-            self.context, effective_path, replace=True,
-            merge_opts=self.is_opts_only)
+        if effective_path.is_token:
+            for path in effective_path.generate_for_superclasses():
+                self._set_for_path(
+                    self.context, path, replace=True,
+                    merge_opts=self.is_opts_only)
+        else:
+            self._set_for_path(
+                self.context, effective_path, replace=True,
+                merge_opts=self.is_opts_only)
 
     def __getstate__(self):
         d = self.__dict__.copy()
index 97782bffdcaf5d60a4a2d0a3a5267acb7662b5da..3f2a8a06b449e7937d5ad02069da131c40cf0e33 100644 (file)
@@ -913,6 +913,12 @@ class SelfReferentialMultiPathTest(testing.fixtures.DeclarativeMappedTest):
 class InheritanceTest(_Polymorphic):
     __dialect__ = 'default'
 
+    @classmethod
+    def setup_mappers(cls):
+        super(InheritanceTest, cls).setup_mappers()
+        from sqlalchemy import inspect
+        inspect(Company).add_property("managers", relationship(Manager))
+
     def test_load_only_subclass(self):
         s = Session()
         q = s.query(Manager).order_by(Manager.person_id).\
@@ -929,6 +935,22 @@ class InheritanceTest(_Polymorphic):
             "ORDER BY managers.person_id"
         )
 
+    def test_load_only_subclass_bound(self):
+        s = Session()
+        q = s.query(Manager).order_by(Manager.person_id).\
+            options(Load(Manager).load_only("status", "manager_name"))
+        self.assert_compile(
+            q,
+            "SELECT managers.person_id AS managers_person_id, "
+            "people.person_id AS people_person_id, "
+            "people.type AS people_type, "
+            "managers.status AS managers_status, "
+            "managers.manager_name AS managers_manager_name "
+            "FROM people JOIN managers "
+            "ON people.person_id = managers.person_id "
+            "ORDER BY managers.person_id"
+        )
+
     def test_load_only_subclass_and_superclass(self):
         s = Session()
         q = s.query(Boss).order_by(Person.person_id).\
@@ -945,6 +967,22 @@ class InheritanceTest(_Polymorphic):
             "ON managers.person_id = boss.boss_id ORDER BY people.person_id"
         )
 
+    def test_load_only_subclass_and_superclass_bound(self):
+        s = Session()
+        q = s.query(Boss).order_by(Person.person_id).\
+            options(Load(Boss).load_only("status", "manager_name"))
+        self.assert_compile(
+            q,
+            "SELECT managers.person_id AS managers_person_id, "
+            "people.person_id AS people_person_id, "
+            "people.type AS people_type, "
+            "managers.status AS managers_status, "
+            "managers.manager_name AS managers_manager_name "
+            "FROM people JOIN managers "
+            "ON people.person_id = managers.person_id JOIN boss "
+            "ON managers.person_id = boss.boss_id ORDER BY people.person_id"
+        )
+
     def test_load_only_alias_subclass(self):
         s = Session()
         m1 = aliased(Manager, flat=True)
@@ -962,6 +1000,23 @@ class InheritanceTest(_Polymorphic):
             "ORDER BY managers_1.person_id"
         )
 
+    def test_load_only_alias_subclass_bound(self):
+        s = Session()
+        m1 = aliased(Manager, flat=True)
+        q = s.query(m1).order_by(m1.person_id).\
+            options(Load(m1).load_only("status", "manager_name"))
+        self.assert_compile(
+            q,
+            "SELECT managers_1.person_id AS managers_1_person_id, "
+            "people_1.person_id AS people_1_person_id, "
+            "people_1.type AS people_1_type, "
+            "managers_1.status AS managers_1_status, "
+            "managers_1.manager_name AS managers_1_manager_name "
+            "FROM people AS people_1 JOIN managers AS "
+            "managers_1 ON people_1.person_id = managers_1.person_id "
+            "ORDER BY managers_1.person_id"
+        )
+
     def test_load_only_subclass_from_relationship_polymorphic(self):
         s = Session()
         wp = with_polymorphic(Person, [Manager], flat=True)
@@ -984,10 +1039,30 @@ class InheritanceTest(_Polymorphic):
             "people_1.company_id"
         )
 
+    def test_load_only_subclass_from_relationship_polymorphic_bound(self):
+        s = Session()
+        wp = with_polymorphic(Person, [Manager], flat=True)
+        q = s.query(Company).join(Company.employees.of_type(wp)).options(
+            Load(Company).contains_eager(Company.employees.of_type(wp)).
+            load_only(wp.Manager.status, wp.Manager.manager_name)
+        )
+        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, "
+            "managers_1.status AS managers_1_status, "
+            "managers_1.manager_name AS managers_1_manager_name, "
+            "companies.company_id AS companies_company_id, "
+            "companies.name AS companies_name "
+            "FROM companies JOIN (people AS people_1 LEFT OUTER JOIN "
+            "managers AS managers_1 ON people_1.person_id = "
+            "managers_1.person_id) ON companies.company_id = "
+            "people_1.company_id"
+        )
+
     def test_load_only_subclass_from_relationship(self):
         s = Session()
-        from sqlalchemy import inspect
-        inspect(Company).add_property("managers", relationship(Manager))
         q = s.query(Company).join(Company.managers).options(
             contains_eager(Company.managers).
             load_only("status", "manager_name")
@@ -1005,10 +1080,31 @@ class InheritanceTest(_Polymorphic):
             "managers.person_id) ON companies.company_id = people.company_id"
         )
 
+    def test_load_only_subclass_from_relationship_bound(self):
+        s = Session()
+        q = s.query(Company).join(Company.managers).options(
+            Load(Company).contains_eager(Company.managers).
+            load_only("status", "manager_name")
+        )
+        self.assert_compile(
+            q,
+            "SELECT companies.company_id AS companies_company_id, "
+            "companies.name AS companies_name, "
+            "managers.person_id AS managers_person_id, "
+            "people.person_id AS people_person_id, "
+            "people.type AS people_type, "
+            "managers.status AS managers_status, "
+            "managers.manager_name AS managers_manager_name "
+            "FROM companies JOIN (people JOIN managers ON people.person_id = "
+            "managers.person_id) ON companies.company_id = people.company_id"
+        )
+
     def test_defer_on_wildcard_subclass(self):
         # pretty much the same as load_only except doesn't
         # exclude the primary key
 
+        # TODO: what is ".*"?  this is not documented anywhere, how did this
+        # get implemented without docs ?  see #4390
         s = Session()
         q = s.query(Manager).order_by(Person.person_id).options(
             defer(".*"), undefer("status"))
@@ -1019,6 +1115,9 @@ class InheritanceTest(_Polymorphic):
             "people.person_id = managers.person_id ORDER BY people.person_id"
         )
 
+        # note this doesn't apply to "bound" loaders since they don't seem
+        # to have this ".*" featue.
+
     def test_defer_super_name_on_subclass(self):
         s = Session()
         q = s.query(Manager).order_by(Person.person_id).options(defer("name"))
@@ -1034,6 +1133,22 @@ class InheritanceTest(_Polymorphic):
             "ORDER BY people.person_id"
         )
 
+    def test_defer_super_name_on_subclass_bound(self):
+        s = Session()
+        q = s.query(Manager).order_by(Person.person_id).options(
+            Load(Manager).defer("name"))
+        self.assert_compile(
+            q,
+            "SELECT managers.person_id AS managers_person_id, "
+            "people.person_id AS people_person_id, "
+            "people.company_id AS people_company_id, "
+            "people.type AS people_type, managers.status AS managers_status, "
+            "managers.manager_name AS managers_manager_name "
+            "FROM people JOIN managers "
+            "ON people.person_id = managers.person_id "
+            "ORDER BY people.person_id"
+        )
+
 
 
 class WithExpressionTest(fixtures.DeclarativeMappedTest):