File Coverage

blib/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm
Criterion Covered Total %
statement 393 428 91.8
branch 79 114 69.3
condition 11 20 55.0
subroutine 68 82 82.9
pod 2 11 18.1
total 553 655 84.4


line stmt bran cond sub pod time code
1             package DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator;
2             $DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::VERSION = '0.002231';
3 18     18   987390 use Moose;
  18         2525746  
  18         134  
4              
5             # ABSTRACT: Manage your SQL and Perl migrations in nicely laid out directories
6              
7 18     18   127172 use autodie;
  18         243722  
  18         158  
8 18     18   118275 use Carp qw( carp croak );
  18         47  
  18         1205  
9 18     18   3856 use DBIx::Class::DeploymentHandler::LogImporter qw(:log :dlog);
  18         61  
  18         287  
10 18     18   9077 use Context::Preserve;
  18         8840  
  18         896  
11 18     18   145 use Digest::MD5;
  18         44  
  18         537  
12              
13 18     18   104 use Try::Tiny;
  18         115  
  18         872  
14              
15 18     18   9080 use SQL::Translator;
  18         5179734  
  18         1019  
16             require SQL::Translator::Diff;
17              
18             require DBIx::Class::Storage; # loaded for type constraint
19 18     18   3990 use DBIx::Class::DeploymentHandler::Types;
  18         52  
  18         633  
20              
21 18     18   8090 use Path::Class qw(file dir);
  18         305106  
  18         72459  
