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