Ticket #7210: 7210-F-support.patch

File 7210-F-support.patch, 14.5 KB (added by Collin Grady, 16 years ago)

updated patch to r7631

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

     
    44import datetime
    55
    66from django.utils import tree
     7from django.utils.functional import curry
    78from django.db import connection
    89from django.db.models.fields import Field
    910from django.db.models.query_utils import QueryWrapper
     11from django.db.models.sql.expressions import Expression, Literal
    1012from datastructures import EmptyResultSet, FullResultSet
    1113
    1214# Connection types
     
    2628    """
    2729    default = AND
    2830
    29     def as_sql(self, node=None, qn=None):
     31    def as_sql(self, opts, node=None, qn=None):
    3032        """
    3133        Returns the SQL version of the where clause and the value to be
    3234        substituted in. Returns None, None if this node is empty.
     
    4749        for child in node.children:
    4850            try:
    4951                if hasattr(child, 'as_sql'):
    50                     sql, params = child.as_sql(qn=qn)
     52                    sql, params = child.as_sql(opts, qn=qn)
    5153                    format = '(%s)'
    5254                elif isinstance(child, tree.Node):
    53                     sql, params = self.as_sql(child, qn)
     55                    sql, params = self.as_sql(opts, child, qn)
    5456                    if child.negated:
    5557                        format = 'NOT (%s)'
    5658                    elif len(child.children) == 1:
     
    5860                    else:
    5961                        format = '(%s)'
    6062                else:
    61                     sql, params = self.make_atom(child, qn)
     63                    sql, params = self.make_atom(opts, child, qn)
    6264                    format = '%s'
    6365            except EmptyResultSet:
    6466                if node.connector == AND and not node.negated:
     
    8688        conn = ' %s ' % node.connector
    8789        return conn.join(result), result_params
    8890
    89     def make_atom(self, child, qn):
     91    def make_atom(self, opts, child, qn):
    9092        """
    9193        Turn a tuple (table_alias, field_name, field_class, lookup_type, value)
    9294        into valid SQL.
     
    99101            lhs = '%s.%s' % (qn(table_alias), qn(name))
    100102        else:
    101103            lhs = qn(name)
     104        if not field:
     105            field = Field()
    102106        db_type = field and field.db_type() or None
    103107        field_sql = connection.ops.field_cast_sql(db_type) % lhs
     108        prep_func = curry(field.get_db_prep_lookup, lookup_type)
    104109
    105         if isinstance(value, datetime.datetime):
    106             cast_sql = connection.ops.datetime_cast_sql()
    107         else:
    108             cast_sql = '%s'
     110        if lookup_type in connection.operators:
     111            if isinstance(value, Expression):
     112                sql, params = value.as_sql(opts, field, prep_func, qn)
     113            else:
     114                sql, params = Literal(value).as_sql(opts, field, prep_func, qn)
    109115
    110         if field:
    111             params = field.get_db_prep_lookup(lookup_type, value)
    112         else:
    113             params = Field().get_db_prep_lookup(lookup_type, value)
     116            format = '%s %s' % (
     117                connection.ops.lookup_cast(lookup_type),
     118                connection.operators[lookup_type])
     119            return format % (field_sql, sql), params
     120
     121        if isinstance(value, Expression):
     122            raise TypeError('Invalid lookup_type for use with %s object: %r' % (
     123                value.__class__.__name__, lookup_type))
     124
     125        params = prep_func(value)
    114126        if isinstance(params, QueryWrapper):
    115127            extra, params = params.data
    116128        else:
    117129            extra = ''
    118130
    119         if lookup_type in connection.operators:
    120             format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type),
    121                     extra)
    122             return (format % (field_sql,
    123                     connection.operators[lookup_type] % cast_sql), params)
    124 
    125131        if lookup_type == 'in':
    126132            if not value:
    127133                raise EmptyResultSet
    128134            if extra:
    129135                return ('%s IN %s' % (field_sql, extra), params)
    130             return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))),
    131                     params)
     136            return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))), params)
    132137        elif lookup_type in ('range', 'year'):
    133138            return ('%s BETWEEN %%s and %%s' % field_sql, params)
    134139        elif lookup_type in ('month', 'day'):
     
    164169    """
    165170    A node that matches everything.
    166171    """
    167     def as_sql(self, qn=None):
     172    def as_sql(self, opts, qn=None):
    168173        raise FullResultSet
    169174
    170175    def relabel_aliases(self, change_map, node=None):
  • django/db/models/sql/query.py

     
    250250        # get_from_clause() for details.
    251251        from_, f_params = self.get_from_clause()
    252252
    253         where, w_params = self.where.as_sql(qn=self.quote_name_unless_alias)
     253        where, w_params = self.where.as_sql(self.get_meta(), qn=self.quote_name_unless_alias)
    254254        params = list(self.extra_select_params)
    255255
    256256        result = ['SELECT']
  • django/db/models/sql/subqueries.py

     
    88from django.db.models.sql.datastructures import RawValue, Date
    99from django.db.models.sql.query import Query
    1010from django.db.models.sql.where import AND
     11from django.db.models.sql.expressions import Expression, Literal
    1112
    1213__all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery',
    1314        'CountQuery']
     
    2526        assert len(self.tables) == 1, \
    2627                "Can only delete from one table at a time."
    2728        result = ['DELETE FROM %s' % self.quote_name_unless_alias(self.tables[0])]
    28         where, params = self.where.as_sql()
     29        where, params = self.where.as_sql(self.get_meta())
    2930        result.append('WHERE %s' % where)
    3031        return ' '.join(result), tuple(params)
    3132
     
    126127        result = ['UPDATE %s' % qn(table)]
    127128        result.append('SET')
    128129        values, update_params = [], []
    129         for name, val, placeholder in self.values:
    130             if val is not None:
    131                 values.append('%s = %s' % (qn(name), placeholder))
    132                 update_params.append(val)
    133             else:
    134                 values.append('%s = NULL' % qn(name))
     130        for name, sql, params in self.values:
     131            values.append('%s = %s' % (qn(name), sql))
     132            update_params.extend(params)
    135133        result.append(', '.join(values))
    136         where, params = self.where.as_sql()
     134        where, params = self.where.as_sql(self.get_meta())
    137135        if where:
    138136            result.append('WHERE %s' % where)
    139137        return ' '.join(result), tuple(update_params + params)
     
    207205            self.where.add((None, f.column, f, 'in',
    208206                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    209207                    AND)
    210             self.values = [(related_field.column, None, '%s')]
     208            self.values = [(related_field.column, 'NULL', ())]
    211209            self.execute_sql(None)
    212210
    213211    def add_update_values(self, values):
     
    232230        """
    233231        from django.db.models.base import Model
    234232        for field, model, val in values_seq:
    235             # FIXME: Some sort of db_prep_* is probably more appropriate here.
    236             if field.rel and isinstance(val, Model):
    237                 val = val.pk
    238 
    239             # Getting the placeholder for the field.
    240             if hasattr(field, 'get_placeholder'):
    241                 placeholder = field.get_placeholder(val)
     233            if isinstance(val, Expression):
     234                expr = val
    242235            else:
    243                 placeholder = '%s'
     236                expr = Literal(val)
    244237
     238            sql, params = expr.as_sql(
     239                self.get_meta(),
     240                field,
     241                lambda x: field.get_db_prep_lookup('exact', field.get_db_prep_save(x)),
     242                self.connection.ops.quote_name)
     243
    245244            if model:
    246                 self.add_related_update(model, field.column, val, placeholder)
     245                self.add_related_update(model, field.column, sql, params)
    247246            else:
    248                 self.values.append((field.column, val, placeholder))
     247                self.values.append((field.column, sql, params))
    249248
    250     def add_related_update(self, model, column, value, placeholder):
     249    def add_related_update(self, model, column, sql, params):
    251250        """
    252251        Adds (name, value) to an update query for an ancestor model.
    253252
    254253        Updates are coalesced so that we only run one update query per ancestor.
    255254        """
    256255        try:
    257             self.related_updates[model].append((column, value, placeholder))
     256            self.related_updates[model].append((column, sql, params))
    258257        except KeyError:
    259             self.related_updates[model] = [(column, value, placeholder)]
     258            self.related_updates[model] = [(column, sql, params)]
    260259
    261260    def get_related_updates(self):
    262261        """
     
    312311        parameters. This provides a way to insert NULL and DEFAULT keywords
    313312        into the query, for example.
    314313        """
    315         placeholders, values = [], []
     314        values = []
    316315        for field, val in insert_values:
    317             if hasattr(field, 'get_placeholder'):
    318                 # Some fields (e.g. geo fields) need special munging before
    319                 # they can be inserted.
    320                 placeholders.append(field.get_placeholder(val))
    321             else:
    322                 placeholders.append('%s')
    323 
    324316            self.columns.append(field.column)
    325317            values.append(val)
    326318        if raw_values:
    327319            self.values.extend(values)
    328320        else:
    329321            self.params += tuple(values)
    330             self.values.extend(placeholders)
     322            self.values.extend(['%s'] * len(values))
    331323
    332324class DateQuery(Query):
    333325    """
  • django/db/models/__init__.py

     
    44from django.db import connection
    55from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
    66from django.db.models.query import Q
     7from django.db.models.sql.expressions import F
    78from django.db.models.manager import Manager
    89from django.db.models.base import Model, AdminOptions
    910from django.db.models.fields import *
  • tests/modeltests/update/models.py

     
    44"""
    55
    66from django.db import models
     7from django.conf import settings
    78
    8 class DataPoint(models.Model):
     9class Product(models.Model):
    910    name = models.CharField(max_length=20)
    10     value = models.CharField(max_length=20)
    11     another_value = models.CharField(max_length=20, blank=True)
     11    description = models.CharField(max_length=20)
     12    expires = models.DateTimeField(null=True)
    1213
    1314    def __unicode__(self):
    1415        return unicode(self.name)
    1516
    16 class RelatedPoint(models.Model):
     17class RelatedProduct(models.Model):
    1718    name = models.CharField(max_length=20)
    18     data = models.ForeignKey(DataPoint)
     19    data = models.ForeignKey(Product)
    1920
    2021    def __unicode__(self):
    2122        return unicode(self.name)
    2223
    2324
    2425__test__ = {'API_TESTS': """
    25 >>> DataPoint(name="d0", value="apple").save()
    26 >>> DataPoint(name="d2", value="banana").save()
    27 >>> d3 = DataPoint(name="d3", value="banana")
    28 >>> d3.save()
    29 >>> RelatedPoint(name="r1", data=d3).save()
     26>>> from datetime import datetime
    3027
     28>>> Product(name="p0", description="apple").save()
     29>>> Product(name="p2", description="banana").save()
     30>>> p3 = Product(name="p3", description="banana")
     31>>> p3.save()
     32>>> RelatedProduct(name="r1", data=p3).save()
     33
    3134Objects are updated by first filtering the candidates into a queryset and then
    3235calling the update() method. It executes immediately and returns nothing.
    3336
    34 >>> DataPoint.objects.filter(value="apple").update(name="d1")
    35 >>> DataPoint.objects.filter(value="apple")
    36 [<DataPoint: d1>]
     37>>> Product.objects.filter(description="apple").update(name="p1")
     38>>> Product.objects.filter(description="apple")
     39[<Product: p1>]
    3740
    3841We can update multiple objects at once.
    3942
    40 >>> DataPoint.objects.filter(value="banana").update(value="pineapple")
    41 >>> DataPoint.objects.get(name="d2").value
     43>>> Product.objects.filter(description="banana").update(description="pineapple")
     44>>> Product.objects.get(name="p2").description
    4245u'pineapple'
    4346
    4447Foreign key fields can also be updated, although you can only update the object
    4548referred to, not anything inside the related object.
    4649
    47 >>> d = DataPoint.objects.get(name="d1")
    48 >>> RelatedPoint.objects.filter(name="r1").update(data=d)
    49 >>> RelatedPoint.objects.filter(data__name="d1")
    50 [<RelatedPoint: r1>]
     50>>> p = Product.objects.get(name="p1")
     51>>> RelatedProduct.objects.filter(name="r1").update(data=p)
     52>>> RelatedProduct.objects.filter(data__name="p1")
     53[<RelatedProduct: r1>]
    5154
    52 Multiple fields can be updated at once
     55Multiple fields can be updated at once. If DATABASE_ENGINE is mysql microseconds
     56must be truncated.
    5357
    54 >>> DataPoint.objects.filter(value="pineapple").update(value="fruit", another_value="peaches")
    55 >>> d = DataPoint.objects.get(name="d2")
    56 >>> d.value, d.another_value
    57 (u'fruit', u'peaches')
     58>>> Product.objects.filter(description="pineapple").update(
     59...     description="fruit",
     60...     expires=datetime(2010, 1, 1, 12, 0, 0, 123456))
     61>>> p = Product.objects.get(name="p2")
     62>>> p.description, p.expires
     63"""}
    5864
     65if settings.DATABASE_ENGINE == 'mysql':
     66    __test__['API_TESTS'] += "(u'fruit', datetime.datetime(2010, 1, 1, 12, 0))"
     67else:
     68    __test__['API_TESTS'] += "(u'fruit', datetime.datetime(2010, 1, 1, 12, 0, 0, 123456))"
     69
     70__test__['API_TESTS'] += """
     71
    5972In the rare case you want to update every instance of a model, update() is also
    60 a manager method.
     73a manager method and update with None works as well.
    6174
    62 >>> DataPoint.objects.update(value='thing')
    63 >>> DataPoint.objects.values('value').distinct()
    64 [{'value': u'thing'}]
     75>>> Product.objects.update(expires=None)
     76>>> Product.objects.values('expires').distinct()
     77[{'expires': None}]
    6578
    6679We do not support update on already sliced query sets.
    6780
     
    7184AssertionError: Cannot update a query once a slice has been taken.
    7285
    7386"""
    74 }
Back to Top