Commits

mtre...@bcc190cf-cafb-0310-a4f2-bffc1f526a37  committed 1ed5562

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.

  • Participants
  • Parent commits 0739f49
  • Branches queryset-refactor

Comments (0)

Files changed (4)

File django/db/models/base.py

             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)

File django/db/models/manager.py

     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.

File django/db/models/query.py

         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 #
     ##################################################
 
     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)
 

File django/db/models/sql/subqueries.py

             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
         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.