def _create_eager_join(self, context, entity, path, adapter, parentmapper): # check for join_depth or basic recursion, # if the current path was not explicitly stated as # a desired "loaderstrategy" (i.e. via query.options()) if ("loaderstrategy", path) not in context.attributes: if self.join_depth: if len(path) / 2 > self.join_depth: return else: if self.mapper.base_mapper in path: return if parentmapper is None: localparent = entity.mapper else: localparent = parentmapper # whether or not the Query will wrap the selectable in a subquery, # and then attach eager load joins to that (i.e., in the case of LIMIT/OFFSET etc.) should_nest_selectable = context.query._should_nest_selectable if entity in context.eager_joins: entity_key, default_towrap = entity, entity.selectable elif should_nest_selectable or not context.from_clause or not sql_util.search( context.from_clause, entity.selectable): # if no from_clause, or a from_clause we can't join to, or a subquery is going to be generated, # store eager joins per _MappedEntity; Query._compile_context will # add them as separate selectables to the select(), or splice them together # after the subquery is generated entity_key, default_towrap = entity, entity.selectable else: # otherwise, create a single eager join from the from clause. # Query._compile_context will adapt as needed and append to the # FROM clause of the select(). entity_key, default_towrap = None, context.from_clause towrap = context.eager_joins.setdefault(entity_key, default_towrap) # create AliasedClauses object to build up the eager query. clauses = mapperutil.ORMAdapter( mapperutil.AliasedClass(self.mapper), equivalents=self.mapper._equivalent_columns) join_to_left = False if adapter: if getattr(adapter, 'aliased_class', None): onclause = getattr(adapter.aliased_class, self.key, self.parent_property) else: onclause = getattr( mapperutil.AliasedClass(self.parent, adapter.selectable), self.key, self.parent_property) if onclause is self.parent_property: # TODO: this is a temporary hack to account for polymorphic eager loads where # the eagerload is referencing via of_type(). join_to_left = True else: onclause = self.parent_property context.eager_joins[entity_key] = eagerjoin = mapperutil.outerjoin( towrap, clauses.aliased_class, onclause, join_to_left=join_to_left) # send a hint to the Query as to where it may "splice" this join eagerjoin.stop_on = entity.selectable if not self.parent_property.secondary and context.query._should_nest_selectable and not parentmapper: # for parentclause that is the non-eager end of the join, # ensure all the parent cols in the primaryjoin are actually in the # columns clause (i.e. are not deferred), so that aliasing applied by the Query propagates # those columns outward. This has the effect of "undefering" those columns. for col in sql_util.find_columns(self.parent_property.primaryjoin): if localparent.mapped_table.c.contains_column(col): if adapter: col = adapter.columns[col] context.primary_columns.append(col) if self.parent_property.order_by: context.eager_order_by += eagerjoin._target_adapter.copy_and_process( util.to_list(self.parent_property.order_by)) return clauses
def setup_query(self, context, entity, path, reduced_path, adapter, \ column_collection=None, parentmapper=None, allow_innerjoin=True, **kwargs): """Add a left outer join to the statement thats being constructed.""" if not context.query._enable_eagerloads: return path = path + (self.key, ) reduced_path = reduced_path + (self.key, ) # check for user-defined eager alias if ("user_defined_eager_row_processor", reduced_path) in\ context.attributes: clauses = context.attributes[("user_defined_eager_row_processor", reduced_path)] adapter = entity._get_entity_clauses(context.query, context) if adapter and clauses: context.attributes[( "user_defined_eager_row_processor", reduced_path)] = clauses = clauses.wrap(adapter) elif adapter: context.attributes[("user_defined_eager_row_processor", reduced_path)] = clauses = adapter add_to_collection = context.primary_columns else: # check for join_depth or basic recursion, # if the current path was not explicitly stated as # a desired "loaderstrategy" (i.e. via query.options()) if ("loaderstrategy", reduced_path) not in context.attributes: if self.join_depth: if len(path) / 2 > self.join_depth: return else: if self.mapper.base_mapper in reduced_path: return clauses = mapperutil.ORMAdapter( mapperutil.AliasedClass(self.mapper), equivalents=self.mapper._equivalent_columns, adapt_required=True) if self.parent_property.direction != interfaces.MANYTOONE: context.multi_row_eager_loaders = True innerjoin = allow_innerjoin and context.attributes.get( ("eager_join_type", path), self.parent_property.innerjoin) if not innerjoin: # if this is an outer join, all eager joins from # here must also be outer joins allow_innerjoin = False context.create_eager_joins.append( (self._create_eager_join, context, entity, path, adapter, parentmapper, clauses, innerjoin)) add_to_collection = context.secondary_columns context.attributes[("eager_row_processor", reduced_path)] = clauses path += (self.mapper, ) reduced_path += (self.mapper.base_mapper, ) for value in self.mapper._polymorphic_properties: value.setup(context, entity, path, reduced_path, clauses, parentmapper=self.mapper, column_collection=add_to_collection, allow_innerjoin=allow_innerjoin)