Commit 5792e6a8 authored by Markus Holtermann's avatar Markus Holtermann
Browse files

Fixed #24163 -- Removed unique constraint after index on MySQL

Thanks Łukasz Harasimowicz for the report.
parent 8e435a56
Loading
Loading
Loading
Loading
+12 −12
Original line number Diff line number Diff line
@@ -488,18 +488,6 @@ class BaseDatabaseSchemaEditor(object):
                     old_db_params, new_db_params, strict=False):
        """Actually perform a "physical" (non-ManyToMany) field update."""

        # Has unique been removed?
        if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)):
            # Find the unique constraint for this field
            constraint_names = self._constraint_names(model, [old_field.column], unique=True)
            if strict and len(constraint_names) != 1:
                raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
                    len(constraint_names),
                    model._meta.db_table,
                    old_field.column,
                ))
            for constraint_name in constraint_names:
                self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
        # Drop any FK constraints, we'll remake them later
        fks_dropped = set()
        if old_field.rel and old_field.db_constraint:
@@ -513,6 +501,18 @@ class BaseDatabaseSchemaEditor(object):
            for fk_name in fk_names:
                fks_dropped.add((old_field.column,))
                self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name))
        # Has unique been removed?
        if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)):
            # Find the unique constraint for this field
            constraint_names = self._constraint_names(model, [old_field.column], unique=True)
            if strict and len(constraint_names) != 1:
                raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
                    len(constraint_names),
                    model._meta.db_table,
                    old_field.column,
                ))
            for constraint_name in constraint_names:
                self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
        # Drop incoming FK constraints if we're a primary key and things are going
        # to change.
        if old_field.primary_key and new_field.primary_key and old_type != new_type:
+3 −0
Original line number Diff line number Diff line
@@ -14,3 +14,6 @@ Bugfixes

* Made the migration's ``RenameModel`` operation rename ``ManyToManyField``
  tables (:ticket:`24135`).

* Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a
  ``ForeignKey`` (:ticket:`24163`).
+10 −0
Original line number Diff line number Diff line
@@ -67,6 +67,16 @@ class BookWeak(models.Model):
        apps = new_apps


class BookWithO2O(models.Model):
    author = models.OneToOneField(Author)
    title = models.CharField(max_length=100, db_index=True)
    pub_date = models.DateTimeField()

    class Meta:
        apps = new_apps
        db_table = "schema_book"


class BookWithM2M(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100, db_index=True)
+103 −3
Original line number Diff line number Diff line
@@ -5,12 +5,12 @@ from django.test import TransactionTestCase
from django.db import connection, DatabaseError, IntegrityError, OperationalError
from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
    PositiveIntegerField, SlugField, TextField)
from django.db.models.fields.related import ManyToManyField, ForeignKey
from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField
from django.db.transaction import atomic
from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
    BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
    UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
    AuthorWithEvenLongerName, BookWeak, Note)
    AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O)


class SchemaTests(TransactionTestCase):
@@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase):
        Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
        BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
        Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
        BookWeak,
        BookWeak, BookWithO2O,
    ]

    # Utility functions
@@ -528,6 +528,106 @@ class SchemaTests(TransactionTestCase):
        else:
            self.fail("No FK constraint for author_id found")

    @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
    def test_alter_o2o_to_fk(self):
        """
        #24163 - Tests altering of OneToOne to FK
        """
        # Create the table
        with connection.schema_editor() as editor:
            editor.create_model(Author)
            editor.create_model(BookWithO2O)
        # Ensure the field is right to begin with
        columns = self.column_classes(BookWithO2O)
        self.assertEqual(columns['author_id'][0], "IntegerField")
        # Make sure the FK and unique constraints are present
        constraints = self.get_constraints(BookWithO2O._meta.db_table)
        author_is_fk = False
        author_is_unique = False
        for name, details in constraints.items():
            if details['columns'] == ['author_id']:
                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
                    author_is_fk = True
                if details['unique']:
                    author_is_unique = True
        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
        self.assertTrue(author_is_unique, "No unique constraint for author_id found")
        # Alter the O2O to FK
        new_field = ForeignKey(Author)
        new_field.set_attributes_from_name("author")
        with connection.schema_editor() as editor:
            editor.alter_field(
                BookWithO2O,
                BookWithO2O._meta.get_field("author"),
                new_field,
                strict=True,
            )
        # Ensure the field is right afterwards
        columns = self.column_classes(Book)
        self.assertEqual(columns['author_id'][0], "IntegerField")
        # Make sure the FK constraint is present and unique constraint is absent
        constraints = self.get_constraints(Book._meta.db_table)
        author_is_fk = False
        author_is_unique = True
        for name, details in constraints.items():
            if details['columns'] == ['author_id']:
                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
                    author_is_fk = True
                if not details['unique']:
                    author_is_unique = False
        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
        self.assertFalse(author_is_unique, "Unique constraint for author_id found")

    @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
    def test_alter_fk_to_o2o(self):
        """
        #24163 - Tests altering of FK to OneToOne
        """
        # Create the table
        with connection.schema_editor() as editor:
            editor.create_model(Author)
            editor.create_model(Book)
        # Ensure the field is right to begin with
        columns = self.column_classes(Book)
        self.assertEqual(columns['author_id'][0], "IntegerField")
        # Make sure the FK constraint is present and unique constraint is absent
        constraints = self.get_constraints(Book._meta.db_table)
        author_is_fk = False
        author_is_unique = True
        for name, details in constraints.items():
            if details['columns'] == ['author_id']:
                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
                    author_is_fk = True
                if not details['unique']:
                    author_is_unique = False
        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
        self.assertFalse(author_is_unique, "Unique constraint for author_id found")
        # Alter the O2O to FK
        new_field = OneToOneField(Author)
        new_field.set_attributes_from_name("author")
        with connection.schema_editor() as editor:
            editor.alter_field(
                Book,
                Book._meta.get_field("author"),
                new_field,
                strict=True,
            )
        # Ensure the field is right afterwards
        columns = self.column_classes(BookWithO2O)
        self.assertEqual(columns['author_id'][0], "IntegerField")
        # Make sure the FK and unique constraints are present
        constraints = self.get_constraints(BookWithO2O._meta.db_table)
        author_is_fk = False
        author_is_unique = False
        for name, details in constraints.items():
            if details['columns'] == ['author_id']:
                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
                    author_is_fk = True
                if details['unique']:
                    author_is_unique = True
        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
        self.assertTrue(author_is_unique, "No unique constraint for author_id found")

    def test_alter_implicit_id_to_explicit(self):
        """
        Should be able to convert an implicit "id" field to an explicit "id"