]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
dial back the default "flatness" a bit, it will be there for joinedload and query...
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Jun 2013 22:06:02 +0000 (18:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Jun 2013 22:06:02 +0000 (18:06 -0400)
you're dealing with aliased() or with_polymorphic() you need to say "flat=True".  Just the one
flag though, "flat" implies "aliased".

lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/sql/util.py
test/orm/inheritance/test_polymorphic_rel.py
test/orm/inheritance/test_relationship.py
test/orm/test_of_type.py

index 285d338dea48aa438b91db237fef24187ab8247b..7f14d83cb4f11488f19ca1e79abcff853721c8a5 100644 (file)
@@ -1767,7 +1767,7 @@ class Mapper(_InspectionAttr):
         while stack:
             item = stack.popleft()
             descendants.append(item)
-            stack.extend(item._inheriting_mappers)
+            stack.extend(sorted(item._inheriting_mappers, key=lambda m: m.class_.__name__))
         return util.WeakSequence(descendants)
 
     def polymorphic_iterator(self):
index d251f983fe4c77ddca0049800cb721d9b77263ca..54f5d7393a4b73fe37a1728d0a083a258887e16a 100644 (file)
@@ -1897,7 +1897,7 @@ class Query(object):
                             )
 
         if not need_adapter and (create_aliases or aliased_entity):
-            right = aliased(right)
+            right = aliased(right, flat=True)
             need_adapter = True
 
         # if an alias() of the right side was generated here,
index cabfb35b96b440dd18cf5f95b5f34e3b29125cee..6394003b3ed391abcc63bca538888243ddfe443f 100644 (file)
@@ -1060,6 +1060,14 @@ class JoinedLoader(AbstractRelationshipLoader):
                 column_collection=add_to_collection,
                 allow_innerjoin=allow_innerjoin)
 
+        if with_poly_info is not None and \
+            None in set(context.secondary_columns):
+            raise sa_exc.InvalidRequestError(
+                    "Detected unaliased columns when generating joined "
+                    "load.  Make sure to use aliased=True or flat=True "
+                    "when using joined loading with with_polymorphic()."
+                )
+
     def _get_user_defined_adapter(self, context, entity,
                                 path, adapter, user_defined_adapter):
 
