web/lib/django/db/models/sql/subqueries.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Query subclasses which provide extra functionality beyond simple data retrieval.
       
     3 """
       
     4 
       
     5 from django.core.exceptions import FieldError
       
     6 from django.db.models.sql.constants import *
       
     7 from django.db.models.sql.datastructures import Date
       
     8 from django.db.models.sql.expressions import SQLEvaluator
       
     9 from django.db.models.sql.query import Query
       
    10 from django.db.models.sql.where import AND, Constraint
       
    11 
       
    12 __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery',
       
    13         'AggregateQuery']
       
    14 
       
    15 class DeleteQuery(Query):
       
    16     """
       
    17     Delete queries are done through this class, since they are more constrained
       
    18     than general queries.
       
    19     """
       
    20     def as_sql(self):
       
    21         """
       
    22         Creates the SQL for this query. Returns the SQL string and list of
       
    23         parameters.
       
    24         """
       
    25         assert len(self.tables) == 1, \
       
    26                 "Can only delete from one table at a time."
       
    27         result = ['DELETE FROM %s' % self.quote_name_unless_alias(self.tables[0])]
       
    28         where, params = self.where.as_sql()
       
    29         result.append('WHERE %s' % where)
       
    30         return ' '.join(result), tuple(params)
       
    31 
       
    32     def do_query(self, table, where):
       
    33         self.tables = [table]
       
    34         self.where = where
       
    35         self.execute_sql(None)
       
    36 
       
    37     def delete_batch_related(self, pk_list):
       
    38         """
       
    39         Set up and execute delete queries for all the objects related to the
       
    40         primary key values in pk_list. To delete the objects themselves, use
       
    41         the delete_batch() method.
       
    42 
       
    43         More than one physical query may be executed if there are a
       
    44         lot of values in pk_list.
       
    45         """
       
    46         from django.contrib.contenttypes import generic
       
    47         cls = self.model
       
    48         for related in cls._meta.get_all_related_many_to_many_objects():
       
    49             if not isinstance(related.field, generic.GenericRelation):
       
    50                 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
       
    51                     where = self.where_class()
       
    52                     where.add((Constraint(None,
       
    53                             related.field.m2m_reverse_name(), related.field),
       
    54                             'in',
       
    55                             pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
       
    56                             AND)
       
    57                     self.do_query(related.field.m2m_db_table(), where)
       
    58 
       
    59         for f in cls._meta.many_to_many:
       
    60             w1 = self.where_class()
       
    61             if isinstance(f, generic.GenericRelation):
       
    62                 from django.contrib.contenttypes.models import ContentType
       
    63                 field = f.rel.to._meta.get_field(f.content_type_field_name)
       
    64                 w1.add((Constraint(None, field.column, field), 'exact',
       
    65                         ContentType.objects.get_for_model(cls).id), AND)
       
    66             for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
       
    67                 where = self.where_class()
       
    68                 where.add((Constraint(None, f.m2m_column_name(), f), 'in',
       
    69                         pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
       
    70                         AND)
       
    71                 if w1:
       
    72                     where.add(w1, AND)
       
    73                 self.do_query(f.m2m_db_table(), where)
       
    74 
       
    75     def delete_batch(self, pk_list):
       
    76         """
       
    77         Set up and execute delete queries for all the objects in pk_list. This
       
    78         should be called after delete_batch_related(), if necessary.
       
    79 
       
    80         More than one physical query may be executed if there are a
       
    81         lot of values in pk_list.
       
    82         """
       
    83         for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
       
    84             where = self.where_class()
       
    85             field = self.model._meta.pk
       
    86             where.add((Constraint(None, field.column, field), 'in',
       
    87                     pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
       
    88             self.do_query(self.model._meta.db_table, where)
       
    89 
       
    90 class UpdateQuery(Query):
       
    91     """
       
    92     Represents an "update" SQL query.
       
    93     """
       
    94     def __init__(self, *args, **kwargs):
       
    95         super(UpdateQuery, self).__init__(*args, **kwargs)
       
    96         self._setup_query()
       
    97 
       
    98     def _setup_query(self):
       
    99         """
       
   100         Runs on initialization and after cloning. Any attributes that would
       
   101         normally be set in __init__ should go in here, instead, so that they
       
   102         are also set up after a clone() call.
       
   103         """
       
   104         self.values = []
       
   105         self.related_ids = None
       
   106         if not hasattr(self, 'related_updates'):
       
   107             self.related_updates = {}
       
   108 
       
   109     def clone(self, klass=None, **kwargs):
       
   110         return super(UpdateQuery, self).clone(klass,
       
   111                 related_updates=self.related_updates.copy(), **kwargs)
       
   112 
       
   113     def execute_sql(self, result_type=None):
       
   114         """
       
   115         Execute the specified update. Returns the number of rows affected by
       
   116         the primary update query. The "primary update query" is the first
       
   117         non-empty query that is executed. Row counts for any subsequent,
       
   118         related queries are not available.
       
   119         """
       
   120         cursor = super(UpdateQuery, self).execute_sql(result_type)
       
   121         rows = cursor and cursor.rowcount or 0
       
   122         is_empty = cursor is None
       
   123         del cursor
       
   124         for query in self.get_related_updates():
       
   125             aux_rows = query.execute_sql(result_type)
       
   126             if is_empty:
       
   127                 rows = aux_rows
       
   128                 is_empty = False
       
   129         return rows
       
   130 
       
   131     def as_sql(self):
       
   132         """
       
   133         Creates the SQL for this query. Returns the SQL string and list of
       
   134         parameters.
       
   135         """
       
   136         self.pre_sql_setup()
       
   137         if not self.values:
       
   138             return '', ()
       
   139         table = self.tables[0]
       
   140         qn = self.quote_name_unless_alias
       
   141         result = ['UPDATE %s' % qn(table)]
       
   142         result.append('SET')
       
   143         values, update_params = [], []
       
   144         for name, val, placeholder in self.values:
       
   145             if hasattr(val, 'as_sql'):
       
   146                 sql, params = val.as_sql(qn)
       
   147                 values.append('%s = %s' % (qn(name), sql))
       
   148                 update_params.extend(params)
       
   149             elif val is not None:
       
   150                 values.append('%s = %s' % (qn(name), placeholder))
       
   151                 update_params.append(val)
       
   152             else:
       
   153                 values.append('%s = NULL' % qn(name))
       
   154         result.append(', '.join(values))
       
   155         where, params = self.where.as_sql()
       
   156         if where:
       
   157             result.append('WHERE %s' % where)
       
   158         return ' '.join(result), tuple(update_params + params)
       
   159 
       
   160     def pre_sql_setup(self):
       
   161         """
       
   162         If the update depends on results from other tables, we need to do some
       
   163         munging of the "where" conditions to match the format required for
       
   164         (portable) SQL updates. That is done here.
       
   165 
       
   166         Further, if we are going to be running multiple updates, we pull out
       
   167         the id values to update at this point so that they don't change as a
       
   168         result of the progressive updates.
       
   169         """
       
   170         self.select_related = False
       
   171         self.clear_ordering(True)
       
   172         super(UpdateQuery, self).pre_sql_setup()
       
   173         count = self.count_active_tables()
       
   174         if not self.related_updates and count == 1:
       
   175             return
       
   176 
       
   177         # We need to use a sub-select in the where clause to filter on things
       
   178         # from other tables.
       
   179         query = self.clone(klass=Query)
       
   180         query.bump_prefix()
       
   181         query.extra = {}
       
   182         query.select = []
       
   183         query.add_fields([query.model._meta.pk.name])
       
   184         must_pre_select = count > 1 and not self.connection.features.update_can_self_select
       
   185 
       
   186         # Now we adjust the current query: reset the where clause and get rid
       
   187         # of all the tables we don't need (since they're in the sub-select).
       
   188         self.where = self.where_class()
       
   189         if self.related_updates or must_pre_select:
       
   190             # Either we're using the idents in multiple update queries (so
       
   191             # don't want them to change), or the db backend doesn't support
       
   192             # selecting from the updating table (e.g. MySQL).
       
   193             idents = []
       
   194             for rows in query.execute_sql(MULTI):
       
   195                 idents.extend([r[0] for r in rows])
       
   196             self.add_filter(('pk__in', idents))
       
   197             self.related_ids = idents
       
   198         else:
       
   199             # The fast path. Filters and updates in one query.
       
   200             self.add_filter(('pk__in', query))
       
   201         for alias in self.tables[1:]:
       
   202             self.alias_refcount[alias] = 0
       
   203 
       
   204     def clear_related(self, related_field, pk_list):
       
   205         """
       
   206         Set up and execute an update query that clears related entries for the
       
   207         keys in pk_list.
       
   208 
       
   209         This is used by the QuerySet.delete_objects() method.
       
   210         """
       
   211         for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
       
   212             self.where = self.where_class()
       
   213             f = self.model._meta.pk
       
   214             self.where.add((Constraint(None, f.column, f), 'in',
       
   215                     pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
       
   216                     AND)
       
   217             self.values = [(related_field.column, None, '%s')]
       
   218             self.execute_sql(None)
       
   219 
       
   220     def add_update_values(self, values):
       
   221         """
       
   222         Convert a dictionary of field name to value mappings into an update
       
   223         query. This is the entry point for the public update() method on
       
   224         querysets.
       
   225         """
       
   226         values_seq = []
       
   227         for name, val in values.iteritems():
       
   228             field, model, direct, m2m = self.model._meta.get_field_by_name(name)
       
   229             if not direct or m2m:
       
   230                 raise FieldError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field)
       
   231             values_seq.append((field, model, val))
       
   232         return self.add_update_fields(values_seq)
       
   233 
       
   234     def add_update_fields(self, values_seq):
       
   235         """
       
   236         Turn a sequence of (field, model, value) triples into an update query.
       
   237         Used by add_update_values() as well as the "fast" update path when
       
   238         saving models.
       
   239         """
       
   240         from django.db.models.base import Model
       
   241         for field, model, val in values_seq:
       
   242             if hasattr(val, 'prepare_database_save'):
       
   243                 val = val.prepare_database_save(field)
       
   244             else:
       
   245                 val = field.get_db_prep_save(val)
       
   246 
       
   247             # Getting the placeholder for the field.
       
   248             if hasattr(field, 'get_placeholder'):
       
   249                 placeholder = field.get_placeholder(val)
       
   250             else:
       
   251                 placeholder = '%s'
       
   252 
       
   253             if hasattr(val, 'evaluate'):
       
   254                 val = SQLEvaluator(val, self, allow_joins=False)
       
   255             if model:
       
   256                 self.add_related_update(model, field.column, val, placeholder)
       
   257             else:
       
   258                 self.values.append((field.column, val, placeholder))
       
   259 
       
   260     def add_related_update(self, model, column, value, placeholder):
       
   261         """
       
   262         Adds (name, value) to an update query for an ancestor model.
       
   263 
       
   264         Updates are coalesced so that we only run one update query per ancestor.
       
   265         """
       
   266         try:
       
   267             self.related_updates[model].append((column, value, placeholder))
       
   268         except KeyError:
       
   269             self.related_updates[model] = [(column, value, placeholder)]
       
   270 
       
   271     def get_related_updates(self):
       
   272         """
       
   273         Returns a list of query objects: one for each update required to an
       
   274         ancestor model. Each query will have the same filtering conditions as
       
   275         the current query but will only update a single table.
       
   276         """
       
   277         if not self.related_updates:
       
   278             return []
       
   279         result = []
       
   280         for model, values in self.related_updates.iteritems():
       
   281             query = UpdateQuery(model, self.connection)
       
   282             query.values = values
       
   283             if self.related_ids:
       
   284                 query.add_filter(('pk__in', self.related_ids))
       
   285             result.append(query)
       
   286         return result
       
   287 
       
   288 class InsertQuery(Query):
       
   289     def __init__(self, *args, **kwargs):
       
   290         super(InsertQuery, self).__init__(*args, **kwargs)
       
   291         self.columns = []
       
   292         self.values = []
       
   293         self.params = ()
       
   294         self.return_id = False
       
   295 
       
   296     def clone(self, klass=None, **kwargs):
       
   297         extras = {'columns': self.columns[:], 'values': self.values[:],
       
   298                   'params': self.params, 'return_id': self.return_id}
       
   299         extras.update(kwargs)
       
   300         return super(InsertQuery, self).clone(klass, **extras)
       
   301 
       
   302     def as_sql(self):
       
   303         # We don't need quote_name_unless_alias() here, since these are all
       
   304         # going to be column names (so we can avoid the extra overhead).
       
   305         qn = self.connection.ops.quote_name
       
   306         opts = self.model._meta
       
   307         result = ['INSERT INTO %s' % qn(opts.db_table)]
       
   308         result.append('(%s)' % ', '.join([qn(c) for c in self.columns]))
       
   309         result.append('VALUES (%s)' % ', '.join(self.values))
       
   310         params = self.params
       
   311         if self.return_id and self.connection.features.can_return_id_from_insert:
       
   312             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
       
   313             r_fmt, r_params = self.connection.ops.return_insert_id()
       
   314             result.append(r_fmt % col)
       
   315             params = params + r_params
       
   316         return ' '.join(result), params
       
   317 
       
   318     def execute_sql(self, return_id=False):
       
   319         self.return_id = return_id
       
   320         cursor = super(InsertQuery, self).execute_sql(None)
       
   321         if not (return_id and cursor):
       
   322             return
       
   323         if self.connection.features.can_return_id_from_insert:
       
   324             return self.connection.ops.fetch_returned_insert_id(cursor)
       
   325         return self.connection.ops.last_insert_id(cursor,
       
   326                 self.model._meta.db_table, self.model._meta.pk.column)
       
   327 
       
   328     def insert_values(self, insert_values, raw_values=False):
       
   329         """
       
   330         Set up the insert query from the 'insert_values' dictionary. The
       
   331         dictionary gives the model field names and their target values.
       
   332 
       
   333         If 'raw_values' is True, the values in the 'insert_values' dictionary
       
   334         are inserted directly into the query, rather than passed as SQL
       
   335         parameters. This provides a way to insert NULL and DEFAULT keywords
       
   336         into the query, for example.
       
   337         """
       
   338         placeholders, values = [], []
       
   339         for field, val in insert_values:
       
   340             if hasattr(field, 'get_placeholder'):
       
   341                 # Some fields (e.g. geo fields) need special munging before
       
   342                 # they can be inserted.
       
   343                 placeholders.append(field.get_placeholder(val))
       
   344             else:
       
   345                 placeholders.append('%s')
       
   346 
       
   347             self.columns.append(field.column)
       
   348             values.append(val)
       
   349         if raw_values:
       
   350             self.values.extend(values)
       
   351         else:
       
   352             self.params += tuple(values)
       
   353             self.values.extend(placeholders)
       
   354 
       
   355 class DateQuery(Query):
       
   356     """
       
   357     A DateQuery is a normal query, except that it specifically selects a single
       
   358     date field. This requires some special handling when converting the results
       
   359     back to Python objects, so we put it in a separate class.
       
   360     """
       
   361     def __getstate__(self):
       
   362         """
       
   363         Special DateQuery-specific pickle handling.
       
   364         """
       
   365         for elt in self.select:
       
   366             if isinstance(elt, Date):
       
   367                 # Eliminate a method reference that can't be pickled. The
       
   368                 # __setstate__ method restores this.
       
   369                 elt.date_sql_func = None
       
   370         return super(DateQuery, self).__getstate__()
       
   371 
       
   372     def __setstate__(self, obj_dict):
       
   373         super(DateQuery, self).__setstate__(obj_dict)
       
   374         for elt in self.select:
       
   375             if isinstance(elt, Date):
       
   376                 self.date_sql_func = self.connection.ops.date_trunc_sql
       
   377 
       
   378     def results_iter(self):
       
   379         """
       
   380         Returns an iterator over the results from executing this query.
       
   381         """
       
   382         resolve_columns = hasattr(self, 'resolve_columns')
       
   383         if resolve_columns:
       
   384             from django.db.models.fields import DateTimeField
       
   385             fields = [DateTimeField()]
       
   386         else:
       
   387             from django.db.backends.util import typecast_timestamp
       
   388             needs_string_cast = self.connection.features.needs_datetime_string_cast
       
   389 
       
   390         offset = len(self.extra_select)
       
   391         for rows in self.execute_sql(MULTI):
       
   392             for row in rows:
       
   393                 date = row[offset]
       
   394                 if resolve_columns:
       
   395                     date = self.resolve_columns(row, fields)[offset]
       
   396                 elif needs_string_cast:
       
   397                     date = typecast_timestamp(str(date))
       
   398                 yield date
       
   399 
       
   400     def add_date_select(self, field, lookup_type, order='ASC'):
       
   401         """
       
   402         Converts the query into a date extraction query.
       
   403         """
       
   404         result = self.setup_joins([field.name], self.get_meta(),
       
   405                 self.get_initial_alias(), False)
       
   406         alias = result[3][-1]
       
   407         select = Date((alias, field.column), lookup_type,
       
   408                 self.connection.ops.date_trunc_sql)
       
   409         self.select = [select]
       
   410         self.select_fields = [None]
       
   411         self.select_related = False # See #7097.
       
   412         self.extra = {}
       
   413         self.distinct = True
       
   414         self.order_by = order == 'ASC' and [1] or [-1]
       
   415 
       
   416 class AggregateQuery(Query):
       
   417     """
       
   418     An AggregateQuery takes another query as a parameter to the FROM
       
   419     clause and only selects the elements in the provided list.
       
   420     """
       
   421     def add_subquery(self, query):
       
   422         self.subquery, self.sub_params = query.as_sql(with_col_aliases=True)
       
   423 
       
   424     def as_sql(self, quote_func=None):
       
   425         """
       
   426         Creates the SQL for this query. Returns the SQL string and list of
       
   427         parameters.
       
   428         """
       
   429         sql = ('SELECT %s FROM (%s) subquery' % (
       
   430             ', '.join([
       
   431                 aggregate.as_sql()
       
   432                 for aggregate in self.aggregate_select.values()
       
   433             ]),
       
   434             self.subquery)
       
   435         )
       
   436         params = self.sub_params
       
   437         return (sql, params)