Search code examples
mysqlinnodbdjango-southdatabase-migration

Entire table missing after Django South migration


In an automatically generated Django South (0.7.6) migration file, it contains a simple forward migration that deletes a unique constraint on a field, and then makes the field a foreign key to another Django model.

class Migration(SchemaMigration):

    def forwards(self, orm):

        # Removing unique constraint on 'model2', fields ['field']
        db.delete_unique('app2_model2', ['field_id'])

        # Changing field 'model2.field'
        db.alter_column('app2_model2', 'field_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['app1.model1']))

When that is executed on my MySQL 5.5 database running on InnoDB engine, it "fails to rename" the table

...
File "/opt/python/bundle/3/app/apps/app2/migrations/0020_auto__chg_field_model2_field__del_unique_model2_field__chg_field_model2.py", line 19, in forwards
    db.alter_column('app2_model2', 'field_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['app1.model1']))
File "/opt/python/run/venv/lib/python2.6/site-packages/south/db/generic.py", line 44, in _cache_clear
    return func(self, table, *args, **opts)
File "/opt/python/run/venv/lib/python2.6/site-packages/south/db/generic.py", line 487, in alter_column
    self.delete_foreign_key(table_name, name)
File "/opt/python/run/venv/lib/python2.6/site-packages/south/db/generic.py", line 44, in _cache_clear
    return func(self, table, *args, **opts)
File "/opt/python/run/venv/lib/python2.6/site-packages/south/db/generic.py", line 780, in delete_foreign_key
    "constraint": self.quote_name(constraint_name),
File "/opt/python/run/venv/lib/python2.6/site-packages/south/db/generic.py", line 273, in execute
    cursor.execute(sql, params)
File "/opt/python/run/venv/lib/python2.6/site-packages/django/db/backends/mysql/base.py", line 114, in execute
    return self.cursor.execute(query, args)
File "/opt/python/run/venv/lib/python2.6/site-packages/MySQLdb/cursors.py", line 174, in execute
    self.errorhandler(self, exc, value)
File "/opt/python/run/venv/lib/python2.6/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
django.db.utils.DatabaseError: (1025, "Error on rename of './ebdb/#sql-260e_45b9' to './ebdb/app2_model2' (errno: 150)")

which means that the table is effectively erased, and invisible to Django:

...
    cursor.execute(sql, params)
File "/opt/python/run/venv/lib/python2.6/site-packages/django/db/backends/mysql/base.py", line 114, in execute
    return self.cursor.execute(query, args)
File "/opt/python/run/venv/lib/python2.6/site-packages/MySQLdb/cursors.py", line 174, in execute
    self.errorhandler(self, exc, value)
File "/opt/python/run/venv/lib/python2.6/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
DatabaseError: (1146, "Table 'ebdb.app2_model2' doesn't exist")

The database was restored and this migration was re-run, repeated for five times, with confidence that this migration did not just fail coincidentally.

My question is: what went wrong?


Solution

  • Found the problem. This occurs with the rare combination of converting a unique, one-to-one Django relation to a simple foreign key relation, with no uniqueness constraints, done on a MySQL table, either MyISAM or InnoDB, because MySQL supports transactional schema alterations on neither, even when the engine does.

    To solve the problem, I just removed the db.alter_column commands, following the advice of a kind fellow, who finds no functional difference in the underlying schema between a OneToOne relationship and a ForeignKey relationship.