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