Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #31071 -- Disabled insert optimization for primary keys with defaults when loading fixtures. #12209

Merged
merged 2 commits into from Dec 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions django/db/models/base.py
Expand Up @@ -849,6 +849,7 @@ def _save_table(self, raw=False, cls=None, force_insert=False,
updated = False
# Skip an UPDATE when adding an instance and primary key has a default.
if (
not raw and
not force_insert and
self._state.adding and
self._meta.pk.default and
Expand Down
16 changes: 14 additions & 2 deletions docs/ref/models/instances.txt
Expand Up @@ -465,8 +465,9 @@ How Django knows to UPDATE vs. INSERT

You may have noticed Django database objects use the same ``save()`` method
for creating and changing objects. Django abstracts the need to use ``INSERT``
or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django
follows this algorithm:
or ``UPDATE`` SQL statements. Specifically, when you call ``save()`` and the
object's primary key attribute does **not** define a
:attr:`~django.db.models.Field.default`, Django follows this algorithm:

* If the object's primary key attribute is set to a value that evaluates to
``True`` (i.e., a value other than ``None`` or the empty string), Django
Expand All @@ -475,6 +476,11 @@ follows this algorithm:
didn't update anything (e.g. if primary key is set to a value that doesn't
exist in the database), Django executes an ``INSERT``.

If the object's primary key attribute defines a
:attr:`~django.db.models.Field.default` then Django executes an ``UPDATE`` if
it is an existing model instance and primary key is set to a value that exists
in the database. Otherwise, Django executes an ``INSERT``.

The one gotcha here is that you should be careful not to specify a primary-key
value explicitly when saving new objects, if you cannot guarantee the
primary-key value is unused. For more on this nuance, see `Explicitly specifying
Expand All @@ -490,6 +496,12 @@ which returns ``NULL``. In such cases it is possible to revert to the old
algorithm by setting the :attr:`~django.db.models.Options.select_on_save`
option to ``True``.

.. versionchanged:: 3.0

``Model.save()`` no longer attempts to find a row when saving a new
``Model`` instance and a default value for the primary key is provided, and
always executes an ``INSERT``.

.. _ref-models-force-insert:

Forcing an INSERT or UPDATE
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/3.0.2.txt
Expand Up @@ -18,3 +18,7 @@ Bugfixes
* Fixed a regression in Django 3.0 that caused a migration crash on PostgreSQL
10+ when adding a foreign key and changing data in the same migration
(:ticket:`31106`).

* Fixed a regression in Django 3.0 where loading fixtures crashed for models
defining a :attr:`~django.db.models.Field.default` for the primary key
(:ticket:`31071`).
21 changes: 21 additions & 0 deletions docs/releases/3.0.txt
Expand Up @@ -372,6 +372,27 @@ Tests
Backwards incompatible changes in 3.0
=====================================

``Model.save()`` when providing a default for the primary key
-------------------------------------------------------------

:meth:`.Model.save` no longer attempts to find a row when saving a new
``Model`` instance and a default value for the primary key is provided, and
always performs a single ``INSERT`` query. In older Django versions,
``Model.save()`` performed either an ``INSERT`` or an ``UPDATE`` based on
whether or not the row exists.

This makes calling ``Model.save()`` while providing a default primary key value
equivalent to passing :ref:`force_insert=True <ref-models-force-insert>` to
model's ``save()``. Attempts to use a new ``Model`` instance to update an
existing row will result in an ``IntegrityError``.

In order to update an existing model for a specific primary key value, use the
:meth:`~django.db.models.query.QuerySet.update_or_create` method or
``QuerySet.filter(pk=…).update(…)`` instead. For example::

>>> MyModel.objects.update_or_create(pk=existing_pk, defaults={'name': 'new name'})
felixxm marked this conversation as resolved.
Show resolved Hide resolved
>>> MyModel.objects.filter(pk=existing_pk).update(name='new name')

Database backend API
--------------------

Expand Down
6 changes: 6 additions & 0 deletions tests/serializers/models/data.py
Expand Up @@ -4,6 +4,8 @@
NULL values, where allowed.
The basic idea is to have a model for each Django data type.
"""
import uuid

from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation,
)
Expand Down Expand Up @@ -257,6 +259,10 @@ class UUIDData(models.Model):
data = models.UUIDField(primary_key=True)


class UUIDDefaultData(models.Model):
data = models.UUIDField(primary_key=True, default=uuid.uuid4)


class FKToUUID(models.Model):
data = models.ForeignKey(UUIDData, models.CASCADE)

Expand Down
3 changes: 2 additions & 1 deletion tests/serializers/test_data.py
Expand Up @@ -26,7 +26,7 @@
ModifyingSaveData, NullBooleanData, O2OData, PositiveBigIntegerData,
PositiveIntegerData, PositiveIntegerPKData, PositiveSmallIntegerData,
PositiveSmallIntegerPKData, SlugData, SlugPKData, SmallData, SmallPKData,
Tag, TextData, TimeData, UniqueAnchor, UUIDData,
Tag, TextData, TimeData, UniqueAnchor, UUIDData, UUIDDefaultData,
)
from .tests import register_tests

Expand Down Expand Up @@ -351,6 +351,7 @@ def inherited_compare(testcase, pk, klass, data):
# (pk_obj, 790, XMLPKData, "<foo></foo>"),
(pk_obj, 791, UUIDData, uuid_obj),
(fk_obj, 792, FKToUUID, uuid_obj),
(pk_obj, 793, UUIDDefaultData, uuid_obj),

(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006, 6, 16, 10, 42, 37)),
(data_obj, 810, ModifyingSaveData, 42),
Expand Down