reordered methods of Versioned.pm and factored the initialisation stuff from upgrade...
[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
93e4d41a 106
107=head1 GETTING STARTED
108
109
110=cut
111
8424c090 112=head1 METHODS
113
114=head2 upgrade_directory
115
116Use this to set the directory your upgrade files are stored in.
117
118=head2 backup_directory
119
120Use this to set the directory you want your backups stored in.
121
122=cut
123
c9d2e0a2 124package DBIx::Class::Schema::Versioned;
125
126use strict;
127use warnings;
128use base 'DBIx::Class';
129use POSIX 'strftime';
130use Data::Dumper;
131
132__PACKAGE__->mk_classdata('_filedata');
133__PACKAGE__->mk_classdata('upgrade_directory');
8795fefb 134__PACKAGE__->mk_classdata('backup_directory');
f925f7cb 135__PACKAGE__->mk_classdata('do_backup');
8424c090 136__PACKAGE__->mk_classdata('do_diff_on_init');
137
93e4d41a 138=head2 install
c9d2e0a2 139
93e4d41a 140=over 4
c9d2e0a2 141
93e4d41a 142=item Arguments: $db_version
e6129e56 143
93e4d41a 144=back
8424c090 145
93e4d41a 146Call this to initialise a previously unversioned database. The table 'dbix_class_schema_versions' will be created which will be used to store the database version.
8424c090 147
93e4d41a 148Takes one argument which should be the version that the database is currently at. Defaults to the return value of L</schema_version>.
8424c090 149
93e4d41a 150See L</getting_started> for more details.
f81b9157 151
8424c090 152=cut
153
93e4d41a 154sub install
c9d2e0a2 155{
93e4d41a 156 my ($self, $new_version) = @_;
c9d2e0a2 157
93e4d41a 158 # must be called on a fresh database
159 if ($self->get_db_version()) {
160 warn 'Install not possible as versions table already exists in database';
8424c090 161 }
c9d2e0a2 162
93e4d41a 163 # default to current version if none passed
164 $new_version ||= $self->schema_version();
8424c090 165
93e4d41a 166 unless ($new_version) {
167 # create versions table and version row
168 $self->{vschema}->deploy;
169 $self->_set_db_version;
8424c090 170 }
c9d2e0a2 171}
172
8424c090 173=head2 upgrade
e2c0df8e 174
8424c090 175Call this to attempt to upgrade your database from the version it is at to the version
176this DBIC schema is at.
c9d2e0a2 177
8424c090 178It requires an SQL diff file to exist in $schema->upgrade_directory, normally you will
179have created this using $schema->create_ddl_dir.
c9d2e0a2 180
8424c090 181=cut
c9d2e0a2 182
8424c090 183sub upgrade
184{
185 my ($self) = @_;
186 my $db_version = $self->get_db_version();
c9d2e0a2 187
8424c090 188 # db unversioned
189 unless ($db_version) {
93e4d41a 190 warn 'Upgrade not possible as database is unversioned. Please call install first.';
8424c090 191 return;
c9d2e0a2 192 }
193
8424c090 194 # db and schema at same version. do nothing
195 if ($db_version eq $self->schema_version) {
196 print "Upgrade not necessary\n";
197 return;
c9d2e0a2 198 }
199
37fcb5b5 200 # strangely the first time this is called can
201 # differ to subsequent times. so we call it
202 # here to be sure.
203 # XXX - just fix it
204 $self->storage->sqlt_type;
205
8424c090 206 my $upgrade_file = $self->ddl_filename(
207 $self->storage->sqlt_type,
8424c090 208 $self->schema_version,
99a74c4a 209 $self->upgrade_directory,
8424c090 210 $db_version,
211 );
c9d2e0a2 212
8424c090 213 unless (-f $upgrade_file) {
214 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
215 return;
216 }
c9d2e0a2 217
8424c090 218 # backup if necessary then apply upgrade
219 $self->_filedata($self->_read_sql_file($upgrade_file));
220 $self->backup() if($self->do_backup);
221 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 222
b4b1e91c 223 # set row in dbix_class_schema_versions table
8424c090 224 $self->_set_db_version;
225}
c9d2e0a2 226
e6129e56 227=head2 do_upgrade
228
c9d2e0a2 229This is an overwritable method used to run your upgrade. The freeform method
230allows you to run your upgrade any way you please, you can call C<run_upgrade>
231any number of times to run the actual SQL commands, and in between you can
232sandwich your data upgrading. For example, first run all the B<CREATE>
233commands, then migrate your data from old to new tables/formats, then
e7b14c5b 234issue the DROP commands when you are finished. Will run the whole file as it is by default.
8424c090 235
236=cut
237
238sub do_upgrade
239{
e7b14c5b 240 my ($self) = @_;
8424c090 241
e7b14c5b 242 # just run all the commands (including inserts) in order
243 $self->run_upgrade(qr/.*?/);
8424c090 244}
245
c9d2e0a2 246=head2 run_upgrade
247
248 $self->run_upgrade(qr/create/i);
249
250Runs a set of SQL statements matching a passed in regular expression. The
251idea is that this method can be called any number of times from your
252C<upgrade> method, running whichever commands you specify via the
8424c090 253regex in the parameter. Probably won't work unless called from the overridable
254do_upgrade method.
c9d2e0a2 255
8424c090 256=cut
8795fefb 257
8424c090 258sub run_upgrade
259{
260 my ($self, $stm) = @_;
8795fefb 261
8424c090 262 return unless ($self->_filedata);
263 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
264 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
8795fefb 265
8424c090 266 for (@statements)
267 {
268 $self->storage->debugobj->query_start($_) if $self->storage->debug;
269 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
270 $self->storage->debugobj->query_end($_) if $self->storage->debug;
271 }
8795fefb 272
8424c090 273 return 1;
274}
42416a0b 275
93e4d41a 276=head2 get_db_version
277
278Returns the version that your database is currently at. This is determined by the values in the
279dbix_class_schema_versions table that $self->upgrade writes to.
280
281=cut
282
283sub get_db_version
284{
285 my ($self, $rs) = @_;
286
287 my $vtable = $self->{vschema}->resultset('Table');
288 my $version = 0;
289 eval {
290 my $stamp = $vtable->get_column('installed')->max;
291 $version = $vtable->search({ installed => $stamp })->first->version;
292 };
293 return $version;
294}
295
296=head2 schema_version
297
298Returns the current schema class' $VERSION
299
300=cut
301
302=head2 backup
303
304This is an overwritable method which is called just before the upgrade, to
305allow you to make a backup of the database. Per default this method attempts
306to call C<< $self->storage->backup >>, to run the standard backup on each
307database type.
308
309This method should return the name of the backup file, if appropriate..
310
311This method is disabled by default. Set $schema->do_backup(1) to enable it.
312
313=cut
314
315sub backup
316{
317 my ($self) = @_;
318 ## Make each ::DBI::Foo do this
319 $self->storage->backup($self->backup_directory());
320}
321
ecea7937 322=head2 connection
323
324Overloaded method. This checks the DBIC schema version against the DB version and
325warns if they are not the same or if the DB is unversioned. It also provides
326compatibility between the old versions table (SchemaVersions) and the new one
327(dbix_class_schema_versions).
328
f81b9157 329To 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:
330
331 my $schema = MyApp::Schema->connect(
332 $dsn,
333 $user,
334 $password,
335 { ignore_version => 1 },
336 );
ecea7937 337
338=cut
339
8424c090 340sub connection {
341 my $self = shift;
342 $self->next::method(@_);
f81b9157 343 $self->_on_connect($_[3]);
8424c090 344 return $self;
345}
346
347sub _on_connect
348{
f81b9157 349 my ($self, $args) = @_;
350
351 $args = {} unless $args;
8424c090 352 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 353 my $vtable = $self->{vschema}->resultset('Table');
354
355 # check for legacy versions table and move to new if exists
356 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
357 unless ($self->_source_exists($vtable)) {
358 my $vtable_compat = $vschema_compat->resultset('TableCompat');
359 if ($self->_source_exists($vtable_compat)) {
360 $self->{vschema}->deploy;
732dc718 361 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 362 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
363 }
364 }
f81b9157 365
ecea7937 366 # useful when connecting from scripts etc
f81b9157 367 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
8424c090 368 my $pversion = $self->get_db_version();
369
370 if($pversion eq $self->schema_version)
371 {
ffdf4f11 372# warn "This version is already installed\n";
8424c090 373 return 1;
374 }
42416a0b 375
8424c090 376 if(!$pversion)
377 {
378 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
379 return 1;
380 }
381
382 warn "Versions out of sync. This is " . $self->schema_version .
383 ", your database contains version $pversion, please call upgrade on your Schema.\n";
384}
385
93e4d41a 386# is this just a waste of time? if not then merge with DBI.pm
387sub _create_db_to_schema_diff {
388 my $self = shift;
389
390 my %driver_to_db_map = (
391 'mysql' => 'MySQL'
392 );
393
394 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
395 unless ($db) {
396 print "Sorry, this is an unsupported DB\n";
397 return;
398 }
399
400 eval 'require SQL::Translator "0.09"';
401 if ($@) {
402 $self->throw_exception("SQL::Translator 0.09 required");
403 }
404
405 my $db_tr = SQL::Translator->new({
406 add_drop_table => 1,
407 parser => 'DBI',
408 parser_args => { dbh => $self->storage->dbh }
409 });
410
411 $db_tr->producer($db);
412 my $dbic_tr = SQL::Translator->new;
413 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
414 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
415 $dbic_tr->data($self);
416 $dbic_tr->producer($db);
417
418 $db_tr->schema->name('db_schema');
419 $dbic_tr->schema->name('dbic_schema');
420
421 # is this really necessary?
422 foreach my $tr ($db_tr, $dbic_tr) {
423 my $data = $tr->data;
424 $tr->parser->($tr, $$data);
425 }
426
427 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
428 $dbic_tr->schema, $db,
429 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
430
431 my $filename = $self->ddl_filename(
432 $db,
433 $self->schema_version,
434 $self->upgrade_directory,
435 'PRE',
436 );
437 my $file;
438 if(!open($file, ">$filename"))
439 {
440 $self->throw_exception("Can't open $filename for writing ($!)");
441 next;
442 }
443 print $file $diff;
444 close($file);
445
446 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";
447}
448
449
450sub _set_db_version {
451 my $self = shift;
452
453 my $vtable = $self->{vschema}->resultset('Table');
454 $vtable->create({ version => $self->schema_version,
455 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
456 });
457
458}
459
460sub _read_sql_file {
461 my $self = shift;
462 my $file = shift || return;
463
464 my $fh;
465 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
466 my @data = split(/\n/, join('', <$fh>));
467 @data = grep(!/^--/, @data);
468 @data = split(/;/, join('', @data));
469 close($fh);
470 @data = grep { $_ && $_ !~ /^-- / } @data;
471 @data = grep { $_ !~ /^(BEGIN|BEGIN TRANSACTION|COMMIT)/m } @data;
472 return \@data;
473}
474
475sub _source_exists
476{
477 my ($self, $rs) = @_;
478
479 my $c = eval {
480 $rs->search({ 1, 0 })->count;
481 };
482 return 0 if $@ || !defined $c;
483
484 return 1;
485}
486
8424c090 4871;
488
489
490=head1 AUTHORS
c9d2e0a2 491
492Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 493Luke Saunders <luke@shadowcatsystems.co.uk>
494
495=head1 LICENSE
496
497You may distribute this code under the same terms as Perl itself.