Fixed #24163 -- Removed unique constraint after index on MySQL
Thanks Łukasz Harasimowicz for the report.
This commit is contained in:
parent
8e435a5640
commit
5792e6a88c
|
@ -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:
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue