File Coverage

blib/lib/DBIx/Migration/Directories.pm
Criterion Covered Total %
statement 295 314 93.9
branch 106 120 88.3
condition 41 57 71.9
subroutine 41 41 100.0
pod 20 31 64.5
total 503 563 89.3


line stmt bran cond sub pod time code
1             package DBIx::Migration::Directories;
2              
3 5     5   295751 use 5.006;
  5         22  
  5         239  
4 5     5   35 use strict;
  5         19  
  5         185  
5 5     5   46 use warnings;
  5         11  
  5         408  
6 5     5   37 use Carp qw(carp croak);
  5         9  
  5         413  
7 5     5   71751 use DBIx::Migration::Directories::Base;
  5         17  
  5         183  
8 5     5   53 use base q(DBIx::Migration::Directories::Base);
  5         12  
  5         765  
9 5     5   5332 use DBIx::Migration::Directories::ConfigData;
  5         13  
  5         176  
10 5     5   5203 use File::Basename::Object;
  5         28504  
  5         4663  
11              
12             our $VERSION = '0.12';
13             our $SCHEMA_VERSION = '0.03';
14             our $schema = 'DBIx-Migration-Directories';
15              
16             return 1;
17              
18             sub set_preinit_defaults {
19 21     21 0 174 my($class, %args) = @_;
20 21         172 ($class, %args) = $class->SUPER::set_preinit_defaults(%args);
21              
22 21 100 100     404 if($args{desired_version_from} && !$args{schema}) {
23 1         5 $args{schema} = $args{desired_version_from};
24             }
25              
26 21 100       508 croak qq{$class\->new\() requires "schema" parameter}
27             unless defined $args{schema};
28              
29 20         46 my $s = $args{schema};
30 20 50 33     559 if($args{schema} =~ s{::}{-}g && !$args{desired_version_from}) {
31 0         0 $args{desired_version_from} = $s;
32             }
33            
34 20         170 return($class, %args);
35             }
36              
37             sub set_postinit_defaults {
38 20     20 0 443 my $self = shift;
39 20         126 $self->SUPER::set_postinit_defaults(@_);
40              
41 19 100       171 $self->{base} =
42             DBIx::Migration::Directories::ConfigData->config('schema_dir')
43             unless($self->{base});
44              
45 19 50       133 $self->{schema_dir} = join('/', $self->{base}, $self->{schema})
46             unless($self->{schema_dir});
47              
48 19 100       81 unless(exists $self->{dir}) {
49 6         27 my $dir = $self->detect_dir;
50 6 50       34 $self->{dir} = $dir if defined $dir;
51             }
52            
53 19 100       1122 unless(-d $self->{dir}) {
54 1         357 croak "$self->{dir} is not a directory!";
55             }
56            
57 18 100 33     384 if($self->{base} && $self->{schema} && !$self->{common_dir}) {
      66        
58 12         57 my $common = join('/', @$self{'base', 'schema'}, '_common');
59 12 100       505 if(-d $common) {
60 8         31 $self->{common_dir} = $common;
61             }
62             }
63              
64 18         92 $self->refresh();
65            
66 18 100       96 $self->get_current_version() unless defined $self->{current_version};
67 18 100       195 $self->set_desired_version() unless defined $self->{desired_version};
68            
69 15         225 return $self;
70             }
71              
72             sub detect_dir {
73 6     6 0 15 my $self = shift;
74 6 50 33     452 if($self->{schema} && $self->{base}) {
75 6         62 my $dir = join('/', $self->{schema_dir}, $self->db->driver);
76              
77             # if a driver-specific schema isn't available, but a _generic schema
78             # is, use that instead. however, if _generic isn't available either,
79             # we want to fail out on the original driver directory name.
80              
81 6 50       317 if(!-d $dir) {
82 6         23 my $generic_dir = join('/', $self->{schema_dir}, '_generic');
83 6 100       144 if(-d $generic_dir) {
84 5         13 $dir = $generic_dir;
85             }
86             }
87            
88 6         20 return $dir;
89             } else {
90 0         0 return;
91             }
92             }
93              
94             sub desired_version {
95 22     22 1 66 my($self, $version) = @_;
96 22 100       75 if(@_ == 2) {
97 17         37 my $old = $self->{desired_version};
98 17         46 $self->{desired_version} = $version;
99 17         31 return $old;
100             } else {
101 5         34 return $self->{desired_version};
102             }
103             }
104              
105             sub detect_package_version {
106 23     23 0 43 my $self = shift;
107 23 100       79 if($self->{desired_version_from}) {
108 5     5   55 no strict 'refs';
  5         12  
  5         1196  
109              
110 7         432 my $svar = join('::', $self->{desired_version_from}, 'SCHEMA_VERSION');
111 7         24 my $vvar = join('::', $self->{desired_version_from}, 'VERSION');
112              
113 7 100       9 if(!defined(${$vvar})) {
  7         51  
114 3         765 eval qq{require $self->{desired_version_from};};
115            
116 3 100       152 if($@) {
117 1         462 croak qq{require $self->{desired_version_from} failed: $@};
118             }
119             }
120            
121 6 100       13 if(defined ${$svar}) {
  6 100       39  
  2         14  
122 4 50 33     7 if(ref(${$svar}) && ${$svar}->can('numify')) {
  4         27  
  0         0  
123 0         0 return ${$svar}->numify;
  0         0  
124             } else {
125 4         9 return ${$svar};
  4         34  
126             }
127             } elsif(defined ${$vvar}) {
128 1 50 33     4 if(ref(${$vvar}) && ${$vvar}->can('numify')) {
  1         14  
  0         0  
129 0         0 return ${$vvar}->numify;
  0         0  
130             } else {
131 1         3 return ${$vvar};
  1         17  
132             }
133             } else {
134 1         215 croak qq{package "}, $self->{desired_version_from},
135             qq{" did not define \$VERSION};
136             }
137            
138 5     5   27 use strict 'refs';
  5         19  
  5         20995  
139             } else {
140 16         97 return;
141             }
142             }
143              
144             sub detect_highest_version {
145 16     16 0 25 my $self = shift;
146            
147 16         22 my @options = @{$self->{versions}};
  16         60  
148              
149 16         67 while(my $ver = shift(@options)) {
150 17         30 eval { $self->migration_path($self->{current_version}, $ver); };
  17         119  
151            
152 17 100       189 if(!$@) {
153 15         196 return $ver;
154             }
155             }
156              
157 1         197 return;
158             }
159              
160             sub detect_desired_version {
161 23     23 1 1284 my $self = shift;
162             return
163 23   100     90 $self->detect_package_version ||
164             $self->detect_highest_version ||
165             undef;
166             }
167              
168             sub set_desired_version {
169 20     20 1 2094 my $self = shift;
170 20 100       77 my $version = $self->detect_desired_version
171             or croak qq{Failed to detect the highest version in $self->{dir}!};
172 17         746 $self->desired_version($version);
173 17         44 return $version;
174             }
175              
176             sub migration_map {
177 18     18 0 61 my($self, @dirs) = @_;
178              
179 18         32 my @subs;
180 18         55 foreach my $dir (grep {$_} @dirs) {
  36         88  
181 32         63 my @s = do {
182 32 50       1310 opendir(my $dh, $dir) or croak qq{opendir("$dir") failed: $!};
183 32   100     5364 grep((!/^\./) && -d("$dir/$_"), readdir($dh));
184             };
185 32         134 push(@subs, \@s);
186             }
187            
188 18         36 my %migration_map;
189             my %versions;
190            
191 18         45 foreach my $major (@subs) {
192 32         70 foreach my $i (@$major) {
193 122         1156 my($from, $to) = $self->versions($i);
194 122   100     2904 $versions{$self->version_as_number($to)} ||= $to;
195 122 100       430 if(defined $to) {
196 106   100     479 $migration_map{$from} ||= {};
197 106   66     1829 $migration_map{$from}{$to} ||= $i;
198             }
199             }
200             }
201            
202 18         129 my $versions = [ @versions{(sort { $b <=> $a } (keys(%versions)))} ];
  81         202  
203 18         317 return(\%migration_map, $versions);
204             }
205              
206             sub refresh {
207 18     18 1 33 my $self = shift;
208 18         31 my $dh;
209            
210 18         86 my($migration_map, $versions) =
211             $self->migration_map(@$self{'dir', 'common_dir'});
212            
213 18         59 $self->{migrations} = $migration_map;
214 18         97 $self->{versions} = $versions;
215            
216 18         63 return $self->{migrations};
217             }
218              
219             sub migration_path {
220 79     79 1 731 my($self, $from_ver, $to_ver) = @_;
221 79         270 my @rv = ();
222 79         405 $from_ver = $self->version_as_number($from_ver);
223 79         255 $to_ver = $self->version_as_number($to_ver);
224            
225 79 100       230 if($from_ver == $to_ver) {
226 8         29 return @rv;
227             }
228            
229 71 100       1490 if(!$self->{migrations}{$from_ver}) {
230 1         229 croak qq{No migrations available for $from_ver};
231             }
232            
233 70 100       339 if($self->{migrations}{$from_ver}{$to_ver}) {
234 28         197 return($self->{migrations}{$from_ver}{$to_ver});
235             }
236            
237 42         204 my $direction = $self->direction($from_ver, $to_ver);
238            
239 24         91 my @candidates = sort { ($b * $direction) <=> ($a * $direction) } grep(
  42         2089  
240             $self->direction($from_ver, $_) == $direction,
241 42         75 keys(%{$self->{migrations}{$from_ver}})
242             );
243            
244             # never allow a schema to be dropped and re-created to switch versions
245             # as this could destroy data!
246 42 100       140 if($to_ver) {
247 32         274 @candidates = grep($_, @candidates);
248             }
249            
250 42 100       120 if(!@candidates) {
251 15         5768 croak qq{No migrations in direction $direction for $from_ver};
252             }
253            
254 27   66     160 while((!@rv) && (@candidates)) {
255 37         69 my $candidate = shift @candidates;
256 37         63 my @path = eval { $self->migration_path($candidate, $to_ver) };
  37         159  
257            
258 37 100       1463 if(@path) {
259 27         276 @rv = ($self->{migrations}{$from_ver}{$candidate}, @path);
260             }
261             }
262            
263 27 50       67 if(!@rv) {
264 0         0 croak qq{Failed to find a migration path from $from_ver to $to_ver};
265             }
266            
267 27         112 return(@rv);
268             }
269              
270             sub ls_overlay {
271 2     2 0 5 my($self, $dir, $overlay) = @_;
272 2         16 my %dir = map { $_->basename => $_ } $self->ls($dir);
  2         28  
273 2         22 $dir{$_->basename} = $_
274 2         287 foreach grep { !$dir{$_->basename} } $self->ls($overlay);
275 2         289 return map { $dir{$_} } sort keys %dir;
  4         14  
276             }
277              
278             sub ls {
279 37     37 0 56 my($self, $dn) = @_;
280 40         415 map { File::Basename::Object->new($_) }
  40         156  
281 37 100 66     63 sort map { "$dn/$_" } grep { !/^\./ && !/\~$/ && -f "$dn/$_" } readdir do {
  112         1853  
282 37 100       54 my $d; opendir($d, $dn) ? $d : croak qq{opendir("$dn") failed: $!};
  37         2468  
283             };
284             }
285              
286             sub read_sql_file {
287 40     40 0 65 my($self, $file) = @_;
288 40         244 \"$file", grep { m{\S}s } split(m{;\s*\n}s, $self->read_file($file));
  52         502  
289             }
290              
291             sub dir_flat_sql {
292 33     33 0 72 my($self, $dir) = @_;
293 33         94 map { $self->read_sql_file($_) } $self->ls($dir);
  36         319  
294             }
295              
296             sub dir_overlay_sql {
297 2     2 0 4 my($self, $dir, $overlay) = @_;
298 2         10 map { $self->read_sql_file($_) } $self->ls_overlay($dir, $overlay);
  4         20  
299             }
300              
301             sub dir_sql {
302 35     35 1 102 my($self, $dir) = @_;
303 35         150 my $d1 = "$self->{dir}/$dir";
304 35 100 66     214 if($self->{common_dir} && $dir ne $self->{common_dir}) {
305 33         86 my $d2 = "$self->{common_dir}/$dir";
306 33 100 100     1706 if(-d $d1 && -d $d2) {
    100          
307 2         10 $self->dir_overlay_sql($d1, $d2);
308             } elsif (-d $d2) {
309 6         19 $self->dir_flat_sql($d2);
310             } else {
311 25         6714 $self->dir_flat_sql($d1);
312             }
313             } else {
314 2         9 $self->dir_flat_sql($d1);
315             }
316             }
317              
318             sub version_update_sql {
319 33     33 1 955 my($self, $from, $to) = @_;
320 33         69 my $dbh = $self->{dbh};
321 33 100       110 my $ver =
322             exists($self->{_current_version}) ? '_current_version' :
323             'current_version';
324            
325 33 100       102 my $ins = defined($self->{$ver}) ? 0 : 1;
326 33         41 my @sql;
327            
328 33 100       100 if($ins) {
329 6         38 push(@sql,
330             $self->db->sql_insert_migration_schema_version($self->{schema}, $to)
331             );
332             } else {
333 27         125 push(@sql,
334             $self->db->sql_update_migration_schema_version($self->{schema}, $to)
335             );
336             }
337            
338 33         1374 push(@sql,
339             $self->db->sql_insert_migration_schema_log($self->{schema}, $from, $to)
340             );
341            
342 33         1368 return @sql;
343             }
344              
345             sub dir_migration_sql {
346 32     32 1 60 my($self, $dir) = @_;
347 32         112 my($from, $to) = ($self->versions($dir));
348            
349 32         102 my @sql = ($self->dir_sql($dir));
350            
351 32 100 66     846 if(
      100        
352             !$self->{schema} ||
353             $self->{schema} ne $schema ||
354             $self->version_as_number($to)
355             ) {
356 30         102 push(@sql, $self->version_update_sql($from, $to));
357 30         121 $self->{_current_version} = $self->version_as_number($to);
358             }
359            
360 32         197 return @sql;
361             }
362              
363             sub migration_path_sql {
364 18     18 1 45 my($self, @path) = @_;
365 18         28 my @sql;
366            
367 18         99 $self->{_current_version} = $self->{current_version};
368            
369 18         41 foreach my $dir (@path) {
370 32         101 push(@sql, $self->dir_migration_sql($dir));
371             }
372            
373 18         412 delete $self->{_current_version};
374            
375 18         108 return @sql;
376             }
377              
378             sub migrate_from_to {
379 20     20 1 35 my($self, $from, $to) = @_;
380            
381 20         67 my @path = $self->migration_path($from, $to);
382 18         75 my @sql = $self->migration_path_sql(@path);
383 18         224 my $rv = $self->run_sql(@sql);
384 18 100 100     617297 if($self->{schema} eq $schema && !$self->version_as_number($to)) {
385 2         10 delete $self->{current_version};
386             } else {
387 16         136 $self->get_current_version();
388             }
389 18         237 return $rv;
390             }
391              
392             sub migrate_to {
393 20     20 1 4884 my($self, $to) = @_;
394 20   100     102 my $from = $self->{current_version} || 0;
395 20         73 return $self->migrate_from_to($from, $to);
396             }
397              
398             sub migrate {
399 9     9 1 627 my $self = shift;
400 9         19 my $to;
401            
402 9 100       33 if(defined($self->{desired_version})) {
403 8         17 $to = $self->{desired_version};
404             } else {
405 1         318 croak qq{migrate called without desired_version being set!};
406             }
407            
408 8         32 return $self->migrate_to($to);
409             }
410              
411             sub migration_schema {
412 5     5 1 20 my($self, %args) = @_;
413 5         38 return $self->new(
414             dbh => $self->{dbh},
415             schema => $schema,
416             %args
417             );
418             }
419              
420             sub migrate_migration {
421 4     4 1 12 my($self, %args) = @_;
422 4         32 return $self->migration_schema(%args)->migrate();
423             }
424              
425             sub delete_migration {
426 1     1 1 5 my($self, %args) = @_;
427 1         7 return $self->migration_schema(%args)->delete_schema();
428             }
429              
430             sub full_migrate {
431 5     5 1 4546 my($self, %args) = @_;
432 5 100       27 if($self->{schema} eq $schema) {
433 1         5 return $self->migrate;
434             } else {
435 4 100       27 if($self->migrate_migration(%args)) {
436 3         53 return $self->migrate;
437             } else {
438 1         10 return 0;
439             }
440             }
441             }
442              
443              
444             sub delete_schema {
445 7     7 1 921 my $self = shift;
446 7         16 my $dbh = $self->{dbh};
447 7         21 $dbh->begin_work;
448 7         299 my $rv;
449 7         10 eval { $rv = $self->migrate_to(0); };
  7         28  
450              
451 7 100       58 if($@) {
452 1         7 $dbh->rollback;
453 1         183 croak $@;
454             }
455              
456 6 100       21 if($rv) {
457 4 100       16 if($self->{schema} ne $schema) {
458 2 50       9 unless($self->delete_schema_record) {
459 0         0 $dbh->rollback;
460 0         0 return 0;
461             }
462             }
463            
464 4         17 $self->get_current_version;
465 4 50       15 if($dbh->transaction_error) {
466 0         0 $dbh->rollback;
467 0         0 return 0;
468             } else {
469 4         41 $dbh->commit;
470 4         189254 return 1;
471             }
472             } else {
473 2         14 $dbh->rollback;
474 2         585 return 0;
475             }
476             }
477              
478             sub full_delete_schema {
479 5     5 1 3838 my($self, %args) = @_;
480            
481 5 100       27 if($self->{schema} eq $schema) {
482 1         6 return $self->delete_schema;
483             } else {
484 4         34 my $schemas = $self->schemas;
485 4         20 delete($schemas->{$schema});
486 4         14 delete($schemas->{$self->{schema}});
487 4 100       18 if(scalar keys %$schemas) {
488 1         6 return $self->delete_schema;
489             } else {
490 3         7 my $dbh = $self->{dbh};
491 3         12 $dbh->begin_work;
492 3         85 my $rv = eval { $self->delete_schema; };
  3         14  
493            
494 3 100       40 if($@) {
495 1         4 $dbh->rollback;
496 1         400 croak $@;
497             }
498            
499 2 100       9 if($rv) {
500 1         2 $rv = eval { $self->delete_migration(%args); };
  1         7  
501            
502 1 50       24 if($@) {
503 0         0 $dbh->rollback;
504 0         0 croak $@;
505             }
506            
507 1 50       4 if($rv) {
508 1         5 $dbh->commit;
509 1         17408 return 1;
510             } else {
511 0         0 $dbh->rollback;
512 0         0 return 0;
513             }
514             } else {
515 1         6 $dbh->rollback;
516 1         1180 return 0;
517             }
518             }
519             }
520             }
521              
522             sub delete_schema_record {
523 3     3 1 5269 my $self = shift;
524 3         26 return $self->db->db_delete_schema_record($self->{schema});
525             }
526              
527             sub get_current_version {
528 43     43 1 5865 my $self = shift;
529 43         69 my $version;
530              
531 43         90 eval { $version = $self->db->db_get_current_version($self->{schema}); };
  43         249  
532              
533 43 50       266 if($@) {
    100          
534 0         0 delete $self->{current_version};
535 0         0 die $@;
536             } elsif(!defined $version) {
537 19         83 delete $self->{current_version};
538 19         49 return;
539             } else {
540 24         84 $self->{current_version} = $version;
541 24         384 return $version;
542             }
543             }
544