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

Thanks Łukasz Harasimowicz for the report.
This commit is contained in:
Markus Holtermann 2015-01-19 15:31:23 +01:00
parent 8e435a5640
commit 5792e6a88c
4 changed files with 128 additions and 15 deletions

View File

@ -488,18 +488,6 @@ class BaseDatabaseSchemaEditor(object):
old_db_params, new_db_params, strict=False): old_db_params, new_db_params, strict=False):
"""Actually perform a "physical" (non-ManyToMany) field update.""" """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 # Drop any FK constraints, we'll remake them later
fks_dropped = set() fks_dropped = set()
if old_field.rel and old_field.db_constraint: if old_field.rel and old_field.db_constraint:
@ -513,6 +501,18 @@ class BaseDatabaseSchemaEditor(object):
for fk_name in fk_names: for fk_name in fk_names:
fks_dropped.add((old_field.column,)) fks_dropped.add((old_field.column,))
self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name)) 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 # Drop incoming FK constraints if we're a primary key and things are going
# to change. # to change.
if old_field.primary_key and new_field.primary_key and old_type != new_type: if old_field.primary_key and new_field.primary_key and old_type != new_type:

View File

@ -14,3 +14,6 @@ Bugfixes
* Made the migration's ``RenameModel`` operation rename ``ManyToManyField`` * Made the migration's ``RenameModel`` operation rename ``ManyToManyField``
tables (:ticket:`24135`). tables (:ticket:`24135`).
* Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a
``ForeignKey`` (:ticket:`24163`).

View File

@ -67,6 +67,16 @@ class BookWeak(models.Model):
apps = new_apps 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): class BookWithM2M(models.Model):
author = models.ForeignKey(Author) author = models.ForeignKey(Author)
title = models.CharField(max_length=100, db_index=True) title = models.CharField(max_length=100, db_index=True)

View File

@ -5,12 +5,12 @@ from django.test import TransactionTestCase
from django.db import connection, DatabaseError, IntegrityError, OperationalError from django.db import connection, DatabaseError, IntegrityError, OperationalError
from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField, from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
PositiveIntegerField, SlugField, TextField) 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 django.db.transaction import atomic
from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
AuthorWithEvenLongerName, BookWeak, Note) AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O)
class SchemaTests(TransactionTestCase): class SchemaTests(TransactionTestCase):
@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase):
Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
BookWeak, BookWeak, BookWithO2O,
] ]
# Utility functions # Utility functions
@ -528,6 +528,106 @@ class SchemaTests(TransactionTestCase):
else: else:
self.fail("No FK constraint for author_id found") 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): def test_alter_implicit_id_to_explicit(self):
""" """
Should be able to convert an implicit "id" field to an explicit "id" Should be able to convert an implicit "id" field to an explicit "id"