Fixed #14700 - speed up RawQuerySet iterator.

This moves constant work out of the loop, and uses the much faster *args
based model instantiation where possible, to produce very large speed ups.

Thanks to akaariai for the report and patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14692 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2010-11-24 15:26:29 +00:00
parent 3abcd9d3e2
commit 073412b411
1 changed files with 61 additions and 46 deletions

View File

@ -1299,8 +1299,67 @@ class RawQuerySet(object):
self.translations = translations or {}
def __iter__(self):
for row in self.query:
yield self.transform_results(row)
# Mapping of attrnames to row column positions. Used for constructing
# the model using kwargs, needed when not all model's fields are present
# in the query.
model_init_field_names = {}
# A list of tuples of (column name, column position). Used for
# annotation fields.
annotation_fields = []
# Cache some things for performance reasons outside the loop.
db = self.db
compiler = connections[db].ops.compiler('SQLCompiler')(self.query, connections[db], db)
need_resolv_columns = hasattr(compiler, 'resolve_columns')
# Find out which columns are model's fields, and which ones should be
# annotated to the model.
for pos, column in enumerate(self.columns):
if column in self.model_fields:
model_init_field_names[self.model_fields[column].attname] = pos
else:
annotation_fields.append((column, pos))
# Find out which model's fields are not present in the query.
skip = set()
for field in self.model._meta.fields:
if field.attname not in model_init_field_names:
skip.add(field.attname)
if skip:
if self.model._meta.pk.attname in skip:
raise InvalidQuery('Raw query must include the primary key')
model_cls = deferred_class_factory(self.model, skip)
else:
model_cls = self.model
# All model's fields are present in the query. So, it is possible
# to use *args based model instantation. For each field of the model,
# record the query column position matching that field.
model_init_field_pos = []
for field in self.model._meta.fields:
model_init_field_pos.append(model_init_field_names[field.attname])
if need_resolv_columns:
fields = [self.model_fields.get(c, None) for c in self.columns]
# Begin looping through the query values.
for values in self.query:
if need_resolv_columns:
values = compiler.resolve_columns(values, fields)
# Associate fields to values
if skip:
model_init_kwargs = {}
for attname, pos in model_init_field_names.iteritems():
model_init_kwargs[attname] = values[pos]
instance = model_cls(**model_init_kwargs)
else:
model_init_args = [values[pos] for pos in model_init_field_pos]
instance = model_cls(*model_init_args)
if annotation_fields:
for column, pos in annotation_fields:
setattr(instance, column, values[pos])
instance._state.db = db
instance._state.adding = False
yield instance
def __repr__(self):
return "<RawQuerySet: %r>" % (self.raw_query % self.params)
@ -1355,50 +1414,6 @@ class RawQuerySet(object):
self._model_fields[converter(column)] = field
return self._model_fields
def transform_results(self, values):
model_init_kwargs = {}
annotations = ()
# Perform database backend type resolution
connection = connections[self.db]
compiler = connection.ops.compiler('SQLCompiler')(self.query, connection, self.db)
if hasattr(compiler, 'resolve_columns'):
fields = [self.model_fields.get(c,None) for c in self.columns]
values = compiler.resolve_columns(values, fields)
# Associate fields to values
for pos, value in enumerate(values):
column = self.columns[pos]
# Separate properties from annotations
if column in self.model_fields.keys():
model_init_kwargs[self.model_fields[column].attname] = value
else:
annotations += (column, value),
# Construct model instance and apply annotations
skip = set()
for field in self.model._meta.fields:
if field.attname not in model_init_kwargs.keys():
skip.add(field.attname)
if skip:
if self.model._meta.pk.attname in skip:
raise InvalidQuery('Raw query must include the primary key')
model_cls = deferred_class_factory(self.model, skip)
else:
model_cls = self.model
instance = model_cls(**model_init_kwargs)
for field, value in annotations:
setattr(instance, field, value)
instance._state.db = self.query.using
instance._state.adding = False
return instance
def insert_query(model, values, return_id=False, raw_values=False, using=None):
"""
Inserts a new record for the given model. This provides an interface to