Code

Ticket #2874: select_related.2.patch

File select_related.2.patch, 6.3 KB (added by bangcok_dangerus ( at ] hotmail.com, 8 years ago)

same as above, but this one leaves out a couple of unnecessary lines

  • django/db/models/query.py

     
    446446        where.extend(where2) 
    447447        params.extend(params2) 
    448448 
    449         # Add additional tables and WHERE clauses based on select_related. 
    450         if self._select_related: 
    451             fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table]) 
    452  
    453         # Add any additional SELECTs. 
    454         if self._select: 
    455             select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) 
    456  
    457         # Start composing the body of the SQL statement. 
    458         sql = [" FROM", backend.quote_name(opts.db_table)] 
    459  
    460         # Compose the join dictionary into SQL describing the joins. 
    461         if joins: 
    462             sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition) 
    463                             for (alias, (table, join_type, condition)) in joins.items()])) 
    464  
    465         # Compose the tables clause into SQL. 
    466         if tables: 
    467             sql.append(", " + ", ".join(tables)) 
    468  
    469         # Compose the where clause into SQL. 
    470         if where: 
    471             sql.append(where and "WHERE " + " AND ".join(where)) 
    472  
    473         # ORDER BY clause 
     449        # ORDER BY clause (this done before the other clauses since it is needed by follow_foreignkeys) 
    474450        order_by = [] 
     451        ordering_tables = [] 
    475452        if self._order_by is not None: 
    476453            ordering_to_use = self._order_by 
    477454        else: 
     
    488465                    order = "ASC" 
    489466                if "." in col_name: 
    490467                    table_prefix, col_name = col_name.split('.', 1) 
     468                    ordering_tables.append(table_prefix) 
    491469                    table_prefix = backend.quote_name(table_prefix) + '.' 
    492470                else: 
    493471                    # Use the database table as a column prefix if it wasn't given, 
     
    497475                    else: 
    498476                        table_prefix = '' 
    499477                order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order)) 
     478 
     479        # Add additional tables and WHERE clauses based on select_related. 
     480        if self._select_related: 
     481            more_select, more_tables, more_where = follow_foreignkeys(opts, [opts.db_table], ordering_tables) 
     482            select.extend(more_select) 
     483            tables.extend(more_tables) 
     484            where.extend(more_where) 
     485 
     486        # Add any additional SELECTs. 
     487        if self._select: 
     488            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) 
     489 
     490        # Start composing the body of the SQL statement. 
     491        sql = [" FROM", backend.quote_name(opts.db_table)] 
     492 
     493        # Compose the join dictionary into SQL describing the joins. 
     494        if joins: 
     495            sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition) 
     496                            for (alias, (table, join_type, condition)) in joins.items()])) 
     497 
     498        # Compose the tables clause into SQL. 
     499        if tables: 
     500            sql.append(", " + ", ".join(tables)) 
     501 
     502        # Compose the where clause into SQL. 
     503        if where: 
     504            sql.append(where and "WHERE " + " AND ".join(where)) 
     505 
     506        # finish up ORDER BY clause 
    500507        if order_by: 
    501508            sql.append("ORDER BY " + ", ".join(order_by)) 
    502509 
     
    662669            setattr(obj, f.get_cache_name(), rel_obj) 
    663670    return obj, index_end 
    664671 
    665 def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen): 
    666     """ 
    667     Helper function that recursively populates the select, tables and where (in 
    668     place) for select_related queries. 
    669     """ 
     672def follow_foreignkeys(object_to_follow, tables_seen, ordering_tables, recursion_level=0): 
     673    """Helper function that recursively follows ForeignKey fields in object_to_follow and returns 
     674    the approriate select, tables, and where lists for select_related queries""" 
     675    select = [] 
     676    tables = [] 
     677    where = [] 
    670678    qn = backend.quote_name 
    671     for f in opts.fields: 
    672         if f.rel and not f.null: 
    673             db_table = f.rel.to._meta.db_table 
    674             if db_table not in cache_tables_seen: 
    675                 tables.append(qn(db_table)) 
    676             else: # The table was already seen, so give it a table alias. 
    677                 new_prefix = '%s%s' % (db_table, len(cache_tables_seen)) 
    678                 tables.append('%s %s' % (qn(db_table), qn(new_prefix))) 
    679                 db_table = new_prefix 
    680             cache_tables_seen.append(db_table) 
     679    for field in object_to_follow.fields: 
     680        if field.rel and not field.null: 
     681            related_table = field.rel.to._meta.db_table 
     682            if related_table == tables_seen[0]: 
     683                #stop if we follow a circular foreignkey relationship chain 
     684                return select, tables, where 
     685            if (related_table not in tables_seen) and ((related_table not in ordering_tables) or (recursion_level == 0)): 
     686                tables.append(qn(related_table)) 
     687            else: 
     688                table_alias = '%s%s' % (related_table, len(tables_seen)) 
     689                tables.append('%s %s' % (qn(related_table), qn(table_alias))) 
     690                related_table = table_alias 
     691            tables_seen.append(related_table) 
    681692            where.append('%s.%s = %s.%s' % \ 
    682                 (qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column))) 
    683             select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields]) 
    684             fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen) 
     693                (qn(object_to_follow.db_table), qn(field.column), qn(related_table), qn(field.rel.get_related_field().column))) 
     694            select.extend(['%s.%s' % (qn(related_table), qn(f2.column)) for f2 in field.rel.to._meta.fields]) 
     695            more_select, more_tables, more_where = follow_foreignkeys(field.rel.to._meta, tables_seen, ordering_tables, recursion_level + 1) 
     696            select.extend(more_select) 
     697            tables.extend(more_tables) 
     698            where.extend(more_where) 
     699    return select, tables, where 
    685700 
    686701def parse_lookup(kwarg_items, opts): 
    687702    # Helper function that handles converting API kwargs