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