Merge 'DBIx-Class-current' into 'versioning'
[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/);
7__PACKAGE__->table('SchemaVersions');
8
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 },
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'
27 },
28 );
29__PACKAGE__->set_primary_key('Version');
30
31package DBIx::Class::Version;
32use base 'DBIx::Class::Schema';
33use strict;
34use warnings;
35
36__PACKAGE__->register_class('Table', 'DBIx::Class::Version::Table');
37
38
39# ---------------------------------------------------------------------------
40package DBIx::Class::Schema::Versioned;
41
42use strict;
43use warnings;
44use base 'DBIx::Class';
45use POSIX 'strftime';
46use Data::Dumper;
47
48__PACKAGE__->mk_classdata('_filedata');
49__PACKAGE__->mk_classdata('upgrade_directory');
50
51sub on_connect
52{
53 my ($self) = @_;
54 my $vschema = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
55 my $vtable = $vschema->resultset('Table');
56 my $pversion;
57 if(!$self->exists($vtable))
58 {
59# $vschema->storage->debug(1);
60 $vschema->storage->ensure_connected();
61 $vschema->deploy();
62 $pversion = 0;
63 }
64 else
65 {
66 my $psearch = $vtable->search(undef,
67 { select => [
68 { 'max' => 'Installed' },
69 ],
70 as => ['maxinstall'],
71 })->first;
72 $pversion = $vtable->search({ Installed => $psearch->get_column('maxinstall'),
73 })->first;
74 $pversion = $pversion->Version if($pversion);
75 }
76# warn("Previous version: $pversion\n");
77 if($pversion eq $self->VERSION)
78 {
79 warn "This version is already installed\n";
80 return 1;
81 }
82
83## use IC::DT?
84
85 if(!$pversion)
86 {
87 $vtable->create({ Version => $self->VERSION,
88 Installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
89 });
90 ## If we let the user do this, where does the Version table get updated?
91 warn "No previous version found, calling deploy to install this version.\n";
92 $self->deploy();
93 return 1;
94 }
95
96 my $file = $self->ddl_filename(
97 $self->storage->sqlt_type,
98 $self->upgrade_directory,
99 $self->VERSION
100 );
101 if(!$file)
102 {
103 # No upgrade path between these two versions
104 return 1;
105 }
106
107 $file = $self->ddl_filename(
108 $self->storage->sqlt_type,
109 $self->upgrade_directory,
110 $self->VERSION,
111 $pversion,
112 );
113# $file =~ s/@{[ $self->VERSION ]}/"${pversion}-" . $self->VERSION/e;
114 if(!-f $file)
115 {
116 warn "Upgrade not possible, no upgrade file found ($file)\n";
117 return;
118 }
119
120 my $fh;
121 open $fh, "<$file" or warn("Can't open upgrade file, $file ($!)");
122 my @data = split(/;\n/, join('', <$fh>));
123 close($fh);
124 @data = grep { $_ && $_ !~ /^-- / } @data;
125 @data = grep { $_ !~ /^(BEGIN TRANACTION|COMMIT)/m } @data;
126
127 $self->_filedata(\@data);
128
129 ## Don't do this yet, do only on command?
130 ## If we do this later, where does the Version table get updated??
131 warn "Versions out of sync. This is " . $self->VERSION .
132 ", your database contains version $pversion, please call upgrade on your Schema.\n";
133# $self->upgrade($pversion, $self->VERSION);
134}
135
136sub exists
137{
138 my ($self, $rs) = @_;
139
140 my $c = eval {
141 $rs->search({ 1, 0 })->count;
142 };
143 return 0 if $@ || !defined $c;
144
145 return 1;
146}
147
148sub backup
149{
150 my ($self) = @_;
151 ## Make each ::DBI::Foo do this
152 $self->storage->backup();
153}
154
155sub upgrade
156{
157 my ($self) = @_;
158
159 ## overridable sub, per default just run all the commands.
160
161 $self->backup();
162
163 $self->run_upgrade(qr/create/i);
164 $self->run_upgrade(qr/alter table .*? add/i);
165 $self->run_upgrade(qr/alter table .*? (?!drop)/i);
166 $self->run_upgrade(qr/alter table .*? drop/i);
167 $self->run_upgrade(qr/drop/i);
168# $self->run_upgrade(qr//i);
169
170 my $vschema = DBIx::Class::Version->connect(@{$self->storage->connect_info()});
171 my $vtable = $vschema->resultset('Table');
172 $vtable->create({ Version => $self->VERSION,
173 Installed => strftime("%Y-%m-%d %H:%M:%S", gmtime())
174 });
175}
176
177
178sub run_upgrade
179{
180 my ($self, $stm) = @_;
181# print "Reg: $stm\n";
182 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
183# print "Statements: ", join("\n", @statements), "\n";
184 $self->_filedata([ grep { $_ !~ /$stm/i } @{$self->_filedata} ]);
185
186 for (@statements)
187 {
188 $self->storage->debugfh->print("$_\n") if $self->storage->debug;
189# print "Running \n>>$_<<\n";
190 $self->storage->dbh->do($_) or warn "SQL was:\n $_";
191 }
192
193 return 1;
194}
195
196=head1 NAME
197
198DBIx::Class::Versioning - DBIx::Class::Schema plugin for Schema upgrades
199
200=head1 SYNOPSIS
201
202 package Library::Schema;
203 use base qw/DBIx::Class::Schema/;
204 # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
205 __PACKAGE__->load_classes(qw/CD Book DVD/);
206
207 __PACKAGE__->load_components(qw/+DBIx::Class::Schema::Versioned/);
208 __PACKAGE__->upgrade_directory('/path/to/upgrades/');
209
210 sub backup
211 {
212 my ($self) = @_;
213 # my special backup process
214 }
215
216 sub upgrade
217 {
218 my ($self) = @_;
219
220 ## overridable sub, per default just runs all the commands.
221
222 $self->run_upgrade(qr/create/i);
223 $self->run_upgrade(qr/alter table .*? add/i);
224 $self->run_upgrade(qr/alter table .*? (?!drop)/i);
225 $self->run_upgrade(qr/alter table .*? drop/i);
226 $self->run_upgrade(qr/drop/i);
227 $self->run_upgrade(qr//i);
228 }
229
230=head1 DESCRIPTION
231
232This module is a component designed to extend L<DBIx::Class::Schema>
233classes, to enable them to upgrade to newer schema layouts. To use this
234module, you need to have called C<create_ddl_dir> on your Schema to
235create your upgrade files to include with your delivery.
236
237A table called I<SchemaVersions> is created and maintained by the
238module. This contains two fields, 'Version' and 'Installed', which
239contain each VERSION of your Schema, and the date+time it was installed.
240
241If you would like to influence which levels of version change need
242upgrades in your Schema, you can override the method C<ddl_filename>
243in L<DBIx::Class::Schema>. Return a false value if there is no upgrade
244path between the two versions supplied. By default, every change in
245your VERSION is regarded as needing an upgrade.
246
247NB: At the moment, SQLite upgrading is rather spotty, as SQL::Translator::Diff
248returns SQL statements that SQLite does not support.
249
250
251=head1 METHODS
252
253=head2 backup
254
255This is an overwritable method which is called just before the upgrade, to
256allow you to make a backup of the database. Per default this method attempts
257to call C<< $self->storage->backup >>, to run the standard backup on each
258database type.
259
260This method should return the name of the backup file, if appropriate.
261
262C<backup> is called from C<upgrade>, make sure you call it, if you write your
263own <upgrade> method.
264
265=head2 upgrade
266
267This is an overwritable method used to run your upgrade. The freeform method
268allows you to run your upgrade any way you please, you can call C<run_upgrade>
269any number of times to run the actual SQL commands, and in between you can
270sandwich your data upgrading. For example, first run all the B<CREATE>
271commands, then migrate your data from old to new tables/formats, then
272issue the DROP commands when you are finished.
273
274=head2 run_upgrade
275
276 $self->run_upgrade(qr/create/i);
277
278Runs a set of SQL statements matching a passed in regular expression. The
279idea is that this method can be called any number of times from your
280C<upgrade> method, running whichever commands you specify via the
281regex in the parameter.
282
283=head1 AUTHOR
284
285Jess Robinson <castaway@desert-island.demon.co.uk>