@@ -1089,6 +1097,7 @@ class JoinedLoader(AbstractRelationshipLoader):
             to_adapt = with_poly_info.entity
         else:
             to_adapt = orm_util.AliasedClass(self.mapper,
+                                flat=True,
                                 use_mapper_path=True)
         clauses = orm_util.ORMAdapter(
                     to_adapt,
@@ -1415,8 +1424,7 @@ class LoadEagerFromAliasOption(PropertyOption):
                                                 "path_with_polymorphic")
                 adapter = orm_util.ORMAdapter(
                             with_poly_info.entity,
-                            equivalents=prop.mapper._equivalent_columns,
-                            adapt_required=True)
+                            equivalents=prop.mapper._equivalent_columns)
             else:
                 adapter = query._polymorphic_adapters.get(prop.mapper, None)
             paths[-1].set(query._attributes,
index c21e7eace9ef73ececad16c74256e05a5c0ff854..7ac3ac96a4c4e78e279584fa2973b3a4917e3b20 100644 (file)
@@ -223,8 +223,8 @@ class ORMAdapter(sql_util.ColumnAdapter):
     and the AliasedClass if any is referenced.
 
     """
-    def __init__(self, entity, equivalents=None,
-                            chain_to=None, adapt_required=False):
+    def __init__(self, entity, equivalents=None, adapt_required=False,
+                            chain_to=None):
         info = inspection.inspect(entity)
 
         self.mapper = info.mapper
@@ -493,7 +493,7 @@ class AliasedClass(object):
     """
     def __init__(self, cls, alias=None,
                             name=None,
-                            flat=True,
+                            flat=False,
                             adapt_on_names=False,
                             #  TODO: None for default here?
                             with_polymorphic_mappers=(),
@@ -502,7 +502,8 @@ class AliasedClass(object):
                             use_mapper_path=False):
         mapper = _class_to_mapper(cls)
         if alias is None:
-            alias = mapper._with_polymorphic_selectable.alias(name=name, flat=flat)
+            alias = mapper._with_polymorphic_selectable.alias(
+                                            name=name, flat=flat)
         self._aliased_insp = AliasedInsp(
             self,
             mapper,
@@ -701,7 +702,7 @@ inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
 inspection._inspects(AliasedInsp)(lambda target: target)
 
 
-def aliased(element, alias=None, name=None, adapt_on_names=False):
+def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False):
     """Produce an alias of the given element, usually an :class:`.AliasedClass`
     instance.
 
@@ -775,13 +776,14 @@ def aliased(element, alias=None, name=None, adapt_on_names=False):
             raise sa_exc.ArgumentError(
                 "adapt_on_names only applies to ORM elements"
             )
-        return element.alias(name)
+        return element.alias(name, flat=flat)
     else:
-        return AliasedClass(element, alias=alias,
+        return AliasedClass(element, alias=alias, flat=flat,
                     name=name, adapt_on_names=adapt_on_names)
 
 
 def with_polymorphic(base, classes, selectable=False,
+                        flat=False,
                         polymorphic_on=None, aliased=False,
                         innerjoin=False, _use_mapper_path=False):
     """Produce an :class:`.AliasedClass` construct which specifies
@@ -837,8 +839,8 @@ def with_polymorphic(base, classes, selectable=False,
     mappers, selectable = primary_mapper.\
                     _with_polymorphic_args(classes, selectable,
                                 innerjoin=innerjoin)
-    if aliased:
-        selectable = selectable.alias(flat=True)
+    if aliased or flat:
+        selectable = selectable.alias(flat=flat)
     return AliasedClass(base,
                 selectable,
                 with_polymorphic_mappers=mappers,
index 6f4d27e1bdebce3e075de0aa9c8ea294ec9944a4..4422705cda80609993a4762d1dbef9d91ee9bc5a 100644 (file)
@@ -905,12 +905,11 @@ class ColumnAdapter(ClauseAdapter):
             if isinstance(c, expression.Label):
                 c = c.label(None)
 
-        # adapt_required indicates that if we got the same column
-        # back which we put in (i.e. it passed through),
-        # it's not correct.  this is used by eagerloading which
-        # knows that all columns and expressions need to be adapted
-        # to a result row, and a "passthrough" is definitely targeting
-        # the wrong column.
+        # adapt_required used by eager loading to indicate that
+        # we don't trust a result row column that is not translated.
+        # this is to prevent a column from being interpreted as that
+        # of the child row in a self-referential scenario, see
+        # inheritance/test_basic.py->EagerTargetingTest.test_adapt_stringency
         if self.adapt_required and c is col:
             return None
 
index 4330dbf41bb28a5f37160730eec835a987302ef2..be3731fad788fcf85ad67dadc6c646aec453181e 100644 (file)
@@ -1286,12 +1286,66 @@ class PolymorphicPolymorphicTest(_PolymorphicTestBase, _PolymorphicPolymorphic):
     __dialect__ = 'default'
 
     def test_aliased_not_polluted_by_join(self):
+        # aliased(polymorphic) will normally do the old-school
+        # "(SELECT * FROM a JOIN b ...) AS anon_1" thing.
+        # this is the safest
         sess = create_session()
         palias = aliased(Person)
         self.assert_compile(
             sess.query(palias, Company.name)
                 .join(Person, Company.employees)
                 .filter(palias.name == 'dilbert'),
+            "SELECT anon_1.people_person_id AS anon_1_people_person_id, "
+            "anon_1.people_company_id AS anon_1_people_company_id, "
+            "anon_1.people_name AS anon_1_people_name, "
+            "anon_1.people_type AS anon_1_people_type, "
+            "anon_1.engineers_person_id AS anon_1_engineers_person_id, "
+            "anon_1.engineers_status AS anon_1_engineers_status, "
+            "anon_1.engineers_engineer_name AS anon_1_engineers_engineer_name, "
+            "anon_1.engineers_primary_language AS "
+                "anon_1_engineers_primary_language, "
+            "anon_1.managers_person_id AS anon_1_managers_person_id, "
+            "anon_1.managers_status AS anon_1_managers_status, "
+            "anon_1.managers_manager_name AS anon_1_managers_manager_name, "
+            "anon_1.boss_boss_id AS anon_1_boss_boss_id, "
+            "anon_1.boss_golf_swing AS anon_1_boss_golf_swing, "
+            "companies.name AS companies_name "
+            "FROM (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, "
+                "engineers.person_id AS engineers_person_id, "
+                "engineers.status AS engineers_status, "
+                "engineers.engineer_name AS engineers_engineer_name, "
+                "engineers.primary_language AS engineers_primary_language, "
+                "managers.person_id AS managers_person_id, "
+                "managers.status AS managers_status, "
+                "managers.manager_name AS managers_manager_name, "
+                "boss.boss_id AS boss_boss_id, "
+                "boss.golf_swing AS boss_golf_swing "
+                "FROM people LEFT OUTER JOIN engineers "
+                "ON people.person_id = engineers.person_id "
+                "LEFT OUTER JOIN managers "
+                "ON people.person_id = managers.person_id LEFT OUTER JOIN boss "
+                "ON managers.person_id = boss.boss_id) AS anon_1, "
+                "companies JOIN "
+                "(people LEFT OUTER JOIN engineers "
+                    "ON people.person_id = engineers.person_id "
+                    "LEFT OUTER JOIN managers "
+                    "ON people.person_id = managers.person_id "
+                    "LEFT OUTER JOIN boss ON managers.person_id = boss.boss_id) "
+                "ON companies.company_id = people.company_id "
+                "WHERE anon_1.people_name = :people_name_1 "
+                "ORDER BY anon_1.people_person_id"
+            )
+
+    def test_flat_aliased_w_select_from(self):
+        sess = create_session()
+        palias = aliased(Person, flat=True)
+        self.assert_compile(
+            sess.query(palias, Company.name)
+                .select_from(palias)
+                .join(Person, Company.employees)
+                .filter(palias.name == 'dilbert'),
             "SELECT people_1.person_id AS people_1_person_id, "
             "people_1.company_id AS people_1_company_id, "
             "people_1.name AS people_1_name, people_1.type AS people_1_type, "
@@ -1320,16 +1374,6 @@ class PolymorphicPolymorphicTest(_PolymorphicTestBase, _PolymorphicPolymorphic):
             "WHERE people_1.name = :name_1 ORDER BY people_1.person_id"
         )
 
-    def test_mixed_entities_compiled_four(self):
-        sess = create_session()
-        palias = aliased(Person)
-        self.assert_compile(
-            sess.query(palias, Company.name, Person)
-                .join(Company.employees)
-                .filter(Company.name == 'Elbonia, Inc.')
-                .filter(palias.name == 'dilbert'),
-            ""
-        )
 
 class PolymorphicUnionsTest(_PolymorphicTestBase, _PolymorphicUnions):
     pass
index 3f1eb849fb646ce720df055dbb9f3b9ba6ffc870..f30a37941a4635d581ad47aed0f707ebd81b7835 100644 (file)
@@ -1234,7 +1234,7 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
     def test_two(self):
         Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
 
-        s2a = aliased(Sub2)
+        s2a = aliased(Sub2, flat=True)
 
         s = Session()
         self.assert_compile(
index d002fd50f734d932b38490af0c7cc62bedbb378a..67baddb5236d580f692e8f409e2a786bdb12b5ea 100644 (file)
@@ -2,9 +2,9 @@ from sqlalchemy.orm import Session, aliased, with_polymorphic, \
     contains_eager, joinedload, subqueryload, relationship,\
     subqueryload_all, joinedload_all
 from sqlalchemy import and_
-from sqlalchemy import testing
+from sqlalchemy import testing, exc as sa_exc
 from sqlalchemy.testing import fixtures
-from sqlalchemy.testing import assert_raises, eq_
+from sqlalchemy.testing import assert_raises, assert_raises_message, eq_
 from sqlalchemy.testing.schema import Column
 from sqlalchemy.engine import default
 from sqlalchemy.testing.entities import ComparableEntity
@@ -86,11 +86,12 @@ class _PolymorphicTestBase(object):
 
     def test_with_polymorphic_join_compile_one(self):
         sess = Session()
-# MARKMARK
+
         self.assert_compile(
             sess.query(Company).join(
                     Company.employees.of_type(
-                        with_polymorphic(Person, [Engineer, Manager], aliased=True)
+                        with_polymorphic(Person, [Engineer, Manager],
+                                    aliased=True, flat=True)
                     )
                 ),
             "SELECT companies.company_id AS companies_company_id, "
@@ -104,7 +105,8 @@ class _PolymorphicTestBase(object):
     def test_with_polymorphic_join_exec_contains_eager_one(self):
         sess = Session()
         def go():
-            wp = with_polymorphic(Person, [Engineer, Manager], aliased=True)
+            wp = with_polymorphic(Person, [Engineer, Manager],
+                                    aliased=True, flat=True)
             eq_(
                 sess.query(Company).join(
                     Company.employees.of_type(wp)
@@ -166,7 +168,7 @@ class _PolymorphicTestBase(object):
     def test_subqueryload_explicit_withpoly(self):
         sess = Session()
         def go():
-            target = with_polymorphic(Person, Engineer, aliased=True)
+            target = with_polymorphic(Person, Engineer)
             eq_(
                 sess.query(Company).\
                     filter_by(company_id=1).\
@@ -179,7 +181,7 @@ class _PolymorphicTestBase(object):
     def test_joinedload_explicit_withpoly(self):
         sess = Session()
         def go():
-            target = with_polymorphic(Person, Engineer, aliased=True)
+            target = with_polymorphic(Person, Engineer, flat=True)
             eq_(
                 sess.query(Company).\
                     filter_by(company_id=1).\
@@ -238,6 +240,44 @@ class PolymorphicJoinsTest(_PolymorphicTestBase, _PolymorphicJoins):
             comp_sel.process(sel, asfrom=True).replace("\n", "") + \
             " ON companies.company_id = people_1.company_id"
 
+    def test_joinedload_explicit_with_unaliased_poly_compile(self):
+        sess = Session()
+        target = with_polymorphic(Person, Engineer)
+        q = sess.query(Company).\
+            filter_by(company_id=1).\
+            options(joinedload(Company.employees.of_type(target)))
+        assert_raises_message(
+            sa_exc.InvalidRequestError,
+            "Detected unaliased columns when generating joined load.",
+            q._compile_context
+        )
+
+
+    def test_joinedload_explicit_with_flataliased_poly_compile(self):
+        sess = Session()
+        target = with_polymorphic(Person, Engineer, flat=True)
+        q = sess.query(Company).\
+            filter_by(company_id=1).\
+            options(joinedload(Company.employees.of_type(target)))
+        self.assert_compile(q,
+            "SELECT companies.company_id AS companies_company_id, "
+            "companies.name AS companies_name, "
+            "people_1.person_id AS people_1_person_id, "
+            "people_1.company_id AS people_1_company_id, "
+            "people_1.name AS people_1_name, people_1.type AS people_1_type, "
+            "engineers_1.person_id AS engineers_1_person_id, "
+            "engineers_1.status AS engineers_1_status, "
+            "engineers_1.engineer_name AS engineers_1_engineer_name, "
+            "engineers_1.primary_language AS engineers_1_primary_language "
+            "FROM companies LEFT OUTER JOIN (people AS people_1 "
+            "LEFT OUTER JOIN engineers AS engineers_1 "
+            "ON people_1.person_id = engineers_1.person_id "
+            "LEFT OUTER JOIN managers AS managers_1 "
+            "ON people_1.person_id = managers_1.person_id) "
+            "ON companies.company_id = people_1.company_id "
+            "WHERE companies.company_id = :company_id_1 "
+            "ORDER BY people_1.person_id"
+        )
 
 class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeMappedTest):
     """There's overlap here vs. the ones above."""
@@ -447,7 +487,7 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             self.classes.Job,\
             self.classes.SubJob
 
-        Job_P = with_polymorphic(Job, SubJob, aliased=True)
+        Job_P = with_polymorphic(Job, SubJob, aliased=True, flat=True)
 
         s = Session()
         q = s.query(Job).join(DataContainer.jobs).\
@@ -501,7 +541,7 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             self.classes.Job,\
             self.classes.SubJob
 
-        Job_P = with_polymorphic(Job, SubJob, aliased=True)
+        Job_P = with_polymorphic(Job, SubJob)
 
         s = Session()
         q = s.query(DataContainer).join(DataContainer.jobs.of_type(Job_P))
@@ -509,9 +549,9 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             "SELECT data_container.id AS data_container_id, "
             "data_container.name AS data_container_name "
             "FROM data_container JOIN "
-            "(job AS job_1 LEFT OUTER JOIN subjob AS subjob_1 "
-                "ON job_1.id = subjob_1.id) "
-            "ON data_container.id = job_1.container_id")
+            "(job LEFT OUTER JOIN subjob "
+                "ON job.id = subjob.id) "
+            "ON data_container.id = job.container_id")
 
     def test_join_wsubclass(self):
         ParentThing, DataContainer, Job, SubJob = \
@@ -541,7 +581,7 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             self.classes.Job,\
             self.classes.SubJob
 
-        Job_P = with_polymorphic(Job, SubJob, aliased=True, innerjoin=True)
+        Job_P = with_polymorphic(Job, SubJob, innerjoin=True)
 
         s = Session()
         q = s.query(DataContainer).join(DataContainer.jobs.of_type(Job_P))
@@ -549,8 +589,8 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             "SELECT data_container.id AS data_container_id, "
             "data_container.name AS data_container_name "
             "FROM data_container JOIN "
-            "(job AS job_1 JOIN subjob AS subjob_1 ON job_1.id = subjob_1.id) "
-            "ON data_container.id = job_1.container_id")
+            "(job JOIN subjob ON job.id = subjob.id) "
+            "ON data_container.id = job.container_id")
 
     def test_join_walias(self):
         ParentThing, DataContainer, Job, SubJob = \
@@ -569,14 +609,34 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             "FROM data_container JOIN job AS job_1 "
             "ON data_container.id = job_1.container_id")
 
-    def test_join_explicit_wpoly(self):
+    def test_join_explicit_wpoly_noalias(self):
         ParentThing, DataContainer, Job, SubJob = \
             self.classes.ParentThing,\
             self.classes.DataContainer,\
             self.classes.Job,\
             self.classes.SubJob
 
-        Job_P = with_polymorphic(Job, SubJob, aliased=True)
+        Job_P = with_polymorphic(Job, SubJob)
+
+        s = Session()
+        q = s.query(DataContainer).join(Job_P, DataContainer.jobs)
+        self.assert_compile(q,
+            "SELECT data_container.id AS data_container_id, "
+            "data_container.name AS data_container_name "
+            "FROM data_container JOIN "
+            "(job LEFT OUTER JOIN subjob "
+            "ON job.id = subjob.id) "
+            "ON data_container.id = job.container_id")
+
+
+    def test_join_explicit_wpoly_flat(self):
+        ParentThing, DataContainer, Job, SubJob = \
+            self.classes.ParentThing,\
+            self.classes.DataContainer,\
+            self.classes.Job,\
+            self.classes.SubJob
+
+        Job_P = with_polymorphic(Job, SubJob, flat=True)
 
         s = Session()
         q = s.query(DataContainer).join(Job_P, DataContainer.jobs)
@@ -588,3 +648,25 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM
             "ON job_1.id = subjob_1.id) "
             "ON data_container.id = job_1.container_id")
 
+    def test_join_explicit_wpoly_full_alias(self):
+        ParentThing, DataContainer, Job, SubJob = \
+            self.classes.ParentThing,\
+            self.classes.DataContainer,\
+            self.classes.Job,\
+            self.classes.SubJob
+
+        Job_P = with_polymorphic(Job, SubJob, aliased=True)
+
+        s = Session()
+        q = s.query(DataContainer).join(Job_P, DataContainer.jobs)
+        self.assert_compile(q,
+            "SELECT data_container.id AS data_container_id, "
+            "data_container.name AS data_container_name "
+            "FROM data_container JOIN "
+            "(SELECT job.id AS job_id, job.type AS job_type, "
+                "job.container_id AS job_container_id, "
+                "subjob.id AS subjob_id, subjob.attr AS subjob_attr "
+                "FROM job LEFT OUTER JOIN subjob ON job.id = subjob.id) "
+                "AS anon_1 ON data_container.id = anon_1.job_container_id"
+        )
+