#28273: Have a way to prevent adding columns with defaults in migrations
-------------------------------------+-------------------------------------
Reporter: Raphael Gaschignard | Owner: nobody
Type: Uncategorized | Status: new
Component: Database layer | Version: 1.10
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Raphael Gaschignard):
Replying to [comment:1 Tim Graham]:
> Can't you accomplish this by adding the column as `null=True`,
populating the values, then changing the column to `null=False` (similar
to the [https://docs.djangoproject.com/en/dev/howto/writing-migrations
/#migrations-that-add-unique-fields Migrations that add unique fields]
howto)? I believe defaults should only be added when adding non-nullable
columns -- if that behavior were changed, how would the non-null
constraint be avoided?
Because we want to deploy without downtime, we already deploy fields with
{{{null=True}}}, and add a backfill later. The issue I'm trying to point
out here is that with `default=a_value` set (even with `null=True`) we
still hit an issue because Postgres backfills defaults into existing rows
when a column is added (even if the column is nullable). I am not aware of
how other DB backends handle adding a column with `DEFAULT` + `NULL` set.
Our conclusion is that any `AddField` with a `default` set cannot be
accomplished without downtime in a production system (similar to a non-
null field), so we've written the following check for our migrations:
{{{
from django.db.migrations.operations.fields import AddField
from django.db.models.fields import NOT_PROVIDED
from django.db.models.signals import pre_migrate
def check_migration_safety(**kwargs):
# `plan` is not part of the public API and is subject to change in
later
# versions of Django.
plan = kwargs['plan']
for migration_cls, rolled_back in plan:
# Check if this is going forwards or rolling back a migration.
if not rolled_back:
for operation in migration_cls.operations:
if isinstance(operation, AddField):
description = "{migration!r} model '{model}' field
'{field}': ".format(
migration=migration_cls,
model=operation.model_name,
field=operation.name,
)
if not operation.field.null:
raise ValueError(description + "do not add new
non-nullable columns")
if operation.field.default != NOT_PROVIDED:
raise ValueError(description + "do not set a
default when adding columns")
pre_migrate.connect(
check_migration_safety,
dispatch_uid="operations.check_migration_safety",
)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28273#comment:2>
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 post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/062.08c3c0c81a1ba1f87f2c7474e8131eca%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.