fix related resultsets and multi-create
[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;
238 $self->_set_db_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
8424c090 254=head2 upgrade
e2c0df8e 255
8424c090 256Call this to attempt to upgrade your database from the version it is at to the version
e84a43c1 257this DBIC schema is at. If they are the same it does nothing.
258
259It requires an SQL diff file to exist in you I<upgrade_directory>, normally you will
260have created this using L<DBIx::Class::Schema/create_ddl_dir>.
c9d2e0a2 261
e84a43c1 262If successful the dbix_class_schema_versions table is updated with the current
263DBIC schema version.
c9d2e0a2 264
8424c090 265=cut
c9d2e0a2 266
8424c090 267sub upgrade
268{
269 my ($self) = @_;
270 my $db_version = $self->get_db_version();
c9d2e0a2 271
8424c090 272 # db unversioned
273 unless ($db_version) {
93e4d41a 274 warn 'Upgrade not possible as database is unversioned. Please call install first.';
8424c090 275 return;
c9d2e0a2 276 }
277
8424c090 278 # db and schema at same version. do nothing
279 if ($db_version eq $self->schema_version) {
280 print "Upgrade not necessary\n";
281 return;
c9d2e0a2 282 }
283
37fcb5b5 284 # strangely the first time this is called can
285 # differ to subsequent times. so we call it
286 # here to be sure.
287 # XXX - just fix it
288 $self->storage->sqlt_type;
289
8424c090 290 my $upgrade_file = $self->ddl_filename(
291 $self->storage->sqlt_type,
8424c090 292 $self->schema_version,
99a74c4a 293 $self->upgrade_directory,
8424c090 294 $db_version,
295 );
c9d2e0a2 296
8424c090 297 unless (-f $upgrade_file) {
298 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
299 return;
300 }
c9d2e0a2 301
8424c090 302 # backup if necessary then apply upgrade
303 $self->_filedata($self->_read_sql_file($upgrade_file));
304 $self->backup() if($self->do_backup);
305 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 306
b4b1e91c 307 # set row in dbix_class_schema_versions table
8424c090 308 $self->_set_db_version;
309}
c9d2e0a2 310
e6129e56 311=head2 do_upgrade
312
c9d2e0a2 313This is an overwritable method used to run your upgrade. The freeform method
314allows you to run your upgrade any way you please, you can call C<run_upgrade>
315any number of times to run the actual SQL commands, and in between you can
316sandwich your data upgrading. For example, first run all the B<CREATE>
317commands, then migrate your data from old to new tables/formats, then
e7b14c5b 318issue the DROP commands when you are finished. Will run the whole file as it is by default.
8424c090 319
320=cut
321
322sub do_upgrade
323{
e7b14c5b 324 my ($self) = @_;
8424c090 325
e7b14c5b 326 # just run all the commands (including inserts) in order
327 $self->run_upgrade(qr/.*?/);
8424c090 328}
329
c9d2e0a2 330=head2 run_upgrade
331
332 $self->run_upgrade(qr/create/i);
333
334Runs a set of SQL statements matching a passed in regular expression. The
335idea is that this method can be called any number of times from your
e84a43c1 336C<do_upgrade> method, running whichever commands you specify via the
8424c090 337regex in the parameter. Probably won't work unless called from the overridable
338do_upgrade method.
c9d2e0a2 339
8424c090 340=cut
8795fefb 341
8424c090 342sub run_upgrade
343{
344 my ($self, $stm) = @_;
8795fefb 345
8424c090 346 return unless ($self->_filedata);
347 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
348 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
8795fefb 349
8424c090 350 for (@statements)
351 {
352 $self->storage->debugobj->query_start($_) if $self->storage->debug;
abc8f12a 353 $self->apply_statement($_);
8424c090 354 $self->storage->debugobj->query_end($_) if $self->storage->debug;
355 }
8795fefb 356
8424c090 357 return 1;
358}
42416a0b 359
abc8f12a 360=head2 apply_statement
361
362Takes an SQL statement and runs it. Override this if you want to handle errors
363differently.
364
365=cut
366
367sub apply_statement {
368 my ($self, $statement) = @_;
369
370 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
371}
372
93e4d41a 373=head2 get_db_version
374
375Returns the version that your database is currently at. This is determined by the values in the
e84a43c1 376dbix_class_schema_versions table that C<upgrade> and C<install> write to.
93e4d41a 377
378=cut
379
380sub get_db_version
381{
382 my ($self, $rs) = @_;
383
384 my $vtable = $self->{vschema}->resultset('Table');
385 my $version = 0;
386 eval {
387 my $stamp = $vtable->get_column('installed')->max;
388 $version = $vtable->search({ installed => $stamp })->first->version;
389 };
390 return $version;
391}
392
393=head2 schema_version
394
395Returns the current schema class' $VERSION
396
397=cut
398
399=head2 backup
400
401This is an overwritable method which is called just before the upgrade, to
402allow you to make a backup of the database. Per default this method attempts
403to call C<< $self->storage->backup >>, to run the standard backup on each
404database type.
405
406This method should return the name of the backup file, if appropriate..
407
408This method is disabled by default. Set $schema->do_backup(1) to enable it.
409
410=cut
411
412sub backup
413{
414 my ($self) = @_;
415 ## Make each ::DBI::Foo do this
416 $self->storage->backup($self->backup_directory());
417}
418
ecea7937 419=head2 connection
420
421Overloaded method. This checks the DBIC schema version against the DB version and
422warns if they are not the same or if the DB is unversioned. It also provides
423compatibility between the old versions table (SchemaVersions) and the new one
424(dbix_class_schema_versions).
425
e84a43c1 426To 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 427
428 my $schema = MyApp::Schema->connect(
429 $dsn,
430 $user,
431 $password,
432 { ignore_version => 1 },
433 );
ecea7937 434
435=cut
436
8424c090 437sub connection {
438 my $self = shift;
439 $self->next::method(@_);
f81b9157 440 $self->_on_connect($_[3]);
8424c090 441 return $self;
442}
443
444sub _on_connect
445{
f81b9157 446 my ($self, $args) = @_;
447
448 $args = {} unless $args;
8424c090 449 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 450 my $vtable = $self->{vschema}->resultset('Table');
451
452 # check for legacy versions table and move to new if exists
453 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
454 unless ($self->_source_exists($vtable)) {
455 my $vtable_compat = $vschema_compat->resultset('TableCompat');
456 if ($self->_source_exists($vtable_compat)) {
457 $self->{vschema}->deploy;
732dc718 458 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 459 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
460 }
461 }
f81b9157 462
ecea7937 463 # useful when connecting from scripts etc
f81b9157 464 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
8424c090 465 my $pversion = $self->get_db_version();
466
467 if($pversion eq $self->schema_version)
468 {
ffdf4f11 469# warn "This version is already installed\n";
8424c090 470 return 1;
471 }
42416a0b 472
8424c090 473 if(!$pversion)
474 {
475 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
476 return 1;
477 }
478
479 warn "Versions out of sync. This is " . $self->schema_version .
480 ", your database contains version $pversion, please call upgrade on your Schema.\n";
481}
482
93e4d41a 483# is this just a waste of time? if not then merge with DBI.pm
484sub _create_db_to_schema_diff {
485 my $self = shift;
486
487 my %driver_to_db_map = (
488 'mysql' => 'MySQL'
489 );
490
491 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
492 unless ($db) {
493 print "Sorry, this is an unsupported DB\n";
494 return;
495 }
496
497 eval 'require SQL::Translator "0.09"';
498 if ($@) {
499 $self->throw_exception("SQL::Translator 0.09 required");
500 }
501
502 my $db_tr = SQL::Translator->new({
503 add_drop_table => 1,
504 parser => 'DBI',
505 parser_args => { dbh => $self->storage->dbh }
506 });
507
508 $db_tr->producer($db);
509 my $dbic_tr = SQL::Translator->new;
510 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
511 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
512 $dbic_tr->data($self);
513 $dbic_tr->producer($db);
514
515 $db_tr->schema->name('db_schema');
516 $dbic_tr->schema->name('dbic_schema');
517
518 # is this really necessary?
519 foreach my $tr ($db_tr, $dbic_tr) {
520 my $data = $tr->data;
521 $tr->parser->($tr, $$data);
522 }
523
524 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
525 $dbic_tr->schema, $db,
526 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
527
528 my $filename = $self->ddl_filename(
529 $db,
530 $self->schema_version,
531 $self->upgrade_directory,
532 'PRE',
533 );
534 my $file;
535 if(!open($file, ">$filename"))
536 {
537 $self->throw_exception("Can't open $filename for writing ($!)");
538 next;
539 }
540 print $file $diff;
541 close($file);
542
543 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";
544}
545
546
547sub _set_db_version {
548 my $self = shift;
549
550 my $vtable = $self->{vschema}->resultset('Table');
551 $vtable->create({ version => $self->schema_version,
552 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
553 });
554
555}
556
557sub _read_sql_file {
558 my $self = shift;
559 my $file = shift || return;
560
561 my $fh;
562 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
563 my @data = split(/\n/, join('', <$fh>));
564 @data = grep(!/^--/, @data);
565 @data = split(/;/, join('', @data));
566 close($fh);
567 @data = grep { $_ && $_ !~ /^-- / } @data;
568 @data = grep { $_ !~ /^(BEGIN|BEGIN TRANSACTION|COMMIT)/m } @data;
569 return \@data;
570}
571
572sub _source_exists
573{
574 my ($self, $rs) = @_;
575
576 my $c = eval {
577 $rs->search({ 1, 0 })->count;
578 };
579 return 0 if $@ || !defined $c;
580
581 return 1;
582}
583
8424c090 5841;
585
586
587=head1 AUTHORS
c9d2e0a2 588
589Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 590Luke Saunders <luke@shadowcatsystems.co.uk>
591
592=head1 LICENSE
593
594You may distribute this code under the same terms as Perl itself.