Initial work on specification docs
[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
03882cab 662 |- _protoschema
663 | |- schema
664 | |- 1
665 | | `- 001-auto.yml
666 | |- 2
667 | | `- 001-auto.yml
668 | `- 3
669 | `- 001-auto.yml
92c34cab 670 |- SQLite
671 | |- down
4f85efc6 672 | | `- 2-1
e62add58 673 | | `- 001-auto.sql
92c34cab 674 | |- schema
675 | | `- 1
e62add58 676 | | `- 001-auto.sql
92c34cab 677 | `- up
678 | |- 1-2
e62add58 679 | | `- 001-auto.sql
92c34cab 680 | `- 2-3
e62add58 681 | `- 001-auto.sql
92c34cab 682 |- _common
683 | |- down
4f85efc6 684 | | `- 2-1
92c34cab 685 | | `- 002-remove-customers.pl
686 | `- up
687 | `- 1-2
688 | `- 002-generate-customers.pl
689 |- _generic
690 | |- down
4f85efc6 691 | | `- 2-1
e62add58 692 | | `- 001-auto.sql
92c34cab 693 | |- schema
694 | | `- 1
e62add58 695 | | `- 001-auto.sql
92c34cab 696 | `- up
697 | `- 1-2
e62add58 698 | |- 001-auto.sql
92c34cab 699 | `- 002-create-stored-procedures.sql
700 `- MySQL
701 |- down
4f85efc6 702 | `- 2-1
e62add58 703 | `- 001-auto.sql
80ff6f6d 704 |- preinstall
705 | `- 1
706 | |- 001-create_database.pl
707 | `- 002-create_users_and_permissions.pl
92c34cab 708 |- schema
709 | `- 1
e62add58 710 | `- 001-auto.sql
92c34cab 711 `- up
712 `- 1-2
e62add58 713 `- 001-auto.sql
92c34cab 714
715So basically, the code
716
717 $dm->deploy(1)
718
719on an C<SQLite> database that would simply run
e62add58 720C<$sql_migration_dir/SQLite/schema/1/001-auto.sql>. Next,
92c34cab 721
722 $dm->upgrade_single_step([1,2])
723
e62add58 724would run C<$sql_migration_dir/SQLite/up/1-2/001-auto.sql> followed by
92c34cab 725C<$sql_migration_dir/_common/up/1-2/002-generate-customers.pl>.
726
0824f31f 727C<.pl> files don't have to be in the C<_common> directory, but most of the time
728they should be, because perl scripts are generally be database independent.
92c34cab 729
730C<_generic> exists for when you for some reason are sure that your SQL is
731generic enough to run on all databases. Good luck with that one.
732
80ff6f6d 733Note that unlike most steps in the process, C<preinstall> will not run SQL, as
734there may not even be an database at preinstall time. It will run perl scripts
735just like the other steps in the process, but nothing is passed to them.
736Until people have used this more it will remain freeform, but a recommended use
737of preinstall is to have it prompt for username and password, and then call the
738appropriate C<< CREATE DATABASE >> commands etc.
739
03882cab 740=head2 Directory Specification
741
742The following subdirectories are recognized by this DeployMethod:
743
744=over 2
745
746=item C<_protoschema> This directory can have the following subdirs:
747
748=over 2
749
750=item C<down> This directory merely contains directories named after
751migrations, which are of the form C<$from_version-$to_version>. Inside of
752these directories you may put Perl scripts which are to return a subref
753that takes the arguments C<< $from_schema, $to_schema >>, which are
754L<SQL::Translator::Schema> objects.
755
756=item C<up> This directory merely contains directories named after
757migrations, which are of the form C<$from_version-$to_version>. Inside of
758these directories you may put Perl scripts which are to return a subref
759that takes the arguments C<< $from_schema, $to_schema >>, which are
760L<SQL::Translator::Schema> objects.
761
762=item C<schema> This directory merely contains directories named after schema
763versions, which in turn contain C<yaml> files that are serialized versions
764of the schema at that version. These files are not for editing by hand.
765
766=back
767
768=back
769
92c34cab 770=head1 PERL SCRIPTS
771
7d0b0f2b 772A perl script for this tool is very simple. It merely needs to contain an
773anonymous sub that takes a L<DBIx::Class::Schema> as it's only argument.
92c34cab 774A very basic perl script might look like:
775
776 #!perl
777
778 use strict;
779 use warnings;
780
7d0b0f2b 781 sub {
92c34cab 782 my $schema = shift;
783
784 $schema->resultset('Users')->create({
785 name => 'root',
786 password => 'root',
787 })
788 }
bcc72297 789
eb28403b 790=attr schema
a65184c8 791
bcc72297 792The L<DBIx::Class::Schema> (B<required>) that is used to talk to the database
793and generate the DDL.
794
eb28403b 795=attr storage
a65184c8 796
bcc72297 797The L<DBIx::Class::Storage> that is I<actually> used to talk to the database
798and generate the DDL. This is automatically created with L</_build_storage>.
799
02a7b8ac 800=attr sql_translator_args
cfc9edf9 801
02a7b8ac 802The arguments that get passed to L<SQL::Translator> when it's used.
a65184c8 803
91adde75 804=attr script_directory
cfc9edf9 805
91adde75 806The directory (default C<'sql'>) that scripts are stored in
cfc9edf9 807
eb28403b 808=attr databases
cfc9edf9 809
810The types of databases (default C<< [qw( MySQL SQLite PostgreSQL )] >>) to
811generate files for
812
eb28403b 813=attr txn_wrap
814
bcc72297 815Set to true (which is the default) to wrap all upgrades and deploys in a single
816transaction.
817
73caa630 818=attr schema_version
819
820The version the schema on your harddrive is at. Defaults to
821C<< $self->schema->schema_version >>.
822
db223aff 823=begin comment
824
825=head2 __ddl_consume_with_prefix
a65184c8 826
bcc72297 827 $dm->__ddl_consume_with_prefix( 'SQLite', [qw( 1.00 1.01 )], 'up' )
828
829This is the meat of the multi-file upgrade/deploy stuff. It returns a list of
830files in the order that they should be run for a generic "type" of upgrade.
831You should not be calling this in user code.
832
db223aff 833=head2 _ddl_schema_consume_filenames
a65184c8 834
bcc72297 835 $dm->__ddl_schema_consume_filenames( 'SQLite', [qw( 1.00 )] )
836
837Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for an
838initial deploy.
839
db223aff 840=head2 _ddl_schema_produce_filename
a65184c8 841
bcc72297 842 $dm->__ddl_schema_produce_filename( 'SQLite', [qw( 1.00 )] )
843
844Returns a single file in which an initial schema will be stored.
845
db223aff 846=head2 _ddl_schema_up_consume_filenames
a65184c8 847
bcc72297 848 $dm->_ddl_schema_up_consume_filenames( 'SQLite', [qw( 1.00 )] )
849
850Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for an
851upgrade.
852
db223aff 853=head2 _ddl_schema_down_consume_filenames
a65184c8 854
bcc72297 855 $dm->_ddl_schema_down_consume_filenames( 'SQLite', [qw( 1.00 )] )
856
857Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for a
858downgrade.
859
db223aff 860=head2 _ddl_schema_up_produce_filenames
a65184c8 861
bcc72297 862 $dm->_ddl_schema_up_produce_filename( 'SQLite', [qw( 1.00 1.01 )] )
863
864Returns a single file in which the sql to upgrade from one schema to another
865will be stored.
866
db223aff 867=head2 _ddl_schema_down_produce_filename
bcc72297 868
869 $dm->_ddl_schema_down_produce_filename( 'SQLite', [qw( 1.00 1.01 )] )
870
871Returns a single file in which the sql to downgrade from one schema to another
872will be stored.
a65184c8 873
db223aff 874=head2 _resultsource_install_filename
a65184c8 875
bcc72297 876 my $filename_fn = $dm->_resultsource_install_filename('User');
877 $dm->$filename_fn('SQLite', '1.00')
878
879Returns a function which in turn returns a single filename used to install a
880single resultsource. Weird interface is convenient for me. Deal with it.
881
db223aff 882=head2 _run_sql_and_perl
eb28403b 883
bcc72297 884 $dm->_run_sql_and_perl([qw( list of filenames )])
a65184c8 885
bcc72297 886Simply put, this runs the list of files passed to it. If the file ends in
887C<.sql> it runs it as sql and if it ends in C<.pl> it runs it as a perl file.
a65184c8 888
bcc72297 889Depending on L</txn_wrap> all of the files run will be wrapped in a single
890transaction.
eb28403b 891
db223aff 892=head2 _prepare_install
a65184c8 893
bcc72297 894 $dm->_prepare_install({ add_drop_table => 0 }, sub { 'file_to_create' })
a65184c8 895
bcc72297 896Generates the sql file for installing the database. First arg is simply
897L<SQL::Translator> args and the second is a coderef that returns the filename
898to store the sql in.
a65184c8 899
db223aff 900=head2 _prepare_changegrade
bcc72297 901
902 $dm->_prepare_changegrade('1.00', '1.01', [qw( 1.00 1.01)], 'up')
a65184c8 903
bcc72297 904Generates the sql file for migrating from one schema version to another. First
905arg is the version to start from, second is the version to go to, third is the
906L<version set|DBIx::Class::DeploymentHandler/VERSION SET>, and last is the
907direction of the changegrade, be it 'up' or 'down'.
a65184c8 908
db223aff 909=head2 _read_sql_file
a65184c8 910
bcc72297 911 $dm->_read_sql_file('foo.sql')
a65184c8 912
bcc72297 913Reads a sql file and returns lines in an C<ArrayRef>. Strips out comments,
914transactions, and blank lines.
eb28403b 915
db223aff 916=end comment