From d4956cb42784770b319e5fdadd69add792c864a9 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 20 Apr 2008 08:47:21 +0000 Subject: [PATCH] queryset-refactor: Added faster paths for updates and inserts that are done from other core code. This saves a round-trip from field object to field name and back to field object when we already have the right information to hand. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7437 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/base.py | 15 +++++++-------- django/db/models/manager.py | 7 +++++-- django/db/models/query.py | 21 +++++++++++++++++---- django/db/models/sql/subqueries.py | 26 ++++++++++++++++++-------- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 40af8a0ed9..33ed01a9ef 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -308,29 +308,28 @@ class Model(object): if manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by(): # It does already exist, so do an UPDATE. if non_pks: - values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] - manager.filter(pk=pk_val).update(**dict(values)) + values = [(f, None, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] + manager.filter(pk=pk_val)._update(values) else: record_exists = False if not pk_set or not record_exists: if not pk_set: - values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] + values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] else: - values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] + values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] if meta.order_with_respect_to: field = meta.order_with_respect_to - values.append(('_order', manager.filter(**{field.name: getattr(self, field.attname)}).count())) + values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count())) record_exists = False update_pk = bool(meta.has_auto_field and not pk_set) if values: # Create a new record. - result = manager._insert(__return_id=update_pk, **dict(values)) + result = manager._insert(values, return_id=update_pk) else: # Create a new record with defaults for everything. - result = manager._insert(__return_id=update_pk, - __raw_values=True, pk=connection.ops.pk_default_value()) + result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True) if update_pk: setattr(self, meta.pk.attname, result) diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 1e2cd58850..d58a1c75d0 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -123,8 +123,11 @@ class Manager(object): def reverse(self, *args, **kwargs): return self.get_query_set().reverse(*args, **kwargs) - def _insert(self, **kwargs): - return insert_query(self.model, **kwargs) + def _insert(self, values, **kwargs): + return insert_query(self.model, values, **kwargs) + + def _update(self, values, **kwargs): + return self.get_query_set()._update(values, **kwargs) class ManagerDescriptor(object): # This class ensures managers aren't accessible via model instances. diff --git a/django/db/models/query.py b/django/db/models/query.py index 15d49b6bac..3e8b570a73 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -287,6 +287,19 @@ class QuerySet(object): self._result_cache = None update.alters_data = True + def _update(self, values): + """ + A version of update that accepts field objects instead of field names. + Used primarily for model saving and not intended for use by general + code (it requires too much poking around at model internals to be + useful at that level). + """ + query = self.query.clone(sql.UpdateQuery) + query.add_update_fields(values) + query.execute_sql(None) + self._result_cache = None + _update.alters_data = True + ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## @@ -692,13 +705,13 @@ def delete_objects(seen_objs): transaction.commit_unless_managed() -def insert_query(__model, __return_id=False, __raw_values=False, **kwargs): +def insert_query(model, values, return_id=False, raw_values=False): """ Inserts a new record for the given model. This provides an interface to the InsertQuery class and is how Model.save() is implemented. It is not part of the public API. """ - query = sql.InsertQuery(__model, connection) - query.insert_values(kwargs, __raw_values) - return query.execute_sql(__return_id) + query = sql.InsertQuery(model, connection) + query.insert_values(values, raw_values) + return query.execute_sql(return_id) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 9b5b9ba0f6..0c32abd6a5 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -194,11 +194,27 @@ class UpdateQuery(Query): self.execute_sql(None) def add_update_values(self, values): - from django.db.models.base import Model + """ + Convert a dictionary of field name to value mappings into an update + query. This is the entry point for the public update() method on + querysets. + """ + values_seq = [] for name, val in values.iteritems(): field, model, direct, m2m = self.model._meta.get_field_by_name(name) if not direct or m2m: raise FieldError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field) + values_seq.append((field, model, val)) + return self.add_update_fields(values_seq) + + def add_update_fields(self, values_seq): + """ + Turn a sequence of (field, model, value) triples into an update query. + Used by add_update_values() as well as the "fast" update path when + saving models. + """ + from django.db.models.base import Model + for field, model, val in values_seq: # FIXME: Some sort of db_prep_* is probably more appropriate here. if field.rel and isinstance(val, Model): val = val.pk @@ -279,17 +295,11 @@ class InsertQuery(Query): parameters. This provides a way to insert NULL and DEFAULT keywords into the query, for example. """ - func = lambda x: self.model._meta.get_field_by_name(x)[0] # keys() and values() return items in the same order, providing the # dictionary hasn't changed between calls. So the dual iteration here # works as intended. placeholders, values = [], [] - for name, val in insert_values.iteritems(): - if name == 'pk': - name = self.model._meta.pk.name - # Getting the Field associated w/the name. - field = func(name) - + for field, val in insert_values: if hasattr(field, 'get_placeholder'): # Some fields (e.g. geo fields) need special munging before # they can be inserted.