From: Mike Bayer Date: Thu, 19 Apr 2018 21:07:32 +0000 (-0400) Subject: Ensure select_from_entity adapter is used in adjust_for_single_inheritance X-Git-Tag: rel_1_3_0b1~206^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4f2d0913fe4fe4f5182f85903a6b3be65ac4fd94;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Ensure select_from_entity adapter is used in adjust_for_single_inheritance Fixed issue in single-inheritance loading where the use of an aliased entity against a single-inheritance subclass in conjunction with the :meth:`.Query.select_from` method would cause the SQL to be rendered with the unaliased table mixed in to the query, causing a cartesian product. In particular this was affecting the new "selectin" loader when used against a single-inheritance subclass. Change-Id: Ic2cbe94a5269c101b1f98da9a466180dd4452783 Fixes: #4241 --- diff --git a/doc/build/changelog/unreleased_12/4241.rst b/doc/build/changelog/unreleased_12/4241.rst new file mode 100644 index 0000000000..62a20086a8 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4241.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm + :tickets: 4241 + + Fixed issue in single-inheritance loading where the use of an aliased + entity against a single-inheritance subclass in conjunction with the + :meth:`.Query.select_from` method would cause the SQL to be rendered with + the unaliased table mixed in to the query, causing a cartesian product. In + particular this was affecting the new "selectin" loader when used against a + single-inheritance subclass. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 54be930559..42f1b26732 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -3542,12 +3542,14 @@ class Query(object): """ search = set(self._mapper_adapter_map.values()) - if self._select_from_entity: - # based on the behavior in _set_select_from, - # when we have self._select_from_entity, we don't - # have _from_obj_alias. - # assert self._from_obj_alias is None - search = search.union([(self._select_from_entity, None)]) + if self._select_from_entity and \ + self._select_from_entity not in self._mapper_adapter_map: + insp = inspect(self._select_from_entity) + if insp.is_aliased_class: + adapter = insp._adapter + else: + adapter = None + search = search.union([(self._select_from_entity, adapter)]) for (ext_info, adapter) in search: if ext_info in self._join_entities: diff --git a/test/orm/inheritance/test_single.py b/test/orm/inheritance/test_single.py index 0ad6dcdeea..2416fdc294 100644 --- a/test/orm/inheritance/test_single.py +++ b/test/orm/inheritance/test_single.py @@ -247,6 +247,25 @@ class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest): 'anon_1', use_default_dialect=True) + def test_select_from_aliased_w_subclass(self): + Engineer = self.classes.Engineer + + sess = create_session() + + a1 = aliased(Engineer) + self.assert_compile( + sess.query(a1.employee_id).select_from(a1), + "SELECT employees_1.employee_id AS employees_1_employee_id " + "FROM employees AS employees_1 WHERE employees_1.type " + "IN (:type_1, :type_2)", + ) + + self.assert_compile( + sess.query(literal('1')).select_from(a1), + "SELECT :param_1 AS param_1 FROM employees AS employees_1 " + "WHERE employees_1.type IN (:type_1, :type_2)" + ) + def test_union_modifiers(self): Engineer, Manager = self.classes("Engineer", "Manager") diff --git a/test/orm/test_selectin_relations.py b/test/orm/test_selectin_relations.py index ff1d0d40f1..57bca5a74c 100644 --- a/test/orm/test_selectin_relations.py +++ b/test/orm/test_selectin_relations.py @@ -2126,3 +2126,62 @@ class TestExistingRowPopulation(fixtures.DeclarativeMappedTest): a1 = q.all()[0] is_true('c1_m2o' in a1.b.__dict__) is_true('c2_m2o' in a1.b.__dict__) + + +class SingleInhSubclassTest( + fixtures.DeclarativeMappedTest, + testing.AssertsExecutionResults): + + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class User(Base): + __tablename__ = 'user' + + id = Column(Integer, primary_key=True) + type = Column(String(10)) + + __mapper_args__ = {'polymorphic_on': type} + + class EmployerUser(User): + roles = relationship('Role', lazy='selectin') + __mapper_args__ = {'polymorphic_identity': 'employer'} + + class Role(Base): + __tablename__ = 'role' + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('user.id')) + + @classmethod + def insert_data(cls): + EmployerUser, Role = cls.classes("EmployerUser", "Role") + + s = Session() + s.add(EmployerUser(roles=[Role(), Role(), Role()])) + s.commit() + + def test_load(self): + EmployerUser, = self.classes("EmployerUser") + s = Session() + + q = s.query(EmployerUser) + + self.assert_sql_execution( + testing.db, + q.all, + CompiledSQL( + 'SELECT "user".id AS user_id, "user".type AS user_type ' + 'FROM "user" WHERE "user".type IN (:type_1)', + {'type_1': 'employer'} + ), + CompiledSQL( + 'SELECT user_1.id AS user_1_id, role.id AS role_id, ' + 'role.user_id AS role_user_id FROM "user" AS user_1 ' + 'JOIN role ON user_1.id = role.user_id WHERE user_1.id IN ' + '([EXPANDING_primary_keys]) ' + 'AND user_1.type IN (:type_1) ORDER BY user_1.id', + {'primary_keys': [1], 'type_1': 'employer'} + ), + ) \ No newline at end of file