Permalink
Browse files

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

Thanks Łukasz Harasimowicz for the report.
  • Loading branch information...
1 parent 8e435a5 commit 5792e6a88c1444d4ec84abe62077338ad3765b80 @MarkusH MarkusH committed Jan 19, 2015
Showing with 128 additions and 15 deletions.
  1. +12 −12 django/db/backends/base/schema.py
  2. +3 −0 docs/releases/1.7.4.txt
  3. +10 −0 tests/schema/models.py
  4. +103 −3 tests/schema/tests.py
@@ -488,18 +488,6 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
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 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
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:
View
@@ -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`).
View
@@ -67,6 +67,16 @@ class Meta:
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)
View
@@ -5,12 +5,12 @@
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 @@ def test_alter_fk(self):
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"

0 comments on commit 5792e6a

Please sign in to comment.