fixed braindead typo in docs telling people to use a version of Moose that does not...
[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;
e84a43c1 74 use base qw/DBIx::Class::Schema/;
75
76 our $VERSION = 0.001;
77
8424c090 78 # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
79 __PACKAGE__->load_classes(qw/CD Book DVD/);
80
e84a43c1 81 __PACKAGE__->load_components(qw/Schema::Versioned/);
8424c090 82 __PACKAGE__->upgrade_directory('/path/to/upgrades/');
8424c090 83
84
85=head1 DESCRIPTION
86
e84a43c1 87This module provides methods to apply DDL changes to your database using SQL
88diff files. Normally these diff files would be created using
89L<DBIx::Class::Schema/create_ddl_dir>.
8424c090 90
b4b1e91c 91A table called I<dbix_class_schema_versions> is created and maintained by the
e84a43c1 92module. This is used to determine which version your database is currently at.
93Similarly the $VERSION in your DBIC schema class is used to determine the
94current DBIC schema version.
8424c090 95
e84a43c1 96The upgrade is initiated manually by calling C<upgrade> on your schema object,
97this will attempt to upgrade the database from its current version to the current
98schema version using a diff from your I<upgrade_directory>. If a suitable diff is
99not found then no upgrade is possible.
8424c090 100
101NB: At the moment, only SQLite and MySQL are supported. This is due to
102spotty behaviour in the SQL::Translator producers, please help us by
e63a82f7 103enhancing them. Ask on the mailing list or IRC channel for details (community details
e84a43c1 104in L<DBIx::Class>).
93e4d41a 105
106=head1 GETTING STARTED
107
e84a43c1 108Firstly you need to setup your schema class as per the L</SYNOPSIS>, make sure
109you have specified an upgrade_directory and an initial $VERSION.
93e4d41a 110
e84a43c1 111Then you'll need two scripts, one to create DDL files and diffs and another to perform
112upgrades. Your creation script might look like a bit like this:
93e4d41a 113
e84a43c1 114 use strict;
115 use Pod::Usage;
116 use Getopt::Long;
117 use MyApp::Schema;
8424c090 118
e84a43c1 119 my ( $preversion, $help );
120 GetOptions(
121 'p|preversion:s' => \$preversion,
122 ) or die pod2usage;
8424c090 123
e84a43c1 124 my $schema = MyApp::Schema->connect(
125 $dsn,
126 $user,
127 $password,
128 );
129 my $sql_dir = './sql';
130 my $version = $schema->schema_version();
131 $schema->create_ddl_dir( 'MySQL', $version, $sql_dir, $preversion );
8424c090 132
e84a43c1 133Then your upgrade script might look like so:
134
135 use strict;
136 use MyApp::Schema;
137
138 my $schema = MyApp::Schema->connect(
139 $dsn,
140 $user,
141 $password,
142 );
8424c090 143
e84a43c1 144 if (!$schema->get_db_version()) {
145 # schema is unversioned
146 $schema->deploy();
147 } else {
148 $schema->upgrade();
149 }
150
151The script above assumes that if the database is unversioned then it is empty
152and we can safely deploy the DDL to it. However things are not always so simple.
153
154if you want to initialise a pre-existing database where the DDL is not the same
155as the DDL for your current schema version then you will need a diff which
156converts the database's DDL to the current DDL. The best way to do this is
157to get a dump of the database schema (without data) and save that in your
158SQL directory as version 0.000 (the filename must be as with
159L<DBIx::Class::Schema/ddl_filename>) then create a diff using your create DDL
160script given above from version 0.000 to the current version. Then hand check
161and if necessary edit the resulting diff to ensure that it will apply. Once you have
162done all that you can do this:
163
164 if (!$schema->get_db_version()) {
165 # schema is unversioned
166 $schema->install("0.000");
167 }
168
169 # this will now apply the 0.000 to current version diff
170 $schema->upgrade();
171
172In the case of an unversioned database the above code will create the
173dbix_class_schema_versions table and write version 0.000 to it, then
174upgrade will then apply the diff we talked about creating in the previous paragraph
175and then you're good to go.
8424c090 176
177=cut
178
c9d2e0a2 179package DBIx::Class::Schema::Versioned;
180
181use strict;
182use warnings;
183use base 'DBIx::Class';
184use POSIX 'strftime';
185use Data::Dumper;
186
187__PACKAGE__->mk_classdata('_filedata');
188__PACKAGE__->mk_classdata('upgrade_directory');
8795fefb 189__PACKAGE__->mk_classdata('backup_directory');
f925f7cb 190__PACKAGE__->mk_classdata('do_backup');
8424c090 191__PACKAGE__->mk_classdata('do_diff_on_init');
192
e84a43c1 193
194=head1 METHODS
195
196=head2 upgrade_directory
197
198Use this to set the directory your upgrade files are stored in.
199
200=head2 backup_directory
201
202Use this to set the directory you want your backups stored in (note that backups
203are disabled by default).
204
205=cut
206
93e4d41a 207=head2 install
c9d2e0a2 208
93e4d41a 209=over 4
c9d2e0a2 210
93e4d41a 211=item Arguments: $db_version
e6129e56 212
93e4d41a 213=back
8424c090 214
93e4d41a 215Call 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 216
93e4d41a 217Takes one argument which should be the version that the database is currently at. Defaults to the return value of L</schema_version>.
8424c090 218
93e4d41a 219See L</getting_started> for more details.
f81b9157 220
8424c090 221=cut
222
93e4d41a 223sub install
c9d2e0a2 224{
93e4d41a 225 my ($self, $new_version) = @_;
c9d2e0a2 226
93e4d41a 227 # must be called on a fresh database
228 if ($self->get_db_version()) {
229 warn 'Install not possible as versions table already exists in database';
8424c090 230 }
c9d2e0a2 231
93e4d41a 232 # default to current version if none passed
233 $new_version ||= $self->schema_version();
8424c090 234
e84a43c1 235 if ($new_version) {
93e4d41a 236 # create versions table and version row
237 $self->{vschema}->deploy;
a354b842 238 $self->_set_db_version({ version => $new_version });
8424c090 239 }
c9d2e0a2 240}
241
e84a43c1 242=head2 deploy
243
244Same as L<DBIx::Class::Schema/deploy> but also calls C<install>.
245
246=cut
247
248sub deploy {
249 my $self = shift;
250 $self->next::method(@_);
251 $self->install();
252}
253
a354b842 254=head2 create_upgrade_path
255
256=over 4
257
258=item Arguments: { upgrade_file => $file }
259
260=back
261
262Virtual method that should be overriden to create an upgrade file.
263This is useful in the case of upgrading across multiple versions
264to concatenate several files to create one upgrade file.
265
266=cut
267
268sub create_upgrade_path {
269 ## override this method
270}
271
8424c090 272=head2 upgrade
e2c0df8e 273
8424c090 274Call this to attempt to upgrade your database from the version it is at to the version
e84a43c1 275this DBIC schema is at. If they are the same it does nothing.
276
277It requires an SQL diff file to exist in you I<upgrade_directory>, normally you will
278have created this using L<DBIx::Class::Schema/create_ddl_dir>.
c9d2e0a2 279
e84a43c1 280If successful the dbix_class_schema_versions table is updated with the current
281DBIC schema version.
c9d2e0a2 282
8424c090 283=cut
c9d2e0a2 284
8424c090 285sub upgrade
286{
287 my ($self) = @_;
288 my $db_version = $self->get_db_version();
c9d2e0a2 289
8424c090 290 # db unversioned
291 unless ($db_version) {
93e4d41a 292 warn 'Upgrade not possible as database is unversioned. Please call install first.';
8424c090 293 return;
c9d2e0a2 294 }
295
8424c090 296 # db and schema at same version. do nothing
297 if ($db_version eq $self->schema_version) {
298 print "Upgrade not necessary\n";
299 return;
c9d2e0a2 300 }
301
37fcb5b5 302 # strangely the first time this is called can
303 # differ to subsequent times. so we call it
304 # here to be sure.
305 # XXX - just fix it
306 $self->storage->sqlt_type;
307
8424c090 308 my $upgrade_file = $self->ddl_filename(
309 $self->storage->sqlt_type,
8424c090 310 $self->schema_version,
99a74c4a 311 $self->upgrade_directory,
8424c090 312 $db_version,
313 );
c9d2e0a2 314
a354b842 315 $self->create_upgrade_path({ upgrade_file => $upgrade_file });
316
8424c090 317 unless (-f $upgrade_file) {
318 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
319 return;
320 }
c9d2e0a2 321
a354b842 322 warn "\nDB version ($db_version) is lower than the schema version (".$self->schema_version."). Attempting upgrade.\n";
323
8424c090 324 # backup if necessary then apply upgrade
325 $self->_filedata($self->_read_sql_file($upgrade_file));
326 $self->backup() if($self->do_backup);
327 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 328
b4b1e91c 329 # set row in dbix_class_schema_versions table
8424c090 330 $self->_set_db_version;
331}
c9d2e0a2 332
e6129e56 333=head2 do_upgrade
334
c9d2e0a2 335This is an overwritable method used to run your upgrade. The freeform method
336allows you to run your upgrade any way you please, you can call C<run_upgrade>
337any number of times to run the actual SQL commands, and in between you can
338sandwich your data upgrading. For example, first run all the B<CREATE>
339commands, then migrate your data from old to new tables/formats, then
e7b14c5b 340issue the DROP commands when you are finished. Will run the whole file as it is by default.
8424c090 341
342=cut
343
344sub do_upgrade
345{
e7b14c5b 346 my ($self) = @_;
8424c090 347
e7b14c5b 348 # just run all the commands (including inserts) in order
349 $self->run_upgrade(qr/.*?/);
8424c090 350}
351
c9d2e0a2 352=head2 run_upgrade
353
354 $self->run_upgrade(qr/create/i);
355
356Runs a set of SQL statements matching a passed in regular expression. The
357idea is that this method can be called any number of times from your
e84a43c1 358C<do_upgrade> method, running whichever commands you specify via the
8424c090 359regex in the parameter. Probably won't work unless called from the overridable
360do_upgrade method.
c9d2e0a2 361
8424c090 362=cut
8795fefb 363
8424c090 364sub run_upgrade
365{
366 my ($self, $stm) = @_;
8795fefb 367
8424c090 368 return unless ($self->_filedata);
369 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
370 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
8795fefb 371
8424c090 372 for (@statements)
373 {
374 $self->storage->debugobj->query_start($_) if $self->storage->debug;
abc8f12a 375 $self->apply_statement($_);
8424c090 376 $self->storage->debugobj->query_end($_) if $self->storage->debug;
377 }
8795fefb 378
8424c090 379 return 1;
380}
42416a0b 381
abc8f12a 382=head2 apply_statement
383
384Takes an SQL statement and runs it. Override this if you want to handle errors
385differently.
386
387=cut
388
389sub apply_statement {
390 my ($self, $statement) = @_;
391
392 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
393}
394
93e4d41a 395=head2 get_db_version
396
397Returns the version that your database is currently at. This is determined by the values in the
e84a43c1 398dbix_class_schema_versions table that C<upgrade> and C<install> write to.
93e4d41a 399
400=cut
401
402sub get_db_version
403{
404 my ($self, $rs) = @_;
405
406 my $vtable = $self->{vschema}->resultset('Table');
407 my $version = 0;
408 eval {
409 my $stamp = $vtable->get_column('installed')->max;
410 $version = $vtable->search({ installed => $stamp })->first->version;
411 };
412 return $version;
413}
414
415=head2 schema_version
416
417Returns the current schema class' $VERSION
418
419=cut
420
421=head2 backup
422
423This is an overwritable method which is called just before the upgrade, to
424allow you to make a backup of the database. Per default this method attempts
425to call C<< $self->storage->backup >>, to run the standard backup on each
426database type.
427
428This method should return the name of the backup file, if appropriate..
429
430This method is disabled by default. Set $schema->do_backup(1) to enable it.
431
432=cut
433
434sub backup
435{
436 my ($self) = @_;
437 ## Make each ::DBI::Foo do this
438 $self->storage->backup($self->backup_directory());
439}
440
ecea7937 441=head2 connection
442
443Overloaded method. This checks the DBIC schema version against the DB version and
444warns if they are not the same or if the DB is unversioned. It also provides
445compatibility between the old versions table (SchemaVersions) and the new one
446(dbix_class_schema_versions).
447
e84a43c1 448To 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 argument like so:
f81b9157 449
450 my $schema = MyApp::Schema->connect(
451 $dsn,
452 $user,
453 $password,
454 { ignore_version => 1 },
455 );
ecea7937 456
457=cut
458
8424c090 459sub connection {
460 my $self = shift;
461 $self->next::method(@_);
f81b9157 462 $self->_on_connect($_[3]);
8424c090 463 return $self;
464}
465
466sub _on_connect
467{
f81b9157 468 my ($self, $args) = @_;
469
470 $args = {} unless $args;
8424c090 471 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 472 my $vtable = $self->{vschema}->resultset('Table');
473
474 # check for legacy versions table and move to new if exists
475 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
476 unless ($self->_source_exists($vtable)) {
477 my $vtable_compat = $vschema_compat->resultset('TableCompat');
478 if ($self->_source_exists($vtable_compat)) {
479 $self->{vschema}->deploy;
732dc718 480 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 481 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
482 }
483 }
f81b9157 484
ecea7937 485 # useful when connecting from scripts etc
f81b9157 486 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
8424c090 487 my $pversion = $self->get_db_version();
488
489 if($pversion eq $self->schema_version)
490 {
ffdf4f11 491# warn "This version is already installed\n";
8424c090 492 return 1;
493 }
42416a0b 494
8424c090 495 if(!$pversion)
496 {
497 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
498 return 1;
499 }
500
501 warn "Versions out of sync. This is " . $self->schema_version .
502 ", your database contains version $pversion, please call upgrade on your Schema.\n";
503}
504
93e4d41a 505# is this just a waste of time? if not then merge with DBI.pm
506sub _create_db_to_schema_diff {
507 my $self = shift;
508
509 my %driver_to_db_map = (
510 'mysql' => 'MySQL'
511 );
512
513 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
514 unless ($db) {
515 print "Sorry, this is an unsupported DB\n";
516 return;
517 }
518
519 eval 'require SQL::Translator "0.09"';
520 if ($@) {
521 $self->throw_exception("SQL::Translator 0.09 required");
522 }
523
524 my $db_tr = SQL::Translator->new({
525 add_drop_table => 1,
526 parser => 'DBI',
527 parser_args => { dbh => $self->storage->dbh }
528 });
529
530 $db_tr->producer($db);
531 my $dbic_tr = SQL::Translator->new;
532 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
533 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
534 $dbic_tr->data($self);
535 $dbic_tr->producer($db);
536
537 $db_tr->schema->name('db_schema');
538 $dbic_tr->schema->name('dbic_schema');
539
540 # is this really necessary?
541 foreach my $tr ($db_tr, $dbic_tr) {
542 my $data = $tr->data;
543 $tr->parser->($tr, $$data);
544 }
545
546 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
547 $dbic_tr->schema, $db,
548 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
549
550 my $filename = $self->ddl_filename(
551 $db,
552 $self->schema_version,
553 $self->upgrade_directory,
554 'PRE',
555 );
556 my $file;
557 if(!open($file, ">$filename"))
558 {
559 $self->throw_exception("Can't open $filename for writing ($!)");
560 next;
561 }
562 print $file $diff;
563 close($file);
564
565 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";
566}
567
568
569sub _set_db_version {
570 my $self = shift;
a354b842 571 my ($params) = @_;
572 $params ||= {};
93e4d41a 573
a354b842 574 my $version = $params->{version} ? $params->{version} : $self->schema_version;
93e4d41a 575 my $vtable = $self->{vschema}->resultset('Table');
a354b842 576 $vtable->create({ version => $version,
93e4d41a 577 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
578 });
579
580}
581
582sub _read_sql_file {
583 my $self = shift;
584 my $file = shift || return;
585
586 my $fh;
587 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
588 my @data = split(/\n/, join('', <$fh>));
589 @data = grep(!/^--/, @data);
590 @data = split(/;/, join('', @data));
591 close($fh);
592 @data = grep { $_ && $_ !~ /^-- / } @data;
593 @data = grep { $_ !~ /^(BEGIN|BEGIN TRANSACTION|COMMIT)/m } @data;
594 return \@data;
595}
596
597sub _source_exists
598{
599 my ($self, $rs) = @_;
600
601 my $c = eval {
602 $rs->search({ 1, 0 })->count;
603 };
604 return 0 if $@ || !defined $c;
605
606 return 1;
607}
608
8424c090 6091;
610
611
612=head1 AUTHORS
c9d2e0a2 613
614Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 615Luke Saunders <luke@shadowcatsystems.co.uk>
616
617=head1 LICENSE
618
619You may distribute this code under the same terms as Perl itself.