Ticket #2874: select_related.3.patch

File select_related.3.patch, 6.7 KB (added by bangcok_dangerus ( at ] hotmail.com, 18 years ago)

more improvements (see post)

  • 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, careful_aliasing=False):
     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    if recursion_level == 0:
     680        primary_related_tables = []
     681        # set careful_aliasing to True if a table directly related to the base table is in order_by
     682        for field in object_to_follow.fields:
     683            if field.rel and not field.null:
     684                if field.rel.to._meta.db_table in ordering_tables:
     685                    careful_aliasing = True
     686    for field in object_to_follow.fields:
     687        if field.rel and not field.null:
     688            related_table = field.rel.to._meta.db_table
     689            if related_table == tables_seen[0]:
     690                #stop if we follow a circular foreignkey relationship chain
     691                return select, tables, where
     692            if (related_table not in tables_seen) and ((related_table not in ordering_tables) or ((recursion_level == 0) or not careful_aliasing)):
     693                tables.append(qn(related_table))
     694            else:
     695                # give the table an alias
     696                table_alias = '%s%s' % (related_table, len(tables_seen))
     697                tables.append('%s %s' % (qn(related_table), qn(table_alias)))
     698                related_table = table_alias
     699            tables_seen.append(related_table)
    681700            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)
     701                (qn(object_to_follow.db_table), qn(field.column), qn(related_table), qn(field.rel.get_related_field().column)))
     702            select.extend(['%s.%s' % (qn(related_table), qn(f2.column)) for f2 in field.rel.to._meta.fields])
     703            more_select, more_tables, more_where = follow_foreignkeys(field.rel.to._meta, tables_seen, ordering_tables, recursion_level + 1, careful_aliasing)
     704            select.extend(more_select)
     705            tables.extend(more_tables)
     706            where.extend(more_where)
     707    return select, tables, where
    685708
    686709def parse_lookup(kwarg_items, opts):
    687710    # Helper function that handles converting API kwargs
Back to Top