rename prepare_install to be prepare_deploy
[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
4use autodie;
5use Carp qw( carp croak );
6
2e68a8e1 7use Method::Signatures::Simple;
7f50d101 8use Try::Tiny;
9af9d0b2 9
d23c7c77 10use SQL::Translator;
11require SQL::Translator::Diff;
9af9d0b2 12
d23c7c77 13require DBIx::Class::Storage; # loaded for type constraint
41863428 14use DBIx::Class::DeploymentHandler::Types;
15
9af9d0b2 16use File::Path 'mkpath';
17use File::Spec::Functions;
2e68a8e1 18
7521a845 19with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
3c1b5ee8 20
d54b8d69 21has schema => (
22 isa => 'DBIx::Class::Schema',
23 is => 'ro',
24 required => 1,
25 handles => [qw( schema_version )],
26);
27
334bced5 28has storage => (
29 isa => 'DBIx::Class::Storage',
30 is => 'ro',
31 lazy_build => 1,
32);
33
2eaf903b 34method _build_storage {
35 my $s = $self->schema->storage;
36 $s->_determine_driver;
37 $s
38}
39
334bced5 40has sqltargs => (
41 isa => 'HashRef',
42 is => 'ro',
43 default => sub { {} },
44);
45has upgrade_directory => (
46 isa => 'Str',
47 is => 'ro',
48 required => 1,
49 default => 'sql',
50);
51
334bced5 52has databases => (
53 coerce => 1,
54 isa => 'DBIx::Class::DeploymentHandler::Databases',
55 is => 'ro',
56 default => sub { [qw( MySQL SQLite PostgreSQL )] },
57);
58
a7d53deb 59has txn_wrap => (
60 is => 'ro',
61 isa => 'Bool',
62 default => 1,
63);
64
76d311e7 65method __ddl_consume_with_prefix($type, $versions, $prefix) {
262166c1 66 my $base_dir = $self->upgrade_directory;
67
76d08d08 68 my $main = catfile( $base_dir, $type );
69 my $generic = catfile( $base_dir, '_generic' );
70 my $common =
71 catfile( $base_dir, '_common', $prefix, join q(-), @{$versions} );
262166c1 72
73 my $dir;
74 if (-d $main) {
76d08d08 75 $dir = catfile($main, $prefix, join q(-), @{$versions})
262166c1 76 } elsif (-d $generic) {
9af9d0b2 77 $dir = catfile($generic, $prefix, join q(-), @{$versions});
262166c1 78 } else {
9af9d0b2 79 croak "neither $main or $generic exist; please write/generate some SQL";
262166c1 80 }
81
82 opendir my($dh), $dir;
41219a5d 83 my %files = map { $_ => "$dir/$_" } grep { /\.(?:sql|pl)$/ && -f "$dir/$_" } readdir $dh;
262166c1 84 closedir $dh;
85
86 if (-d $common) {
87 opendir my($dh), $common;
41219a5d 88 for my $filename (grep { /\.(?:sql|pl)$/ && -f catfile($common,$_) } readdir $dh) {
262166c1 89 unless ($files{$filename}) {
9af9d0b2 90 $files{$filename} = catfile($common,$filename);
262166c1 91 }
92 }
93 closedir $dh;
94 }
95
96 return [@files{sort keys %files}]
97}
3c1b5ee8 98
76d311e7 99method _ddl_schema_consume_filenames($type, $version) {
100 $self->__ddl_consume_with_prefix($type, [ $version ], 'schema')
3c1b5ee8 101}
102
76d311e7 103method _ddl_schema_produce_filename($type, $version) {
76d08d08 104 my $dirname = catfile( $self->upgrade_directory, $type, 'schema', $version );
105 mkpath($dirname) unless -d $dirname;
d54b8d69 106
76d08d08 107 return catfile( $dirname, '001-auto.sql' );
d54b8d69 108}
109
76d311e7 110method _ddl_schema_up_consume_filenames($type, $versions) {
111 $self->__ddl_consume_with_prefix($type, $versions, 'up')
3c1b5ee8 112}
113
76d311e7 114method _ddl_schema_down_consume_filenames($type, $versions) {
115 $self->__ddl_consume_with_prefix($type, $versions, 'down')
a41a04e5 116}
117
76d311e7 118method _ddl_schema_up_produce_filename($type, $versions) {
119 my $dir = $self->upgrade_directory;
120
76d08d08 121 my $dirname = catfile( $dir, $type, 'up', join q(-), @{$versions});
122 mkpath($dirname) unless -d $dirname;
a41a04e5 123
76d08d08 124 return catfile( $dirname, '001-auto.sql'
a41a04e5 125 );
126}
127
76d311e7 128method _ddl_schema_down_produce_filename($type, $versions, $dir) {
76d08d08 129 my $dirname = catfile( $dir, $type, 'down', join q(-), @{$versions} );
130 mkpath($dirname) unless -d $dirname;
24f4524b 131
76d08d08 132 return catfile( $dirname, '001-auto.sql');
24f4524b 133}
134
41219a5d 135method _run_sql_and_perl($filenames) {
136 my @files = @{$filenames};
137 my $storage = $self->storage;
2e68a8e1 138
c8a2f7bd 139
a7d53deb 140 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
141
41219a5d 142 my $sql;
143 for my $filename (@files) {
144 if ($filename =~ /\.sql$/) {
145 my @sql = @{$self->_read_sql_file($filename)};
146 $sql .= join "\n", @sql;
147
148 foreach my $line (@sql) {
149 $storage->_query_start($line);
150 try {
151 # do a dbh_do cycle here, as we need some error checking in
152 # place (even though we will ignore errors)
153 $storage->dbh_do (sub { $_[1]->do($line) });
154 }
155 catch {
156 carp "$_ (running '${line}')"
157 }
158 $storage->_query_end($line);
159 }
0841a743 160 } elsif ( $filename =~ /^(.+)\.pl$/ ) {
161 my $package = $1;
162 my $filedata = do { local( @ARGV, $/ ) = $filename; <> };
163 # make the package name more palateable to perl
164 $package =~ s/\W/_/g;
165
166 no warnings 'redefine';
167 eval "package $package;\n\n$filedata";
168 use warnings;
169
98c9484a 170 if (my $fn = $package->can('run')) {
171 $fn->($self->schema);
172 } else {
173 carp "$filename should define a run method that takes a schema but it didn't!";
174 }
41219a5d 175 } else {
176 croak "A file got to deploy that wasn't sql or perl!";
2e68a8e1 177 }
2e68a8e1 178 }
a7d53deb 179
180 $guard->commit if $self->txn_wrap;
41219a5d 181
182 return $sql;
183}
184
185sub deploy {
186 my $self = shift;
92c34cab 187 my $version = shift || $self->schema_version;
41219a5d 188
189 return $self->_run_sql_and_perl($self->_ddl_schema_consume_filenames(
190 $self->storage->sqlt_type,
92c34cab 191 $version,
41219a5d 192 ));
2e68a8e1 193}
194
c8a2f7bd 195sub _prepare_install {
7521a845 196 my $self = shift;
c8a2f7bd 197 my $sqltargs = { %{$self->sqltargs}, %{shift @_} };
198 my $to_file = shift;
2e68a8e1 199 my $schema = $self->schema;
200 my $databases = $self->databases;
201 my $dir = $self->upgrade_directory;
d54b8d69 202 my $version = $schema->schema_version;
203
9600776d 204 my $sqlt = SQL::Translator->new({
d54b8d69 205 add_drop_table => 1,
2e68a8e1 206 ignore_constraint_names => 1,
d54b8d69 207 ignore_index_names => 1,
208 parser => 'SQL::Translator::Parser::DBIx::Class',
3aaf766f 209 %{$sqltargs}
9600776d 210 });
2e68a8e1 211
d53e0bfc 212 my $sqlt_schema = $sqlt->translate( data => $schema )
387b11d2 213 or croak($sqlt->error);
2e68a8e1 214
215 foreach my $db (@$databases) {
216 $sqlt->reset;
217 $sqlt->{schema} = $sqlt_schema;
218 $sqlt->producer($db);
219
c8a2f7bd 220 my $filename = $self->$to_file($db, $version, $dir);
9600776d 221 if (-e $filename ) {
2e68a8e1 222 carp "Overwriting existing DDL file - $filename";
223 unlink $filename;
224 }
225
226 my $output = $sqlt->translate;
227 if(!$output) {
228 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
229 next;
230 }
387b11d2 231 open my $file, q(>), $filename;
2e68a8e1 232 print {$file} $output;
233 close $file;
234 }
235}
236
c8a2f7bd 237sub _resultsource_install_filename {
238 my ($self, $source_name) = @_;
239 return sub {
240 my ($self, $type, $version) = @_;
241 my $dirname = catfile( $self->upgrade_directory, $type, 'schema', $version );
242 mkpath($dirname) unless -d $dirname;
243
244 return catfile( $dirname, "001-auto-$source_name.sql" );
245 }
246}
247
248sub install_resultsource {
249 my ($self, $source, $version) = @_;
250
251 my $rs_install_file =
252 $self->_resultsource_install_filename($source->source_name);
253
254 my $files = [
255 $self->$rs_install_file(
256 $self->storage->sqlt_type,
257 $version,
258 )
259 ];
260 $self->_run_sql_and_perl($files);
261}
262
263sub prepare_resultsource_install {
264 my $self = shift;
265 my $source = shift;
266
267 my $filename = $self->_resultsource_install_filename($source->source_name);
268 $self->_prepare_install({
269 parser_args => { sources => [$source->source_name], }
270 }, $filename);
271}
272
91557c90 273sub prepare_deploy {
c8a2f7bd 274 my $self = shift;
275 $self->_prepare_install({}, '_ddl_schema_produce_filename');
276}
277
a41a04e5 278sub prepare_upgrade {
9600776d 279 my ($self, $from_version, $to_version, $version_set) = @_;
76d311e7 280 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up');
281}
282
283sub prepare_downgrade {
284 my ($self, $from_version, $to_version, $version_set) = @_;
285
76d311e7 286 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'down');
287}
288
289method _prepare_changegrade($from_version, $to_version, $version_set, $direction) {
2e68a8e1 290 my $schema = $self->schema;
291 my $databases = $self->databases;
292 my $dir = $self->upgrade_directory;
293 my $sqltargs = $self->sqltargs;
294
9600776d 295 my $schema_version = $schema->schema_version;
2e68a8e1 296
297 $sqltargs = {
298 add_drop_table => 1,
299 ignore_constraint_names => 1,
300 ignore_index_names => 1,
301 %{$sqltargs}
302 };
303
304 my $sqlt = SQL::Translator->new( $sqltargs );
305
306 $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
d53e0bfc 307 my $sqlt_schema = $sqlt->translate( data => $schema )
387b11d2 308 or croak($sqlt->error);
2e68a8e1 309
310 foreach my $db (@$databases) {
311 $sqlt->reset;
312 $sqlt->{schema} = $sqlt_schema;
313 $sqlt->producer($db);
314
76d311e7 315 my $prefilename = $self->_ddl_schema_produce_filename($db, $from_version, $dir);
2e68a8e1 316 unless(-e $prefilename) {
317 carp("No previous schema file found ($prefilename)");
318 next;
319 }
76d311e7 320 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
321 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
2e68a8e1 322 if(-e $diff_file) {
76d311e7 323 carp("Overwriting existing $direction-diff file - $diff_file");
2e68a8e1 324 unlink $diff_file;
325 }
326
327 my $source_schema;
328 {
329 my $t = SQL::Translator->new({
330 %{$sqltargs},
331 debug => 0,
332 trace => 0,
333 });
334
335 $t->parser( $db ) # could this really throw an exception?
387b11d2 336 or croak($t->error);
2e68a8e1 337
338 my $out = $t->translate( $prefilename )
387b11d2 339 or croak($t->error);
2e68a8e1 340
341 $source_schema = $t->schema;
342
343 $source_schema->name( $prefilename )
344 unless $source_schema->name;
345 }
346
347 # The "new" style of producers have sane normalization and can support
348 # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
349 # And we have to diff parsed SQL against parsed SQL.
350 my $dest_schema = $sqlt_schema;
351
352 unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) {
353 my $t = SQL::Translator->new({
354 %{$sqltargs},
355 debug => 0,
356 trace => 0,
357 });
358
359 $t->parser( $db ) # could this really throw an exception?
387b11d2 360 or croak($t->error);
2e68a8e1 361
76d311e7 362 my $filename = $self->_ddl_schema_produce_filename($db, $to_version, $dir);
2e68a8e1 363 my $out = $t->translate( $filename )
387b11d2 364 or croak($t->error);
2e68a8e1 365
366 $dest_schema = $t->schema;
367
368 $dest_schema->name( $filename )
369 unless $dest_schema->name;
370 }
371
372 my $diff = SQL::Translator::Diff::schema_diff(
373 $source_schema, $db,
374 $dest_schema, $db,
375 $sqltargs
376 );
387b11d2 377 open my $file, q(>), $diff_file;
2e68a8e1 378 print {$file} $diff;
379 close $file;
380 }
381}
382
334bced5 383method _read_sql_file($file) {
384 return unless $file;
385
aabd4237 386 open my $fh, '<', $file;
0d19af1d 387 my @data = split /;\n/, join '', <$fh>;
334bced5 388 close $fh;
389
390 @data = grep {
0d19af1d 391 $_ && # remove blank lines
392 !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/ # strip txn's
393 } map {
394 s/^\s+//; s/\s+$//; # trim whitespace
395 join '', grep { !/^--/ } split /\n/ # remove comments
396 } @data;
334bced5 397
398 return \@data;
399}
400
7d2a6974 401sub downgrade_single_step {
76d311e7 402 my $self = shift;
627581cd 403 my $version_set = shift @_;
41219a5d 404
405 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_down_consume_filenames(
76d311e7 406 $self->storage->sqlt_type,
627581cd 407 $version_set,
41219a5d 408 ));
3249629f 409
41219a5d 410 return ['', $sql];
76d311e7 411}
412
7d2a6974 413sub upgrade_single_step {
7521a845 414 my $self = shift;
627581cd 415 my $version_set = shift @_;
41219a5d 416
417 my $sql = $self->_run_sql_and_perl($self->_ddl_schema_up_consume_filenames(
334bced5 418 $self->storage->sqlt_type,
627581cd 419 $version_set,
41219a5d 420 ));
421 return ['', $sql];
334bced5 422}
423
aabd4237 424__PACKAGE__->meta->make_immutable;
425
2e68a8e1 4261;
e051bb00 427
e52174e3 428# vim: ts=2 sw=2 expandtab
429
e051bb00 430__END__
431
bcc72297 432=head1 DESCRIPTION
433
434This class is the meat of L<DBIx::Class::DeploymentHandler>. It takes care of
435generating sql files representing schemata as well as sql files to move from
436one version of a schema to the rest. One of the hallmark features of this
437class is that it allows for multiple sql files for deploy and upgrade, allowing
438developers to fine tune deployment. In addition it also allows for perl files
439to be run at any stage of the process.
440
441For basic usage see L<DBIx::Class::DeploymentHandler::HandlesDeploy>. What's
442documented here is extra fun stuff or private methods.
443
444=head1 DIRECTORY LAYOUT
445
92c34cab 446Arguably this is the best feature of L<DBIx::Class::DeploymentHandler>. It's
447heavily based upon L<DBIx::Migration::Directories>, but has some extensions and
448modifications, so even if you are familiar with it, please read this. I feel
449like the best way to describe the layout is with the following example:
450
451 $sql_migration_dir
452 |- SQLite
453 | |- down
454 | | `- 1-2
455 | | `- 001-auto.sql
456 | |- schema
457 | | `- 1
458 | | `- 001-auto.sql
459 | `- up
460 | |- 1-2
461 | | `- 001-auto.sql
462 | `- 2-3
463 | `- 001-auto.sql
464 |- _common
465 | |- down
466 | | `- 1-2
467 | | `- 002-remove-customers.pl
468 | `- up
469 | `- 1-2
470 | `- 002-generate-customers.pl
471 |- _generic
472 | |- down
473 | | `- 1-2
474 | | `- 001-auto.sql
475 | |- schema
476 | | `- 1
477 | | `- 001-auto.sql
478 | `- up
479 | `- 1-2
480 | |- 001-auto.sql
481 | `- 002-create-stored-procedures.sql
482 `- MySQL
483 |- down
484 | `- 1-2
485 | `- 001-auto.sql
486 |- schema
487 | `- 1
488 | `- 001-auto.sql
489 `- up
490 `- 1-2
491 `- 001-auto.sql
492
493So basically, the code
494
495 $dm->deploy(1)
496
497on an C<SQLite> database that would simply run
498C<$sql_migration_dir/SQLite/schema/1/001-auto.sql>. Next,
499
500 $dm->upgrade_single_step([1,2])
501
502would run C<$sql_migration_dir/SQLite/up/1-2/001-auto.sql> followed by
503C<$sql_migration_dir/_common/up/1-2/002-generate-customers.pl>.
504
505Now, a C<.pl> file doesn't have to be in the C<_common> directory, but most of
506the time it probably should be, since perl scripts will mostly be database
507independent.
508
509C<_generic> exists for when you for some reason are sure that your SQL is
510generic enough to run on all databases. Good luck with that one.
511
512=head1 PERL SCRIPTS
513
514A perl script for this tool is very simple. It merely needs to contain a
515sub called C<run> that takes a L<DBIx::Class::Schema> as it's only argument.
516A very basic perl script might look like:
517
518 #!perl
519
520 use strict;
521 use warnings;
522
523 sub run {
524 my $schema = shift;
525
526 $schema->resultset('Users')->create({
527 name => 'root',
528 password => 'root',
529 })
530 }
bcc72297 531
eb28403b 532=attr schema
a65184c8 533
bcc72297 534The L<DBIx::Class::Schema> (B<required>) that is used to talk to the database
535and generate the DDL.
536
eb28403b 537=attr storage
a65184c8 538
bcc72297 539The L<DBIx::Class::Storage> that is I<actually> used to talk to the database
540and generate the DDL. This is automatically created with L</_build_storage>.
541
eb28403b 542=attr sqltargs
cfc9edf9 543
544#rename
a65184c8 545
eb28403b 546=attr upgrade_directory
cfc9edf9 547
548The directory (default C<'sql'>) that upgrades are stored in
549
eb28403b 550=attr databases
cfc9edf9 551
552The types of databases (default C<< [qw( MySQL SQLite PostgreSQL )] >>) to
553generate files for
554
eb28403b 555=attr txn_wrap
556
bcc72297 557Set to true (which is the default) to wrap all upgrades and deploys in a single
558transaction.
559
eb28403b 560=method __ddl_consume_with_prefix
a65184c8 561
bcc72297 562 $dm->__ddl_consume_with_prefix( 'SQLite', [qw( 1.00 1.01 )], 'up' )
563
564This is the meat of the multi-file upgrade/deploy stuff. It returns a list of
565files in the order that they should be run for a generic "type" of upgrade.
566You should not be calling this in user code.
567
eb28403b 568=method _ddl_schema_consume_filenames
a65184c8 569
bcc72297 570 $dm->__ddl_schema_consume_filenames( 'SQLite', [qw( 1.00 )] )
571
572Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for an
573initial deploy.
574
eb28403b 575=method _ddl_schema_produce_filename
a65184c8 576
bcc72297 577 $dm->__ddl_schema_produce_filename( 'SQLite', [qw( 1.00 )] )
578
579Returns a single file in which an initial schema will be stored.
580
eb28403b 581=method _ddl_schema_up_consume_filenames
a65184c8 582
bcc72297 583 $dm->_ddl_schema_up_consume_filenames( 'SQLite', [qw( 1.00 )] )
584
585Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for an
586upgrade.
587
eb28403b 588=method _ddl_schema_down_consume_filenames
a65184c8 589
bcc72297 590 $dm->_ddl_schema_down_consume_filenames( 'SQLite', [qw( 1.00 )] )
591
592Just a curried L</__ddl_consume_with_prefix>. Get's a list of files for a
593downgrade.
594
eb28403b 595=method _ddl_schema_up_produce_filenames
a65184c8 596
bcc72297 597 $dm->_ddl_schema_up_produce_filename( 'SQLite', [qw( 1.00 1.01 )] )
598
599Returns a single file in which the sql to upgrade from one schema to another
600will be stored.
601
602=method _ddl_schema_down_produce_filename
603
604 $dm->_ddl_schema_down_produce_filename( 'SQLite', [qw( 1.00 1.01 )] )
605
606Returns a single file in which the sql to downgrade from one schema to another
607will be stored.
a65184c8 608
eb28403b 609=method _resultsource_install_filename
a65184c8 610
bcc72297 611 my $filename_fn = $dm->_resultsource_install_filename('User');
612 $dm->$filename_fn('SQLite', '1.00')
613
614Returns a function which in turn returns a single filename used to install a
615single resultsource. Weird interface is convenient for me. Deal with it.
616
eb28403b 617=method _run_sql_and_perl
618
bcc72297 619 $dm->_run_sql_and_perl([qw( list of filenames )])
a65184c8 620
bcc72297 621Simply put, this runs the list of files passed to it. If the file ends in
622C<.sql> it runs it as sql and if it ends in C<.pl> it runs it as a perl file.
a65184c8 623
bcc72297 624Depending on L</txn_wrap> all of the files run will be wrapped in a single
625transaction.
eb28403b 626
bcc72297 627=method _prepare_install
a65184c8 628
bcc72297 629 $dm->_prepare_install({ add_drop_table => 0 }, sub { 'file_to_create' })
a65184c8 630
bcc72297 631Generates the sql file for installing the database. First arg is simply
632L<SQL::Translator> args and the second is a coderef that returns the filename
633to store the sql in.
a65184c8 634
bcc72297 635=method _prepare_changegrade
636
637 $dm->_prepare_changegrade('1.00', '1.01', [qw( 1.00 1.01)], 'up')
a65184c8 638
bcc72297 639Generates the sql file for migrating from one schema version to another. First
640arg is the version to start from, second is the version to go to, third is the
641L<version set|DBIx::Class::DeploymentHandler/VERSION SET>, and last is the
642direction of the changegrade, be it 'up' or 'down'.
a65184c8 643
bcc72297 644=method _read_sql_file
a65184c8 645
bcc72297 646 $dm->_read_sql_file('foo.sql')
a65184c8 647
bcc72297 648Reads a sql file and returns lines in an C<ArrayRef>. Strips out comments,
649transactions, and blank lines.
eb28403b 650