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