Minor cookbook fix (two adjacent examples were mixed up)
[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
f81b9157 193This method is disabled by default. Set $schema->do_backup(1) to enable it.
194
8424c090 195=cut
196
c9d2e0a2 197sub backup
198{
199 my ($self) = @_;
200 ## Make each ::DBI::Foo do this
8795fefb 201 $self->storage->backup($self->backup_directory());
c9d2e0a2 202}
203
b6d9f089 204# is this just a waste of time? if not then merge with DBI.pm
8424c090 205sub _create_db_to_schema_diff {
206 my $self = shift;
c9d2e0a2 207
8424c090 208 my %driver_to_db_map = (
209 'mysql' => 'MySQL'
210 );
e6129e56 211
8424c090 212 my $db = $driver_to_db_map{$self->storage->dbh->{Driver}->{Name}};
213 unless ($db) {
214 print "Sorry, this is an unsupported DB\n";
215 return;
216 }
c9d2e0a2 217
b6d9f089 218 eval 'require SQL::Translator "0.09"';
219 if ($@) {
220 $self->throw_exception("SQL::Translator 0.09 required");
221 }
8424c090 222
223 my $db_tr = SQL::Translator->new({
224 add_drop_table => 1,
225 parser => 'DBI',
226 parser_args => { dbh => $self->storage->dbh }
227 });
228
229 $db_tr->producer($db);
230 my $dbic_tr = SQL::Translator->new;
231 $dbic_tr->parser('SQL::Translator::Parser::DBIx::Class');
232 $dbic_tr = $self->storage->configure_sqlt($dbic_tr, $db);
233 $dbic_tr->data($self);
234 $dbic_tr->producer($db);
235
236 $db_tr->schema->name('db_schema');
237 $dbic_tr->schema->name('dbic_schema');
238
239 # is this really necessary?
240 foreach my $tr ($db_tr, $dbic_tr) {
241 my $data = $tr->data;
242 $tr->parser->($tr, $$data);
243 }
c9d2e0a2 244
8424c090 245 my $diff = SQL::Translator::Diff::schema_diff($db_tr->schema, $db,
246 $dbic_tr->schema, $db,
247 { ignore_constraint_names => 1, ignore_index_names => 1, caseopt => 1 });
248
249 my $filename = $self->ddl_filename(
250 $db,
251 $self->upgrade_directory,
252 $self->schema_version,
253 'PRE',
254 );
255 my $file;
256 if(!open($file, ">$filename"))
257 {
258 $self->throw_exception("Can't open $filename for writing ($!)");
259 next;
c9d2e0a2 260 }
8424c090 261 print $file $diff;
262 close($file);
c9d2e0a2 263
8424c090 264 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 265}
266
8424c090 267=head2 upgrade
e2c0df8e 268
8424c090 269Call this to attempt to upgrade your database from the version it is at to the version
270this DBIC schema is at.
c9d2e0a2 271
8424c090 272It requires an SQL diff file to exist in $schema->upgrade_directory, normally you will
273have created this using $schema->create_ddl_dir.
c9d2e0a2 274
8424c090 275=cut
c9d2e0a2 276
8424c090 277sub upgrade
278{
279 my ($self) = @_;
280 my $db_version = $self->get_db_version();
c9d2e0a2 281
8424c090 282 # db unversioned
283 unless ($db_version) {
b4b1e91c 284 # set version in dbix_class_schema_versions table, can't actually upgrade as we don 't know what version the DB is at
8424c090 285 $self->_create_db_to_schema_diff() if ($self->do_diff_on_init);
c9d2e0a2 286
8424c090 287 # create versions table and version row
288 $self->{vschema}->deploy;
289 $self->_set_db_version;
290 return;
c9d2e0a2 291 }
292
8424c090 293 # db and schema at same version. do nothing
294 if ($db_version eq $self->schema_version) {
295 print "Upgrade not necessary\n";
296 return;
c9d2e0a2 297 }
298
37fcb5b5 299 # strangely the first time this is called can
300 # differ to subsequent times. so we call it
301 # here to be sure.
302 # XXX - just fix it
303 $self->storage->sqlt_type;
304
8424c090 305 my $upgrade_file = $self->ddl_filename(
306 $self->storage->sqlt_type,
307 $self->upgrade_directory,
308 $self->schema_version,
309 $db_version,
310 );
c9d2e0a2 311
8424c090 312 unless (-f $upgrade_file) {
313 warn "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
314 return;
315 }
c9d2e0a2 316
8424c090 317 # backup if necessary then apply upgrade
318 $self->_filedata($self->_read_sql_file($upgrade_file));
319 $self->backup() if($self->do_backup);
320 $self->txn_do(sub { $self->do_upgrade() });
c9d2e0a2 321
b4b1e91c 322 # set row in dbix_class_schema_versions table
8424c090 323 $self->_set_db_version;
324}
c9d2e0a2 325
8424c090 326sub _set_db_version {
327 my $self = shift;
c9d2e0a2 328
8424c090 329 my $vtable = $self->{vschema}->resultset('Table');
732dc718 330 $vtable->create({ version => $self->schema_version,
331 installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
8424c090 332 });
c9d2e0a2 333
8424c090 334}
c9d2e0a2 335
8424c090 336sub _read_sql_file {
337 my $self = shift;
338 my $file = shift || return;
339
340 my $fh;
341 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
d89d8604 342 my @data = split(/\n/, join('', <$fh>));
343 @data = grep(!/^--/, @data);
344 @data = split(/;/, join('', @data));
8424c090 345 close($fh);
346 @data = grep { $_ && $_ !~ /^-- / } @data;
ced42e95 347 @data = grep { $_ !~ /^(BEGIN TRANSACTION|COMMIT)/m } @data;
8424c090 348 return \@data;
349}
e6129e56 350
351=head2 do_upgrade
352
c9d2e0a2 353This is an overwritable method used to run your upgrade. The freeform method
354allows you to run your upgrade any way you please, you can call C<run_upgrade>
355any number of times to run the actual SQL commands, and in between you can
356sandwich your data upgrading. For example, first run all the B<CREATE>
357commands, then migrate your data from old to new tables/formats, then
e7b14c5b 358issue the DROP commands when you are finished. Will run the whole file as it is by default.
8424c090 359
360=cut
361
362sub do_upgrade
363{
e7b14c5b 364 my ($self) = @_;
8424c090 365
e7b14c5b 366 # just run all the commands (including inserts) in order
367 $self->run_upgrade(qr/.*?/);
8424c090 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
f81b9157 407To 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 arg like so:
408
409 my $schema = MyApp::Schema->connect(
410 $dsn,
411 $user,
412 $password,
413 { ignore_version => 1 },
414 );
ecea7937 415
416=cut
417
8424c090 418sub connection {
419 my $self = shift;
420 $self->next::method(@_);
f81b9157 421 $self->_on_connect($_[3]);
8424c090 422 return $self;
423}
424
425sub _on_connect
426{
f81b9157 427 my ($self, $args) = @_;
428
429 $args = {} unless $args;
8424c090 430 $self->{vschema} = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
b4b1e91c 431 my $vtable = $self->{vschema}->resultset('Table');
432
433 # check for legacy versions table and move to new if exists
434 my $vschema_compat = DBIx::Class::VersionCompat->connect(@{$self->storage->connect_info()});
435 unless ($self->_source_exists($vtable)) {
436 my $vtable_compat = $vschema_compat->resultset('TableCompat');
437 if ($self->_source_exists($vtable_compat)) {
438 $self->{vschema}->deploy;
732dc718 439 map { $vtable->create({ installed => $_->Installed, version => $_->Version }) } $vtable_compat->all;
b4b1e91c 440 $self->storage->dbh->do("DROP TABLE " . $vtable_compat->result_source->from);
441 }
442 }
f81b9157 443
ecea7937 444 # useful when connecting from scripts etc
f81b9157 445 return if ($args->{ignore_version} || ($ENV{DBIC_NO_VERSION_CHECK} && !exists $args->{ignore_version}));
8424c090 446 my $pversion = $self->get_db_version();
447
448 if($pversion eq $self->schema_version)
449 {
ffdf4f11 450# warn "This version is already installed\n";
8424c090 451 return 1;
452 }
42416a0b 453
8424c090 454 if(!$pversion)
455 {
456 warn "Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.\n";
457 return 1;
458 }
459
460 warn "Versions out of sync. This is " . $self->schema_version .
461 ", your database contains version $pversion, please call upgrade on your Schema.\n";
462}
463
4641;
465
466
467=head1 AUTHORS
c9d2e0a2 468
469Jess Robinson <castaway@desert-island.demon.co.uk>
8424c090 470Luke Saunders <luke@shadowcatsystems.co.uk>
471
472=head1 LICENSE
473
474You may distribute this code under the same terms as Perl itself.