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