File Coverage

blib/lib/DBIx/Class/DeploymentHandler/DeployMethod/SQL/Translator.pm
Criterion Covered Total %
statement 388 423 91.7
branch 77 108 71.3
condition 17 27 62.9
subroutine 69 83 83.1
pod 2 11 18.1
total 553 652 84.8


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