Schema migrations

Zulip uses the standard Django system for doing schema migrations. There is some example usage in the new feature tutorial.

This page documents some important issues related to writing schema migrations.

There is one special case where this doesn't work: you can't copy the definition of a model (like Realm) into a migration, and you can't import it from zerver.models for the reasons above. In this situation you should use Django's apps.get_model to get access to a model as it is at the time of a migration. Note that this will work for doing something like Realm.objects.filter(..), but shouldn't be used for accessing properties like Realm.subdomain or anything not related to the Django ORM.

Another important note is that making changes to the data in a table via RunPython code and ALTER TABLE operations within a single, atomic migration don't mix well. If you encounter an error such as text django.db.utils.OperationalError: cannot ALTER TABLE "table_name" because it has pending trigger events when testing the migration, the reason is often that these operations were incorrectly mixed. To resolve this, consider making the migration non-atomic, splitting it into two migration files (recommended), or replacing the RunPython logic with pure SQL (though this can generally be difficult).

Automated testing for migrations

Zulip has support for writing automated tests for your database migrations, using the MigrationsTestCase test class. This system is inspired by a great blog post on the subject.

We have integrated this system with our test framework so that if you use the use_db_models decorator, you can use some helper methods from test_classes.py and friends from inside the tests (which is normally not possible in Django's migrations framework).

If you find yourself writing logic in a RunPython migration, we highly recommend adding a test using this framework. We may end up deleting the test later (they can get slow once they are many migrations away from current), but it can help prevent disaster where an incorrect migration messes up a database in a way that's impossible to undo without going to backups.

Schema and initial data changes

If you follow the processes described above, tools/provision and tools/test-backend should detect any changes to the declared migrations and run migrations on (./manage.py migrate) or rebuild the relevant database automatically as appropriate.

While developing migrations, you may accidentally corrupt your databases while debugging your new code. You can always rebuild these databases from scratch.

Use tools/rebuild-test-database to rebuild the database used for test-backend and other automated tests.

Use tools/rebuild-dev-database to rebuild the database used in manual testing.