]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Ensure select_from_entity adapter is used in adjust_for_single_inheritance
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 19 Apr 2018 21:07:32 +0000 (17:07 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 19 Apr 2018 21:11:47 +0000 (17:11 -0400)
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
doc/build/changelog/unreleased_12/4241.rst [new file with mode: 0644]
lib/sqlalchemy/orm/query.py
test/orm/inheritance/test_single.py
test/orm/test_selectin_relations.py

diff --git a/doc/build/changelog/unreleased_12/4241.rst b/doc/build/changelog/unreleased_12/4241.rst
new file mode 100644 (file)
index 0000000..62a2008
--- /dev/null
@@ -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.
index 54be930559685e97c8814f0b562d059d90ec5e78..42f1b26732c464ab45f8a53aad01d13ca75f6e5d 100644 (file)
@@ -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:
index 0ad6dcdeea62c16cfa7dfe995044c1ec0f4665b1..2416fdc294f51a9257c4a9fb321991fbd50a1540 100644 (file)
@@ -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")
 
index ff1d0d40f1cb9f4861c3c0d9d66deda795409043..57bca5a74ce843a6b10fdf24f7a6e19a33626b46 100644 (file)
@@ -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