Django

Code

Changeset 8426

Show
Ignore:
Timestamp:
08/17/08 15:07:59 (5 months ago)
Author:
mtredinnick
Message:

Changed the (internal) way extra(select=.., select_params=...) handling is done
so that parameters stay with their select items. This means that merging and
trimming of those items is handled correctly.

Refs #7957, #7961. Fixed #8191.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/db/models/sql/query.py

    r8223 r8426  
    1212from django.utils.tree import Node 
    1313from django.utils.datastructures import SortedDict 
     14from django.utils.encoding import force_unicode 
    1415from django.db import connection 
    1516from django.db.models import signals 
     
    7879        # These are for extensions. The contents are more or less appended 
    7980        # verbatim to the appropriate clause. 
    80         self.extra_select = {}  # Maps col_alias -> col_sql. 
    81         self.extra_select_params = () 
     81        self.extra_select = SortedDict()  # Maps col_alias -> col_sql. 
    8282        self.extra_tables = () 
    8383        self.extra_where = () 
     
    182182        obj.max_depth = self.max_depth 
    183183        obj.extra_select = self.extra_select.copy() 
    184         obj.extra_select_params = self.extra_select_params 
    185184        obj.extra_tables = self.extra_tables 
    186185        obj.extra_where = self.extra_where 
     
    227226                    distinct=False) 
    228227            obj.select = [] 
    229             obj.extra_select = {} 
     228            obj.extra_select = SortedDict() 
    230229        obj.add_count_column() 
    231230        data = obj.execute_sql(SINGLE) 
     
    260259 
    261260        where, w_params = self.where.as_sql(qn=self.quote_name_unless_alias) 
    262         params = list(self.extra_select_params) 
     261        params = [] 
     262        for val in self.extra_select.itervalues(): 
     263            params.extend(val[1]) 
    263264 
    264265        result = ['SELECT'] 
     
    414415        qn = self.quote_name_unless_alias 
    415416        qn2 = self.connection.ops.quote_name 
    416         result = ['(%s) AS %s' % (col, qn2(alias)) for alias, col in self.extra_select.iteritems()] 
     417        result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in self.extra_select.iteritems()] 
    417418        aliases = set(self.extra_select.keys()) 
    418419        if with_aliases: 
     
    15111512        self.select_fields = [None] 
    15121513        self.extra_select = {} 
    1513         self.extra_select_params = () 
    15141514 
    15151515    def add_select_related(self, fields): 
     
    15341534        """ 
    15351535        if select: 
    1536             # The extra select might be ordered (because it will be accepting 
    1537             # parameters). 
    1538             if (isinstance(select, SortedDict) and 
    1539                     not isinstance(self.extra_select, SortedDict)): 
    1540                 self.extra_select = SortedDict(self.extra_select) 
    1541             self.extra_select.update(select) 
    1542         if select_params: 
    1543             self.extra_select_params += tuple(select_params) 
     1536            # We need to pair any placeholder markers in the 'select' 
     1537            # dictionary with their parameters in 'select_params' so that 
     1538            # subsequent updates to the select dictionary also adjust the 
     1539            # parameters appropriately. 
     1540            select_pairs = SortedDict() 
     1541            if select_params: 
     1542                param_iter = iter(select_params) 
     1543            else: 
     1544                param_iter = iter([]) 
     1545            for name, entry in select.items(): 
     1546                entry = force_unicode(entry) 
     1547                entry_params = [] 
     1548                pos = entry.find("%s") 
     1549                while pos != -1: 
     1550                    entry_params.append(param_iter.next()) 
     1551                    pos = entry.find("%s", pos + 2) 
     1552                select_pairs[name] = (entry, entry_params) 
     1553            # This is order preserving, since self.extra_select is a SortedDict. 
     1554            self.extra_select.update(select_pairs) 
    15441555        if where: 
    15451556            self.extra_where += tuple(where) 
  • django/trunk/docs/db-api.txt

    r8406 r8426  
    10151015            select_params=('one', 'two')) 
    10161016 
     1017    The only thing to be careful about when using select parameters in 
     1018    ``extra()`` is to avoid using the substring ``"%%s"`` (that's *two* 
     1019    percent characters before the ``s``) in the select strings. Django's 
     1020    tracking of parameters looks for ``%s`` and an escaped ``%`` character 
     1021    like this isn't detected. That will lead to incorrect results. 
     1022 
    10171023``where`` / ``tables`` 
    10181024    You can define explicit SQL ``WHERE`` clauses -- perhaps to perform 
  • django/trunk/tests/regressiontests/extra_regress/models.py

    r7832 r8426  
    11import copy 
    22 
     3from django.contrib.auth.models import User 
    34from django.db import models 
    45from django.db.models.query import Q 
     6from django.utils.datastructures import SortedDict 
    57 
    68 
     
    2426 
    2527__test__ = {"API_TESTS": """ 
    26 ### Regression tests for #7314 and #7372 
     28# Regression tests for #7314 and #7372 
    2729 
    2830>>> rm = RevisionableModel.objects.create(title='First Revision') 
     
    5355[<RevisionableModel: Second Revision (2, 1)>] 
    5456 
     57>>> u = User.objects.create_user(username="fred", password="secret", email="fred@example.com") 
     58 
     59# General regression tests: extra select parameters should stay tied to their 
     60# corresponding select portions. Applies when portions are updated or otherwise 
     61# moved around. 
     62>>> qs = User.objects.extra(select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), select_params=(1, 3)) 
     63>>> qs = qs.extra(select={"beta": 4}) 
     64>>> qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) 
     65>>> result = {'alpha': 5, 'beta': 4, 'gamma': 3} 
     66>>> list(qs.filter(id=u.id).values('alpha', 'beta', 'gamma')) == [result] 
     67True 
     68 
     69# Regression test for #7957: Combining extra() calls should leave the 
     70# corresponding parameters associated with the right extra() bit. I.e. internal 
     71# dictionary must remain sorted. 
     72>>> User.objects.extra(select={"alpha": "%s"}, select_params=(1,)).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha 
     731 
     74>>> User.objects.extra(select={"beta": "%s"}, select_params=(1,)).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha 
     752 
     76 
     77# Regression test for #7961: When not using a portion of an extra(...) in a 
     78# query, remove any corresponding parameters from the query as well. 
     79>>> list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,)).filter(id=u.id).values_list('id', flat=True)) == [u.id] 
     80True 
     81 
    5582"""}