Your Application, Phase Two - The database change
You've finished coding, testing, and deploying to customers. You put your feet up, and then the boss says "Right, time for Phase 2."
With your code all nicely checked in to a version control system, you're quite happy to add your features, fix a few defects, and deploy a new release. This time around, however, the database structure needs rearranging as well.
One obvious way to upgrade your database structure might be to grab the SQL for the changed parts, and diff it, just like code:
CREATE TABLE books ( id INTEGER AUTOINCREMENT, - title VARCHAR(255), + title VARCHAR(1024) NOT NULL, year INTEGER, );
But when you apply this change to your existing database full of
lovely books, you find you can't run CREATE
on a table that's
already there. You can DROP
the table first, but then your data is
toast and your users have to start over.
You could instead manually go over the database changes, and construct
ALTER
statements to update existing installs to the new plan,
something like this:
ALTER TABLE books MODIFY COLUMN title VARCHAR(1024) NOT NULL;
But for a large set of changes this is probably tedious and time
consuming, and that's just the update from the previous version of the
application. What about crazy customers who for some unknown reason
are still running the version from a year ago? Do you go back and
painstakingly create the ALTER
statements for those as well?
Enter DBIx::Class::Schema::Versioned
If you're using DBIx::Class
to access your database from your
application, then there's a tool which can help solve all these
problems. Versioned
helps keep track of which version of your
application is installed where, and makes it easier to upgrade.
To start using Versioned in your application, add it as a component of
the Schema
class, for example:
package My::Schema; use base 'DBIx::Class::Schema'; our $VERSION = '0.01'; __PACKAGE__->load_components('+DBIx::Class::Schema::Versioned'); __PACKAGE__->upgrade_directory('/path/to/dir'); __PACKAGE__->load_namespaces(); 1;
The important parts here are the load_components
call, which makes
your Schema class a subclass of DBIx::Class::Schema::Versioned
, and
the setting of the $VERSION
variable.
When this Schema is deployed to a database, it will create an extra
table called dbix_class_schema_versions
. An initial row will be
added containing the $VERSION
of the deployed schema, and the date
it was installed:
version | installed --------------------------------- 0.0.1 | 2009-12-01 15:55:02
This data is used to verify, on every re-connection to the database, that the installed software (Schema) matches the version of the database structure it is talking to. If the version does not match, a warning is given.
Upgrading the database structure
First, make sure you have a copy of the SQL for the original Schema:
My::Schema->connect('dbi:SQLite:test.db')->create_ddl_dir();
By default, this will produce an SQL DDL file for PostgreSQL, MySQL, and SQLite. See the DBIx::Class::Schema documentation for other formats and arguments.
Second, change your Result class to represent the new structure:
- title => { data_type => 'VARCHAR', size => 255' }, + title => { data_type => 'VARCHAR', size => 1024, is_nullable => 0 },
And change the version:
- our $VERSION = '0.01'; + our $VERSION = '0.02';
Now create another set of DDL files, upgrading from the original database layout to the new one:
My::Schema->connect('dbi:SQLite:test.db')->create_ddl_dir(undef, '0.01', undef, '0.02');
Repeat this for as many upgrade paths (old version, new version) as you want to support.
The new Schema can now be used, along with the SQL files, to upgrade any existing database:
My::Schema->connect('dbi:SQLite:test.db')->upgrade();
Caveats
This system provides a bare-bones way of upgrading the structure of
your database. It does not know anything about data migration. You are
encouraged to investigate and override the various methods that
Versioned
supplies with any code you need in order to migrate the
data.
Always examine the produced DDL files carefully. SQL::Translator is used to produce the diffs, and while it is good, it may not be perfect for your use.
Further reading
Make sure you read and understand the documentation of DBIx::Class::Schema::Versioned.
AUTHOR
Jess Robinson <castaway@desert-island.me.uk>