diff options
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 25 | ||||
-rw-r--r-- | test/orm/query.py | 21 |
3 files changed, 47 insertions, 4 deletions
@@ -13,6 +13,11 @@ CHANGES - Also re-established viewonly relation() configurations that join across multiple tables. + - contains_eager(), the hot function of the week, suppresses + the eager loader's own generation of the LEFT OUTER JOIN, + so that it is reasonable to use any Query, not just those + which use from_statement(). + - Added an experimental relation() flag to help with primaryjoins across functions, etc., _local_remote_pairs=[tuples]. This complements a complex diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index e758ac08b..65a8b019b 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -502,6 +502,28 @@ class EagerLoader(AbstractRelationLoader): if self.mapper.base_mapper in path: return + if ("eager_row_processor", path) in context.attributes: + # if user defined eager_row_processor, that's contains_eager(). + # don't render LEFT OUTER JOIN, generate an AliasedClauses from + # the decorator (this is a hack here, cleaned up in 0.5) + cl = context.attributes[("eager_row_processor", path)] + if cl: + row = cl(None) + class ActsLikeAliasedClauses(object): + def aliased_column(self, col): + return row.map[col] + clauses = ActsLikeAliasedClauses() + else: + clauses = None + else: + clauses = self.__create_eager_join(context, path, parentclauses, parentmapper, **kwargs) + if not clauses: + return + + for value in self.mapper._iterate_polymorphic_properties(): + context.exec_with_path(self.mapper, value.key, value.setup, context, parentclauses=clauses, parentmapper=self.mapper) + + def __create_eager_join(self, context, path, parentclauses, parentmapper, **kwargs): if parentmapper is None: localparent = context.mapper else: @@ -546,8 +568,7 @@ class EagerLoader(AbstractRelationLoader): if clauses.order_by: context.eager_order_by += util.to_list(clauses.order_by) - for value in self.mapper._iterate_polymorphic_properties(): - context.exec_with_path(self.mapper, value.key, value.setup, context, parentclauses=clauses, parentmapper=self.mapper) + return clauses def _create_row_decorator(self, selectcontext, row, path): """Create a *row decorating* function that will apply eager diff --git a/test/orm/query.py b/test/orm/query.py index cdd50cb70..f1afdb90b 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -906,7 +906,7 @@ class SynonymTest(QueryTest): o = sess.query(Order).with_parent(u1, property=orderprop).all() assert [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")] == o -class InstancesTest(QueryTest): +class InstancesTest(QueryTest, AssertsCompiledSQL): def test_from_alias(self): @@ -927,9 +927,26 @@ class InstancesTest(QueryTest): self.assert_sql_count(testing.db, go, 1) def test_contains_eager(self): + sess = create_session() + q = sess.query(User).outerjoin(User.addresses).options(contains_eager(User.addresses)) + self.assert_compile(q.statement, "SELECT addresses.id AS addresses_id, addresses.user_id AS addresses_user_id, " + "addresses.email_address AS addresses_email_address, users.id AS users_id, users.name AS users_name "\ + "FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id ORDER BY users.id", dialect=default.DefaultDialect()) + + def go(): + assert fixtures.user_address_result == q.all() + self.assert_sql_count(testing.db, go, 1) + sess.clear() + + adalias = addresses.alias() + q = sess.query(User).select_from(users.outerjoin(adalias)).options(contains_eager(User.addresses, alias=adalias)) + def go(): + assert fixtures.user_address_result == q.all() + self.assert_sql_count(testing.db, go, 1) + sess.clear() + selectquery = users.outerjoin(addresses).select(users.c.id<10, use_labels=True, order_by=[users.c.id, addresses.c.id]) - sess = create_session() q = sess.query(User) def go(): |