A Couple of Django South & SQL Server Migration Tips

I was recently writing a bunch of Django South migrations for a project at work and ran into two problems that caused my migrations to fail when run against a SQL Server 2008 database (same behaviour on Microsoft SQL Server 2008R2, SQL Express 2008 & SQL Express 2012). These were migrations that ran smoothly on both SQLite & PostgreSQL so I had to modify the migrations to included a little bit of special case code for SQL Server databases.

I'm going to post the problems & solutions here for myself when I run into them again in the future, and hopefully someone else will find them useful when googling for error messages

Issue 1: Cannot create new connection because in manual or distributed transaction mode.

Issue 2: Cannot drop the index '...' because it does not exist or you do not have permission

Issue 1: Cannot create new connection because in manual or distributed transaction mode.

The first issue that I ran into was dealing with a data migration that looked something like:

class Migration(DataMigration):

    def forwards(self, orm):
        for obj in orm["someapp.SomeModel"].objects.all():
            obj.foo = "bar"
            obj.save()

    def backwards(self, orm):
        "Write your backwards methods here."

    models = {...}

This worked fine for SQLite and Postgres but running the same migration on SQL(Server|Express) gave a somewhat cryptic Cannot create new connection because in manual or distributed transaction mode.

A little bit of googling let me to a Microsoft support page explaining what the error was about (SQLOLEDB only allows a single connection within a transaction). It appears that when interating over the collection above a new connection is created to retrieve the next object on each iteration. Since this is all happening withing a single transaction, SQL Server complains.

The simplest solution is just to pull all your objects into memory before iterating over them by calling list on the orm["someapp.SomeModel"].objects.all().

class Migration(DataMigration):

    def forwards(self, orm):
        for obj in list(orm["someapp.SomeModel"].objects.all()):
            obj.foo = "bar"
            obj.save()

    def backwards(self, orm):
        "Write your backwards methods here."

    models = {...}

The major disclaimer here is that if your collection contains a huge number of objects, you may run into memory limitations. In that case, something more creative probably needs to be done like splitting your data into batches.

Issue 2: Cannot drop the index '...' because it does not exist or you do not have permission

The second issue occured when I was trying to alter the max_length attribute of a column that had an index on it. So the simplest sort of migration where this would occur looks like:

class Migration(SchemaMigration):

    def forwards(self, orm):

        db.alter_column('someapp_somemodel', 'field_ame', self.gf('django.db.models.fields.CharField')(max_length=255))

    def backwards(self, orm):
        ...

Again, this worked fine on both SQLite and Postgres but SQLServer complained with an error like:

AttributeError: 'module' object has no attribute 'Migration'

FATAL ERROR - The following SQL query failed: DROP INDEX [someapp_somemodel_cac2c6] on [dbo].[someapp_somemodel]

The error was: (-2147352567, 'Exception occurred.', (0, u'Microsoft SQL Server Native Client 10.0', u"Cannot drop the index 'dbo.someapp_somemodel

.someapp_somemodel_cac2c6', because it does not exist or you do not have permission.", None, 0, -2147217865), None)

Command:

DROP INDEX [someapp_somemodel_cac2c6] on [dbo].[someapp_somemodel]

Parameters:

[]

I couldn't find much on google to point me in the right direction, but if I dropped the problem index manually in SQL Server Management Studio before running the migration it would run without any issues.

Luckily South comes with a way to explicitly drop and create indexes during migrations so I was able to modify my migration to look like:

class Migration(SchemaMigration):

    def forwards(self, orm):
        from south.db import engine

        if 'sql_server' in engine:
            db.drop_index("someapp_somemodel", "column_name")

        db.alter_column('someapp_somemodel', 'comlumn_ame', self.gf('django.db.models.fields.CharField')(max_length=255))

        if 'sql_server' in engine:
            db.create_index("someapp_somemodel", ["column_name"])

    def backwards(self, orm):
        ...

Problem solved! The migration now runs smoothly on SQLServer, SQL Express, SQLite & Postgres.

I'll write another post some day explaining why I'm stuck using SQLServer for Django projects ;)

comments powered by Disqus