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