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