Merge 'trunk' into 'versioning'
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema / Versioned.pm
CommitLineData
a89c6fc0 1package # Hide from PAUSE
2 DBIx::Class::Version::Table;
c9d2e0a2 3use base 'DBIx::Class';
4use strict;
5use warnings;
6
7__PACKAGE__->load_components(qw/ Core/);
b4b1e91c 8__PACKAGE__->table('dbix_class_schema_versions');
c9d2e0a2 9
ad1446da 10__PACKAGE__->add_columns
732dc718 11 ( 'version' => {
ad1446da 12 'data_type' => 'VARCHAR',
13 'is_auto_increment' => 0,
14 'default_value' => undef,
15 'is_foreign_key' => 0,
732dc718 16 'name' => 'version',
ad1446da 17 'is_nullable' => 0,
18 'size' => '10'
19 },
732dc718 20 'installed' => {
c9d2e0a2 21 'data_type' => 'VARCHAR',
22 'is_auto_increment' => 0,
23 'default_value' => undef,
24 'is_foreign_key' => 0,
732dc718 25 'name' => 'installed',
c9d2e0a2 26 'is_nullable' => 0,
27 'size' => '20'
ad1446da 28 },
29 );
732dc718 30__PACKAGE__->set_primary_key('version');
c9d2e0a2 31
a89c6fc0 32package # Hide from PAUSE
33 DBIx::Class::Version::TableCompat;
732dc718 34use base 'DBIx::Class';
35__PACKAGE__->load_components(qw/ Core/);
b4b1e91c 36__PACKAGE__->table('SchemaVersions');
37
732dc718 38__PACKAGE__->add_columns
39 ( 'Version' => {
40 'data_type' => 'VARCHAR',
41 },
42 'Installed' => {
43 'data_type' => 'VARCHAR',
44 },
45 );
46__PACKAGE__->set_primary_key('Version');
47
a89c6fc0 48package # Hide from PAUSE
49 DBIx::Class::Version;
c9d2e0a2 50use base 'DBIx::Class::Schema';
51use strict;
52use warnings;
53
54__PACKAGE__->register_class('Table', 'DBIx::Class::Version::Table');
55
a89c6fc0 56package # Hide from PAUSE
57 DBIx::Class::VersionCompat;
b4b1e91c 58use base 'DBIx::Class::Schema';
59use strict;
60use warnings;
61
62__PACKAGE__->register_class('TableCompat', 'DBIx::Class::Version::TableCompat');
63
c9d2e0a2 64
65# ---------------------------------------------------------------------------
8424c090 66
67=head1 NAME
68
69DBIx::Class::Schema::Versioned - DBIx::Class::Schema plugin for Schema upgrades
70
71=head1 SYNOPSIS
72
73 package Library::Schema;
74 use base qw/DBIx::Class::Schema/;
75 # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
76 __PACKAGE__->load_classes(qw/CD Book DVD/);
77
78 __PACKAGE__->load_components(qw/+DBIx::Class::Schema::Versioned/);
79 __PACKAGE__->upgrade_directory('/path/to/upgrades/');
80 __PACKAGE__->backup_directory('/path/to/backups/');
81
82
83=head1 DESCRIPTION
84
85This module is a component designed to extend L<DBIx::Class::Schema>
86classes, to enable them to upgrade to newer schema layouts. To use this
87module, you need to have called C<create_ddl_dir> on your Schema to
88create your upgrade files to include with your delivery.
89
b4b1e91c 90A table called I<dbix_class_schema_versions> is created and maintained by the
8424c090 91module. This contains two fields, 'Version' and 'Installed', which
92contain each VERSION of your Schema, and the date+time it was installed.
93
94The actual upgrade is called manually by calling C<upgrade> on your
95schema object. Code is run at connect time to determine whether an
96upgrade is needed, if so, a warning "Versions out of sync" is
97produced.
98
99So you'll probably want to write a script which generates your DDLs and diffs
100and another which executes the upgrade.
101
102NB: At the moment, only SQLite and MySQL are supported. This is due to
103spotty behaviour in the SQL::Translator producers, please help us by
104them.
105
106=head1 METHODS
107
108=head2 upgrade_directory
109
110Use this to set the directory your upgrade files are stored in.
111
112=head2 backup_directory
113
114Use this to set the directory you want your backups stored in.
115
116=cut
117
c9d2e0a2 118package DBIx::Class::Schema::Versioned;
119
120use strict;
121use warnings;
122use base 'DBIx::Class';
123use POSIX 'strftime';
124use Data::Dumper;
125
126__PACKAGE__->mk_classdata('_filedata');
127__PACKAGE__->mk_classdata('upgrade_directory');
8795fefb 128__PACKAGE__->mk_classdata('backup_directory');
f925f7cb 129__PACKAGE__->mk_classdata('do_backup');
8424c090 130__PACKAGE__->mk_classdata('do_diff_on_init');
131
8424c090 132=head2 get_db_version
c9d2e0a2 133
8424c090 134Returns the version that your database is currently at. This is determined by the values in the
b4b1e91c 135dbix_class_schema_versions table that $self->upgrade writes to.
c9d2e0a2 136
8424c090 137=cut
c9d2e0a2 138
e6129e56 139sub get_db_version
140{
141 my ($self, $rs) = @_;
142
143 my $vtable = $self->{vschema}->resultset('Table');
ecea7937 144 my $version = 0;
7146f14f 145 eval {
732dc718 146 my $stamp = $vtable->get_column('installed')->max;
147 $version = $vtable->search({ installed => $stamp })->first->version;
7146f14f 148 };
149 return $version;
e6129e56 150}
151
a2800991 152sub _source_exists
c9d2e0a2 153{
154 my ($self, $rs) = @_;
155
156 my $c = eval {
157 $rs->search({ 1, 0 })->count;
158 };
159 return 0 if $@ || !defined $c;
160
161 return 1;
162}
163
8424c090 164=head2 backup
165
166This is an overwritable method which is called just before the upgrade, to
167allow you to make a backup of the database. Per default this method attempts
168to call C<< $self->storage->backup >>, to run the standard backup on each
169database type.
170
171This method should return the name of the backup file, if appropriate..
172
f81b9157 173This method is disabled by default. Set $schema->do_backup(1) to enable it.
174
8424c090 175=cut
176
c9d2e0a2 177sub backup
178{
179 my ($self) = @_;
180 ## Make each ::DBI::Foo do this
8795fefb 181 $self->storage->backup($self->backup_directory());
c9d2e0a2 182}
183
b6d9f089 184# is this just a waste of time? if not then merge with DBI.pm
8424c090 185sub _create_db_to_schema_diff {
186 my $self = shift;
c9d2e0a2 187
8424c090 188 my %driver_to_db_map = (
189 'mysql' => 'MySQL'
190 );
e6129e56 191
8424c090 192 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
193 unless ($db) {
194 print "Sorry, this is an unsupported DB\n";
195 return;
196 }
c9d2e0a2 197
b6d9f089 198 eval 'require SQL::Translator "0.09"';
199 if ($@) {
200 $self->throw_exception("SQL::Translator 0.09 required");
201 }
8424c090 202
203 my $db_tr = SQL::Translator->new({
204 add_drop_table => 1,
205 parser => 'DBI',
206 parser_args => { dbh => $self->storage->dbh }
207 });
208
209 $db_tr->producer($db);
210 my $dbic_tr = SQL::Translator->new;
211 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
212 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
213 $dbic_tr->data($self);
214 $dbic_tr->producer($db);
215
216 $db_tr->schema->name('db_schema');
217 $dbic_tr->schema->name('dbic_schema');
218
219 # is this really necessary?
220 foreach my $tr ($db_tr, $dbic_tr) {
221 my $data = $tr->data;
222 $tr->parser->($tr, $$data);
223 }
c9d2e0a2 224
8424c090 225 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
226 $dbic_tr->schema, $db,
227 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
228
229 my $filename = $self->ddl_filename(
230 $db,
8424c090 231 $self->schema_version,
99a74c4a 232 $self->upgrade_directory,
8424c090 233 'PRE',
234 );
235 my $file;
236 if(!open($file, ">$filename"))
237 {
238 $self->throw_exception("Can't open $filename for writing ($!)");
239 next;
c9d2e0a2 240 }
8424c090 241 print $file $diff;
242 close($file);
c9d2e0a2 243
8424c090 244 print "WARNING: There may be differences between your DB and your DBIC schema. Please review and if necessary run the SQL in $filename to sync your DB.\n";
c9d2e0a2 245}
246
8424c090 247=head2 upgrade
e2c0df8e 248
8424c090 249Call this to attempt to upgrade your database from the version it is at to the version
250this DBIC schema is at.
c9d2e0a2 251
8424c090 252It requires an SQL diff file to exist in $schema->upgrade_directory, normally you will
253have created this using $schema->create_ddl_dir.
c9d2e0a2 254
8424c090 255=cut
c9d2e0a2 256
8424c090 257sub upgrade
258{
259 my ($self) = @_;
260 my $db_version = $self->get_db_version();
c9d2e0a2 261
8424c090 262 # db unversioned
263 unless ($db_version) {
b4b1e91c 264 # set version in dbix_class_schema_versions table, can't actually upgrade as we don 't know what version the DB is at
8424c090 265 $self->_create_db_to_schema_diff() if ($self->do_diff_on_init);
c9d2e0a2 266
8424c090 267 # create versions table and version row
268 $self->{vschema}->deploy;
269 $self->_set_db_version;
270 return;
c9d2e0a2 271 }
272
8424c090 273 # db and schema at same version. do nothing
274 if ($db_version eq $self->schema_version) {
275 print "Upgrade not necessary\n";
276 return;
c9d2e0a2 277 }
278
37fcb5b5 279 # strangely the first time this is called can
280 # differ to subsequent times. so we call it
281 # here to be sure.
282 # XXX - just fix it
283 $self->storage->sqlt_type;
284
8424c090 285 my $upgrade_file = $self->ddl_filename(
286 $self->storage->sqlt_type,
8424c090 287 $self->schema_version,
99a74c4a 288 $self->upgrade_directory,
8424c090 289 $db_version,
290 );
c9d2e0a2 291
8424c090 292 unless (-f $upgrade_file) {
293 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
294 return;
295 }
c9d2e0a2 296
8424c090 297 # backup if necessary then apply upgrade
298 $self->_filedata($self->_read_sql_file($upgrade_file));
299 $self->backup() if($self->do_backup);
300 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 301
b4b1e91c 302 # set row in dbix_class_schema_versions table
8424c090 303 $self->_set_db_version;
304}
c9d2e0a2 305
8424c090 306sub _set_db_version {
307 my $self = shift;
c9d2e0a2 308
8424c090 309 my $vtable = $self->{vschema}->resultset('Table');
732dc718 310 $vtable->create({ version => $self->schema_version,
311 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
8424c090 312 });
c9d2e0a2 313
8424c090 314}
c9d2e0a2 315
8424c090 316sub _read_sql_file {
317 my $self = shift;
318 my $file = shift || return;
319
320 my $fh;
321 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
d89d8604 322 my @data = split(/\n/, join('', <$fh>));
323 @data = grep(!/^--/, @data);
324 @data = split(/;/, join('', @data));
8424c090 325 close($fh);
326 @data = grep { $_ && $_ !~ /^-- / } @data;
07d404a8 327 @data = grep { $_ !~ /^(BEGIN|BEGIN TRANSACTION|COMMIT)/m } @data;
8424c090 328 return \@data;
329}
e6129e56 330
331=head2 do_upgrade
332
c9d2e0a2 333This is an overwritable method used to run your upgrade. The freeform method
334allows you to run your upgrade any way you please, you can call C<run_upgrade>
335any number of times to run the actual SQL commands, and in between you can
336sandwich your data upgrading. For example, first run all the B<CREATE>
337commands, then migrate your data from old to new tables/formats, then
e7b14c5b 338issue the DROP commands when you are finished. Will run the whole file as it is by default.
8424c090 339
340=cut
341
342sub do_upgrade
343{
e7b14c5b 344 my ($self) = @_;
8424c090 345
e7b14c5b 346 # just run all the commands (including inserts) in order
347 $self->run_upgrade(qr/.*?/);
8424c090 348}
349
c9d2e0a2 350=head2 run_upgrade
351
352 $self->run_upgrade(qr/create/i);
353
354Runs a set of SQL statements matching a passed in regular expression. The
355idea is that this method can be called any number of times from your
356C<upgrade> method, running whichever commands you specify via the
8424c090 357regex in the parameter. Probably won't work unless called from the overridable
358do_upgrade method.
c9d2e0a2 359
8424c090 360=cut
8795fefb 361
8424c090 362sub run_upgrade
363{
364 my ($self, $stm) = @_;
8795fefb 365
8424c090 366 return unless ($self->_filedata);
367 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
368 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
8795fefb 369
8424c090 370 for (@statements)
371 {
372 $self->storage->debugobj->query_start($_) if $self->storage->debug;
373 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
374 $self->storage->debugobj->query_end($_) if $self->storage->debug;
375 }
8795fefb 376
8424c090 377 return 1;
378}
42416a0b 379
ecea7937 380=head2 connection
381
382Overloaded method. This checks the DBIC schema version against the DB version and
383warns if they are not the same or if the DB is unversioned. It also provides
384compatibility between the old versions table (SchemaVersions) and the new one
385(dbix_class_schema_versions).
386
f81b9157 387To avoid the checks on connect, set the env var DBIC_NO_VERSION_CHECK or alternatively you can set the ignore_version attr in the forth arg like so:
388
389 my $schema = MyApp::Schema->connect(
390 $dsn,
391 $user,
392 $password,
393 { ignore_version => 1 },
394 );
ecea7937 395
396=cut
397
8424c090 398sub connection {
399 my $self = shift;
400 $self->next::method(@_);
f81b9157 401 $self->_on_connect($_[3]);
8424c090 402 return $self;
403}
404
405sub _on_connect
406{
f81b9157 407 my ($self, $args) = @_;
408
409 $args = {} unless $args;
8424c090 410 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 411 my $vtable = $self->{vschema}->resultset('Table');
412
413 # check for legacy versions table and move to new if exists
414 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
415 unless ($self->_source_exists($vtable)) {
416 my $vtable_compat = $vschema_compat->resultset('TableCompat');
417 if ($self->_source_exists($vtable_compat)) {
418 $self->{vschema}->deploy;
732dc718 419 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 420 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
421 }
422 }
f81b9157 423
ecea7937 424 # useful when connecting from scripts etc
f81b9157 425 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
8424c090 426 my $pversion = $self->get_db_version();
427
428 if($pversion eq $self->schema_version)
429 {
ffdf4f11 430# warn "This version is already installed\n";
8424c090 431 return 1;
432 }
42416a0b 433
8424c090 434 if(!$pversion)
435 {
436 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
437 return 1;
438 }
439
440 warn "Versions out of sync. This is " . $self->schema_version .
441 ", your database contains version $pversion, please call upgrade on your Schema.\n";
442}
443
4441;
445
446
447=head1 AUTHORS
c9d2e0a2 448
449Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 450Luke Saunders <luke@shadowcatsystems.co.uk>
451
452=head1 LICENSE
453
454You may distribute this code under the same terms as Perl itself.