From: Mike Bayer Date: Sat, 26 Jan 2019 19:53:45 +0000 (-0500) Subject: Ensure of_type subclass taken into account with wildcards X-Git-Tag: rel_1_3_0b3~17^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29e033f496c28e4d7b9a5bfc46096711f0aa5571;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Ensure of_type subclass taken into account with wildcards Fixed a regression in 1.2 where a wildcard/load_only loader option would not work correctly against a loader path where of_type() were used to limit to a particular subclass. The fix only works for of_type() of a simple subclass so far, not a with_polymorphic entity which will be addressed in a separate issue; it is unlikely this latter case was working previously. Since we ensure that the entity is broken out into its superclasses when a wilcard is encountered, we can limit the entity path to the specific entity given in this case. Within this issue some additional issues with with_polymorphic() loaders were found which will be addressed in #4469. Fixes: #4468 Change-Id: Ie91ec27b49104e019636f870776e294321586a9e --- diff --git a/doc/build/changelog/unreleased_12/4468.rst b/doc/build/changelog/unreleased_12/4468.rst new file mode 100644 index 0000000000..0106d2f180 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4468.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm + :tickets: 4468 + + Fixed a regression in 1.2 where a wildcard/load_only loader option would + not work correctly against a loader path where of_type() were used to limit + to a particular subclass. The fix only works for of_type() of a simple + subclass so far, not a with_polymorphic entity which will be addressed in a + separate issue; it is unlikely this latter case was working previously. + diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 19c81860e8..ee3b83caa0 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -203,6 +203,13 @@ class Load(Generative, MapperOption): self.propagate_to_loaders = False if wildcard_key: attr = "%s:%s" % (wildcard_key, attr) + + # TODO: AliasedInsp inside the path for of_type is not + # working for a with_polymorphic entity because the + # relationship loaders don't render the with_poly into the + # path. See #4469 which will try to improve this + if existing_of_type and not existing_of_type.is_aliased_class: + path = path.parent[existing_of_type] path = path.token(attr) self.path = path return path @@ -706,7 +713,6 @@ class _UnboundLoad(Load): if not loader.is_class_strategy: for idx, token in enumerate(start_path): - if not loader._generate_path( loader.path, token, diff --git a/test/orm/test_deferred.py b/test/orm/test_deferred.py index 551952cfe1..d9ac6b0ff5 100644 --- a/test/orm/test_deferred.py +++ b/test/orm/test_deferred.py @@ -1472,6 +1472,54 @@ class InheritanceTest(_Polymorphic): # note this doesn't apply to "bound" loaders since they don't seem # to have this ".*" featue. + def test_load_only_subclass_of_type(self): + s = Session() + q = s.query(Company).options( + joinedload(Company.employees.of_type(Manager)).load_only("status") + ) + self.assert_compile( + q, + "SELECT companies.company_id AS companies_company_id, " + "companies.name AS companies_name, " + "anon_1.people_person_id AS anon_1_people_person_id, " + "anon_1.people_type AS anon_1_people_type, " + "anon_1.managers_person_id AS anon_1_managers_person_id, " + "anon_1.managers_status AS anon_1_managers_status " + "FROM companies LEFT OUTER JOIN " + "(SELECT people.person_id AS people_person_id, " + "people.company_id AS people_company_id, " + "people.name AS people_name, people.type AS people_type, " + "managers.person_id AS managers_person_id, " + "managers.status AS managers_status, " + "managers.manager_name AS managers_manager_name " + "FROM people LEFT OUTER JOIN managers " + "ON people.person_id = managers.person_id) AS anon_1 " + "ON companies.company_id = anon_1.people_company_id " + "ORDER BY anon_1.people_person_id", + ) + + def test_wildcard_subclass_of_type(self): + s = Session() + q = s.query(Company).options( + joinedload(Company.employees.of_type(Manager)).defer("*") + ) + self.assert_compile( + q, + "SELECT companies.company_id AS companies_company_id, " + "companies.name AS companies_name " + "FROM companies LEFT OUTER JOIN " + "(SELECT people.person_id AS people_person_id, " + "people.company_id AS people_company_id, " + "people.name AS people_name, people.type AS people_type, " + "managers.person_id AS managers_person_id, " + "managers.status AS managers_status, " + "managers.manager_name AS managers_manager_name " + "FROM people LEFT OUTER JOIN managers " + "ON people.person_id = managers.person_id) AS anon_1 " + "ON companies.company_id = anon_1.people_company_id " + "ORDER BY anon_1.people_person_id", + ) + def test_defer_super_name_on_subclass(self): s = Session() q = s.query(Manager).order_by(Person.person_id).options(defer("name"))