summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-04-19 17:07:32 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2018-04-19 17:11:47 -0400
commit4f2d0913fe4fe4f5182f85903a6b3be65ac4fd94 (patch)
tree23fc97a6378b880a5d58669bd8bae18914960a81
parenta3473c08d35e2cce32b014519df5f774c0166cf1 (diff)
downloadsqlalchemy-4f2d0913fe4fe4f5182f85903a6b3be65ac4fd94.tar.gz
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
-rw-r--r--doc/build/changelog/unreleased_12/4241.rst10
-rw-r--r--lib/sqlalchemy/orm/query.py14
-rw-r--r--test/orm/inheritance/test_single.py19
-rw-r--r--test/orm/test_selectin_relations.py59
4 files changed, 96 insertions, 6 deletions
diff --git a/doc/build/changelog/unreleased_12/4241.rst b/doc/build/changelog/unreleased_12/4241.rst
new file mode 100644
index 000000000..62a20086a
--- /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 54be93055..42f1b2673 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 0ad6dcdee..2416fdc29 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 ff1d0d40f..57bca5a74 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