#34996: Enhance update_or_create method with upsert sql
-------------------------------------+-------------------------------------
               Reporter:  Jordan     |          Owner:  nobody
  Bae                                |
                   Type:             |         Status:  new
  Cleanup/optimization               |
              Component:  Database   |        Version:  dev
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 == Context

 Current QuerySet.update_or_create method work like below

 1. open transaction or savepoint
 2. get_or_create with lock
 3. exist -> update and not exist - create

 I want to suggest how about refactoring this with upsert SQL (ex. INSERT
 INTO ... ON DUPLICATE KEY UPDATE)
 {{{
     def update_or_create(self, defaults=None, create_defaults=None,
 **kwargs):
         """
         Look up an object with the given kwargs, updating one with
 defaults
         if it exists, otherwise create a new one. Optionally, an object
 can
         be created with different values than defaults by using
         create_defaults.
         Return a tuple (object, created), where created is a boolean
         specifying whether an object was created.
         """
         update_defaults = defaults or {}
         if create_defaults is None:
             create_defaults = update_defaults

         self._for_write = True
         with transaction.atomic(using=self.db):
             # Lock the row so that a concurrent update is blocked until
             # update_or_create() has performed its save.
             obj, created = self.select_for_update().get_or_create(
                 create_defaults, **kwargs
             )
             if created:
                 return obj, created
             for k, v in resolve_callables(update_defaults):
                 setattr(obj, k, v)

             update_fields = set(update_defaults)
             concrete_field_names =
 self.model._meta._non_pk_concrete_field_names
             # update_fields does not support non-concrete fields.
             if concrete_field_names.issuperset(update_fields):
                 # Add fields which are set on pre_save(), e.g. auto_now
 fields.
                 # This is to maintain backward compatibility as these
 fields
                 # are not updated unless explicitly specified in the
                 # update_fields list.
                 for field in self.model._meta.local_concrete_fields:
                     if not (
                         field.primary_key or field.__class__.pre_save is
 Field.pre_save
                     ):
                         update_fields.add(field.name)
                         if field.name != field.attname:
                             update_fields.add(field.attname)
                 obj.save(using=self.db, update_fields=update_fields)
             else:
                 obj.save(using=self.db)
         return obj, False
 }}}
 === Strength
 - Performance: when updates, there is no need transaction and lock. and
 it's single query.
 - Maintenance: It can be simple for maintenance.

 === Consideration
 - database compatibility: need to check support upsert SQL in the all of
 databases.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34996>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018c0abfc512-3089c234-dff0-4d69-be52-ee88e7334548-000000%40eu-central-1.amazonses.com.

Reply via email to