much better coderef sandboxing
[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
a89cd737 293# stolen from Plack::Util::_load_sandbox
294sub _load_sandbox {
295 my $_file = shift;
296
297 my $_package = $_file;
298 $_package =~ s/([^A-Za-z0-9_])/sprintf("_%2x", unpack("C", $1))/eg;
299
300 local $0 = $_file; # so FindBin etc. works
301
302 return eval sprintf <<'END_EVAL', $_package;
303package DBICDH::Sandbox::%s;
304{
305 my $app = do $_file;
306 if ( !$app && ( my $error = $@ || $! )) { die $error; }
307 $app;
308}
309END_EVAL
310}
311
6e2665d3 312sub _run_perl {
313 my ($self, $filename, $versions) = @_;
f4075791 314 log_debug { "Running Perl from $filename" };
5d7b27cf 315 my $filedata = do { local( @ARGV, $/ ) = $filename; <> };
c8a2f7bd 316
a89cd737 317 my $fn = _load_sandbox($filename);
318
f4075791 319 Dlog_trace { "Running Perl $_" } $fn;
5d7b27cf 320
a89cd737 321 if (ref $fn eq 'CODE') {
25c3bec3 322 $fn->($self->schema, $versions)
5d7b27cf 323 } else {
6e9a733d 324 croak "$filename should define an anonymouse sub that takes a schema but it didn't!";
5d7b27cf 325 }
326}
5d7b27cf 327
6e2665d3 328sub _run_sql_and_perl {
329 my ($self, $filenames, $sql_to_run, $versions) = @_;
5d7b27cf 330 my @files = @{$filenames};
331 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
332
ef44838b 333 $self->_run_sql_array($sql_to_run) if $self->ignore_ddl;
334
335 my $sql = ($sql_to_run)?join ";\n", @$sql_to_run:'';
336 FILENAME:
41219a5d 337 for my $filename (@files) {
ef44838b 338 if ($self->ignore_ddl && $filename =~ /^[^_]*-auto.*\.sql$/) {
339 next FILENAME
340 } elsif ($filename =~ /\.sql$/) {
5d7b27cf 341 $sql .= $self->_run_sql($filename)
398b1385 342 } elsif ( $filename =~ /\.pl$/ ) {
25c3bec3 343 $self->_run_perl($filename, $versions)
41219a5d 344 } else {
fc4b7602 345 croak "A file ($filename) got to deploy that wasn't sql or perl!";
2e68a8e1 346 }
2e68a8e1 347 }
a7d53deb 348
349 $guard->commit if $self->txn_wrap;
41219a5d 350
351 return $sql;
352}
353
354sub deploy {
355 my $self = shift;
be140a5f 356 my $version = (shift @_ || {})->{version} || $self->schema_version;
f4075791 357 log_info { "deploying version $version" };
ef44838b 358 my $sqlt_type = $self->storage->sqlt_type;
359 my $sql;
360 if ($self->ignore_ddl) {
361 $sql = $self->_sql_from_yaml({},
4ea5caf5 362 '_ddl_protoschema_deploy_consume_filenames', $sqlt_type
ef44838b 363 );
364 }
365 return $self->_run_sql_and_perl($self->_ddl_schema_consume_filenames(
366 $sqlt_type,
367 $version,
25c3bec3 368 ), $sql, [$version]);
2e68a8e1 369}
370
ff40cb1f 371sub initialize {
9faec51a 372 my $self = shift;
373 my $args = shift;
374 my $version = $args->{version} || $self->schema_version;
ff40cb1f 375 log_info { "initializing version $version" };
9faec51a 376 my $storage_type = $args->{storage_type} || $self->storage->sqlt_type;
fc4b7602 377
ff40cb1f 378 my @files = @{$self->_ddl_initialize_consume_filenames(
9faec51a 379 $storage_type,
fc4b7602 380 $version,
381 )};
382
383 for my $filename (@files) {
384 # We ignore sql for now (till I figure out what to do with it)
385 if ( $filename =~ /^(.+)\.pl$/ ) {
fc4b7602 386 my $filedata = do { local( @ARGV, $/ ) = $filename; <> };
fc4b7602 387
9faec51a 388 no warnings 'redefine';
5b5defbc 389 my $fn = eval "$filedata";
fc4b7602 390 use warnings;
5b5defbc 391
9faec51a 392 if ($@) {
6e9a733d 393 croak "$filename failed to compile: $@";
9faec51a 394 } elsif (ref $fn eq 'CODE') {
fc4b7602 395 $fn->()
396 } else {
6e9a733d 397 croak "$filename should define an anonymous sub but it didn't!";
fc4b7602 398 }
399 } else {
ff40cb1f 400 croak "A file ($filename) got to initialize_scripts that wasn't sql or perl!";
fc4b7602 401 }
402 }
403}
404
6e2665d3 405sub _sqldiff_from_yaml {
406 my ($self, $from_version, $to_version, $db, $direction) = @_;
91adde75 407 my $dir = $self->script_directory;
28563f97 408 my $sqltargs = {
409 add_drop_table => 1,
410 ignore_constraint_names => 1,
411 ignore_index_names => 1,
412 %{$self->sql_translator_args}
413 };
d54b8d69 414
28563f97 415 my $source_schema;
416 {
417 my $prefilename = $self->_ddl_protoschema_produce_filename($from_version, $dir);
418
419 # should probably be a croak
420 carp("No previous schema file found ($prefilename)")
421 unless -e $prefilename;
422
423 my $t = SQL::Translator->new({
424 %{$sqltargs},
425 debug => 0,
426 trace => 0,
427 parser => 'SQL::Translator::Parser::YAML',
428 });
429
430 my $out = $t->translate( $prefilename )
431 or croak($t->error);
432
433 $source_schema = $t->schema;
434
435 $source_schema->name( $prefilename )
436 unless $source_schema->name;
437 }
438
439 my $dest_schema;
440 {
441 my $filename = $self->_ddl_protoschema_produce_filename($to_version, $dir);
442
443 # should probably be a croak
444 carp("No next schema file found ($filename)")
445 unless -e $filename;
446
447 my $t = SQL::Translator->new({
448 %{$sqltargs},
449 debug => 0,
450 trace => 0,
451 parser => 'SQL::Translator::Parser::YAML',
452 });
453
454 my $out = $t->translate( $filename )
455 or croak($t->error);
456
457 $dest_schema = $t->schema;
458
459 $dest_schema->name( $filename )
460 unless $dest_schema->name;
461 }
f9c6ab50 462
463 my $transform_files_method = "_ddl_protoschema_${direction}_consume_filenames";
464 my $transforms = $self->_coderefs_per_files(
465 $self->$transform_files_method([$from_version, $to_version])
466 );
467 $_->($source_schema, $dest_schema) for @$transforms;
468
28563f97 469 return [SQL::Translator::Diff::schema_diff(
470 $source_schema, $db,
471 $dest_schema, $db,
472 $sqltargs
473 )];
474}
475
6e2665d3 476sub _sql_from_yaml {
477 my ($self, $sqltargs, $from_file, $db) = @_;
28563f97 478 my $schema = $self->schema;
479 my $version = $self->schema_version;
93460690 480
4ea5caf5 481 my @sql;
2e68a8e1 482
4ea5caf5 483 my $actual_file = $self->$from_file($version);
484 for my $yaml_filename (@{
485 DlogS_trace { "generating SQL from Serialized SQL Files: $_" }
486 (ref $actual_file?$actual_file:[$actual_file])
487 }) {
488 my $sqlt = SQL::Translator->new({
489 add_drop_table => 0,
490 parser => 'SQL::Translator::Parser::YAML',
491 %{$sqltargs},
492 producer => $db,
493 });
494
495 push @sql, $sqlt->translate($yaml_filename);
496 if(!@sql) {
497 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
498 return undef;
499 }
28563f97 500 }
501 return \@sql;
502}
503
504sub _prepare_install {
505 my $self = shift;
506 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
507 my $from_file = shift;
508 my $to_file = shift;
509 my $dir = $self->script_directory;
510 my $databases = $self->databases;
511 my $version = $self->schema_version;
512
2e68a8e1 513 foreach my $db (@$databases) {
28563f97 514 my $sql = $self->_sql_from_yaml($sqltargs, $from_file, $db ) or next;
2e68a8e1 515
c8a2f7bd 516 my $filename = $self->$to_file($db, $version, $dir);
9600776d 517 if (-e $filename ) {
92624ee5 518 if ($self->force_overwrite) {
519 carp "Overwriting existing DDL file - $filename";
520 unlink $filename;
521 } else {
522 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
523 }
2e68a8e1 524 }
387b11d2 525 open my $file, q(>), $filename;
28563f97 526 print {$file} join ";\n", @$sql;
2e68a8e1 527 close $file;
528 }
529}
530
c8a2f7bd 531sub _resultsource_install_filename {
532 my ($self, $source_name) = @_;
533 return sub {
534 my ($self, $type, $version) = @_;
58eb99c3 535 my $dirname = catfile( $self->script_directory, $type, 'deploy', $version );
c8a2f7bd 536 mkpath($dirname) unless -d $dirname;
537
09bc35e3 538 return catfile( $dirname, "001-auto-$source_name.sql" );
c8a2f7bd 539 }
540}
541
e62add58 542sub _resultsource_protoschema_filename {
543 my ($self, $source_name) = @_;
544 return sub {
545 my ($self, $version) = @_;
d3d6512c 546 my $dirname = catfile( $self->script_directory, '_source', 'deploy', $version );
e62add58 547 mkpath($dirname) unless -d $dirname;
548
549 return catfile( $dirname, "001-auto-$source_name.yml" );
550 }
551}
552
c8a2f7bd 553sub install_resultsource {
be140a5f 554 my ($self, $args) = @_;
ba99ba44 555 my $source = $args->{result_source}
556 or die 'result_source must be passed to install_resultsource';
557 my $version = $args->{version}
558 or die 'version must be passed to install_resultsource';
f4075791 559 log_info { 'installing_resultsource ' . $source->source_name . ", version $version" };
c8a2f7bd 560 my $rs_install_file =
561 $self->_resultsource_install_filename($source->source_name);
562
563 my $files = [
564 $self->$rs_install_file(
565 $self->storage->sqlt_type,
566 $version,
567 )
568 ];
25c3bec3 569 $self->_run_sql_and_perl($files, '', [$version]);
c8a2f7bd 570}
571
572sub prepare_resultsource_install {
573 my $self = shift;
be140a5f 574 my $source = (shift @_)->{result_source};
f4075791 575 log_info { 'preparing install for resultsource ' . $source->source_name };
c8a2f7bd 576
e62add58 577 my $install_filename = $self->_resultsource_install_filename($source->source_name);
578 my $proto_filename = $self->_resultsource_protoschema_filename($source->source_name);
6cae2f56 579 $self->prepare_protoschema({
c8a2f7bd 580 parser_args => { sources => [$source->source_name], }
e62add58 581 }, $proto_filename);
582 $self->_prepare_install({}, $proto_filename, $install_filename);
c8a2f7bd 583}
584
91557c90 585sub prepare_deploy {
f4075791 586 log_info { 'preparing deploy' };
c8a2f7bd 587 my $self = shift;
6776a6d4 588 $self->prepare_protoschema({
589 # Exclude __VERSION so that it gets installed separately
590 parser_args => { sources => [grep { $_ ne '__VERSION' } $self->schema->sources], }
591 }, '_ddl_protoschema_produce_filename');
e62add58 592 $self->_prepare_install({}, '_ddl_protoschema_produce_filename', '_ddl_schema_produce_filename');
c8a2f7bd 593}
594
a41a04e5 595sub prepare_upgrade {
be140a5f 596 my ($self, $args) = @_;
0df68524 597 log_info {
f4075791 598 "preparing upgrade from $args->{from_version} to $args->{to_version}"
0df68524 599 };
be140a5f 600 $self->_prepare_changegrade(
58eb99c3 601 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'upgrade'
be140a5f 602 );
76d311e7 603}
604
605sub prepare_downgrade {
be140a5f 606 my ($self, $args) = @_;
0df68524 607 log_info {
f4075791 608 "preparing downgrade from $args->{from_version} to $args->{to_version}"
0df68524 609 };
be140a5f 610 $self->_prepare_changegrade(
58eb99c3 611 $args->{from_version}, $args->{to_version}, $args->{version_set}, 'downgrade'
be140a5f 612 );
76d311e7 613}
614
6e2665d3 615sub _coderefs_per_files {
616 my ($self, $files) = @_;
f9c6ab50 617 no warnings 'redefine';
618 [map eval do { local( @ARGV, $/ ) = $_; <> }, @$files]
619}
620
6e2665d3 621sub _prepare_changegrade {
622 my ($self, $from_version, $to_version, $version_set, $direction) = @_;
2e68a8e1 623 my $schema = $self->schema;
624 my $databases = $self->databases;
91adde75 625 my $dir = $self->script_directory;
2e68a8e1 626
73caa630 627 my $schema_version = $self->schema_version;
e62add58 628 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
e62add58 629 foreach my $db (@$databases) {
630 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
631 if(-e $diff_file) {
92624ee5 632 if ($self->force_overwrite) {
633 carp("Overwriting existing $direction-diff file - $diff_file");
634 unlink $diff_file;
635 } else {
636 die "Cannot overwrite '$diff_file', either enable force_overwrite or delete it"
637 }
2e68a8e1 638 }
639
387b11d2 640 open my $file, q(>), $diff_file;
f9c6ab50 641 print {$file} join ";\n", @{$self->_sqldiff_from_yaml($from_version, $to_version, $db, $direction)};
2e68a8e1 642 close $file;
643 }
644}
645
6e2665d3 646sub _read_sql_file {
647 my ($self, $file) = @_;
334bced5 648 return unless $file;
649
115c68ce 650 local $/ = undef; #sluuuuuurp
651
aabd4237 652 open my $fh, '<', $file;
115c68ce 653 return [ _split_sql_chunk( <$fh> ) ];
1f0d0633 654}
655
7d2a6974 656sub downgrade_single_step {
76d311e7 657 my $self = shift;
be140a5f 658 my $version_set = (shift @_)->{version_set};
f4075791 659 Dlog_info { "downgrade_single_step'ing $_" } $version_set;
41219a5d 660
ef44838b 661 my $sqlt_type = $self->storage->sqlt_type;
662 my $sql_to_run;
663 if ($self->ignore_ddl) {
664 $sql_to_run = $self->_sqldiff_from_yaml(
58eb99c3 665 $version_set->[0], $version_set->[1], $sqlt_type, 'downgrade',
ef44838b 666 );
667 }
58eb99c3 668 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_downgrade_consume_filenames(
ef44838b 669 $sqlt_type,
627581cd 670 $version_set,
25c3bec3 671 ), $sql_to_run, $version_set);
3249629f 672
41219a5d 673 return ['', $sql];
76d311e7 674}
675
7d2a6974 676sub upgrade_single_step {
7521a845 677 my $self = shift;
be140a5f 678 my $version_set = (shift @_)->{version_set};
f4075791 679 Dlog_info { "upgrade_single_step'ing $_" } $version_set;
41219a5d 680
ef44838b 681 my $sqlt_type = $self->storage->sqlt_type;
682 my $sql_to_run;
683 if ($self->ignore_ddl) {
684 $sql_to_run = $self->_sqldiff_from_yaml(
58eb99c3 685 $version_set->[0], $version_set->[1], $sqlt_type, 'upgrade',
ef44838b 686 );
687 }
58eb99c3 688 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_upgrade_consume_filenames(
ef44838b 689 $sqlt_type,
627581cd 690 $version_set,
25c3bec3 691 ), $sql_to_run, $version_set);
41219a5d 692 return ['', $sql];
334bced5 693}
694
6cae2f56 695sub prepare_protoschema {
7e08eddd 696 my $self = shift;
e62add58 697 my $sqltargs = { %{$self->sql_translator_args}, %{shift @_} };
698 my $to_file = shift;
7e08eddd 699 my $filename
e62add58 700 = $self->$to_file($self->schema_version);
7e08eddd 701
e62add58 702 # we do this because the code that uses this sets parser args,
703 # so we just need to merge in the package
704 $sqltargs->{parser_args}{package} = $self->schema;
7e08eddd 705 my $sqlt = SQL::Translator->new({
706 parser => 'SQL::Translator::Parser::DBIx::Class',
707 producer => 'SQL::Translator::Producer::YAML',
e62add58 708 %{ $sqltargs },
7e08eddd 709 });
710
711 my $yml = $sqlt->translate;
712
713 croak("Failed to translate to YAML: " . $sqlt->error)
714 unless $yml;
715
716 if (-e $filename ) {
92624ee5 717 if ($self->force_overwrite) {
718 carp "Overwriting existing DDL-YML file - $filename";
719 unlink $filename;
720 } else {
721 die "Cannot overwrite '$filename', either enable force_overwrite or delete it"
722 }
7e08eddd 723 }
724
725 open my $file, q(>), $filename;
726 print {$file} $yml;
727 close $file;
728}
729
aabd4237 730__PACKAGE__->meta->make_immutable;
731
2e68a8e1 7321;
e051bb00 733
e52174e3 734# vim: ts=2 sw=2 expandtab
735
e051bb00 736__END__
737
bcc72297 738=head1 DESCRIPTION
739
e62add58 740This class is the meat of L<DBIx::Class::DeploymentHandler>. It takes care
741of generating serialized schemata as well as sql files to move from one
742version of a schema to the rest. One of the hallmark features of this class
743is that it allows for multiple sql files for deploy and upgrade, allowing
744developers to fine tune deployment. In addition it also allows for perl
745files to be run at any stage of the process.
bcc72297 746
747For basic usage see L<DBIx::Class::DeploymentHandler::HandlesDeploy>. What's
748documented here is extra fun stuff or private methods.
749
750=head1 DIRECTORY LAYOUT
751
39c88a9a 752Arguably this is the best feature of L<DBIx::Class::DeploymentHandler>.
753It's spiritually based upon L<DBIx::Migration::Directories>, but has a
754lot of extensions and modifications, so even if you are familiar with it,
755please read this. I feel like the best way to describe the layout is with
756the following example:
92c34cab 757
758 $sql_migration_dir
58eb99c3 759 |- _source
760 | |- deploy
03882cab 761 | |- 1
762 | | `- 001-auto.yml
763 | |- 2
764 | | `- 001-auto.yml
765 | `- 3
766 | `- 001-auto.yml
92c34cab 767 |- SQLite
58eb99c3 768 | |- downgrade
4f85efc6 769 | | `- 2-1
e62add58 770 | | `- 001-auto.sql
58eb99c3 771 | |- deploy
92c34cab 772 | | `- 1
e62add58 773 | | `- 001-auto.sql
58eb99c3 774 | `- upgrade
92c34cab 775 | |- 1-2
e62add58 776 | | `- 001-auto.sql
92c34cab 777 | `- 2-3
e62add58 778 | `- 001-auto.sql
92c34cab 779 |- _common
58eb99c3 780 | |- downgrade
4f85efc6 781 | | `- 2-1
92c34cab 782 | | `- 002-remove-customers.pl
58eb99c3 783 | `- upgrade
92c34cab 784 | `- 1-2
25c3bec3 785 | | `- 002-generate-customers.pl
786 | `- _any
787 | `- 999-bump-action.pl
92c34cab 788 `- MySQL
58eb99c3 789 |- downgrade
4f85efc6 790 | `- 2-1
e62add58 791 | `- 001-auto.sql
ff40cb1f 792 |- initialize
80ff6f6d 793 | `- 1
794 | |- 001-create_database.pl
795 | `- 002-create_users_and_permissions.pl
58eb99c3 796 |- deploy
92c34cab 797 | `- 1
e62add58 798 | `- 001-auto.sql
58eb99c3 799 `- upgrade
92c34cab 800 `- 1-2
e62add58 801 `- 001-auto.sql
92c34cab 802
803So basically, the code
804
805 $dm->deploy(1)
806
807on an C<SQLite> database that would simply run
58eb99c3 808C<$sql_migration_dir/SQLite/deploy/1/001-auto.sql>. Next,
92c34cab 809
810 $dm->upgrade_single_step([1,2])
811
58eb99c3 812would run C<$sql_migration_dir/SQLite/upgrade/1-2/001-auto.sql> followed by
25c3bec3 813C<$sql_migration_dir/_common/upgrade/1-2/002-generate-customers.pl>, and
814finally punctuated by
815C<$sql_migration_dir/_common/upgrade/_any/999-bump-action.pl>.
92c34cab 816
0824f31f 817C<.pl> files don't have to be in the C<_common> directory, but most of the time
39c88a9a 818they should be, because perl scripts are generally database independent.
92c34cab 819
ff40cb1f 820Note that unlike most steps in the process, C<initialize> will not run SQL, as
821there may not even be an database at initialize time. It will run perl scripts
80ff6f6d 822just like the other steps in the process, but nothing is passed to them.
823Until people have used this more it will remain freeform, but a recommended use
ff40cb1f 824of initialize is to have it prompt for username and password, and then call the
80ff6f6d 825appropriate C<< CREATE DATABASE >> commands etc.
826
03882cab 827=head2 Directory Specification
828
829The following subdirectories are recognized by this DeployMethod:
830
831=over 2
832
58eb99c3 833=item C<_source> This directory can contain the following directories:
03882cab 834
835=over 2
836
39c88a9a 837=item C<deploy> This directory merely contains directories named after schema
838versions, which in turn contain C<yaml> files that are serialized versions
839of the schema at that version. These files are not for editing by hand.
840
841=back
842
843=item C<_preprocess_schema> This directory can contain the following
844directories:
845
846=over 2
847
58eb99c3 848=item C<downgrade> This directory merely contains directories named after
03882cab 849migrations, which are of the form C<$from_version-$to_version>. Inside of
850these directories you may put Perl scripts which are to return a subref
851that takes the arguments C<< $from_schema, $to_schema >>, which are
852L<SQL::Translator::Schema> objects.
853
58eb99c3 854=item C<upgrade> This directory merely contains directories named after
03882cab 855migrations, which are of the form C<$from_version-$to_version>. Inside of
856these directories you may put Perl scripts which are to return a subref
857that takes the arguments C<< $from_schema, $to_schema >>, which are
858L<SQL::Translator::Schema> objects.
859
03882cab 860=back
861
5b766a24 862=item C<$storage_type> This is a set of scripts that gets run depending on what
863your storage type is. If you are not sure what your storage type is, take a
864look at the producers listed for L<SQL::Translator>. Also note, C<_common>
865is a special case. C<_common> will get merged into whatever other files you
25c3bec3 866already have. This directory can contain the following directories itself:
71d00500 867
868=over 2
869
ff40cb1f 870=item C<initialize> Gets run before the C<deploy> is C<deploy>ed. Has the
58eb99c3 871same structure as the C<deploy> subdirectory as well; that is, it has a
872directory for each schema version. Unlike C<deploy>, C<upgrade>, and C<downgrade>
71d00500 873though, it can only run C<.pl> files, and the coderef in the perl files get
874no arguments passed to them.
875
58eb99c3 876=item C<deploy> Gets run when the schema is C<deploy>ed. Structure is a
71d00500 877directory per schema version, and then files are merged with C<_common> and run
878in filename order. C<.sql> files are merely run, as expected. C<.pl> files are
879run according to L</PERL SCRIPTS>.
880
58eb99c3 881=item C<upgrade> Gets run when the schema is C<upgrade>d. Structure is a directory
71d00500 882per upgrade step, (for example, C<1-2> for upgrading from version 1 to version
8832,) and then files are merged with C<_common> and run in filename order.
884C<.sql> files are merely run, as expected. C<.pl> files are run according
885to L</PERL SCRIPTS>.
886
58eb99c3 887=item C<downgrade> Gets run when the schema is C<downgrade>d. Structure is a directory
71d00500 888per downgrade step, (for example, C<2-1> for downgrading from version 2 to version
8891,) and then files are merged with C<_common> and run in filename order.
890C<.sql> files are merely run, as expected. C<.pl> files are run according
891to L</PERL SCRIPTS>.
892
893
894=back
895
03882cab 896=back
897
25c3bec3 898Note that there can be an C<_any> in the place of any of the versions (like
899C<1-2> or C<1>), which means those scripts will be run B<every> time. So if
900you have an C<_any> in C<_common/upgrade>, that script will get run for every
901upgrade.
902
92c34cab 903=head1 PERL SCRIPTS
904
7d0b0f2b 905A perl script for this tool is very simple. It merely needs to contain an
25c3bec3 906anonymous sub that takes a L<DBIx::Class::Schema> and the version set as it's
907arguments.
908
92c34cab 909A very basic perl script might look like:
910
911 #!perl
912
913 use strict;
914 use warnings;
915
57a30fa8 916 use DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers
917 'schema_from_schema_loader';
918
919 schema_from_schema_loader({ naming => 'v4' }, sub {
92c34cab 920 my $schema = shift;
921
25c3bec3 922 # [1] for deploy, [1,2] for upgrade or downgrade, probably used with _any
923 my $versions = shift;
924
92c34cab 925 $schema->resultset('Users')->create({
926 name => 'root',
927 password => 'root',
928 })
57a30fa8 929 })
930
931Note that the above uses
932L<DBIx::Class::DeploymentHanlder::DeployMethod::SQL::Translator::ScriptHelpers/schema_from_schema_loader>.
933Using a raw coderef is strongly discouraged as it is likely to break as you
934modify your schema.
bcc72297 935
39c88a9a 936=attr ignore_ddl
937
938This attribute will, when set to true (default is false), cause the DM to use
939L<SQL::Translator> to use the C<_source>'s serialized SQL::Translator::Schema
940instead of any pregenerated SQL. If you have a development server this is
941probably the best plan of action as you will not be putting as many generated
942files in your version control. Goes well with with C<databases> of C<[]>.
943
92624ee5 944=attr force_overwrite
945
946When this attribute is true generated files will be overwritten when the
947methods which create such files are run again. The default is false, in which
948case the program will die with a message saying which file needs to be deleted.
949
eb28403b 950=attr schema
a65184c8 951
bcc72297 952The L<DBIx::Class::Schema> (B<required>) that is used to talk to the database
953and generate the DDL.
954
eb28403b 955=attr storage
a65184c8 956
bcc72297 957The L<DBIx::Class::Storage> that is I<actually> used to talk to the database
958and generate the DDL. This is automatically created with L</_build_storage>.
959
02a7b8ac 960=attr sql_translator_args
cfc9edf9 961
02a7b8ac 962The arguments that get passed to L<SQL::Translator> when it's used.
a65184c8 963
91adde75 964=attr script_directory
cfc9edf9 965
91adde75 966The directory (default C<'sql'>) that scripts are stored in
cfc9edf9 967
eb28403b 968=attr databases
cfc9edf9 969
970The types of databases (default C<< [qw( MySQL SQLite PostgreSQL )] >>) to
971generate files for
972
eb28403b 973=attr txn_wrap
974
bcc72297 975Set to true (which is the default) to wrap all upgrades and deploys in a single
976transaction.
977
73caa630 978=attr schema_version
979
980The version the schema on your harddrive is at. Defaults to
981C<< $self->schema->schema_version >>.