From: Mike Bayer Date: Tue, 1 Jul 2014 00:25:04 +0000 (-0400) Subject: - repair the _enable_single_crit method, it was named the same X-Git-Tag: rel_1_0_0b1~365 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2d8d1dcf60e023275b8dcfea015ec16cad69d266;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - repair the _enable_single_crit method, it was named the same as the attribute and probably just replaced itself, so that is now _set_enable_single_crit - as a side effect of the main issue fixed here, correct the case in adjust_for_single_inheritance where the same mapper appears more than once in mapper_adapter_map; run through a set() for uniqueness. - Fixed bug in subquery eager loading in conjunction with :func:`.with_polymorphic`, the targeting of entities and columns in the subquery load has been made more accurate with respect to this type of entity and others. Fixes #3106 --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 56d43a9e62..fa83c4b346 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,16 @@ :version: 0.9.7 :released: + .. change:: + :tags: bug, orm + :tickets: 3106 + :versions: 1.0.0 + + Fixed bug in subquery eager loading in conjunction with + :func:`.with_polymorphic`, the targeting of entities and columns + in the subquery load has been made more accurate with respect + to this type of entity and others. + .. change:: :tags: bug, orm :tickets: 3099 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 5d60c4e29d..728f7787a2 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -941,7 +941,7 @@ class Query(object): """ fromclause = self.with_labels().enable_eagerloads(False).\ - _enable_single_crit(False).\ + _set_enable_single_crit(False).\ statement.correlate(None) q = self._from_selectable(fromclause) if entities: @@ -949,7 +949,7 @@ class Query(object): return q @_generative() - def _enable_single_crit(self, val): + def _set_enable_single_crit(self, val): self._enable_single_crit = val @_generative() @@ -2908,7 +2908,8 @@ class Query(object): subtypes are selected from the total results. """ - for (ext_info, adapter) in self._mapper_adapter_map.values(): + + for (ext_info, adapter) in set(self._mapper_adapter_map.values()): if ext_info in self._join_entities: continue single_crit = ext_info.mapper._single_table_criterion diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 567c09fff1..81860e0459 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -713,7 +713,7 @@ class SubqueryLoader(AbstractRelationshipLoader): elif subq_path.contains_mapper(self.mapper): return - subq_mapper, leftmost_mapper, leftmost_attr, leftmost_relationship = \ + leftmost_mapper, leftmost_attr, leftmost_relationship = \ self._get_leftmost(subq_path) orig_query = context.attributes.get( @@ -725,7 +725,7 @@ class SubqueryLoader(AbstractRelationshipLoader): left_alias = self._generate_from_original_query( orig_query, leftmost_mapper, leftmost_attr, leftmost_relationship, - entity.mapper + entity.entity_zero ) # generate another Query that will join the @@ -738,13 +738,12 @@ class SubqueryLoader(AbstractRelationshipLoader): ("orig_query", SubqueryLoader): orig_query, ('subquery_path', None): subq_path } - q = q._enable_single_crit(False) + q = q._set_enable_single_crit(False) to_join, local_attr, parent_alias = \ self._prep_for_joins(left_alias, subq_path) q = q.order_by(*local_attr) q = q.add_columns(*local_attr) - q = self._apply_joins(q, to_join, left_alias, parent_alias, effective_entity) @@ -771,15 +770,17 @@ class SubqueryLoader(AbstractRelationshipLoader): leftmost_cols = leftmost_prop.local_columns leftmost_attr = [ - leftmost_mapper._columntoproperty[c].class_attribute + getattr(subq_path[0].entity, + leftmost_mapper._columntoproperty[c].key) for c in leftmost_cols ] - return subq_mapper, leftmost_mapper, leftmost_attr, leftmost_prop + + return leftmost_mapper, leftmost_attr, leftmost_prop def _generate_from_original_query(self, orig_query, leftmost_mapper, leftmost_attr, leftmost_relationship, - entity_mapper + orig_entity ): # reformat the original query # to look only for significant columns @@ -787,9 +788,8 @@ class SubqueryLoader(AbstractRelationshipLoader): # set a real "from" if not present, as this is more # accurate than just going off of the column expression - if not q._from_obj and entity_mapper.isa(leftmost_mapper): - q._set_select_from([entity_mapper], False) - + if not q._from_obj and orig_entity.mapper.isa(leftmost_mapper): + q._set_select_from([orig_entity], False) target_cols = q._adapt_col_list(leftmost_attr) # select from the identity columns of the outer diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 03498d7ea3..0f8ffb6e72 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -14,6 +14,8 @@ from sqlalchemy.testing.entities import ComparableEntity from test.orm import _fixtures import sqlalchemy as sa +from sqlalchemy.orm import with_polymorphic + class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): run_inserts = 'once' run_deletes = None @@ -1133,6 +1135,158 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic): ) ) + def test_correct_subquery_with_polymorphic_no_alias(self): + # test #3106 + sess = create_session() + + wp = with_polymorphic(Person, [Engineer]) + q = sess.query(wp).\ + options(subqueryload(wp.paperwork)).\ + order_by(Engineer.primary_language.desc()) + + def go(): + eq_(q.first(), + Engineer( + paperwork=[ + Paperwork(description="tps report #1"), + Paperwork(description="tps report #2")], + primary_language='java' + ) + + ) + self.assert_sql_execution( + testing.db, + go, + CompiledSQL( + "SELECT people.person_id AS people_person_id, " + "people.name AS people_name, people.type AS people_type, " + "engineers.engineer_id AS engineers_engineer_id, " + "engineers.primary_language AS engineers_primary_language " + "FROM people LEFT OUTER JOIN engineers ON people.person_id = " + "engineers.engineer_id ORDER BY engineers.primary_language " + "DESC LIMIT :param_1"), + CompiledSQL( + "SELECT paperwork.paperwork_id AS paperwork_paperwork_id, " + "paperwork.description AS paperwork_description, " + "paperwork.person_id AS paperwork_person_id, " + "anon_1.people_person_id AS anon_1_people_person_id FROM " + "(SELECT people.person_id AS people_person_id FROM people " + "LEFT OUTER JOIN engineers ON people.person_id = " + "engineers.engineer_id ORDER BY engineers.primary_language " + "DESC LIMIT :param_1) AS anon_1 JOIN paperwork " + "ON anon_1.people_person_id = paperwork.person_id " + "ORDER BY anon_1.people_person_id, paperwork.paperwork_id") + ) + + def test_correct_subquery_with_polymorphic_alias(self): + # test #3106 + sess = create_session() + + wp = with_polymorphic(Person, [Engineer], aliased=True) + q = sess.query(wp).\ + options(subqueryload(wp.paperwork)).\ + order_by(wp.Engineer.primary_language.desc()) + + def go(): + eq_(q.first(), + Engineer( + paperwork=[ + Paperwork(description="tps report #1"), + Paperwork(description="tps report #2")], + primary_language='java' + ) + + ) + self.assert_sql_execution( + testing.db, + go, + CompiledSQL( + "SELECT anon_1.people_person_id AS anon_1_people_person_id, " + "anon_1.people_name AS anon_1_people_name, " + "anon_1.people_type AS anon_1_people_type, " + "anon_1.engineers_engineer_id AS anon_1_engineers_engineer_id, " + "anon_1.engineers_primary_language " + "AS anon_1_engineers_primary_language FROM " + "(SELECT people.person_id AS people_person_id, " + "people.name AS people_name, people.type AS people_type, " + "engineers.engineer_id AS engineers_engineer_id, " + "engineers.primary_language AS engineers_primary_language " + "FROM people LEFT OUTER JOIN engineers ON people.person_id = " + "engineers.engineer_id) AS anon_1 " + "ORDER BY anon_1.engineers_primary_language DESC " + "LIMIT :param_1"), + CompiledSQL( + "SELECT paperwork.paperwork_id AS paperwork_paperwork_id, " + "paperwork.description AS paperwork_description, " + "paperwork.person_id AS paperwork_person_id, " + "anon_1.anon_2_people_person_id AS " + "anon_1_anon_2_people_person_id FROM " + "(SELECT DISTINCT anon_2.people_person_id AS " + "anon_2_people_person_id, " + "anon_2.engineers_primary_language AS " + "anon_2_engineers_primary_language FROM " + "(SELECT people.person_id AS people_person_id, " + "people.name AS people_name, people.type AS people_type, " + "engineers.engineer_id AS engineers_engineer_id, " + "engineers.primary_language AS engineers_primary_language " + "FROM people LEFT OUTER JOIN engineers ON people.person_id = " + "engineers.engineer_id) AS anon_2 " + "ORDER BY anon_2.engineers_primary_language " + "DESC LIMIT :param_1) AS anon_1 " + "JOIN paperwork " + "ON anon_1.anon_2_people_person_id = paperwork.person_id " + "ORDER BY anon_1.anon_2_people_person_id, " + "paperwork.paperwork_id") + ) + + def test_correct_subquery_with_polymorphic_flat_alias(self): + # test #3106 + sess = create_session() + + wp = with_polymorphic(Person, [Engineer], aliased=True, flat=True) + q = sess.query(wp).\ + options(subqueryload(wp.paperwork)).\ + order_by(wp.Engineer.primary_language.desc()) + + def go(): + eq_(q.first(), + Engineer( + paperwork=[ + Paperwork(description="tps report #1"), + Paperwork(description="tps report #2")], + primary_language='java' + ) + + ) + self.assert_sql_execution( + testing.db, + go, + CompiledSQL( + "SELECT people_1.person_id AS people_1_person_id, " + "people_1.name AS people_1_name, " + "people_1.type AS people_1_type, " + "engineers_1.engineer_id AS engineers_1_engineer_id, " + "engineers_1.primary_language AS engineers_1_primary_language " + "FROM people AS people_1 " + "LEFT OUTER JOIN engineers AS engineers_1 " + "ON people_1.person_id = engineers_1.engineer_id " + "ORDER BY engineers_1.primary_language DESC LIMIT :param_1"), + CompiledSQL( + "SELECT paperwork.paperwork_id AS paperwork_paperwork_id, " + "paperwork.description AS paperwork_description, " + "paperwork.person_id AS paperwork_person_id, " + "anon_1.people_1_person_id AS anon_1_people_1_person_id " + "FROM (SELECT people_1.person_id AS people_1_person_id " + "FROM people AS people_1 " + "LEFT OUTER JOIN engineers AS engineers_1 " + "ON people_1.person_id = engineers_1.engineer_id " + "ORDER BY engineers_1.primary_language DESC LIMIT :param_1) " + "AS anon_1 JOIN paperwork ON anon_1.people_1_person_id = " + "paperwork.person_id ORDER BY anon_1.people_1_person_id, " + "paperwork.paperwork_id" + ) + ) + class SubRelationFromJoinedSubclassMultiLevelTest(_Polymorphic): @classmethod def define_tables(cls, metadata):