Opened 3 years ago

Last modified 22 months ago

#23740 new Bug

Cannot drop unique_together constraint on a single field with its own unique=True constraint

Reported by: Mihail Milushev Owned by: nobody
Component: Migrations Version: 1.7
Severity: Normal Keywords: unique_together
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I have an erroneous unique_together constraint on a model's primary key (unique_together = (('id',),)) that cannot be dropped by a migration. Apparently the migration tries to find all unique constraints on the column and expects there to be only one, but I've got two — the primary key and the unique_together constraint:

Indexes:
    "foo_bar_pkey" PRIMARY KEY, btree (id)
    "foo_bar_id_1c3b3088c74c3b17_uniq" UNIQUE CONSTRAINT, btree (id)

Database is PostgreSQL, if that makes any difference.

Change History (6)

comment:1 Changed 3 years ago by Markus Holtermann

Can you share a traceback that shows how and where the migration fails, please.

comment:2 Changed 3 years ago by Mihail Milushev

Here it is:

Traceback (most recent call last):
  File "./bin/django", line 56, in <module>
    sys.exit(djangorecipe.manage.main('project.settings.local.foobar'))
  File "/foo/bar/eggs/djangorecipe-1.10-py2.7.egg/djangorecipe/manage.py", line 9, in main
    management.execute_from_command_line(sys.argv)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/core/management/commands/migrate.py", line 160, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/db/migrations/executor.py", line 63, in migrate
    self.apply_migration(migration, fake=fake)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/db/migrations/executor.py", line 97, in apply_migration
    migration.apply(project_state, schema_editor)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/db/migrations/migration.py", line 107, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/db/migrations/operations/models.py", line 253, in database_forwards
    getattr(new_model._meta, self.option_name, set()),
  File "/foo/bar/eggs/Django-1.7-py2.7.egg/django/db/backends/schema.py", line 315, in alter_unique_together
    ", ".join(columns),
ValueError: Found wrong number (2) of constraints for foo_bar(id)

(some identifier names and paths were changed to protect the innocent :))

comment:3 Changed 3 years ago by matthewwestcott

Triage Stage: UnreviewedAccepted
Type: UncategorizedBug

Confirmed on current master. On a new postgresql-based project:

  • create a new app 'foo' with the following models.py
from django.db import models

class Bar(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        unique_together = (('id',),)
  • ./manage.py makemigrations
  • ./manage.py migrate
  • comment out the 'class Meta'
  • ./manage.py makemigrations
  • ./manage.py migrate

This is not specific to the primary key field - it happens more generally on single-field unique_together constraints that duplicate a unique=True constraint, such as:

class Bar(models.Model):
    name = models.CharField(max_length=255, unique=True)

    class Meta:
        unique_together = (('name'),)

comment:4 Changed 3 years ago by matthewwestcott

Keywords: unique_together added

comment:5 Changed 3 years ago by matthewwestcott

Summary: Cannot drop unique_together constraint on primary keyCannot drop unique_together constraint on a single field with its own unique=True constraint

Can't see a good way of fixing this without breaking backwards compatibility. The alter_unique_together logic in db/backends/schema.py relies on the UNIQUE constraints generated by unique_together to be distinguishable within the database from constraints generated through other mechanisms (e.g. unique=True) - but this isn't possible when the unique_together rule only contains a single field.

Another way this manifests itself is to perform the following steps, in separate migrations:

  • create a model
  • add unique=True to one if its fields
  • add a unique_together constraint for that same field

This results in two identical calls to _create_unique_sql, leading to a 'relation "foo_bar_name_1e64ed8ec8cfa1c7_uniq" already exists' error.

comment:6 in reply to:  5 Changed 22 months ago by Dave Peticolas

Replying to matthewwestcott:

Can't see a good way of fixing this without breaking backwards compatibility. The alter_unique_together logic in db/backends/schema.py relies on the UNIQUE constraints generated by unique_together to be distinguishable within the database from constraints generated through other mechanisms (e.g. unique=True) - but this isn't possible when the unique_together rule only contains a single field.

I have encountered this same issue. Could you explain the backwards compatibility issue here? It seems to me that the bug is in attempting to drop or create an index when that is not called for, either because the index is required by a different constraint or was already created by another constraint. Is that behavior that needs to be kept.

Note: See TracTickets for help on using tickets.
Back to Top