22              
23             with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
24              
25             has ignore_ddl => (
26             isa => 'Bool',
27             is => 'ro',
28             default => undef,
29             );
30              
31             has force_overwrite => (
32             isa => 'Bool',
33             is => 'ro',
34             default => undef,
35             );
36              
37             has schema => (
38             is => 'ro',
39             required => 1,
40             );
41              
42             has storage => (
43             isa => 'DBIx::Class::Storage',
44             is => 'ro',
45             lazy_build => 1,
46             );
47              
48             has version_source => (
49             is => 'ro',
50             default => '__VERSION',
51             );
52              
53             sub _build_storage {
54 40     40   111 my $self = shift;
55 40         1546 my $s = $self->schema->storage;
56 40         795 $s->_determine_driver;
57 40         24132 $s
58             }
59              
60             has sql_translator_args => (
61             isa => 'HashRef',
62             is => 'ro',
63             default => sub { {} },
64             );
65             has script_directory => (
66             isa => 'Str',
67             is => 'ro',
68             required => 1,
69             default => 'sql',
70             );
71              
72             has databases => (
73             coerce => 1,
74             isa => 'DBIx::Class::DeploymentHandler::Databases',
75             is => 'ro',
76             default => sub { [qw( MySQL SQLite PostgreSQL )] },
77             );
78              
79             has txn_wrap => (
80             is => 'ro',
81             isa => 'Bool',
82             default => 1,
83             );
84              
85             has schema_version => (
86             is => 'ro',
87             lazy_build => 1,
88             );
89              
90             # this will probably never get called as the DBICDH
91             # will be passing down a schema_version normally, which
92             # is built the same way, but we leave this in place
93             sub _build_schema_version {
94 13     13   40 my $self = shift;
95 13         548 $self->schema->schema_version
96             }
97              
98             sub __ddl_consume_with_prefix {
99 42     42   164 my ($self, $type, $versions, $prefix) = @_;
100 42         1706 my $base_dir = $self->script_directory;
101              
102 42         228 my $main = dir( $base_dir, $type );
103             my $common =
104 42         2867 dir( $base_dir, '_common', $prefix, join q(-), @{$versions} );
  42         191  
105              
106 42         1831 my $common_any =
107             dir( $base_dir, '_common', $prefix, '_any' );
108              
109 42         1786 my $dir_any = dir($main, $prefix, '_any');
110              
111 42         1648 my %files;
112             try {
113 42     42   1905 my $dir = dir( $main, $prefix, join q(-), @{$versions} );
  42         185  
114 42         1918 opendir my($dh), $dir;
115             %files =
116 46         381 map { $_ => "$dir/$_" }
117 37 100       13747 grep { /\.(?:sql|pl|sql-\w+)$/ && -f "$dir/$_" }
  120         2586  
118             readdir $dh;
119 37         827 closedir $dh;
120             } catch {
121 5 50   5   14220 die $_ unless $self->ignore_ddl;
122 42         386 };
123 42         8879 for my $dirname (grep { -d $_ } $common, $common_any, $dir_any) {
  126         3008  
124 17         637 opendir my($dh), $dirname;
125 17 100       1710 for my $filename (grep { /\.(?:sql|pl)$/ && -f file($dirname,$_) } readdir $dh) {
  53         1486  
126 19 50       917 unless ($files{$filename}) {
127 19         58 $files{$filename} = file($dirname,$filename);
128             }
129             }
130 17         995 closedir $dh;
131             }
132              
133 42         2444 return [@files{sort keys %files}]
134             }
135              
136             sub _ddl_initialize_consume_filenames {
137 2     2   9 my ($self, $type, $version) = @_;
138 2         11 $self->__ddl_consume_with_prefix($type, [ $version ], 'initialize')
139             }
140              
141             sub _ddl_schema_consume_filenames {
142 15     15   57 my ($self, $type, $version) = @_;
143 15         88 $self->__ddl_consume_with_prefix($type, [ $version ], 'deploy')
144             }
145              
146             sub _ddl_protoschema_deploy_consume_filenames {
147 2     2   8 my ($self, $version) = @_;
148 2         77 my $base_dir = $self->script_directory;
149              
150 2         8 my $dir = dir( $base_dir, '_source', 'deploy', $version);
151 2 100       145 return [] unless -d $dir;
152              
153 1         54 opendir my($dh), $dir;
154 1 100       744 my %files = map { $_ => "$dir/$_" } grep { /\.yml$/ && -f "$dir/$_" } readdir $dh;
  2         51  
  4         60  
155 1         21 closedir $dh;
156              
157 1         484 return [@files{sort keys %files}]
158             }
159              
160             sub _ddl_protoschema_upgrade_consume_filenames {
161 19     19   66 my ($self, $versions) = @_;
162 19         815 my $base_dir = $self->script_directory;
163              
164 19         60 my $dir = dir( $base_dir, '_preprocess_schema', 'upgrade', join q(-), @{$versions});
  19         143  
165              
166 19 100       1719 return [] unless -d $dir;
167              
168 1         62 opendir my($dh), $dir;
169 1 100       149 my %files = map { $_ => "$dir/$_" } grep { /\.pl$/ && -f "$dir/$_" } readdir $dh;
  1         5  
  3         52  
170 1         24 closedir $dh;
171              
172 1         67 return [@files{sort keys %files}]
173             }
174              
175             sub _ddl_protoschema_downgrade_consume_filenames {
176 7     7   30 my ($self, $versions) = @_;
177 7         299 my $base_dir = $self->script_directory;
178              
179 7         23 my $dir = dir( $base_dir, '_preprocess_schema', 'downgrade', join q(-), @{$versions});
  7         48  
180              
181 7 50       578 return [] unless -d $dir;
182              
183 0         0 opendir my($dh), $dir;
184 0 0       0 my %files = map { $_ => "$dir/$_" } grep { /\.pl$/ && -f "$dir/$_" } readdir $dh;
  0         0  
  0         0  
185 0         0 closedir $dh;
186              
187 0         0 return [@files{sort keys %files}]
188             }
189              
190             sub _ddl_protoschema_produce_filename {
191 108     108   355 my ($self, $version) = @_;
192 108         4240 my $dirname = dir( $self->script_directory, '_source', 'deploy', $version );
193 108 100       10039 $dirname->mkpath unless -d $dirname;
194              
195 108         16207 return "" . file( $dirname, '001-auto.yml' );
196             }
197              
198             sub _ddl_schema_produce_filename {
199 21     21   98 my ($self, $type, $version) = @_;
200 21         980 my $dirname = dir( $self->script_directory, $type, 'deploy', $version );
201 21 50       1961 $dirname->mkpath unless -d $dirname;
202              
203 21         7074 return "" . file( $dirname, '001-auto.sql' );
204             }
205              
206             sub _ddl_schema_upgrade_consume_filenames {
207 19     19   65 my ($self, $type, $versions) = @_;
208 19         91 $self->__ddl_consume_with_prefix($type, $versions, 'upgrade')
209             }
210              
211             sub _ddl_schema_downgrade_consume_filenames {
212 6     6   23 my ($self, $type, $versions) = @_;
213 6         30 $self->__ddl_consume_with_prefix($type, $versions, 'downgrade')
214             }
215              
216             sub _ddl_schema_upgrade_produce_filename {
217 18     18   66 my ($self, $type, $versions) = @_;
218 18         821 my $dir = $self->script_directory;
219              
220 18         57 my $dirname = dir( $dir, $type, 'upgrade', join q(-), @{$versions});
  18         107  
221 18 100       1214 $dirname->mkpath unless -d $dirname;
222              
223 18         4510 return "" . file( $dirname, '001-auto.sql' );
224             }
225              
226             sub _ddl_schema_downgrade_produce_filename {
227 6     6   26 my ($self, $type, $versions, $dir) = @_;
228 6         18 my $dirname = dir( $dir, $type, 'downgrade', join q(-), @{$versions} );
  6         34  
229 6 100       372 $dirname->mkpath unless -d $dirname;
230              
231 6         1537 return "" . file( $dirname, '001-auto.sql');
232             }
233              
234             sub _run_sql_array {
235 56     56   215 my ($self, $sql) = @_;
236 56         2504 my $storage = $self->storage;
237              
238 56         212 $sql = [ $self->_split_sql_chunk( @$sql ) ];
239              
240 56     0   408 Dlog_trace { "Running SQL $_" } $sql;
  0         0  
241 56         1003 foreach my $line (@{$sql}) {
  56         164  
242 99         713 $storage->_query_start($line);
243             # the whole reason we do this is so that we can see the line that was run
244             try {
245 99     99   4440 $storage->dbh_do (sub { $_[1]->do($line) });
  99         2570  
246             }
247             catch {
248 3     3   3465 die "$_ (running line '$line')"
249 99         1151 };
250 96         16723 $storage->_query_end($line);
251             }
252 53         634 return join "\n", @$sql
253             }
254              
255             my %STORAGE2FEATURE = (
256             SQLServer => {
257             txn => qr/begin\s+transaction\b/i,
258             comment => {
259             DD => 1, # --
260             HASH => 1,
261             SSTAR => 1, # /* */
262             DS => 1, # //
263             PERCENT => 1,
264             },
265             },
266             Sybase => {
267             txn => qr/begin\s+transaction\b/i,
268             comment => {
269             DD => 1,
270             SSTAR => 1,
271             DS => 1,
272             PERCENT => 1,
273             },
274             },
275             SQLite => {
276             txn => qr/begin\b/i,
277             comment => {
278             DD => 1,
279             HASH => 1,
280             },
281             },
282             MySQL => {
283             txn => qr/(begin\b|start\s+transaction\b)/i,
284             comment => {
285             DD => 1,
286             HASH => 1,
287             SS => 1,
288             },
289             },
290             Oracle => {
291             comment => {
292             DD => 1,
293             HASH => 1,
294             SS => 1,
295             },
296             },
297             Pg => {
298             txn => qr/begin\b/i,
299             chunk => sub {
300             my ($c) = @_;
301             my @ret;
302             my $accumulator = '';
303             while (length $c) {
304             if ($c =~ s/\A([^\$]*?);//s) {
305             $accumulator .= $1;
306             push @ret, $accumulator;
307             $accumulator = '';
308             } elsif (
309             $c =~ s/\A(
310             .*?
311             ( \$ [^\$]* \$ )
312             )//xs
313             ) {
314             # got a $...$ .. $...$ chunk
315             $accumulator .= $1;
316             my $anchor = $2;
317             $c =~ s/\A(
318             .*?
319             \Q$anchor\E
320             )//xs;
321             $accumulator .= $1;
322             } elsif ($c =~ s/\A\s*\z//s) {
323             push @ret, $accumulator;
324             $accumulator = '';
325             } else {
326             push @ret, $accumulator.$c;
327             $accumulator = '';
328             last;
329             }
330             }
331             @ret;
332             },
333             comment => {
334             DD => 1,
335             HASH => 1,
336             },
337             },
338             );
339              
340             # split a chunk o' SQL into statements
341             sub _split_sql_chunk {
342 111     111   549 my $self = shift;
343 111         316 my @sql = map { $_.'' } @_; # copy
  171         645  
344 111         4607 my $storage_class = ref $self->storage;
345 111         493 $storage_class =~ s/.*://;
346 111   33     463 my $feature = $STORAGE2FEATURE{$storage_class} || $STORAGE2FEATURE{MySQL};
347 111         305 for ( @sql ) {
348             # strip transactions
349 171         336 my $txn = $feature->{txn};
350 171 50       1888 s/^\s*($txn|COMMIT\b).*//mgi if $txn;
351             # remove comments
352 171         394 my $comment = $feature->{comment};
353 171 50       686 s{--.*}{}gm if $comment->{DD};
354 171 100       419 s{/\* .*? \*/}{}xs if $comment->{SS};
355 171 50       383 s{//.*}{}gm if $comment->{DS};
356 171 50       798 s{#.*}{}gm if $comment->{HASH};
357 171 50       518 s{%.*}{}gm if $comment->{PERCENT};
358             }
359 111   100 169   778 my $chunk = $feature->{chunk} || sub { split /;\n/, $_[0] };
  169         726  
360 111         388 @sql = map $chunk->($_), @sql;
361 111         304 for ( @sql ) {
362             # trim whitespace
363 376         1067 s/^\s+//gm;
364 376         1062 s/\s+$//gm;
365             # remove blank lines
366 376         609 s/^\n//gm;
367             # put on single line
368 376         771 s/\n/ /g;
369             }
370 111         1469 return grep $_, @sql;
371             }
372              
373             sub _run_sql {
374 51     51   154 my ($self, $filename) = @_;
375 51     0   399 log_debug { "Running SQL from $filename" };
  0         0  
376             try {
377 51     51   2396 $self->_run_sql_array($self->_read_sql_file($filename));
378             } catch {
379 3     3   95 die "failed to run SQL in $filename: $_"
380 51         2067 };
381             }
382              
383             my ( %f2p, %p2f );
384             sub _generate_script_package_name {
385 17     17   31 my $file = shift;
386              
387 17         31 my $pkgbase = 'DBICDH::Sandbox::';
388 17         28 my $maxlen = 200; # actual limit is "about 250" according to perldiag
389              
390 17 100       62 return $pkgbase . $f2p{"$file"} if $f2p{"$file"};
391              
392 10         54 my $package = Digest::MD5::md5_hex("$file");
393 10         37 $package++ while exists $p2f{$package}; # increment until unique
394              
395 10 50       33 die "unable to generate a unique short name for '$file'"
396             if length($pkgbase) + length($package) > $maxlen;
397              
398 10         34 $f2p{"$file"} = $package;
399 10         29 $p2f{$package} = "$file";
400              
401 10         30 return $pkgbase . $package;
402             }
403              
404             sub _load_sandbox {
405 17     17   42 my $_file = shift;
406 17         49 $_file = "$_file";
407              
408 17         417 my $_package = _generate_script_package_name($_file);
409              
410 17         2226 my $fn = eval sprintf <<'END_EVAL', $_package;
411             package %s;
412             {
413             our $app;
414             $app ||= require $_file;
415             if ( !$app && ( my $error = $@ || $! )) { die $error; }
416             $app;
417             }
418             END_EVAL
419              
420 17 50       103 croak $@ if $@;
421              
422 17 50 33     87 croak "$_file should define an anonymous sub that takes a schema but it didn't!"
423             unless ref $fn && ref $fn eq 'CODE';
424              
425 17         41 return $fn;
426             }
427              
428             sub _run_perl {
429 17     17   1400 my ($self, $filename, $versions) = @_;
430 17     0   114 log_debug { "Running Perl from $filename" };
  0         0  
431              
432 17         562 my $fn = _load_sandbox($filename);
433              
434 17     0   102 Dlog_trace { "Running Perl $_" } $fn;
  0         0  
435              
436             try {
437 17     17   1334 $fn->($self->schema, $versions)
438             } catch {
439 2     2   46 die "failed to run Perl in $filename: $_"
440 17         350 };
441             }
442              
443             sub txn_do {
444 73     73 0 609 my ( $self, $code ) = @_;
445 73 100       3283 return $code->() unless $self->txn_wrap;
446              
447 71         2807 my $guard = $self->schema->txn_scope_guard;
448              
449 71     68   40790 return preserve_context { $code->() } after => sub { $guard->commit };
  71         1050  
  68         19058  
450             }
451              
452             sub _run_sql_and_perl {
453 44     44   233 my ($self, $filenames, $sql_to_run, $versions) = @_;
454 44         104 my @files = @{$filenames};
  44         143  
455             $self->txn_do(sub {
456 44 100   44   1887 $self->_run_sql_array($sql_to_run) if $self->ignore_ddl;
457              
458 44 100       222 my $sql = ($sql_to_run)?join ";\n", @$sql_to_run:'';
459             FILENAME:
460 44         257 for my $filename (map file($_), @files) {
461 66 50 66     9202 if ($self->ignore_ddl && $filename->basename =~ /^[^-]*-auto.*\.sql$/) {
    100          
    50          
462             next FILENAME
463 0         0 } elsif ($filename =~ /\.sql$/) {
464 51         1970 $sql .= $self->_run_sql($filename)
465             } elsif ( $filename =~ /\.pl$/ ) {
466 15         785 $self->_run_perl($filename, $versions)
467             } else {
468 0         0 croak "A file ($filename) got to deploy that wasn't sql or perl!";
469             }
470             }
471              
472 40         3566 return $sql;
473 44         416 });
474             }
475              
476             sub deploy {
477 17     17 1 49488 my $self = shift;
478 17   66     414 my $version = (shift @_ || {})->{version} || $self->schema_version;
479 17     0   150 log_info { "deploying version $version" };
  0         0  
480 17         1588 my $sqlt_type = $self->storage->sqlt_type;
481 17         7197 my $sql;
482 17         771 my $sqltargs = $self->sql_translator_args;
483 17 100       673 if ($self->ignore_ddl) {
484 2         13 $sql = $self->_sql_from_yaml($sqltargs,
485             '_ddl_protoschema_deploy_consume_filenames', $sqlt_type
486             );
487             }
488 17         100 return $self->_run_sql_and_perl($self->_ddl_schema_consume_filenames(
489             $sqlt_type,
490             $version,
491             ), $sql, [$version]);
492             }
493              
494             sub initialize {
495 2     2 1 749 my $self = shift;
496 2         12 my $args = shift;
497 2   33     12 my $version = $args->{version} || $self->schema_version;
498 2     0   21 log_info { "initializing version $version" };
  0         0  
499 2   66     162 my $storage_type = $args->{storage_type} || $self->storage->sqlt_type;
500              
501 2         521 my @files = @{$self->_ddl_initialize_consume_filenames(
  2         11  
502             $storage_type,
503             $version,
504             )};
505              
506 2         10 for my $filename (@files) {
507             # We ignore sql for now (till I figure out what to do with it)
508 3 50       16 if ( $filename =~ /^(.+)\.pl$/ ) {
509 3         71 my $filedata = do { local( @ARGV, $/ ) = $filename; <> };
  3         16  
  3         70  
510              
511 18     18   206 no warnings 'redefine';
  18         52  
  18         1320  
512 3     1   398 my $fn = eval "$filedata";
  1     1   8  
  1         57  
  1         101  
  1         43  
  1         4  
  1         80  
513 18     18   135 use warnings;
  18         50  
  18         31433  
514              
515 3 50       18 if ($@) {
    50          
516 0         0 croak "$filename failed to compile: $@";
517             } elsif (ref $fn eq 'CODE') {
518 3         83 $fn->()
519             } else {
520 0         0 croak "$filename should define an anonymous sub but it didn't!";
521             }
522             } else {
523 0         0 croak "A file ($filename) got to initialize_scripts that wasn't sql or perl!";
524             }
525             }
526             }
527              
528             sub _sqldiff_from_yaml {
529 26     26   112 my ($self, $from_version, $to_version, $db, $direction) = @_;
530 26         1032 my $dir = $self->script_directory;
531             my $sqltargs = {
532             add_drop_table => 0,
533             ignore_constraint_names => 1,
534             ignore_index_names => 1,
535 26         84 %{$self->sql_translator_args}
  26         1011  
536             };
537              
538 26         62 my $source_schema;
539             {
540 26         60 my $prefilename = $self->_ddl_protoschema_produce_filename($from_version, $dir);
  26         238  
541              
542             # should probably be a croak
543 26 100       3321 carp("No previous schema file found ($prefilename)")
544             unless -e $prefilename;
545              
546             my $t = SQL::Translator->new({
547 26         152 %{$sqltargs},
  26         920  
548             debug => 0,
549             trace => 0,
550             parser => 'SQL::Translator::Parser::YAML',
551             });
552              
553 26 50       24973 my $out = $t->translate( $prefilename )
554             or croak($t->error);
555              
556 26         966422 $source_schema = $t->schema;
557              
558 26 50       1295 $source_schema->name( $prefilename )
559             unless $source_schema->name;
560             }
561              
562 26         78 my $dest_schema;
563             {
564 26         66 my $filename = $self->_ddl_protoschema_produce_filename($to_version, $dir);
  26         153  
565              
566             # should probably be a croak
567 26 50       3415 carp("No next schema file found ($filename)")
568             unless -e $filename;
569              
570             my $t = SQL::Translator->new({
571 26         179 %{$sqltargs},
  26         897  
572             debug => 0,
573             trace => 0,
574             parser => 'SQL::Translator::Parser::YAML',
575             });
576              
577 26 50       23610 my $out = $t->translate( $filename )
578             or croak($t->error);
579              
580 26         1050901 $dest_schema = $t->schema;
581              
582 26 50       1259 $dest_schema->name( $filename )
583             unless $dest_schema->name;
584             }
585              
586 26         121 my $transform_files_method = "_ddl_protoschema_${direction}_consume_filenames";
587 26         256 my $transforms = $self->_coderefs_per_files(
588             $self->$transform_files_method([$from_version, $to_version])
589             );
590 26         143 $_->($source_schema, $dest_schema) for @$transforms;
591              
592 26         628 return [SQL::Translator::Diff::schema_diff(
593             $source_schema, $db,
594             $dest_schema, $db,
595             { producer_args => $sqltargs }
596             )];
597             }
598              
599             sub _sql_from_yaml {
600 46     46   198 my ($self, $sqltargs, $from_file, $db) = @_;
601 46         1887 my $schema = $self->schema;
602 46         1832 my $version = $self->schema_version;
603              
604 46         135 my @sql;
605              
606 46         264 my $actual_file = $self->$from_file($version);
607 46         5395 for my $yaml_filename (@{(
608 0     0   0 DlogS_trace { "generating SQL from Serialized SQL Files: $_" }
609 46 100       533 (ref $actual_file?$actual_file:[$actual_file])
610             )}) {
611             my $sqlt = SQL::Translator->new({
612             add_drop_table => 0,
613             parser => 'SQL::Translator::Parser::YAML',
614 46         1259 %{$sqltargs},
  46         1371  
615             producer => $db,
616             });
617              
618 46         360772 push @sql, $sqlt->translate($yaml_filename);
619 46 50       2577119 if(!@sql) {
620 0         0 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
621 0         0 return undef;
622             }
623             }
624 46         538 return \@sql;
625             }
626              
627             sub _prepare_install {
628 50     50   169 my $self = shift;
629 50         138 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
  50         2644  
  50         231  
630 50         158 my $from_file = shift;
631 50         134 my $to_file = shift;
632 50         2137 my $dir = $self->script_directory;
633 50         2168 my $databases = $self->databases;
634 50         2351 my $version = $self->schema_version;
635              
636 50         264 foreach my $db (@$databases) {
637 44 50       246 my $sql = $self->_sql_from_yaml($sqltargs, $from_file, $db ) or next;
638              
639 44         293 my $filename = $self->$to_file($db, $version, $dir);
640 44 50       6066 if (-e $filename ) {
641 0 0       0 if ($self->force_overwrite) {
642 0         0 carp "Overwriting existing DDL file - $filename";
643 0         0 unlink $filename;
644             } else {
645 0         0 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
646             }
647             }
648 44         388 open my $file, q(>), $filename;
649 44         7544 binmode $file;
650 44         2002 print {$file} join ";\n", @$sql, '';
  44         1179  
651 44         296 close $file;
652             }
653             }
654              
655             sub _resultsource_install_filename {
656 23     23   1787 my ($self, $source_name) = @_;
657             return sub {
658 20     20   1433 my ($self, $type, $version) = @_;
659 20         1075 my $dirname = dir( $self->script_directory, $type, 'deploy', $version );
660 20 100       2000 $dirname->mkpath unless -d $dirname;
661              
662 20         2252 return "" . file( $dirname, "001-auto-$source_name.sql" );
663             }
664 23         150 }
665              
666             sub _resultsource_protoschema_filename {
667 21     21   72 my ($self, $source_name) = @_;
668             return sub {
669 39     39   129 my ($self, $version) = @_;
670 39         1557 my $dirname = dir( $self->script_directory, '_source', 'deploy', $version );
671 39 100       3630 $dirname->mkpath unless -d $dirname;
672              
673 39         2752 return "" . file( $dirname, "001-auto-$source_name.yml" );
674             }
675 21         100 }
676              
677             sub install_resultsource {
678 2     2 0 26 my ($self, $args) = @_;
679             my $source = $args->{result_source}
680 2 50       11 or die 'result_source must be passed to install_resultsource';
681             my $version = $args->{version}
682 2 50       10 or die 'version must be passed to install_resultsource';
683 2     0   22 log_info { 'installing_resultsource ' . $source->source_name . ", version $version" };
  0         0  
684 2         144 my $rs_install_file =
685             $self->_resultsource_install_filename($source->source_name);
686              
687 2         98 my $files = [
688             $self->$rs_install_file(
689             $self->storage->sqlt_type,
690             $version,
691             )
692             ];
693 2         277 $self->_run_sql_and_perl($files, [], [$version]);
694             }
695              
696             sub prepare_resultsource_install {
697 21     21 0 307 my $self = shift;
698 21         72 my $source = (shift @_)->{result_source};
699 21     0   196 log_info { 'preparing install for resultsource ' . $source->source_name };
  0         0  
700              
701 21         1506 my $install_filename = $self->_resultsource_install_filename($source->source_name);
702 21         127 my $proto_filename = $self->_resultsource_protoschema_filename($source->source_name);
703 21         207 $self->prepare_protoschema({
704             parser_args => { sources => [$source->source_name], }
705             }, $proto_filename);
706 21         4727 $self->_prepare_install({}, $proto_filename, $install_filename);
707             }
708              
709             sub prepare_deploy {
710 0     0 0 0 log_info { 'preparing deploy' };
  30     30   2927  
711 30         1737 my $self = shift;
712             $self->prepare_protoschema({
713             # Exclude version table so that it gets installed separately
714             parser_args => {
715             sources => [
716 3         40 sort { $a cmp $b }
717 30         1360 grep { $_ ne $self->version_source }
  55         3250  
718             $self->schema->sources
719             ],
720             }
721             }, '_ddl_protoschema_produce_filename');
722 29         13060 $self->_prepare_install({}, '_ddl_protoschema_produce_filename', '_ddl_schema_produce_filename');
723             }
724              
725             sub prepare_upgrade {
726 18     18 0 3748 my ($self, $args) = @_;
727             log_info {
728 0     0   0 "preparing upgrade from $args->{from_version} to $args->{to_version}"
729 18         186 };
730             $self->_prepare_changegrade(
731 18         1253 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'upgrade'
732             );
733             }
734              
735             sub prepare_downgrade {
736 6     6 0 1936 my ($self, $args) = @_;
737             log_info {
738 0     0   0 "preparing downgrade from $args->{from_version} to $args->{to_version}"
739 6         53 };
740             $self->_prepare_changegrade(
741 6         325 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'downgrade'
742             );
743             }
744              
745             sub _coderefs_per_files {
746 26     26   1833 my ($self, $files) = @_;
747 18     18   196 no warnings 'redefine';
  18         61  
  18         16501  
748 26         107 [map eval do { local( @ARGV, $/ ) = $_; <> }, @$files]
  1         7  
  1         220  
749             }
750              
751             sub _prepare_changegrade {
752 24     24   113 my ($self, $from_version, $to_version, $version_set, $direction) = @_;
753 24         1045 my $schema = $self->schema;
754 24         933 my $databases = $self->databases;
755 24         922 my $dir = $self->script_directory;
756              
757 24         1068 my $schema_version = $self->schema_version;
758 24         99 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
759 24         95 foreach my $db (@$databases) {
760 24         148 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
761 24 100       3027 if(-e $diff_file) {
762 1 50       44 if ($self->force_overwrite) {
763 0         0 carp("Overwriting existing $direction-diff file - $diff_file");
764 0         0 unlink $diff_file;
765             } else {
766 1         12 die "Cannot overwrite '$diff_file', either enable force_overwrite or delete it"
767             }
768             }
769              
770 23         167 open my $file, q(>), $diff_file;
771 23         2918 binmode $file;
772 23         885 print {$file} join ";\n", @{$self->_sqldiff_from_yaml($from_version, $to_version, $db, $direction)};
  23         166  
  23         144  
773 23         425180 close $file;
774             }
775             }
776              
777             sub _read_sql_file {
778 51     51   149 my ($self, $file) = @_;
779 51 50       201 return unless $file;
780              
781 51         473 local $/ = undef; #sluuuuuurp
782              
783 51         286 open my $fh, '<', $file;
784 51         14256 return [ $self->_split_sql_chunk( <$fh> ) ];
785             }
786              
787             sub downgrade_single_step {
788 6     6 0 15100 my $self = shift;
789 6         22 my $version_set = (shift @_)->{version_set};
790 6     0   47 Dlog_info { "downgrade_single_step'ing $_" } $version_set;
  0         0  
791              
792 6         562 my $sqlt_type = $self->storage->sqlt_type;
793 6         219 my $sql_to_run;
794 6 100       242 if ($self->ignore_ddl) {
795 1         4 $sql_to_run = $self->_sqldiff_from_yaml(
796             $version_set->[0], $version_set->[1], $sqlt_type, 'downgrade',
797             );
798             }
799 6         25517 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_downgrade_consume_filenames(
800             $sqlt_type,
801             $version_set,
802             ), $sql_to_run, $version_set);
803              
804 6         619 return ['', $sql];
805             }
806              
807             sub upgrade_single_step {
808 19     19 0 32124 my $self = shift;
809 19         63 my $version_set = (shift @_)->{version_set};
810 19     0   162 Dlog_info { "upgrade_single_step'ing $_" } $version_set;
  0         0  
811              
812 19         1702 my $sqlt_type = $self->storage->sqlt_type;
813 19         677 my $sql_to_run;
814 19 100       773 if ($self->ignore_ddl) {
815 2         12 $sql_to_run = $self->_sqldiff_from_yaml(
816             $version_set->[0], $version_set->[1], $sqlt_type, 'upgrade',
817             );
818             }
819 19         27634 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_upgrade_consume_filenames(
820             $sqlt_type,
821             $version_set,
822             ), $sql_to_run, $version_set);
823 18         2107 return ['', $sql];
824             }
825              
826             sub prepare_protoschema {
827 51     51 0 133 my $self = shift;
828 51         118 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
  51         2264  
  51         229  
829 51         134 my $to_file = shift;
830 51         2450 my $filename
831             = $self->$to_file($self->schema_version);
832              
833             # we do this because the code that uses this sets parser args,
834             # so we just need to merge in the package
835             my $sqlt = SQL::Translator->new({
836             parser => 'SQL::Translator::Parser::DBIx::Class',
837             producer => 'SQL::Translator::Producer::YAML',
838 51         6683 %{ $sqltargs },
  51         1765  
839             });
840              
841 51         223112 my $yml = $sqlt->translate(data => $self->schema);
842              
843 51 50       1879196 croak("Failed to translate to YAML: " . $sqlt->error)
844             unless $yml;
845              
846 51 100       2373 if (-e $filename ) {
847 1 50       54 if ($self->force_overwrite) {
848 0         0 carp "Overwriting existing DDL-YML file - $filename";
849 0         0 unlink $filename;
850             } else {
851 1         35 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
852             }
853             }
854              
855 50         466 open my $file, q(>), $filename;
856 50         35140 binmode $file;
857 50         13574 print {$file} $yml;
  50         1031  
858 50         337 close $file;
859             }
860              
861             __PACKAGE__->meta->make_immutable;
862              
863             1;
864              
865             # vim: ts=2 sw=2 expandtab
866              
867             __END__
868              
869             =pod
870              
871             =head1 NAME
872              
873             DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator - Manage your SQL and Perl migrations in nicely laid out directories
874              
875             =head1 DESCRIPTION
876              
877             This class is the meat of L<DBIx::Class::DeploymentHandler>. It takes care
878             of generating serialized schemata as well as sql files to move from one
879             version of a schema to the rest. One of the hallmark features of this class
880             is that it allows for multiple sql files for deploy and upgrade, allowing
881             developers to fine tune deployment. In addition it also allows for perl
882             files to be run at any stage of the process.
883              
884             For basic usage see L<DBIx::Class::DeploymentHandler::HandlesDeploy>. What's
885             documented here is extra fun stuff or private methods.
886              
887             =head1 DIRECTORY LAYOUT
888              
889             Arguably this is the best feature of L<DBIx::Class::DeploymentHandler>.
890             It's spiritually based upon L<DBIx::Migration::Directories>, but has a
891             lot of extensions and modifications, so even if you are familiar with it,
892             please read this. I feel like the best way to describe the layout is with
893             the following example:
894              
895             $sql_migration_dir
896             |- _source
897             | |- deploy
898             | |- 1
899             | | `- 001-auto.yml
900             | |- 2
901             | | `- 001-auto.yml
902             | `- 3
903             | `- 001-auto.yml
904             |- SQLite
905             | |- downgrade
906             | | `- 2-1
907             | | `- 001-auto.sql
908             | |- deploy
909             | | `- 1
910             | | `- 001-auto.sql
911             | `- upgrade
912             | |- 1-2
913             | | `- 001-auto.sql
914             | `- 2-3
915             | `- 001-auto.sql
916             |- _common
917             | |- downgrade
918             | | `- 2-1
919             | | `- 002-remove-customers.pl
920             | `- upgrade
921             | `- 1-2
922             | | `- 002-generate-customers.pl
923             | `- _any
924             | `- 999-bump-action.pl
925             `- MySQL
926             |- downgrade
927             | `- 2-1
928             | `- 001-auto.sql
929             |- initialize
930             | `- 1
931             | |- 001-create_database.pl
932             | `- 002-create_users_and_permissions.pl
933             |- deploy
934             | `- 1
935             | `- 001-auto.sql
936             `- upgrade
937             `- 1-2
938             `- 001-auto.sql
939              
940             So basically, the code
941              
942             $dm->deploy(1)
943              
944             on an C<SQLite> database that would simply run
945             C<$sql_migration_dir/SQLite/deploy/1/001-auto.sql>. Next,
946              
947             $dm->upgrade_single_step([1,2])
948              
949             would run C<$sql_migration_dir/SQLite/upgrade/1-2/001-auto.sql> followed by
950             C<$sql_migration_dir/_common/upgrade/1-2/002-generate-customers.pl>, and
951             finally punctuated by
952             C<$sql_migration_dir/_common/upgrade/_any/999-bump-action.pl>.
953              
954             C<.pl> files don't have to be in the C<_common> directory, but most of the time
955             they should be, because perl scripts are generally database independent.
956              
957             Note that unlike most steps in the process, C<initialize> will not run SQL, as
958             there may not even be an database at initialize time. It will run perl scripts
959             just like the other steps in the process, but nothing is passed to them.
960             Until people have used this more it will remain freeform, but a recommended use
961             of initialize is to have it prompt for username and password, and then call the
962             appropriate C<< CREATE DATABASE >> commands etc.
963              
964             =head2 Directory Specification
965              
966             The following subdirectories are recognized by this DeployMethod:
967              
968             =over 2
969              
970             =item C<_source>
971              
972             This directory can contain the following directories:
973              
974             =over 2
975              
976             =item C<deploy>
977              
978             This directory merely contains directories named after schema
979             versions, which in turn contain C<yaml> files that are serialized versions
980             of the schema at that version. These files are not for editing by hand.
981              
982             =back
983              
984             =item C<_preprocess_schema>
985              
986             This directory can contain the following directories:
987              
988             =over 2
989              
990             =item C<downgrade>
991              
992             This directory merely contains directories named after migrations, which are of
993             the form C<$from_version-$to_version>. Inside of these directories you may put
994             Perl scripts which are to return a subref that takes the arguments C<<
995             $from_schema, $to_schema >>, which are L<SQL::Translator::Schema> objects.
996              
997             =item C<upgrade>
998              
999             This directory merely contains directories named after migrations, which are of
1000             the form C<$from_version-$to_version>. Inside of these directories you may put
1001             Perl scripts which are to return a subref that takes the arguments C<<
1002             $from_schema, $to_schema >>, which are L<SQL::Translator::Schema> objects.
1003              
1004             =back
1005              
1006             A typical usage of C<_preprocess_schema> is to define indices or other non-DBIC
1007             type metadata. Here is an example of how one might do that:
1008              
1009             The following coderef could be placed in a file called
1010             F<_preprocess_schema/1-2/001-add-user-index.pl>
1011              
1012             sub {
1013             my ($from, $to) = @_;
1014              
1015             $to->get_table('Users')->add_index(
1016             name => 'idx_Users_name',
1017             fields => ['name'],
1018             )
1019             }
1020              
1021             This would ensure that in version 2 of the schema the generated migrations
1022             include an index on C<< Users.name >>. Frustratingly, due to the nature of
1023             L<SQL::Translator>, you'll need to add this to each migration or it will detect
1024             that it was left out and kindly remove the index for you.
1025              
1026             An alternative to the above, which is likely to be a lot less annoying, is to
1027             define such data in your schema directly, and only change it as you need to:
1028              
1029             package MyApp::Schema::Result::User;
1030              
1031             #[...]
1032              
1033             sub sqlt_deploy_hook ( $self, $sqlt_table ) {
1034             $sqlt_table->add_index(name => 'idx_Users_name', fields => [ 'name' ]);
1035             }
1036              
1037             =item C<$storage_type>
1038              
1039             This is a set of scripts that gets run depending on what your storage type is.
1040             If you are not sure what your storage type is, take a look at the producers
1041             listed for L<SQL::Translator>. Also note, C<_common> is a special case.
1042             C<_common> will get merged into whatever other files you already have. This
1043             directory can contain the following directories itself:
1044              
1045             =over 2
1046              
1047             =item C<initialize>
1048              
1049             If you are using the C<initialize> functionality,
1050             you should call initialize() before calling C<install>. This has the same structure as the
1051             C<deploy> subdirectory as well; that is, it has a directory for each schema
1052             version. Unlike C<deploy>, C<upgrade>, and C<downgrade> though, it can only run
1053             C<.pl> files, and the coderef in the perl files get no arguments passed to them.
1054              
1055             =item C<deploy>
1056              
1057             Gets run when the schema is C<deploy>ed. Structure is a directory per schema
1058             version, and then files are merged with C<_common> and run in filename order.
1059             C<.sql> files are merely run, as expected. C<.pl> files are run according to
1060             L</PERL SCRIPTS>.
1061              
1062             =item C<upgrade>
1063              
1064             Gets run when the schema is C<upgrade>d. Structure is a directory per upgrade
1065             step, (for example, C<1-2> for upgrading from version 1 to version 2,) and then
1066             files are merged with C<_common> and run in filename order. C<.sql> files are
1067             merely run, as expected. C<.pl> files are run according to L</PERL SCRIPTS>.
1068              
1069             =item C<downgrade>
1070              
1071             Gets run when the schema is C<downgrade>d. Structure is a directory per
1072             downgrade step, (for example, C<2-1> for downgrading from version 2 to version
1073             1,) and then files are merged with C<_common> and run in filename order.
1074             C<.sql> files are merely run, as expected. C<.pl> files are run according to
1075             L</PERL SCRIPTS>.
1076              
1077             =back
1078              
1079             =back
1080              
1081             Note that there can be an C<_any> in the place of any of the versions (like
1082             C<1-2> or C<1>), which means those scripts will be run B<every> time. So if
1083             you have an C<_any> in C<_common/upgrade>, that script will get run for every
1084             upgrade.
1085              
1086             =head1 PERL SCRIPTS
1087              
1088             A perl script for this tool is very simple. It merely needs to contain an
1089             anonymous sub that takes a L<DBIx::Class::Schema> and the version set as it's
1090             arguments.
1091              
1092             A very basic perl script might look like:
1093              
1094             #!perl
1095              
1096             use strict;
1097             use warnings;
1098              
1099             use DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers
1100             'schema_from_schema_loader';
1101              
1102             schema_from_schema_loader({ naming => 'v4' }, sub {
1103             my $schema = shift;
1104              
1105             # [1] for deploy, [1,2] for upgrade or downgrade, probably used with _any
1106             my $versions = shift;
1107              
1108             $schema->resultset('Users')->create({
1109             name => 'root',
1110             password => 'root',
1111             })
1112             })
1113              
1114             Note that the above uses
1115             L<DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers/schema_from_schema_loader>.
1116             Using a raw coderef is strongly discouraged as it is likely to break as you
1117             modify your schema.
1118              
1119             =head1 SEE ALSO
1120              
1121             This class is an implementation of
1122             L<DBIx::Class::DeploymentHandler::HandlesDeploy>. Pretty much all the
1123             documentation is there.
1124              
1125             =head1 ATTRIBUTES
1126              
1127             =head2 ignore_ddl
1128              
1129             This attribute will, when set to true (default is false), cause the DM to use
1130             L<SQL::Translator> to use the C<_source>'s serialized SQL::Translator::Schema
1131             instead of any pregenerated SQL. If you have a development server this is
1132             probably the best plan of action as you will not be putting as many generated
1133             files in your version control. Goes well with with C<databases> of C<[]>.
1134              
1135             =head2 force_overwrite
1136              
1137             When this attribute is true generated files will be overwritten when the
1138             methods which create such files are run again. The default is false, in which
1139             case the program will die with a message saying which file needs to be deleted.
1140              
1141             =head2 schema
1142              
1143             The L<DBIx::Class::Schema> (B<required>) that is used to talk to the database
1144             and generate the DDL.
1145              
1146             =head2 storage
1147              
1148             The L<DBIx::Class::Storage> that is I<actually> used to talk to the database
1149             and generate the DDL. This is automatically created with L</_build_storage>.
1150              
1151             =head2 sql_translator_args
1152              
1153             The arguments that get passed to L<SQL::Translator> when it's used.
1154              
1155             =head2 script_directory
1156              
1157             The directory (default C<'sql'>) that scripts are stored in
1158              
1159             =head2 databases
1160              
1161             The types of databases (default C<< [qw( MySQL SQLite PostgreSQL )] >>) to
1162             generate files for
1163              
1164             =head2 txn_wrap
1165              
1166             Set to true (which is the default) to wrap all upgrades and deploys in a single
1167             transaction.
1168              
1169             =head2 schema_version
1170              
1171             The version the schema on your harddrive is at. Defaults to
1172             C<< $self->schema->schema_version >>.
1173              
1174             =head2 version_source
1175              
1176             The source name used to register the version storage with C<schema>. Defaults
1177             to C<__VERSION>.
1178              
1179             =head1 AUTHOR
1180              
1181             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
1182              
1183             =head1 COPYRIGHT AND LICENSE
1184              
1185             This software is copyright (c) 2019 by Arthur Axel "fREW" Schmidt.
1186              
1187             This is free software; you can redistribute it and/or modify it under
1188             the same terms as the Perl 5 programming language system itself.
1189              
1190             =cut