Ticket #9368: query_cleanup.diff

File query_cleanup.diff, 19.8 KB (added by adunar, 7 years ago)

clean up the code for getting a query's columns/fields v1.02

  • django/db/models/sql/query.py

     
    197197            obj._setup_query()
    198198        return obj
    199199
     200    def get_base_fields(self):
     201        """
     202        Returns a list of fields on the base object (i.e., not related objects)
     203        that are retrieved by this query. In the result row, these columns are
     204        row[len(self.extra_select):len(self.extra_select)+len(self.get_base_fields())]
     205        """
     206        if self.select_fields:
     207            return self.select_fields
     208        else:
     209            return self.model._meta.fields
     210
    200211    def results_iter(self):
    201212        """
    202213        Returns an iterator over the results from executing this query.
     
    210221                        # We only set this up here because
    211222                        # related_select_fields isn't populated until
    212223                        # execute_sql() has been called.
    213                         if self.select_fields:
    214                             fields = self.select_fields + self.related_select_fields
    215                         else:
    216                             fields = self.model._meta.fields
     224                        fields = self.get_base_fields()
     225                        if self.related_select_fields:
     226                            fields = fields + self.related_select_fields
    217227                    row = self.resolve_columns(row, fields)
    218228                yield row
    219229
     
    435445        if self.select:
    436446            for col in self.select:
    437447                if isinstance(col, (list, tuple)):
    438                     r = '%s.%s' % (qn(col[0]), qn(col[1]))
    439                     if with_aliases and col[1] in col_aliases:
    440                         c_alias = 'Col%d' % len(col_aliases)
    441                         result.append('%s AS %s' % (r, c_alias))
    442                         aliases.add(c_alias)
    443                         col_aliases.add(c_alias)
    444                     else:
    445                         result.append(r)
    446                         aliases.add(r)
    447                         col_aliases.add(col[1])
     448                    result.append(self._get_column(                           
     449                        table_alias=col[0],column_name=col[1],
     450                        with_aliases=with_aliases,
     451                        col_aliases=col_aliases,
     452                        aliases=aliases))
    448453                else:
    449454                    result.append(col.as_sql(quote_func=qn))
    450455                    if hasattr(col, 'alias'):
    451456                        aliases.add(col.alias)
    452457                        col_aliases.add(col.alias)
    453458        elif self.default_cols:
    454             cols, new_aliases = self.get_default_columns(with_aliases,
    455                     col_aliases)
    456             result.extend(cols)
    457             aliases.update(new_aliases)
    458         for table, col in self.related_select_cols:
    459             r = '%s.%s' % (qn(table), qn(col))
    460             if with_aliases and col in col_aliases:
    461                 c_alias = 'Col%d' % len(col_aliases)
    462                 result.append('%s AS %s' % (r, c_alias))
    463                 aliases.add(c_alias)
    464                 col_aliases.add(c_alias)
    465             else:
    466                 result.append(r)
    467                 aliases.add(r)
    468                 col_aliases.add(col)
     459            result.extend(self.get_base_columns(with_aliases, col_aliases, aliases))
     460       
     461        for table, column_name in self.related_select_cols:
     462            result.append(self._get_column(table, column_name,
     463                with_aliases, col_aliases, aliases))
    469464
    470465        self._select_aliases = aliases
    471466        return result
    472467
    473     def get_default_columns(self, with_aliases=False, col_aliases=None,
    474             start_alias=None, opts=None, as_pairs=False):
     468    def _get_table_alias(self, model, seen, root_pk):
    475469        """
    476         Computes the default columns for selecting every field in the base
    477         model.
     470        Gets a table alias for the given model. Seen is a dict
     471        mapping already-used models to table aliases, and may be updated
     472        as a side effect.
     473        """   
     474        try:
     475            return seen[model]
     476        except KeyError:           
     477            alias = self.join((seen[None], model._meta.db_table,
     478                    root_pk, model._meta.pk.column))
     479            seen[model] = alias       
     480            return alias
     481           
     482    def get_base_columns(self, with_aliases, col_aliases, aliases):
     483        """
     484        Computes the columns to select on the base model. By default, this includes all
     485        fields.
    478486
    479         Returns a list of strings, quoted appropriately for use in SQL
    480         directly, as well as a set of aliases used in the select statement (if
    481         'as_pairs' is True, returns a list of (alias, col_name) pairs instead
    482         of strings as the first component and None as the second component).
     487        Returns a list of strings, quoted appropriately for use in SQL directly.
     488        As a side effect, updates the aliases and col_aliases sets passed in as an argument.
     489        """     
     490        result = []       
     491        opts = self.model._meta
     492        root_pk = opts.pk.column
     493        seen = {None: self.tables[0]}
     494       
     495        for field, model in opts.get_fields_with_model():
     496            table_alias = self._get_table_alias(model, seen, root_pk)   
     497            result.append(self._get_column(table_alias, field.column,
     498                                           with_aliases, col_aliases, aliases))
     499               
     500        return result
     501   
     502    def get_related_columns(self, start_alias, opts):
    483503        """
    484         result = []
    485         if opts is None:
    486             opts = self.model._meta
    487         if start_alias:
    488             table_alias = start_alias
    489         else:
    490             table_alias = self.tables[0]
     504        Computes the default columns for selecting every field for a related
     505        model (described by opts). The related field columns currently cannot be
     506        customized per-query.
     507
     508        Returns a list of (alias, field) pairs, where alias is a string quoted
     509        appropriately for use in SQL directly.
     510        """
     511        result = []
     512        seen = {None: start_alias}
    491513        root_pk = opts.pk.column
    492         seen = {None: table_alias}
    493         qn = self.quote_name_unless_alias
    494         qn2 = self.connection.ops.quote_name
    495         aliases = set()
     514       
    496515        for field, model in opts.get_fields_with_model():
    497             try:
    498                 alias = seen[model]
    499             except KeyError:
    500                 alias = self.join((table_alias, model._meta.db_table,
    501                         root_pk, model._meta.pk.column))
    502                 seen[model] = alias
    503             if as_pairs:
    504                 result.append((alias, field.column))
    505                 continue
    506             if with_aliases and field.column in col_aliases:
    507                 c_alias = 'Col%d' % len(col_aliases)
    508                 result.append('%s.%s AS %s' % (qn(alias),
    509                     qn2(field.column), c_alias))
    510                 col_aliases.add(c_alias)
    511                 aliases.add(c_alias)
    512             else:
    513                 r = '%s.%s' % (qn(alias), qn2(field.column))
    514                 result.append(r)
    515                 aliases.add(r)
    516                 if with_aliases:
    517                     col_aliases.add(field.column)
    518         if as_pairs:
    519             return result, None
    520         return result, aliases
     516            result.append((self._get_table_alias(model, seen, root_pk), field.column))
     517               
     518        return result
    521519
     520    def _get_column(self, table_alias, column_name, with_aliases, col_aliases, aliases):       
     521        """
     522        Returns a string for a SQL select statement, containing a column name
     523        as well as a column alias if necessary.
     524       
     525        The col_aliases and aliases sets are modified as a side effect.
     526        """               
     527        r = '%s.%s' % (self.quote_name_unless_alias(table_alias), self.connection.ops.quote_name(column_name))       
     528        if with_aliases and column_name in col_aliases:
     529            c_alias = 'Col%d' % len(col_aliases)
     530           
     531            col_aliases.add(c_alias)
     532            aliases.add(c_alias)
     533           
     534            return '%s AS %s' % (r, c_alias)
     535        else:
     536            aliases.add(r)
     537            if with_aliases:
     538                col_aliases.add(column_name)   
     539               
     540            return r
     541
    522542    def get_from_clause(self):
    523543        """
    524544        Returns a list of strings that are joined together to go after the
     
    10461066                    f.rel.get_related_field().column),
    10471067                    exclusions=used.union(avoid), promote=promote)
    10481068            used.add(alias)
    1049             self.related_select_cols.extend(self.get_default_columns(
    1050                 start_alias=alias, opts=f.rel.to._meta, as_pairs=True)[0])
     1069            new_related_cols = self.get_related_columns(start_alias=alias, opts=f.rel.to._meta)           
     1070            self.related_select_cols.extend(new_related_cols)
    10511071            self.related_select_fields.extend(f.rel.to._meta.fields)
    10521072            if restricted:
    10531073                next = requested.get(f.name, {})
     
    15291549                        True)
    15301550                final_alias = joins[-1]
    15311551                col = target.column
     1552               
     1553                # When name ends with something like fk_id, setup_joins
     1554                # will add an unnecessary join on the fk. In this case, we undo the
     1555                # last join, and just select fk_id on the previous model.
    15321556                if len(joins) > 1:
    15331557                    join = self.alias_map[final_alias]
    15341558                    if col == join[RHS_JOIN_COL]:
  • django/db/models/base.py

     
    265265                raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
    266266        signals.post_init.send(sender=self.__class__, instance=self)
    267267
     268    @classmethod
     269    def _new_from_select_values(cls, vals, fields):   
     270        """
     271        Creates a new instance of this model class. vals is a list of columns
     272        (e.g., as retrieved by a select query), and fields is a parallel list
     273        of corresponding field objects.
     274       
     275        The class will be instantiated as if calling __init__ with kwargs
     276        fields[i]=vals[i] for all i.
     277       
     278        However, it may call __init__ using positional arguments when all
     279        fields are provided.
     280        """   
     281
     282        # we use pointer-equality to make the test faster in the normal case   
     283        if fields is cls._meta.fields:
     284            return cls(*vals) # the comments for __init__ indicate this is faster
     285        else:           
     286            return cls(**dict(zip([f.attname for f in fields], vals)))                 
     287
    268288    def __repr__(self):
    269289        return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
    270290
  • django/db/models/query.py

     
    272272        max_depth = self.query.max_depth
    273273        extra_select = self.query.extra_select.keys()
    274274        index_start = len(extra_select)
     275       
     276        fields = self.query.get_base_fields() 
     277        model = self.model
     278       
    275279        for row in self.query.results_iter():
    276280            if fill_cache:
    277                 obj, _ = get_cached_row(self.model, row, index_start,
    278                         max_depth, requested=requested)
     281                obj, _ = get_cached_row(model, row, index_start,
     282                        max_depth, requested=requested, fields=fields)
    279283            else:
    280                 obj = self.model(*row[index_start:])
     284                obj = model._new_from_select_values(row[index_start:], fields)
    281285            for i, k in enumerate(extra_select):
    282286                setattr(obj, k, row[i])
    283287            yield obj
     
    683687            field_names = [f.attname for f in self.model._meta.fields]
    684688
    685689        self.query.add_fields(field_names, False)
    686         self.query.default_cols = False
    687690        self.field_names = field_names
    688691
    689692    def _clone(self, klass=None, setup=False, **kwargs):
     
    788791
    789792
    790793def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
    791                    requested=None):
     794                   requested=None, fields=None):
    792795    """
    793796    Helper function that recursively returns an object with the specified
    794797    related attributes already populated.
     
    797800        # We've recursed deeply enough; stop now.
    798801        return None
    799802
     803    if fields is None:
     804        fields = klass._meta.fields
     805
    800806    restricted = requested is not None
    801     index_end = index_start + len(klass._meta.fields)
    802     fields = row[index_start:index_end]
    803     if not [x for x in fields if x is not None]:
     807    index_end = index_start + len(fields)
     808    values = row[index_start:index_end]
     809    if not [x for x in values if x is not None]:
    804810        # If we only have a list of Nones, there was not related object.
    805811        obj = None
    806812    else:
    807         obj = klass(*fields)
     813        obj = klass._new_from_select_values(values, fields)
    808814    for f in klass._meta.fields:
    809815        if not select_related_descend(f, restricted, requested):
    810816            continue
  • django/contrib/gis/db/models/sql/query.py

     
    6666            # This loop customized for GeoQuery.
    6767            for col, field in izip(self.select, self.select_fields):
    6868                if isinstance(col, (list, tuple)):
    69                     r = self.get_field_select(field, col[0])
    70                     if with_aliases and col[1] in col_aliases:
    71                         c_alias = 'Col%d' % len(col_aliases)
    72                         result.append('%s AS %s' % (r, c_alias))
    73                         aliases.add(c_alias)
    74                         col_aliases.add(c_alias)
    75                     else:
    76                         result.append(r)
    77                         aliases.add(r)
    78                         col_aliases.add(col[1])
     69                    result.append(self._get_column_with_field(
     70                        table_alias=col[0],column_name=col[1],field=field,
     71                        with_aliases=with_aliases,
     72                        col_aliases=col_aliases, aliases=aliases))
    7973                else:
    8074                    result.append(col.as_sql(quote_func=qn))
    8175                    if hasattr(col, 'alias'):
    8276                        aliases.add(col.alias)
    8377                        col_aliases.add(col.alias)
    8478        elif self.default_cols:
    85             cols, new_aliases = self.get_default_columns(with_aliases,
    86                     col_aliases)
    87             result.extend(cols)
    88             aliases.update(new_aliases)
     79            result.extend(self.get_base_columns(with_aliases, col_aliases, aliases))
    8980        # This loop customized for GeoQuery.
    9081        if not self.aggregate:
    9182            for (table, col), field in izip(self.related_select_cols, self.related_select_fields):
    92                 r = self.get_field_select(field, table)
    93                 if with_aliases and col in col_aliases:
    94                     c_alias = 'Col%d' % len(col_aliases)
    95                     result.append('%s AS %s' % (r, c_alias))
    96                     aliases.add(c_alias)
    97                     col_aliases.add(c_alias)
    98                 else:
    99                     result.append(r)
    100                     aliases.add(r)
    101                     col_aliases.add(col)
     83                result.append(self._get_column_with_field(table, col, field,
     84                                                          with_aliases, col_aliases, aliases))
    10285
    10386        self._select_aliases = aliases
    10487        return result
    10588
    106     def get_default_columns(self, with_aliases=False, col_aliases=None,
    107                             start_alias=None, opts=None, as_pairs=False):
     89    def _get_column_with_field(self, table_alias, column_name, field, with_aliases, col_aliases, aliases):       
    10890        """
    109         Computes the default columns for selecting every field in the base
    110         model.
     91        Returns a string for a SQL select statement, containing a column name
     92        as well as a column alias if necessary.
     93       
     94        The col_aliases and aliases sets are modified as a side effect.
     95        """               
     96        r = self.get_field_select(field, table_alias)
     97       
     98        if with_aliases and column_name in col_aliases:
     99            c_alias = 'Col%d' % len(col_aliases)
     100           
     101            col_aliases.add(c_alias)
     102            aliases.add(c_alias)
     103           
     104            return '%s AS %s' % (r, c_alias)
     105        else:
     106            aliases.add(r)
     107            if with_aliases:
     108                col_aliases.add(column_name)   
     109               
     110            return r
    111111
    112         Returns a list of strings, quoted appropriately for use in SQL
    113         directly, as well as a set of aliases used in the select statement.
    114 
    115         This routine is overridden from Query to handle customized selection of
    116         geometry columns.
     112    def get_base_columns(self, with_aliases, col_aliases, aliases):
    117113        """
    118         result = []
    119         if opts is None:
    120             opts = self.model._meta
    121         if start_alias:
    122             table_alias = start_alias
    123         else:
    124             table_alias = self.tables[0]
    125         root_pk = self.model._meta.pk.column
    126         seen = {None: table_alias}
    127         aliases = set()
     114        Computes the columns to select on the base model. By default, this includes all
     115        fields.
     116
     117        Returns a list of strings, quoted appropriately for use in SQL directly.
     118        As a side effect, updates the aliases and col_aliases sets passed in as an argument.
     119       
     120        This routine is overridden from Query to handle customized selection of 
     121        geometry columns.
     122        """     
     123        result = []       
     124        opts = self.model._meta
     125        root_pk = opts.pk.column
     126        seen = {None: self.tables[0]}
     127       
    128128        for field, model in opts.get_fields_with_model():
    129             try:
    130                 alias = seen[model]
    131             except KeyError:
    132                 alias = self.join((table_alias, model._meta.db_table,
    133                         root_pk, model._meta.pk.column))
    134                 seen[model] = alias
    135             if as_pairs:
    136                 result.append((alias, field.column))
    137                 continue
    138             # This part of the function is customized for GeoQuery. We
    139             # see if there was any custom selection specified in the
    140             # dictionary, and set up the selection format appropriately.
    141             field_sel = self.get_field_select(field, alias)
    142             if with_aliases and field.column in col_aliases:
    143                 c_alias = 'Col%d' % len(col_aliases)
    144                 result.append('%s AS %s' % (field_sel, c_alias))
    145                 col_aliases.add(c_alias)
    146                 aliases.add(c_alias)
    147             else:
    148                 r = field_sel
    149                 result.append(r)
    150                 aliases.add(r)
    151                 if with_aliases:
    152                     col_aliases.add(field.column)
    153         if as_pairs:
    154             return result, None
    155         return result, aliases
     129            table_alias = self._get_table_alias(model, seen, root_pk)   
     130            result.append(self._get_column_with_field(table_alias, field.column, field,
     131                                           with_aliases, col_aliases, aliases))
     132               
     133        return result   
    156134
    157135    def get_ordering(self):
    158136        """
Back to Top