Django

Code

Changeset 7340

Show
Ignore:
Timestamp:
03/20/08 14:16:04 (4 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Fixed up extra(select=...) calls with parameters so that the
parameters are substituted in correctly in all cases. This introduces an extra
argument to extra() for this purpose; no alternative there.

Also fixed values() to work if you don't specify *all* the extra select aliases
in the values() call.

Refs #3141.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/query.py

    r7284 r7340  
    143143            requested = None 
    144144        max_depth = self.query.max_depth 
    145         index_end = len(self.model._meta.fields) 
    146145        extra_select = self.query.extra_select.keys() 
     146        index_start = len(extra_select) 
    147147        for row in self.query.results_iter(): 
    148148            if fill_cache: 
    149                 obj, index_end = get_cached_row(self.model, row, 0, max_depth
    150                         requested=requested) 
     149                obj, _ = get_cached_row(self.model, row, index_start
     150                        max_depth, requested=requested) 
    151151            else: 
    152                 obj = self.model(*row[:index_end]) 
     152                obj = self.model(*row[index_start:]) 
    153153            for i, k in enumerate(extra_select): 
    154                 setattr(obj, k, row[index_end + i]) 
     154                setattr(obj, k, row[i]) 
    155155            yield obj 
    156156 
     
    414414 
    415415    def extra(self, select=None, where=None, params=None, tables=None, 
    416             order_by=None): 
     416            order_by=None, select_params=None): 
    417417        """ 
    418418        Add extra SQL fragments to the query. 
     
    421421                "Cannot change a query once a slice has been taken" 
    422422        clone = self._clone() 
    423         clone.query.add_extra(select, where, params, tables, order_by) 
     423        clone.query.add_extra(select, select_params, where, params, tables, order_by) 
    424424        return clone 
    425425 
     
    476476 
    477477    def iterator(self): 
    478         self.field_names.extend([f for f in self.query.extra_select.keys()]) 
     478        self.query.trim_extra_select(self.extra_names) 
     479        names = self.query.extra_select.keys() + self.field_names 
    479480        for row in self.query.results_iter(): 
    480             yield dict(zip(self.field_names, row)) 
     481            yield dict(zip(names, row)) 
    481482 
    482483    def _setup_query(self): 
     
    488489        instance. 
    489490        """ 
     491        self.extra_names = [] 
    490492        if self._fields: 
    491493            if not self.query.extra_select: 
     
    497499                    if f in names: 
    498500                        field_names.append(f) 
    499                     elif not self.query.extra_select.has_key(f): 
     501                    elif self.query.extra_select.has_key(f): 
     502                        self.extra_names.append(f) 
     503                    else: 
    500504                        raise FieldDoesNotExist('%s has no field named %r' 
    501505                                % (self.model._meta.object_name, f)) 
     
    514518        c = super(ValuesQuerySet, self)._clone(klass, **kwargs) 
    515519        c._fields = self._fields[:] 
    516         c.field_names = self.field_names[:] 
     520        c.field_names = self.field_names 
     521        c.extra_names = self.extra_names 
    517522        if setup and hasattr(c, '_setup_query'): 
    518523            c._setup_query() 
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r7327 r7340  
    7474        # verbatim to the appropriate clause. 
    7575        self.extra_select = {}  # Maps col_alias -> col_sql. 
     76        self.extra_select_params = () 
    7677        self.extra_tables = () 
    7778        self.extra_where = () 
     
    151152        obj.max_depth = self.max_depth 
    152153        obj.extra_select = self.extra_select.copy() 
     154        obj.extra_select_params = self.extra_select_params 
    153155        obj.extra_tables = self.extra_tables 
    154156        obj.extra_where = self.extra_where 
     
    215217        from_, f_params = self.get_from_clause() 
    216218        where, w_params = self.where.as_sql(qn=self.quote_name_unless_alias) 
     219        params = list(self.extra_select_params) 
    217220 
    218221        result = ['SELECT'] 
     
    223226        result.append('FROM') 
    224227        result.extend(from_) 
    225         params = list(f_params) 
     228        params.extend(f_params) 
    226229 
    227230        if where: 
     
    352355        """ 
    353356        qn = self.quote_name_unless_alias 
    354         result = [
    355         aliases = [] 
     357        result = ['(%s) AS %s' % (col, alias) for alias, col in self.extra_select.items()
     358        aliases = self.extra_select.keys() 
    356359        if self.select: 
    357360            for col in self.select: 
     
    365368                        aliases.append(col.alias) 
    366369        elif self.default_cols: 
    367             result = self.get_default_columns(True) 
    368             aliases = result[:] 
    369  
    370         result.extend(['(%s) AS %s' % (col, alias) 
    371                 for alias, col in self.extra_select.items()]) 
    372         aliases.extend(self.extra_select.keys()) 
     370            cols = self.get_default_columns(True) 
     371            result.extend(cols) 
     372            aliases.extend(cols) 
    373373 
    374374        self._select_aliases = set(aliases) 
     
    404404        """ 
    405405        Returns a list of strings that are joined together to go after the 
    406         "FROM" part of the query, as well as any extra parameters that need to 
    407         be included. Sub-classes, can override this to create a from-clause vi
    408         a "select", for example (e.g. CountQuery). 
     406        "FROM" part of the query, as well as a list any extra parameters that 
     407        need to be included. Sub-classes, can override this to create
     408        from-clause via a "select", for example (e.g. CountQuery). 
    409409 
    410410        This should only be called after any SQL construction methods that 
     
    12541254        self.select = [select] 
    12551255        self.extra_select = {} 
     1256        self.extra_select_params = () 
    12561257 
    12571258    def add_select_related(self, fields): 
     
    12681269        self.select_related = field_dict 
    12691270 
    1270     def add_extra(self, select, where, params, tables, order_by): 
     1271    def add_extra(self, select, select_params, where, params, tables, order_by): 
    12711272        """ 
    12721273        Adds data to the various extra_* attributes for user-created additions 
     
    12801281                self.extra_select = SortedDict(self.extra_select) 
    12811282            self.extra_select.update(select) 
     1283        if select_params: 
     1284            self.extra_select_params += tuple(select_params) 
    12821285        if where: 
    12831286            self.extra_where += tuple(where) 
     
    12891292            self.extra_order_by = order_by 
    12901293 
     1294    def trim_extra_select(self, names): 
     1295        """ 
     1296        Removes any aliases in the extra_select dictionary that aren't in 
     1297        'names'. 
     1298 
     1299        This is needed if we are selecting certain values that don't incldue 
     1300        all of the extra_select names. 
     1301        """ 
     1302        for key in set(self.extra_select).difference(set(names)): 
     1303            del self.extra_select[key] 
     1304 
    12911305    def set_start(self, start): 
    12921306        """ 
  • django/branches/queryset-refactor/docs/db-api.txt

    r7317 r7340  
    842842call, since they are conflicting options. 
    843843 
    844 ``extra(select=None, where=None, params=None, tables=None, order_by=None)`` 
    845 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     844``extra(select=None, where=None, params=None, tables=None, order_by=None, 
     845select_params=None)`` 
     846~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    846847 
    847848Sometimes, the Django query syntax by itself can't easily express a complex 
     
    902903    **New in Django development version** 
    903904    In some rare cases, you might wish to pass parameters to the SQL fragments 
    904     in ``extra(select=...)```. Since the ``params`` attribute is a sequence 
    905     and the ``select`` attribute is a dictionary, some care is required so 
    906     that the parameters are matched up correctly with the extra select pieces. 
    907     Firstly, in this situation, you should use a 
    908     ``django.utils.datastructures.SortedDict`` for the ``select`` value, not 
    909     just a normal Python dictionary. Secondly, make sure that your parameters 
    910     for the ``select`` come first in the list and that you have not passed any 
    911     parameters to an earlier ``extra()`` call for this queryset. 
    912  
    913     This will work:: 
     905    in ``extra(select=...)```. For this purpose, use the ``select_params`` 
     906    parameter. Since ``select_params`` is a sequence and the ``select`` 
     907    attribute is a dictionary, some care is required so that the parameters 
     908    are matched up correctly with the extra select pieces.  In this situation, 
     909    you should use a ``django.utils.datastructures.SortedDict`` for the 
     910    ``select`` value, not just a normal Python dictionary. 
     911 
     912    This will work, for example:: 
    914913 
    915914        Blog.objects.extra( 
    916915            select=SortedDict(('a', '%s'), ('b', '%s')), 
    917             params=('one', 'two')) 
    918  
    919     ... while this won't:: 
    920  
    921         # Will not work! 
    922         Blog.objects.extra(where=['foo=%s'], params=('bar',)).extra( 
    923             select=SortedDict(('a', '%s'), ('b', '%s')), 
    924             params=('one', 'two')) 
    925  
    926     In the second example, the earlier ``params`` usage will mess up the later 
    927     one. So always put your extra select pieces in the first ``extra()`` call 
    928     if you need to use parameters in them. 
     916            select_params=('one', 'two')) 
    929917 
    930918``where`` / ``tables`` 
     
    966954 
    967955``params`` 
    968     The ``select`` and ``where`` parameters described above may use standard 
    969     Python database string placeholders -- ``'%s'`` to indicate parameters th
    970     database engine should automatically quote. The ``params`` argument is
    971     list of any extra parameters to be substituted. 
     956    The ``where`` parameter described above may use standard Python database 
     957    string placeholders -- ``'%s'`` to indicate parameters the database engin
     958    should automatically quote. The ``params`` argument is a list of any extr
     959    parameters to be substituted. 
    972960 
    973961    Example:: 
     
    975963        Entry.objects.extra(where=['headline=%s'], params=['Lennon']) 
    976964 
    977     Always use ``params`` instead of embedding values directly into ``select`` 
    978     or ``where`` because ``params`` will ensure values are quoted correctly 
    979     according to your particular backend. (For example, quotes will be escaped 
    980     correctly.) 
     965    Always use ``params`` instead of embedding values directly into ``where`` 
     966    because ``params`` will ensure values are quoted correctly according to 
     967    your particular backend. (For example, quotes will be escaped correctly.) 
    981968 
    982969    Bad:: 
     
    988975        Entry.objects.extra(where=['headline=%s'], params=['Lennon']) 
    989976 
    990     The combined number of placeholders in the list of strings for ``select`` 
    991     or ``where`` should equal the number of values in the ``params`` list. 
     977**New in Django development version** The ``select_params`` argument to 
     978``extra()`` is new. Previously, you could attempt to pass parameters for 
     979``select`` in the ``params`` argument, but it worked very unreliably. 
    992980 
    993981QuerySet methods that do not return QuerySets 
  • django/branches/queryset-refactor/tests/regressiontests/queries/models.py

    r7317 r7340  
    283283>>> Item.objects.exclude(name='two').values('creator', 'name').distinct().count() 
    2842844 
     285>>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name', 'foo').distinct().count() 
     2864 
     287>>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name').distinct().count() 
     2884 
    285289>>> xx.delete() 
    286290 
     
    387391>>> Author.objects.extra(select={'foo': '1'}).count() 
    3883924 
     393>>> Author.objects.extra(select={'foo': '%s'}, select_params=(1,)).count() 
     3944 
    389395 
    390396Bug #2400 
     
    462468>>> qs.extra(order_by=('-good', 'id')) 
    463469[<Ranking: 3: a1>, <Ranking: 2: a2>, <Ranking: 1: a3>] 
     470 
     471# Despite having some extra aliases in the query, we can still omit them in a 
     472# values() query. 
     473>>> qs.values('id', 'rank').order_by('id') 
     474[{'id': 1, 'rank': 2}, {'id': 2, 'rank': 1}, {'id': 3, 'rank': 3}] 
    464475 
    465476Bugs #2874, #3002 
     
    534545# return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of 
    535546# using constants here and not a real concern. 
    536 >>> d = Item.objects.extra(select=SortedDict(s), params=params).values('a', 'b')[0] 
     547>>> d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0] 
    537548>>> d == {'a': u'one', 'b': u'two'} 
    538549True