make override follow Replication -> Replicated rename
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema / Versioned.pm
CommitLineData
1d342d91 1package DBIx::Class::Version::Table;
c9d2e0a2 2use base 'DBIx::Class';
3use strict;
4use warnings;
5
6__PACKAGE__->load_components(qw/ Core/);
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
1d342d91 31package DBIx::Class::Version::TableCompat;
732dc718 32use base 'DBIx::Class';
33__PACKAGE__->load_components(qw/ 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
1d342d91 46package DBIx::Class::Version;
c9d2e0a2 47use base 'DBIx::Class::Schema';
48use strict;
49use warnings;
50
51__PACKAGE__->register_class('Table', 'DBIx::Class::Version::Table');
52
1d342d91 53package DBIx::Class::VersionCompat;
b4b1e91c 54use base 'DBIx::Class::Schema';
55use strict;
56use warnings;
57
58__PACKAGE__->register_class('TableCompat', 'DBIx::Class::Version::TableCompat');
59
c9d2e0a2 60
61# ---------------------------------------------------------------------------
8424c090 62
63=head1 NAME
64
65DBIx::Class::Schema::Versioned - DBIx::Class::Schema plugin for Schema upgrades
66
67=head1 SYNOPSIS
68
69 package Library::Schema;
70 use base qw/DBIx::Class::Schema/;
71 # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
72 __PACKAGE__->load_classes(qw/CD Book DVD/);
73
74 __PACKAGE__->load_components(qw/+DBIx::Class::Schema::Versioned/);
75 __PACKAGE__->upgrade_directory('/path/to/upgrades/');
76 __PACKAGE__->backup_directory('/path/to/backups/');
77
78
79=head1 DESCRIPTION
80
81This module is a component designed to extend L<DBIx::Class::Schema>
82classes, to enable them to upgrade to newer schema layouts. To use this
83module, you need to have called C<create_ddl_dir> on your Schema to
84create your upgrade files to include with your delivery.
85
b4b1e91c 86A table called I<dbix_class_schema_versions> is created and maintained by the
8424c090 87module. This contains two fields, 'Version' and 'Installed', which
88contain each VERSION of your Schema, and the date+time it was installed.
89
90The actual upgrade is called manually by calling C<upgrade> on your
91schema object. Code is run at connect time to determine whether an
92upgrade is needed, if so, a warning "Versions out of sync" is
93produced.
94
95So you'll probably want to write a script which generates your DDLs and diffs
96and another which executes the upgrade.
97
98NB: At the moment, only SQLite and MySQL are supported. This is due to
99spotty behaviour in the SQL::Translator producers, please help us by
100them.
101
102=head1 METHODS
103
104=head2 upgrade_directory
105
106Use this to set the directory your upgrade files are stored in.
107
108=head2 backup_directory
109
110Use this to set the directory you want your backups stored in.
111
112=cut
113
c9d2e0a2 114package DBIx::Class::Schema::Versioned;
115
116use strict;
117use warnings;
118use base 'DBIx::Class';
119use POSIX 'strftime';
120use Data::Dumper;
121
122__PACKAGE__->mk_classdata('_filedata');
123__PACKAGE__->mk_classdata('upgrade_directory');
8795fefb 124__PACKAGE__->mk_classdata('backup_directory');
f925f7cb 125__PACKAGE__->mk_classdata('do_backup');
8424c090 126__PACKAGE__->mk_classdata('do_diff_on_init');
127
128=head2 schema_version
129
130Returns the current schema class' $VERSION; does -not- use $schema->VERSION
131since that varies in results depending on if version.pm is installed, and if
132so the perl or XS versions. If you want this to change, bug the version.pm
133author to make vpp and vxs behave the same.
134
135=cut
c9d2e0a2 136
42416a0b 137sub schema_version {
138 my ($self) = @_;
139 my $class = ref($self)||$self;
140 my $version;
141 {
142 no strict 'refs';
143 $version = ${"${class}::VERSION"};
144 }
145 return $version;
146}
147
8424c090 148=head2 get_db_version
c9d2e0a2 149
8424c090 150Returns the version that your database is currently at. This is determined by the values in the
b4b1e91c 151dbix_class_schema_versions table that $self->upgrade writes to.
c9d2e0a2 152
8424c090 153=cut
c9d2e0a2 154
e6129e56 155sub get_db_version
156{
157 my ($self, $rs) = @_;
158
159 my $vtable = $self->{vschema}->resultset('Table');
ecea7937 160 my $version = 0;
7146f14f 161 eval {
732dc718 162 my $stamp = $vtable->get_column('installed')->max;
163 $version = $vtable->search({ installed => $stamp })->first->version;
7146f14f 164 };
165 return $version;
e6129e56 166}
167
a2800991 168sub _source_exists
c9d2e0a2 169{
170 my ($self, $rs) = @_;
171
172 my $c = eval {
173 $rs->search({ 1, 0 })->count;
174 };
175 return 0 if $@ || !defined $c;
176
177 return 1;
178}
179
8424c090 180=head2 backup
181
182This is an overwritable method which is called just before the upgrade, to
183allow you to make a backup of the database. Per default this method attempts
184to call C<< $self->storage->backup >>, to run the standard backup on each
185database type.
186
187This method should return the name of the backup file, if appropriate..
188
189=cut
190
c9d2e0a2 191sub backup
192{
193 my ($self) = @_;
194 ## Make each ::DBI::Foo do this
8795fefb 195 $self->storage->backup($self->backup_directory());
c9d2e0a2 196}
197
b6d9f089 198# is this just a waste of time? if not then merge with DBI.pm
8424c090 199sub _create_db_to_schema_diff {
200 my $self = shift;
c9d2e0a2 201
8424c090 202 my %driver_to_db_map = (
203 'mysql' => 'MySQL'
204 );
e6129e56 205
8424c090 206 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
207 unless ($db) {
208 print "Sorry, this is an unsupported DB\n";
209 return;
210 }
c9d2e0a2 211
b6d9f089 212 eval 'require SQL::Translator "0.09"';
213 if ($@) {
214 $self->throw_exception("SQL::Translator 0.09 required");
215 }
8424c090 216
217 my $db_tr = SQL::Translator->new({
218 add_drop_table => 1,
219 parser => 'DBI',
220 parser_args => { dbh => $self->storage->dbh }
221 });
222
223 $db_tr->producer($db);
224 my $dbic_tr = SQL::Translator->new;
225 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
226 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
227 $dbic_tr->data($self);
228 $dbic_tr->producer($db);
229
230 $db_tr->schema->name('db_schema');
231 $dbic_tr->schema->name('dbic_schema');
232
233 # is this really necessary?
234 foreach my $tr ($db_tr, $dbic_tr) {
235 my $data = $tr->data;
236 $tr->parser->($tr, $$data);
237 }
c9d2e0a2 238
8424c090 239 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
240 $dbic_tr->schema, $db,
241 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
242
243 my $filename = $self->ddl_filename(
244 $db,
245 $self->upgrade_directory,
246 $self->schema_version,
247 'PRE',
248 );
249 my $file;
250 if(!open($file, ">$filename"))
251 {
252 $self->throw_exception("Can't open $filename for writing ($!)");
253 next;
c9d2e0a2 254 }
8424c090 255 print $file $diff;
256 close($file);
c9d2e0a2 257
8424c090 258 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 259}
260
8424c090 261=head2 upgrade
e2c0df8e 262
8424c090 263Call this to attempt to upgrade your database from the version it is at to the version
264this DBIC schema is at.
c9d2e0a2 265
8424c090 266It requires an SQL diff file to exist in $schema->upgrade_directory, normally you will
267have created this using $schema->create_ddl_dir.
c9d2e0a2 268
8424c090 269=cut
c9d2e0a2 270
8424c090 271sub upgrade
272{
273 my ($self) = @_;
274 my $db_version = $self->get_db_version();
c9d2e0a2 275
8424c090 276 # db unversioned
277 unless ($db_version) {
b4b1e91c 278 # set version in dbix_class_schema_versions table, can't actually upgrade as we don 't know what version the DB is at
8424c090 279 $self->_create_db_to_schema_diff() if ($self->do_diff_on_init);
c9d2e0a2 280
8424c090 281 # create versions table and version row
282 $self->{vschema}->deploy;
283 $self->_set_db_version;
284 return;
c9d2e0a2 285 }
286
8424c090 287 # db and schema at same version. do nothing
288 if ($db_version eq $self->schema_version) {
289 print "Upgrade not necessary\n";
290 return;
c9d2e0a2 291 }
292
37fcb5b5 293 # strangely the first time this is called can
294 # differ to subsequent times. so we call it
295 # here to be sure.
296 # XXX - just fix it
297 $self->storage->sqlt_type;
298
8424c090 299 my $upgrade_file = $self->ddl_filename(
300 $self->storage->sqlt_type,
301 $self->upgrade_directory,
302 $self->schema_version,
303 $db_version,
304 );
c9d2e0a2 305
8424c090 306 unless (-f $upgrade_file) {
307 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
308 return;
309 }
c9d2e0a2 310
8424c090 311 # backup if necessary then apply upgrade
312 $self->_filedata($self->_read_sql_file($upgrade_file));
313 $self->backup() if($self->do_backup);
314 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 315
b4b1e91c 316 # set row in dbix_class_schema_versions table
8424c090 317 $self->_set_db_version;
318}
c9d2e0a2 319
8424c090 320sub _set_db_version {
321 my $self = shift;
c9d2e0a2 322
8424c090 323 my $vtable = $self->{vschema}->resultset('Table');
732dc718 324 $vtable->create({ version => $self->schema_version,
325 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
8424c090 326 });
c9d2e0a2 327
8424c090 328}
c9d2e0a2 329
8424c090 330sub _read_sql_file {
331 my $self = shift;
332 my $file = shift || return;
333
334 my $fh;
335 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
d89d8604 336 my @data = split(/\n/, join('', <$fh>));
337 @data = grep(!/^--/, @data);
338 @data = split(/;/, join('', @data));
8424c090 339 close($fh);
340 @data = grep { $_ && $_ !~ /^-- / } @data;
341 @data = grep { $_ !~ /^(BEGIN TRANACTION|COMMIT)/m } @data;
342 return \@data;
343}
e6129e56 344
345=head2 do_upgrade
346
c9d2e0a2 347This is an overwritable method used to run your upgrade. The freeform method
348allows you to run your upgrade any way you please, you can call C<run_upgrade>
349any number of times to run the actual SQL commands, and in between you can
350sandwich your data upgrading. For example, first run all the B<CREATE>
351commands, then migrate your data from old to new tables/formats, then
352issue the DROP commands when you are finished.
353
8424c090 354Will run the whole file as it is by default.
355
356=cut
357
358sub do_upgrade
359{
360 my ($self) = @_;
361
362 ## overridable sub, per default just run all the commands.
363 $self->run_upgrade(qr/create/i);
364 $self->run_upgrade(qr/alter table .*? add/i);
365 $self->run_upgrade(qr/alter table .*? (?!drop)/i);
366 $self->run_upgrade(qr/alter table .*? drop/i);
367 $self->run_upgrade(qr/drop/i);
368}
369
c9d2e0a2 370=head2 run_upgrade
371
372 $self->run_upgrade(qr/create/i);
373
374Runs a set of SQL statements matching a passed in regular expression. The
375idea is that this method can be called any number of times from your
376C<upgrade> method, running whichever commands you specify via the
8424c090 377regex in the parameter. Probably won't work unless called from the overridable
378do_upgrade method.
c9d2e0a2 379
8424c090 380=cut
8795fefb 381
8424c090 382sub run_upgrade
383{
384 my ($self, $stm) = @_;
8795fefb 385
8424c090 386 return unless ($self->_filedata);
387 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
388 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
8795fefb 389
8424c090 390 for (@statements)
391 {
392 $self->storage->debugobj->query_start($_) if $self->storage->debug;
393 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
394 $self->storage->debugobj->query_end($_) if $self->storage->debug;
395 }
8795fefb 396
8424c090 397 return 1;
398}
42416a0b 399
ecea7937 400=head2 connection
401
402Overloaded method. This checks the DBIC schema version against the DB version and
403warns if they are not the same or if the DB is unversioned. It also provides
404compatibility between the old versions table (SchemaVersions) and the new one
405(dbix_class_schema_versions).
406
407To avoid the checks on connect, set the env var DBIC_NO_VERSION_CHECK. This can be
408useful for scripts.
409
410=cut
411
8424c090 412sub connection {
413 my $self = shift;
414 $self->next::method(@_);
415 $self->_on_connect;
416 return $self;
417}
418
419sub _on_connect
420{
421 my ($self) = @_;
422 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 423 my $vtable = $self->{vschema}->resultset('Table');
424
425 # check for legacy versions table and move to new if exists
426 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
427 unless ($self->_source_exists($vtable)) {
428 my $vtable_compat = $vschema_compat->resultset('TableCompat');
429 if ($self->_source_exists($vtable_compat)) {
430 $self->{vschema}->deploy;
732dc718 431 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 432 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
433 }
434 }
ecea7937 435
436 # useful when connecting from scripts etc
437 return if ($ENV{DBIC_NO_VERSION_CHECK});
438
8424c090 439 my $pversion = $self->get_db_version();
440
441 if($pversion eq $self->schema_version)
442 {
ffdf4f11 443# warn "This version is already installed\n";
8424c090 444 return 1;
445 }
42416a0b 446
8424c090 447 if(!$pversion)
448 {
449 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
450 return 1;
451 }
452
453 warn "Versions out of sync. This is " . $self->schema_version .
454 ", your database contains version $pversion, please call upgrade on your Schema.\n";
455}
456
4571;
458
459
460=head1 AUTHORS
c9d2e0a2 461
462Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 463Luke Saunders <luke@shadowcatsystems.co.uk>
464
465=head1 LICENSE
466
467You may distribute this code under the same terms as Perl itself.