Handle errors in perl scripts better and remove suport for $0
[dbsrgits/DBIx-Class-DeploymentHandler.git] / lib / DBIx / Class / DeploymentHandler / DeployMethod / SQL / Translator.pm
CommitLineData
45d0d9d5 1package DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator;
334bced5 2use Moose;
9af9d0b2 3
9a3a62f1 4# ABSTRACT: Manage your SQL and Perl migrations in nicely laid out directories
5
9af9d0b2 6use autodie;
7use Carp qw( carp croak );
8465e767 8use DBIx::Class::DeploymentHandler::Logger;
c4f51462 9use Log::Contextual qw(:log :dlog), -package_logger =>
8465e767 10 DBIx::Class::DeploymentHandler::Logger->new({
11 env_prefix => 'DBICDH'
12 });
1fcd4fe6 13use Context::Preserve;
9af9d0b2 14
7f50d101 15use Try::Tiny;
9af9d0b2 16
d23c7c77 17use SQL::Translator;
18require SQL::Translator::Diff;
9af9d0b2 19
d23c7c77 20require DBIx::Class::Storage; # loaded for type constraint
41863428 21use DBIx::Class::DeploymentHandler::Types;
22
9af9d0b2 23use File::Path 'mkpath';
24use File::Spec::Functions;
2e68a8e1 25
7521a845 26with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
3c1b5ee8 27
93460690 28has ignore_ddl => (
29 isa => 'Bool',
30 is => 'ro',
31 default => undef,
32);
33
92624ee5 34has force_overwrite => (
35 isa => 'Bool',
36 is => 'ro',
37 default => undef,
38);
39
d54b8d69 40has schema => (
41 isa => 'DBIx::Class::Schema',
42 is => 'ro',
43 required => 1,
d54b8d69 44);
45
334bced5 46has storage => (
47 isa => 'DBIx::Class::Storage',
48 is => 'ro',
49 lazy_build => 1,
50);
51
6e2665d3 52sub _build_storage {
53 my $self = shift;
2eaf903b 54 my $s = $self->schema->storage;
55 $s->_determine_driver;
56 $s
57}
58
02a7b8ac 59has sql_translator_args => (
334bced5 60 isa => 'HashRef',
61 is => 'ro',
62 default => sub { {} },
63);
91adde75 64has script_directory => (
334bced5 65 isa => 'Str',
66 is => 'ro',
67 required => 1,
68 default => 'sql',
69);
70
334bced5 71has databases => (
72 coerce => 1,
73 isa => 'DBIx::Class::DeploymentHandler::Databases',
74 is => 'ro',
75 default => sub { [qw( MySQL SQLite PostgreSQL )] },
76);
77
a7d53deb 78has txn_wrap => (
79 is => 'ro',
80 isa => 'Bool',
81 default => 1,
82);
83
73caa630 84has schema_version => (
85 is => 'ro',
e86c0c07 86 isa => 'Str',
73caa630 87 lazy_build => 1,
88);
89
6df6dcb9 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
115c68ce 93sub _build_schema_version {
6e2665d3 94 my $self = shift;
115c68ce 95 $self->schema->schema_version
6e2665d3 96}
73caa630 97
6e2665d3 98sub __ddl_consume_with_prefix {
99 my ($self, $type, $versions, $prefix) = @_;
91adde75 100 my $base_dir = $self->script_directory;
262166c1 101
76d08d08 102 my $main = catfile( $base_dir, $type );
76d08d08 103 my $common =
104 catfile( $base_dir, '_common', $prefix, join q(-), @{$versions} );
262166c1 105
25c3bec3 106 my $common_any =
107 catfile( $base_dir, '_common', $prefix, '_any' );
108
262166c1 109 my $dir;
110 if (-d $main) {
76d08d08 111 $dir = catfile($main, $prefix, join q(-), @{$versions})
262166c1 112 } else {
38857f30 113 if ($self->ignore_ddl) {
114 return []
115 } else {
116 croak "$main does not exist; please write/generate some SQL"
117 }
262166c1 118 }
25c3bec3 119 my $dir_any = catfile($main, $prefix, '_any');
262166c1 120
ef44838b 121 my %files;
122 try {
123 opendir my($dh), $dir;
124 %files =
125 map { $_ => "$dir/$_" }
126 grep { /\.(?:sql|pl|sql-\w+)$/ && -f "$dir/$_" }
127 readdir $dh;
128 closedir $dh;
129 } catch {
130 die $_ unless $self->ignore_ddl;
131 };
25c3bec3 132 for my $dirname (grep { -d $_ } $common, $common_any, $dir_any) {
133 opendir my($dh), $dirname;
134 for my $filename (grep { /\.(?:sql|pl)$/ && -f catfile($dirname,$_) } readdir $dh) {
262166c1 135 unless ($files{$filename}) {
25c3bec3 136 $files{$filename} = catfile($dirname,$filename);
262166c1 137 }
138 }
139 closedir $dh;
140 }
141
142 return [@files{sort keys %files}]
143}
3c1b5ee8 144
6e2665d3 145sub _ddl_initialize_consume_filenames {
146 my ($self, $type, $version) = @_;
ff40cb1f 147 $self->__ddl_consume_with_prefix($type, [ $version ], 'initialize')
fc4b7602 148}
149
6e2665d3 150sub _ddl_schema_consume_filenames {
151 my ($self, $type, $version) = @_;
58eb99c3 152 $self->__ddl_consume_with_prefix($type, [ $version ], 'deploy')
3c1b5ee8 153}
154
6e2665d3 155sub _ddl_protoschema_deploy_consume_filenames {
156 my ($self, $version) = @_;
c72832d7 157 my $base_dir = $self->script_directory;
158
159 my $dir = catfile( $base_dir, '_source', 'deploy', $version);
160 return [] unless -d $dir;
161
162 opendir my($dh), $dir;
163 my %files = map { $_ => "$dir/$_" } grep { /\.yml$/ && -f "$dir/$_" } readdir $dh;
164 closedir $dh;
165
166 return [@files{sort keys %files}]
167}
168
6e2665d3 169sub _ddl_protoschema_upgrade_consume_filenames {
170 my ($self, $versions) = @_;
f9c6ab50 171 my $base_dir = $self->script_directory;
172
58eb99c3 173 my $dir = catfile( $base_dir, '_preprocess_schema', 'upgrade', join q(-), @{$versions});
f9c6ab50 174
175 return [] unless -d $dir;
176
177 opendir my($dh), $dir;
178 my %files = map { $_ => "$dir/$_" } grep { /\.pl$/ && -f "$dir/$_" } readdir $dh;
179 closedir $dh;
180
181 return [@files{sort keys %files}]
182}
183
6e2665d3 184sub _ddl_protoschema_downgrade_consume_filenames {
185 my ($self, $versions) = @_;
f9c6ab50 186 my $base_dir = $self->script_directory;
187
58eb99c3 188 my $dir = catfile( $base_dir, '_preprocess_schema', 'downgrade', join q(-), @{$versions});
f9c6ab50 189
190 return [] unless -d $dir;
191
192 opendir my($dh), $dir;
193 my %files = map { $_ => "$dir/$_" } grep { /\.pl$/ && -f "$dir/$_" } readdir $dh;
194 closedir $dh;
195
196 return [@files{sort keys %files}]
197}
198
6e2665d3 199sub _ddl_protoschema_produce_filename {
200 my ($self, $version) = @_;
58eb99c3 201 my $dirname = catfile( $self->script_directory, '_source', 'deploy', $version );
7e08eddd 202 mkpath($dirname) unless -d $dirname;
203
204 return catfile( $dirname, '001-auto.yml' );
205}
206
6e2665d3 207sub _ddl_schema_produce_filename {
208 my ($self, $type, $version) = @_;
58eb99c3 209 my $dirname = catfile( $self->script_directory, $type, 'deploy', $version );
76d08d08 210 mkpath($dirname) unless -d $dirname;
d54b8d69 211
09bc35e3 212 return catfile( $dirname, '001-auto.sql' );
d54b8d69 213}
214
6e2665d3 215sub _ddl_schema_upgrade_consume_filenames {
216 my ($self, $type, $versions) = @_;
58eb99c3 217 $self->__ddl_consume_with_prefix($type, $versions, 'upgrade')
3c1b5ee8 218}
219
6e2665d3 220sub _ddl_schema_downgrade_consume_filenames {
221 my ($self, $type, $versions) = @_;
58eb99c3 222 $self->__ddl_consume_with_prefix($type, $versions, 'downgrade')
a41a04e5 223}
224
6e2665d3 225sub _ddl_schema_upgrade_produce_filename {
226 my ($self, $type, $versions) = @_;
91adde75 227 my $dir = $self->script_directory;
76d311e7 228
58eb99c3 229 my $dirname = catfile( $dir, $type, 'upgrade', join q(-), @{$versions});
76d08d08 230 mkpath($dirname) unless -d $dirname;
a41a04e5 231
e62add58 232 return catfile( $dirname, '001-auto.sql' );
a41a04e5 233}
234
6e2665d3 235sub _ddl_schema_downgrade_produce_filename {
236 my ($self, $type, $versions, $dir) = @_;
58eb99c3 237 my $dirname = catfile( $dir, $type, 'downgrade', join q(-), @{$versions} );
76d08d08 238 mkpath($dirname) unless -d $dirname;
24f4524b 239
09bc35e3 240 return catfile( $dirname, '001-auto.sql');
24f4524b 241}
242
6e2665d3 243sub _run_sql_array {
244 my ($self, $sql) = @_;
41219a5d 245 my $storage = $self->storage;
5d7b27cf 246
115c68ce 247 $sql = [ _split_sql_chunk( @$sql ) ];
1f0d0633 248
f4075791 249 Dlog_trace { "Running SQL $_" } $sql;
f36afe83 250 foreach my $line (@{$sql}) {
5d7b27cf 251 $storage->_query_start($line);
10a62c3d 252 # the whole reason we do this is so that we can see the line that was run
5d7b27cf 253 try {
5d7b27cf 254 $storage->dbh_do (sub { $_[1]->do($line) });
255 }
256 catch {
10a62c3d 257 die "$_ (running line '$line')"
60e09fce 258 };
5d7b27cf 259 $storage->_query_end($line);
260 }
4d09f712 261 return join "\n", @$sql
f36afe83 262}
263
115c68ce 264# split a chunk o' SQL into statements
265sub _split_sql_chunk {
266 my @sql = map { split /;\n/, $_ } @_;
267
268 for ( @sql ) {
269 # strip transactions
270 s/^(?:BEGIN|BEGIN TRANSACTION|COMMIT).*//mgi;
271
272 # trim whitespaces
273 s/^\s+|\s+$//mg;
274
275 # remove comments
276 s/^--.*//gm;
277
278 # remove blank lines
279 s/^\n//mg;
280
281 # put on single line
282 s/\n/ /g;
283 }
284
285 return @sql;
286}
287
6e2665d3 288sub _run_sql {
289 my ($self, $filename) = @_;
f4075791 290 log_debug { "Running SQL from $filename" };
f36afe83 291 return $self->_run_sql_array($self->_read_sql_file($filename));
5d7b27cf 292}
2e68a8e1 293
a89cd737 294sub _load_sandbox {
295 my $_file = shift;
296
297 my $_package = $_file;
32bd7c45 298 $_package =~ s/([^A-Za-z0-9_])/sprintf("_%2x", ord($1))/eg;
a89cd737 299
32bd7c45 300 my $fn = eval sprintf <<'END_EVAL', $_package;
a89cd737 301package DBICDH::Sandbox::%s;
302{
32bd7c45 303 our $app;
304 $app ||= require $_file;
a89cd737 305 if ( !$app && ( my $error = $@ || $! )) { die $error; }
306 $app;
307}
308END_EVAL
32bd7c45 309
310 croak $@ if $@;
311
312 croak "$_file should define an anonymouse sub that takes a schema but it didn't!"
313 unless ref $fn && ref $fn eq 'CODE';
314
315 return $fn;
a89cd737 316}
317
6e2665d3 318sub _run_perl {
319 my ($self, $filename, $versions) = @_;
f4075791 320 log_debug { "Running Perl from $filename" };
c8a2f7bd 321
a89cd737 322 my $fn = _load_sandbox($filename);
323
f4075791 324 Dlog_trace { "Running Perl $_" } $fn;
5d7b27cf 325
32bd7c45 326 $fn->($self->schema, $versions)
5d7b27cf 327}
5d7b27cf 328
1fcd4fe6 329sub txn_do {
330 my ( $self, $code ) = @_;
331 return $code->() unless $self->txn_wrap;
332
333 my $guard = $self->schema->txn_scope_guard;
334
335 return preserve_context { $code->() } after => sub { $guard->commit };
336}
337
6e2665d3 338sub _run_sql_and_perl {
339 my ($self, $filenames, $sql_to_run, $versions) = @_;
5d7b27cf 340 my @files = @{$filenames};
1fcd4fe6 341 $self->txn_do(sub {
342 $self->_run_sql_array($sql_to_run) if $self->ignore_ddl;
343
344 my $sql = ($sql_to_run)?join ";\n", @$sql_to_run:'';
345 FILENAME:
346 for my $filename (@files) {
347 if ($self->ignore_ddl && $filename =~ /^[^_]*-auto.*\.sql$/) {
348 next FILENAME
349 } elsif ($filename =~ /\.sql$/) {
350 $sql .= $self->_run_sql($filename)
351 } elsif ( $filename =~ /\.pl$/ ) {
352 $self->_run_perl($filename, $versions)
353 } else {
354 croak "A file ($filename) got to deploy that wasn't sql or perl!";
355 }
356 }
357
358 return $sql;
359 });
41219a5d 360}
361
362sub deploy {
363 my $self = shift;
be140a5f 364 my $version = (shift @_ || {})->{version} || $self->schema_version;
f4075791 365 log_info { "deploying version $version" };
ef44838b 366 my $sqlt_type = $self->storage->sqlt_type;
367 my $sql;
368 if ($self->ignore_ddl) {
369 $sql = $self->_sql_from_yaml({},
4ea5caf5 370 '_ddl_protoschema_deploy_consume_filenames', $sqlt_type
ef44838b 371 );
372 }
373 return $self->_run_sql_and_perl($self->_ddl_schema_consume_filenames(
374 $sqlt_type,
375 $version,
25c3bec3 376 ), $sql, [$version]);
2e68a8e1 377}
378
ff40cb1f 379sub initialize {
9faec51a 380 my $self = shift;
381 my $args = shift;
382 my $version = $args->{version} || $self->schema_version;
ff40cb1f 383 log_info { "initializing version $version" };
9faec51a 384 my $storage_type = $args->{storage_type} || $self->storage->sqlt_type;
fc4b7602 385
ff40cb1f 386 my @files = @{$self->_ddl_initialize_consume_filenames(
9faec51a 387 $storage_type,
fc4b7602 388 $version,
389 )};
390
391 for my $filename (@files) {
392 # We ignore sql for now (till I figure out what to do with it)
393 if ( $filename =~ /^(.+)\.pl$/ ) {
fc4b7602 394 my $filedata = do { local( @ARGV, $/ ) = $filename; <> };
fc4b7602 395
9faec51a 396 no warnings 'redefine';
5b5defbc 397 my $fn = eval "$filedata";
fc4b7602 398 use warnings;
5b5defbc 399
9faec51a 400 if ($@) {
6e9a733d 401 croak "$filename failed to compile: $@";
9faec51a 402 } elsif (ref $fn eq 'CODE') {
fc4b7602 403 $fn->()
404 } else {
6e9a733d 405 croak "$filename should define an anonymous sub but it didn't!";
fc4b7602 406 }
407 } else {
ff40cb1f 408 croak "A file ($filename) got to initialize_scripts that wasn't sql or perl!";
fc4b7602 409 }
410 }
411}
412
6e2665d3 413sub _sqldiff_from_yaml {
414 my ($self, $from_version, $to_version, $db, $direction) = @_;
91adde75 415 my $dir = $self->script_directory;
28563f97 416 my $sqltargs = {
417 add_drop_table => 1,
418 ignore_constraint_names => 1,
419 ignore_index_names => 1,
420 %{$self->sql_translator_args}
421 };
d54b8d69 422
28563f97 423 my $source_schema;
424 {
425 my $prefilename = $self->_ddl_protoschema_produce_filename($from_version, $dir);
426
427 # should probably be a croak
428 carp("No previous schema file found ($prefilename)")
429 unless -e $prefilename;
430
431 my $t = SQL::Translator->new({
432 %{$sqltargs},
433 debug => 0,
434 trace => 0,
435 parser => 'SQL::Translator::Parser::YAML',
436 });
437
438 my $out = $t->translate( $prefilename )
439 or croak($t->error);
440
441 $source_schema = $t->schema;
442
443 $source_schema->name( $prefilename )
444 unless $source_schema->name;
445 }
446
447 my $dest_schema;
448 {
449 my $filename = $self->_ddl_protoschema_produce_filename($to_version, $dir);
450
451 # should probably be a croak
452 carp("No next schema file found ($filename)")
453 unless -e $filename;
454
455 my $t = SQL::Translator->new({
456 %{$sqltargs},
457 debug => 0,
458 trace => 0,
459 parser => 'SQL::Translator::Parser::YAML',
460 });
461
462 my $out = $t->translate( $filename )
463 or croak($t->error);
464
465 $dest_schema = $t->schema;
466
467 $dest_schema->name( $filename )
468 unless $dest_schema->name;
469 }
f9c6ab50 470
471 my $transform_files_method = "_ddl_protoschema_${direction}_consume_filenames";
472 my $transforms = $self->_coderefs_per_files(
473 $self->$transform_files_method([$from_version, $to_version])
474 );
475 $_->($source_schema, $dest_schema) for @$transforms;
476
28563f97 477 return [SQL::Translator::Diff::schema_diff(
478 $source_schema, $db,
479 $dest_schema, $db,
480 $sqltargs
481 )];
482}
483
6e2665d3 484sub _sql_from_yaml {
485 my ($self, $sqltargs, $from_file, $db) = @_;
28563f97 486 my $schema = $self->schema;
487 my $version = $self->schema_version;
93460690 488
4ea5caf5 489 my @sql;
2e68a8e1 490
4ea5caf5 491 my $actual_file = $self->$from_file($version);
492 for my $yaml_filename (@{
493 DlogS_trace { "generating SQL from Serialized SQL Files: $_" }
494 (ref $actual_file?$actual_file:[$actual_file])
495 }) {
496 my $sqlt = SQL::Translator->new({
497 add_drop_table => 0,
498 parser => 'SQL::Translator::Parser::YAML',
499 %{$sqltargs},
500 producer => $db,
501 });
502
503 push @sql, $sqlt->translate($yaml_filename);
504 if(!@sql) {
505 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
506 return undef;
507 }
28563f97 508 }
509 return \@sql;
510}
511
512sub _prepare_install {
513 my $self = shift;
514 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
515 my $from_file = shift;
516 my $to_file = shift;
517 my $dir = $self->script_directory;
518 my $databases = $self->databases;
519 my $version = $self->schema_version;
520
2e68a8e1 521 foreach my $db (@$databases) {
28563f97 522 my $sql = $self->_sql_from_yaml($sqltargs, $from_file, $db ) or next;
2e68a8e1 523
c8a2f7bd 524 my $filename = $self->$to_file($db, $version, $dir);
9600776d 525 if (-e $filename ) {
92624ee5 526 if ($self->force_overwrite) {
527 carp "Overwriting existing DDL file - $filename";
528 unlink $filename;
529 } else {
530 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
531 }
2e68a8e1 532 }
387b11d2 533 open my $file, q(>), $filename;
28563f97 534 print {$file} join ";\n", @$sql;
2e68a8e1 535 close $file;
536 }
537}
538
c8a2f7bd 539sub _resultsource_install_filename {
540 my ($self, $source_name) = @_;
541 return sub {
542 my ($self, $type, $version) = @_;
58eb99c3 543 my $dirname = catfile( $self->script_directory, $type, 'deploy', $version );
c8a2f7bd 544 mkpath($dirname) unless -d $dirname;
545
09bc35e3 546 return catfile( $dirname, "001-auto-$source_name.sql" );
c8a2f7bd 547 }
548}
549
e62add58 550sub _resultsource_protoschema_filename {
551 my ($self, $source_name) = @_;
552 return sub {
553 my ($self, $version) = @_;
d3d6512c 554 my $dirname = catfile( $self->script_directory, '_source', 'deploy', $version );
e62add58 555 mkpath($dirname) unless -d $dirname;
556
557 return catfile( $dirname, "001-auto-$source_name.yml" );
558 }
559}
560
c8a2f7bd 561sub install_resultsource {
be140a5f 562 my ($self, $args) = @_;
ba99ba44 563 my $source = $args->{result_source}
564 or die 'result_source must be passed to install_resultsource';
565 my $version = $args->{version}
566 or die 'version must be passed to install_resultsource';
f4075791 567 log_info { 'installing_resultsource ' . $source->source_name . ", version $version" };
c8a2f7bd 568 my $rs_install_file =
569 $self->_resultsource_install_filename($source->source_name);
570
571 my $files = [
572 $self->$rs_install_file(
573 $self->storage->sqlt_type,
574 $version,
575 )
576 ];
25c3bec3 577 $self->_run_sql_and_perl($files, '', [$version]);
c8a2f7bd 578}
579
580sub prepare_resultsource_install {
581 my $self = shift;
be140a5f 582 my $source = (shift @_)->{result_source};
f4075791 583 log_info { 'preparing install for resultsource ' . $source->source_name };
c8a2f7bd 584
e62add58 585 my $install_filename = $self->_resultsource_install_filename($source->source_name);
586 my $proto_filename = $self->_resultsource_protoschema_filename($source->source_name);
6cae2f56 587 $self->prepare_protoschema({
c8a2f7bd 588 parser_args => { sources => [$source->source_name], }
e62add58 589 }, $proto_filename);
590 $self->_prepare_install({}, $proto_filename, $install_filename);
c8a2f7bd 591}
592
91557c90 593sub prepare_deploy {
f4075791 594 log_info { 'preparing deploy' };
c8a2f7bd 595 my $self = shift;
6776a6d4 596 $self->prepare_protoschema({
597 # Exclude __VERSION so that it gets installed separately
598 parser_args => { sources => [grep { $_ ne '__VERSION' } $self->schema->sources], }
599 }, '_ddl_protoschema_produce_filename');
e62add58 600 $self->_prepare_install({}, '_ddl_protoschema_produce_filename', '_ddl_schema_produce_filename');
c8a2f7bd 601}
602
a41a04e5 603sub prepare_upgrade {
be140a5f 604 my ($self, $args) = @_;
0df68524 605 log_info {
f4075791 606 "preparing upgrade from $args->{from_version} to $args->{to_version}"
0df68524 607 };
be140a5f 608 $self->_prepare_changegrade(
58eb99c3 609 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'upgrade'
be140a5f 610 );
76d311e7 611}
612
613sub prepare_downgrade {
be140a5f 614 my ($self, $args) = @_;
0df68524 615 log_info {
f4075791 616 "preparing downgrade from $args->{from_version} to $args->{to_version}"
0df68524 617 };
be140a5f 618 $self->_prepare_changegrade(
58eb99c3 619 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'downgrade'
be140a5f 620 );
76d311e7 621}
622
6e2665d3 623sub _coderefs_per_files {
624 my ($self, $files) = @_;
f9c6ab50 625 no warnings 'redefine';
626 [map eval do { local( @ARGV, $/ ) = $_; <> }, @$files]
627}
628
6e2665d3 629sub _prepare_changegrade {
630 my ($self, $from_version, $to_version, $version_set, $direction) = @_;
2e68a8e1 631 my $schema = $self->schema;
632 my $databases = $self->databases;
91adde75 633 my $dir = $self->script_directory;
2e68a8e1 634
73caa630 635 my $schema_version = $self->schema_version;
e62add58 636 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
e62add58 637 foreach my $db (@$databases) {
638 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
639 if(-e $diff_file) {
92624ee5 640 if ($self->force_overwrite) {
641 carp("Overwriting existing $direction-diff file - $diff_file");
642 unlink $diff_file;
643 } else {
644 die "Cannot overwrite '$diff_file', either enable force_overwrite or delete it"
645 }
2e68a8e1 646 }
647
387b11d2 648 open my $file, q(>), $diff_file;
f9c6ab50 649 print {$file} join ";\n", @{$self->_sqldiff_from_yaml($from_version, $to_version, $db, $direction)};
2e68a8e1 650 close $file;
651 }
652}
653
6e2665d3 654sub _read_sql_file {
655 my ($self, $file) = @_;
334bced5 656 return unless $file;
657
115c68ce 658 local $/ = undef; #sluuuuuurp
659
aabd4237 660 open my $fh, '<', $file;
115c68ce 661 return [ _split_sql_chunk( <$fh> ) ];
1f0d0633 662}
663
7d2a6974 664sub downgrade_single_step {
76d311e7 665 my $self = shift;
be140a5f 666 my $version_set = (shift @_)->{version_set};
f4075791 667 Dlog_info { "downgrade_single_step'ing $_" } $version_set;
41219a5d 668
ef44838b 669 my $sqlt_type = $self->storage->sqlt_type;
670 my $sql_to_run;
671 if ($self->ignore_ddl) {
672 $sql_to_run = $self->_sqldiff_from_yaml(
58eb99c3 673 $version_set->[0], $version_set->[1], $sqlt_type, 'downgrade',
ef44838b 674 );
675 }
58eb99c3 676 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_downgrade_consume_filenames(
ef44838b 677 $sqlt_type,
627581cd 678 $version_set,
25c3bec3 679 ), $sql_to_run, $version_set);
3249629f 680
41219a5d 681 return ['', $sql];
76d311e7 682}
683
7d2a6974 684sub upgrade_single_step {
7521a845 685 my $self = shift;
be140a5f 686 my $version_set = (shift @_)->{version_set};
f4075791 687 Dlog_info { "upgrade_single_step'ing $_" } $version_set;
41219a5d 688
ef44838b 689 my $sqlt_type = $self->storage->sqlt_type;
690 my $sql_to_run;
691 if ($self->ignore_ddl) {
692 $sql_to_run = $self->_sqldiff_from_yaml(
58eb99c3 693 $version_set->[0], $version_set->[1], $sqlt_type, 'upgrade',
ef44838b 694 );
695 }
58eb99c3 696 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_upgrade_consume_filenames(
ef44838b 697 $sqlt_type,
627581cd 698 $version_set,
25c3bec3 699 ), $sql_to_run, $version_set);
41219a5d 700 return ['', $sql];
334bced5 701}
702
6cae2f56 703sub prepare_protoschema {
7e08eddd 704 my $self = shift;
e62add58 705 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
706 my $to_file = shift;
7e08eddd 707 my $filename
e62add58 708 = $self->$to_file($self->schema_version);
7e08eddd 709
e62add58 710 # we do this because the code that uses this sets parser args,
711 # so we just need to merge in the package
712 $sqltargs->{parser_args}{package} = $self->schema;
7e08eddd 713 my $sqlt = SQL::Translator->new({
714 parser => 'SQL::Translator::Parser::DBIx::Class',
715 producer => 'SQL::Translator::Producer::YAML',
e62add58 716 %{ $sqltargs },
7e08eddd 717 });
718
719 my $yml = $sqlt->translate;
720
721 croak("Failed to translate to YAML: " . $sqlt->error)
722 unless $yml;
723
724 if (-e $filename ) {
92624ee5 725 if ($self->force_overwrite) {
726 carp "Overwriting existing DDL-YML file - $filename";
727 unlink $filename;
728 } else {
729 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
730 }
7e08eddd 731 }
732
733 open my $file, q(>), $filename;
734 print {$file} $yml;
735 close $file;
736}
737
aabd4237 738__PACKAGE__->meta->make_immutable;
739
2e68a8e1 7401;
e051bb00 741
e52174e3 742# vim: ts=2 sw=2 expandtab
743
e051bb00 744__END__
745
bcc72297 746=head1 DESCRIPTION
747
e62add58 748This class is the meat of L<DBIx::Class::DeploymentHandler>. It takes care
749of generating serialized schemata as well as sql files to move from one
750version of a schema to the rest. One of the hallmark features of this class
751is that it allows for multiple sql files for deploy and upgrade, allowing
752developers to fine tune deployment. In addition it also allows for perl
753files to be run at any stage of the process.
bcc72297 754
755For basic usage see L<DBIx::Class::DeploymentHandler::HandlesDeploy>. What's
756documented here is extra fun stuff or private methods.
757
758=head1 DIRECTORY LAYOUT
759
39c88a9a 760Arguably this is the best feature of L<DBIx::Class::DeploymentHandler>.
761It's spiritually based upon L<DBIx::Migration::Directories>, but has a
762lot of extensions and modifications, so even if you are familiar with it,
763please read this. I feel like the best way to describe the layout is with
764the following example:
92c34cab 765
766 $sql_migration_dir
58eb99c3 767 |- _source
768 | |- deploy
03882cab 769 | |- 1
770 | | `- 001-auto.yml
771 | |- 2
772 | | `- 001-auto.yml
773 | `- 3
774 | `- 001-auto.yml
92c34cab 775 |- SQLite
58eb99c3 776 | |- downgrade
4f85efc6 777 | | `- 2-1
e62add58 778 | | `- 001-auto.sql
58eb99c3 779 | |- deploy
92c34cab 780 | | `- 1
e62add58 781 | | `- 001-auto.sql
58eb99c3 782 | `- upgrade
92c34cab 783 | |- 1-2
e62add58 784 | | `- 001-auto.sql
92c34cab 785 | `- 2-3
e62add58 786 | `- 001-auto.sql
92c34cab 787 |- _common
58eb99c3 788 | |- downgrade
4f85efc6 789 | | `- 2-1
92c34cab 790 | | `- 002-remove-customers.pl
58eb99c3 791 | `- upgrade
92c34cab 792 | `- 1-2
25c3bec3 793 | | `- 002-generate-customers.pl
794 | `- _any
795 | `- 999-bump-action.pl
92c34cab 796 `- MySQL
58eb99c3 797 |- downgrade
4f85efc6 798 | `- 2-1
e62add58 799 | `- 001-auto.sql
ff40cb1f 800 |- initialize
80ff6f6d 801 | `- 1
802 | |- 001-create_database.pl
803 | `- 002-create_users_and_permissions.pl
58eb99c3 804 |- deploy
92c34cab 805 | `- 1
e62add58 806 | `- 001-auto.sql
58eb99c3 807 `- upgrade
92c34cab 808 `- 1-2
e62add58 809 `- 001-auto.sql
92c34cab 810
811So basically, the code
812
813 $dm->deploy(1)
814
815on an C<SQLite> database that would simply run
58eb99c3 816C<$sql_migration_dir/SQLite/deploy/1/001-auto.sql>. Next,
92c34cab 817
818 $dm->upgrade_single_step([1,2])
819
58eb99c3 820would run C<$sql_migration_dir/SQLite/upgrade/1-2/001-auto.sql> followed by
25c3bec3 821C<$sql_migration_dir/_common/upgrade/1-2/002-generate-customers.pl>, and
822finally punctuated by
823C<$sql_migration_dir/_common/upgrade/_any/999-bump-action.pl>.
92c34cab 824
0824f31f 825C<.pl> files don't have to be in the C<_common> directory, but most of the time
39c88a9a 826they should be, because perl scripts are generally database independent.
92c34cab 827
ff40cb1f 828Note that unlike most steps in the process, C<initialize> will not run SQL, as
829there may not even be an database at initialize time. It will run perl scripts
80ff6f6d 830just like the other steps in the process, but nothing is passed to them.
831Until people have used this more it will remain freeform, but a recommended use
ff40cb1f 832of initialize is to have it prompt for username and password, and then call the
80ff6f6d 833appropriate C<< CREATE DATABASE >> commands etc.
834
03882cab 835=head2 Directory Specification
836
837The following subdirectories are recognized by this DeployMethod:
838
839=over 2
840
58eb99c3 841=item C<_source> This directory can contain the following directories:
03882cab 842
843=over 2
844
39c88a9a 845=item C<deploy> This directory merely contains directories named after schema
846versions, which in turn contain C<yaml> files that are serialized versions
847of the schema at that version. These files are not for editing by hand.
848
849=back
850
851=item C<_preprocess_schema> This directory can contain the following
852directories:
853
854=over 2
855
58eb99c3 856=item C<downgrade> This directory merely contains directories named after
03882cab 857migrations, which are of the form C<$from_version-$to_version>. Inside of
858these directories you may put Perl scripts which are to return a subref
859that takes the arguments C<< $from_schema, $to_schema >>, which are
860L<SQL::Translator::Schema> objects.
861
58eb99c3 862=item C<upgrade> This directory merely contains directories named after
03882cab 863migrations, which are of the form C<$from_version-$to_version>. Inside of
864these directories you may put Perl scripts which are to return a subref
865that takes the arguments C<< $from_schema, $to_schema >>, which are
866L<SQL::Translator::Schema> objects.
867
03882cab 868=back
869
5b766a24 870=item C<$storage_type> This is a set of scripts that gets run depending on what
871your storage type is. If you are not sure what your storage type is, take a
872look at the producers listed for L<SQL::Translator>. Also note, C<_common>
873is a special case. C<_common> will get merged into whatever other files you
25c3bec3 874already have. This directory can contain the following directories itself:
71d00500 875
876=over 2
877
ff40cb1f 878=item C<initialize> Gets run before the C<deploy> is C<deploy>ed. Has the
58eb99c3 879same structure as the C<deploy> subdirectory as well; that is, it has a
880directory for each schema version. Unlike C<deploy>, C<upgrade>, and C<downgrade>
71d00500 881though, it can only run C<.pl> files, and the coderef in the perl files get
882no arguments passed to them.
883
58eb99c3 884=item C<deploy> Gets run when the schema is C<deploy>ed. Structure is a
71d00500 885directory per schema version, and then files are merged with C<_common> and run
886in filename order. C<.sql> files are merely run, as expected. C<.pl> files are
887run according to L</PERL SCRIPTS>.
888
58eb99c3 889=item C<upgrade> Gets run when the schema is C<upgrade>d. Structure is a directory
71d00500 890per upgrade step, (for example, C<1-2> for upgrading from version 1 to version
8912,) and then files are merged with C<_common> and run in filename order.
892C<.sql> files are merely run, as expected. C<.pl> files are run according
893to L</PERL SCRIPTS>.
894
58eb99c3 895=item C<downgrade> Gets run when the schema is C<downgrade>d. Structure is a directory
71d00500 896per downgrade step, (for example, C<2-1> for downgrading from version 2 to version
8971,) and then files are merged with C<_common> and run in filename order.
898C<.sql> files are merely run, as expected. C<.pl> files are run according
899to L</PERL SCRIPTS>.
900
901
902=back
903
03882cab 904=back
905
25c3bec3 906Note that there can be an C<_any> in the place of any of the versions (like
907C<1-2> or C<1>), which means those scripts will be run B<every> time. So if
908you have an C<_any> in C<_common/upgrade>, that script will get run for every
909upgrade.
910
92c34cab 911=head1 PERL SCRIPTS
912
7d0b0f2b 913A perl script for this tool is very simple. It merely needs to contain an
25c3bec3 914anonymous sub that takes a L<DBIx::Class::Schema> and the version set as it's
915arguments.
916
92c34cab 917A very basic perl script might look like:
918
919 #!perl
920
921 use strict;
922 use warnings;
923
57a30fa8 924 use DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers
925 'schema_from_schema_loader';
926
927 schema_from_schema_loader({ naming => 'v4' }, sub {
92c34cab 928 my $schema = shift;
929
25c3bec3 930 # [1] for deploy, [1,2] for upgrade or downgrade, probably used with _any
931 my $versions = shift;
932
92c34cab 933 $schema->resultset('Users')->create({
934 name => 'root',
935 password => 'root',
936 })
57a30fa8 937 })
938
939Note that the above uses
940L<DBIx::Class::DeploymentHanlder::DeployMethod::SQL::Translator::ScriptHelpers/schema_from_schema_loader>.
941Using a raw coderef is strongly discouraged as it is likely to break as you
942modify your schema.
bcc72297 943
39c88a9a 944=attr ignore_ddl
945
946This attribute will, when set to true (default is false), cause the DM to use
947L<SQL::Translator> to use the C<_source>'s serialized SQL::Translator::Schema
948instead of any pregenerated SQL. If you have a development server this is
949probably the best plan of action as you will not be putting as many generated
950files in your version control. Goes well with with C<databases> of C<[]>.
951
92624ee5 952=attr force_overwrite
953
954When this attribute is true generated files will be overwritten when the
955methods which create such files are run again. The default is false, in which
956case the program will die with a message saying which file needs to be deleted.
957
eb28403b 958=attr schema
a65184c8 959
bcc72297 960The L<DBIx::Class::Schema> (B<required>) that is used to talk to the database
961and generate the DDL.
962
eb28403b 963=attr storage
a65184c8 964
bcc72297 965The L<DBIx::Class::Storage> that is I<actually> used to talk to the database
966and generate the DDL. This is automatically created with L</_build_storage>.
967
02a7b8ac 968=attr sql_translator_args
cfc9edf9 969
02a7b8ac 970The arguments that get passed to L<SQL::Translator> when it's used.
a65184c8 971
91adde75 972=attr script_directory
cfc9edf9 973
91adde75 974The directory (default C<'sql'>) that scripts are stored in
cfc9edf9 975
eb28403b 976=attr databases
cfc9edf9 977
978The types of databases (default C<< [qw( MySQL SQLite PostgreSQL )] >>) to
979generate files for
980
eb28403b 981=attr txn_wrap
982
bcc72297 983Set to true (which is the default) to wrap all upgrades and deploys in a single
984transaction.
985
73caa630 986=attr schema_version
987
988The version the schema on your harddrive is at. Defaults to
989C<< $self->schema->schema_version >>.
ec167a97 990
991=head1 SEE ALSO
992
993This class is an implementation of
994L<DBIx::Class::DeploymentHandler::HandlesDeploy>. Pretty much all the
995documentation is there.