6b4dec2f3d00c454ae9d20fef2b9eb3d3b9e2633
[dbsrgits/DBIx-Class-DeploymentHandler.git] / lib / DBIx / Class / DeploymentHandler.pm
1 package DBIx::Class::DeploymentHandler;
2
3 use Moose;
4 use Method::Signatures::Simple;
5 require DBIx::Class::Schema;    # loaded for type constraint
6 require DBIx::Class::Storage;   # loaded for type constraint
7 require DBIx::Class::ResultSet; # loaded for type constraint
8 use Carp::Clan '^DBIx::Class::DeploymentHandler';
9 use SQL::Translator;
10 require SQL::Translator::Diff;
11 use Try::Tiny;
12
13 with 'DBIx::Class::DeploymentHandler::SqltDeployMethod';
14
15 BEGIN {
16   use Moose::Util::TypeConstraints;
17   subtype 'DBIx::Class::DeploymentHandler::Databases'
18     => as 'ArrayRef[Str]';
19
20   coerce 'DBIx::Class::DeploymentHandler::Databases'
21     => from 'Str'
22     => via { [$_] };
23   no Moose::Util::TypeConstraints;
24 }
25
26 has schema => (
27   isa      => 'DBIx::Class::Schema',
28   is       => 'ro',
29   required => 1,
30   handles => [qw( ddl_filename schema_version )],
31 );
32
33 has upgrade_directory => (
34   isa      => 'Str',
35   is       => 'ro',
36   required => 1,
37   default  => 'sql',
38 );
39
40 has backup_directory => (
41   isa => 'Str',
42   is  => 'ro',
43 );
44
45 has storage => (
46   isa        => 'DBIx::Class::Storage',
47   is         => 'ro',
48   lazy_build => 1,
49 );
50
51 method _build_storage {
52   my $s = $self->schema->storage;
53   $s->_determine_driver;
54   $s
55 }
56
57 has _filedata => (
58   isa => 'ArrayRef[Str]',
59   is  => 'rw',
60 );
61
62 has do_backup => (
63   isa     => 'Bool',
64   is      => 'ro',
65   default => undef,
66 );
67
68 has do_diff_on_init => (
69   isa     => 'Bool',
70   is      => 'ro',
71   default => undef,
72 );
73
74 has version_rs => (
75   isa        => 'DBIx::Class::ResultSet',
76   is         => 'ro',
77   lazy_build => 1,
78   handles    => [qw( is_installed db_version )],
79 );
80
81 method _build_version_rs {
82    $self->schema->set_us_up_the_bomb;
83    $self->schema->resultset('__VERSION')
84 }
85
86 has databases => (
87   coerce  => 1,
88   isa     => 'DBIx::Class::DeploymentHandler::Databases',
89   is      => 'ro',
90   default => sub { [qw( MySQL SQLite PostgreSQL )] },
91 );
92
93 has sqltargs => (
94   isa => 'HashRef',
95   is  => 'ro',
96   default => sub { {} },
97 );
98
99 method deploy {
100   my $schema   = $self->schema;
101   my $type     = undef;
102   my $sqltargs = $self->sqltargs;
103   my $dir      = $self->upgrade_directory;
104   my $storage  = $self->storage;
105
106   my $deploy = sub {
107     my $line = shift;
108     return if(!$line || $line =~ /^--|^BEGIN TRANSACTION|^COMMIT|^\s+$/);
109     $storage->_query_start($line);
110     try {
111       # do a dbh_do cycle here, as we need some error checking in
112       # place (even though we will ignore errors)
113       $storage->dbh_do (sub { $_[1]->do($line) });
114     }
115     catch {
116       carp "$_ (running '${line}')"
117     }
118     $storage->_query_end($line);
119   };
120   my @statements = $self->deployment_statements();
121   if (@statements > 1) {
122     foreach my $statement (@statements) {
123       $deploy->( $statement );
124     }
125   }
126   elsif (@statements == 1) {
127     foreach my $line ( split(";\n", $statements[0])) {
128       $deploy->( $line );
129     }
130   }
131 }
132
133 method backup { $self->storage->backup($self->backup_directory) }
134
135 method install($new_version) {
136   carp 'Install not possible as versions table already exists in database'
137     if $self->is_installed;
138
139   $new_version ||= $self->schema_version;
140
141   if ($new_version) {
142     $self->deploy();
143
144     $self->version_rs->create({
145       version     => $new_version,
146       # ddl         => $ddl,
147       # upgrade_sql => $upgrade_sql,
148     });
149   }
150 }
151
152 method create_upgrade_path { }
153
154 method ordered_schema_versions { undef }
155
156 method upgrade {
157   my $db_version     = $self->db_version;
158   my $schema_version = $self->schema_version;
159
160   unless ($db_version) {
161     # croak?
162     carp 'Upgrade not possible as database is unversioned. Please call install first.';
163     return;
164   }
165
166   if ( $db_version eq $schema_version ) {
167     # croak?
168     carp "Upgrade not necessary\n";
169     return;
170   }
171
172   my @version_list = $self->ordered_schema_versions ||
173     ( $db_version, $schema_version );
174
175   # remove all versions in list above the required version
176   while ( @version_list && ( $version_list[-1] ne $schema_version ) ) {
177     pop @version_list;
178   }
179
180   # remove all versions in list below the current version
181   while ( @version_list && ( $version_list[0] ne $db_version ) ) {
182     shift @version_list;
183   }
184
185   # check we have an appropriate list of versions
186   die if @version_list < 2;
187
188   # do sets of upgrade
189   while ( @version_list >= 2 ) {
190     $self->upgrade_single_step( $version_list[0], $version_list[1] );
191     shift @version_list;
192   }
193 }
194
195 method upgrade_single_step($db_version, $target_version) {
196   if ($db_version eq $target_version) {
197     # croak?
198     carp "Upgrade not necessary\n";
199     return;
200   }
201
202   my $upgrade_file = $self->ddl_filename(
203     $self->storage->sqlt_type,
204     $target_version,
205     $self->upgrade_directory,
206     $db_version,
207   );
208
209   $self->create_upgrade_path({ upgrade_file => $upgrade_file });
210
211   unless (-f $upgrade_file) {
212     # croak?
213     carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
214     return;
215   }
216
217   carp "DB version ($db_version) is lower than the schema version (".$self->schema_version."). Attempting upgrade.\n";
218
219   $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
220   $self->backup if $self->do_backup;
221   $self->schema->txn_do(sub { $self->do_upgrade });
222
223   $self->version_rs->create({
224     version     => $target_version,
225     # ddl         => $ddl,
226     # upgrade_sql => $upgrade_sql,
227   });
228 }
229
230 method do_upgrade { $self->run_upgrade(qr/.*?/) }
231
232 method run_upgrade($stm) {
233   return unless $self->_filedata;
234   my @statements = grep { $_ =~ $stm } @{$self->_filedata};
235
236   for (@statements) {
237     $self->storage->debugobj->query_start($_) if $self->storage->debug;
238     $self->apply_statement($_);
239     $self->storage->debugobj->query_end($_) if $self->storage->debug;
240   }
241 }
242
243 method apply_statement($statement) {
244   # croak?
245   $self->storage->dbh->do($_) or carp "SQL was: $_"
246 }
247
248 method _read_sql_file($file) {
249   return unless $file;
250
251   open my $fh, '<', $file or carp("Can't open upgrade file, $file ($!)");
252   my @data = split /\n/, join '', <$fh>;
253   close $fh;
254
255   @data = grep {
256     $_ &&
257     !/^--/ &&
258     !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/m
259   } split /;/,
260     join '', @data;
261
262   return \@data;
263 }
264
265 __PACKAGE__->meta->make_immutable;
266
267 1;
268
269 __END__
270
271 vim: ts=2,sw=2,expandtab