From: Mike Bayer Date: Fri, 30 Nov 2018 19:36:20 +0000 (-0500) Subject: Apply path generation for superclasses to Load._set_path_strategy() X-Git-Tag: rel_1_3_0b2~78^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fb05e085fe48a1c48fcb4bcf89ba58c0d7584b8c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Apply path generation for superclasses to Load._set_path_strategy() 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 --- diff --git a/doc/build/changelog/unreleased_13/4373.rst b/doc/build/changelog/unreleased_13/4373.rst new file mode 100644 index 0000000000..91afd247bc --- /dev/null +++ b/doc/build/changelog/unreleased_13/4373.rst @@ -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. diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index af9bd71b6a..0ef34b0b9b 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -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() diff --git a/test/orm/test_deferred.py b/test/orm/test_deferred.py index 97782bffdc..3f2a8a06b4 100644 --- a/test/orm/test_deferred.py +++ b/test/orm/test_deferred.py @@ -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):