diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-06 18:06:02 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-06 18:06:02 -0400 |
commit | 28c3325c4e18d01d7e0403229b452c8fbc345b80 (patch) | |
tree | 67576681ff561bcabeae0330bcf64c214bfe29aa | |
parent | 768108186a48a94948a7763d0f5c4dfb3f0ce773 (diff) | |
download | sqlalchemy-28c3325c4e18d01d7e0403229b452c8fbc345b80.tar.gz |
dial back the default "flatness" a bit, it will be there for joinedload and query.join(), but if
you're dealing with aliased() or with_polymorphic() you need to say "flat=True". Just the one
flag though, "flat" implies "aliased".
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 20 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 11 | ||||
-rw-r--r-- | test/orm/inheritance/test_polymorphic_rel.py | 64 | ||||
-rw-r--r-- | test/orm/inheritance/test_relationship.py | 2 | ||||
-rw-r--r-- | test/orm/test_of_type.py | 116 |
8 files changed, 182 insertions, 47 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 285d338de..7f14d83cb 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -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): diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d251f983f..54f5d7393 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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, diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index cabfb35b9..6394003b3 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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, diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c21e7eace..7ac3ac96a 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -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, diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 6f4d27e1b..4422705cd 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -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 diff --git a/test/orm/inheritance/test_polymorphic_rel.py b/test/orm/inheritance/test_polymorphic_rel.py index 4330dbf41..be3731fad 100644 --- a/test/orm/inheritance/test_polymorphic_rel.py +++ b/test/orm/inheritance/test_polymorphic_rel.py @@ -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 diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index 3f1eb849f..f30a37941 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -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( diff --git a/test/orm/test_of_type.py b/test/orm/test_of_type.py index d002fd50f..67baddb52 100644 --- a/test/orm/test_of_type.py +++ b/test/orm/test_of_type.py @@ -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" + ) + |