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