get rid of unnecesary wantarray code
[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;
2e68a8e1 3use Method::Signatures::Simple;
7f50d101 4use Try::Tiny;
d23c7c77 5use SQL::Translator;
6require SQL::Translator::Diff;
7require DBIx::Class::Storage; # loaded for type constraint
3c1b5ee8 8use autodie;
85998cd9 9use File::Path;
2e68a8e1 10
7521a845 11with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
3c1b5ee8 12
2e68a8e1 13use Carp 'carp';
14
d54b8d69 15has schema => (
16 isa => 'DBIx::Class::Schema',
17 is => 'ro',
18 required => 1,
19 handles => [qw( schema_version )],
20);
21
334bced5 22has storage => (
23 isa => 'DBIx::Class::Storage',
24 is => 'ro',
25 lazy_build => 1,
26);
27
2eaf903b 28method _build_storage {
29 my $s = $self->schema->storage;
30 $s->_determine_driver;
31 $s
32}
33
334bced5 34has sqltargs => (
35 isa => 'HashRef',
36 is => 'ro',
37 default => sub { {} },
38);
39has upgrade_directory => (
40 isa => 'Str',
41 is => 'ro',
42 required => 1,
43 default => 'sql',
44);
45
334bced5 46has databases => (
47 coerce => 1,
48 isa => 'DBIx::Class::DeploymentHandler::Databases',
49 is => 'ro',
50 default => sub { [qw( MySQL SQLite PostgreSQL )] },
51);
52
334bced5 53has _filedata => (
54 isa => 'ArrayRef[Str]',
55 is => 'rw',
56);
57
76d311e7 58method __ddl_consume_with_prefix($type, $versions, $prefix) {
262166c1 59 my $base_dir = $self->upgrade_directory;
60
61 my $main = File::Spec->catfile( $base_dir, $type );
62 my $generic = File::Spec->catfile( $base_dir, '_generic' );
63 my $common = File::Spec->catfile( $base_dir, '_common', $prefix, join q(-), @{$versions} );
64
65 my $dir;
66 if (-d $main) {
67 $dir = File::Spec->catfile($main, $prefix, join q(-), @{$versions})
68 } elsif (-d $generic) {
69 $dir = File::Spec->catfile($main, $prefix, join q(-), @{$versions})
70 } else {
71 die 'PREPARE TO SQL'
72 }
73
74 opendir my($dh), $dir;
75 my %files = map { $_ => "$dir/$_" } grep { /\.sql$/ && -f "$dir/$_" } readdir($dh);
76 closedir $dh;
77
78 if (-d $common) {
79 opendir my($dh), $common;
80 for my $filename (grep { /\.sql$/ && -f "$common/$_" } readdir($dh)) {
81 unless ($files{$filename}) {
82 $files{$filename} = "$common/$_";
83 }
84 }
85 closedir $dh;
86 }
87
88 return [@files{sort keys %files}]
89}
3c1b5ee8 90
76d311e7 91method _ddl_schema_consume_filenames($type, $version) {
92 $self->__ddl_consume_with_prefix($type, [ $version ], 'schema')
3c1b5ee8 93}
94
76d311e7 95method _ddl_schema_produce_filename($type, $version) {
96 my $base_dir = $self->upgrade_directory;
85998cd9 97 my $dirname = File::Spec->catfile(
76d311e7 98 $base_dir, $type, 'schema', $version
d54b8d69 99 );
85998cd9 100 File::Path::mkpath($dirname) unless -d $dirname;
d54b8d69 101
85998cd9 102 return File::Spec->catfile(
103 $dirname, '001-auto.sql'
104 );
d54b8d69 105}
106
76d311e7 107method _ddl_schema_up_consume_filenames($type, $versions) {
108 $self->__ddl_consume_with_prefix($type, $versions, 'up')
3c1b5ee8 109}
110
76d311e7 111method _ddl_schema_down_consume_filenames($type, $versions) {
112 $self->__ddl_consume_with_prefix($type, $versions, 'down')
a41a04e5 113}
114
76d311e7 115method _ddl_schema_up_produce_filename($type, $versions) {
116 my $dir = $self->upgrade_directory;
117
a41a04e5 118 my $dirname = File::Spec->catfile(
119 $dir, $type, 'up', join( q(-), @{$versions} )
120 );
121 File::Path::mkpath($dirname) unless -d $dirname;
122
123 return File::Spec->catfile(
124 $dirname, '001-auto.sql'
125 );
126}
127
76d311e7 128method _ddl_schema_down_produce_filename($type, $versions, $dir) {
85998cd9 129 my $dirname = File::Spec->catfile(
a41a04e5 130 $dir, $type, 'down', join( q(-), @{$versions} )
24f4524b 131 );
85998cd9 132 File::Path::mkpath($dirname) unless -d $dirname;
24f4524b 133
85998cd9 134 return File::Spec->catfile(
135 $dirname, '001-auto.sql'
136 );
24f4524b 137}
138
8a7847f1 139method _deployment_statements {
2e68a8e1 140 my $dir = $self->upgrade_directory;
141 my $schema = $self->schema;
142 my $type = $self->storage->sqlt_type;
143 my $sqltargs = $self->sqltargs;
9600776d 144 my $version = $self->schema_version;
2e68a8e1 145
76d311e7 146 my @filenames = @{$self->_ddl_schema_consume_filenames($type, $version)};
3c1b5ee8 147
148 for my $filename (@filenames) {
149 if(-f $filename) {
150 my $file;
151 open $file, q(<), $filename
152 or carp "Can't open $filename ($!)";
153 my @rows = <$file>;
154 close $file;
155 return join '', @rows;
156 }
2e68a8e1 157 }
158
159 # sources needs to be a parser arg, but for simplicty allow at top level
160 # coming in
161 $sqltargs->{parser_args}{sources} = delete $sqltargs->{sources}
162 if exists $sqltargs->{sources};
163
164 my $tr = SQL::Translator->new(
165 producer => "SQL::Translator::Producer::${type}",
166 %$sqltargs,
167 parser => 'SQL::Translator::Parser::DBIx::Class',
168 data => $schema,
169 );
170
284cda86 171 my $ret = $tr->translate;
2e68a8e1 172
173 $schema->throw_exception( 'Unable to produce deployment statements: ' . $tr->error)
284cda86 174 unless defined $ret;
2e68a8e1 175
284cda86 176 return $ret;
2e68a8e1 177}
178
7521a845 179sub _deploy {
180 my $self = shift;
2e68a8e1 181 my $storage = $self->storage;
182
ff7f0e7d 183#< frew> k, also, we filter out comments and transaction stuff and blank lines
184#< frew> is that really necesary?
185#< frew> and what if I want to run my upgrade in a txn? seems like something you'd
186# always want to do really
187#< ribasushi> again - some stuff chokes
188#< frew> ok, so I see filtering out -- and \s*
189#< frew> but I think the txn filtering should be optional and default to NOT filter it
190# out
191#< ribasushi> then you have a problem
192#< frew> tell me
193#< ribasushi> someone runs a deploy in txn_do
194#< ribasushi> the inner begin will blow up
195#< frew> because it's a nested TXN?
196#< ribasushi> (you an't begin twice on most dbs)
197#< ribasushi> right
198#< ribasushi> on sqlite - for sure
199#< frew> so...read the docs and set txn_filter to true?
200#< ribasushi> more like wrap deploy in a txn
201#< frew> I like that better
202#< ribasushi> and make sure the ddl has no literal txns in them
203#< frew> sure
204#< ribasushi> this way you have stuff under control
205#< frew> so we have txn_wrap default to true
206#< frew> and if people wanna do that by hand they can
77d89422 207 my $sql = $self->_deployment_statements();
208 foreach my $line ( split(";\n", $sql)) {
209 next if !$line || $line =~ /^--|^BEGIN TRANSACTION|^COMMIT|^\s+$/;
2e68a8e1 210 $storage->_query_start($line);
211 try {
212 # do a dbh_do cycle here, as we need some error checking in
213 # place (even though we will ignore errors)
214 $storage->dbh_do (sub { $_[1]->do($line) });
215 }
216 catch {
217 carp "$_ (running '${line}')"
218 }
219 $storage->_query_end($line);
2e68a8e1 220 }
221}
222
7521a845 223sub prepare_install {
224 my $self = shift;
2e68a8e1 225 my $schema = $self->schema;
226 my $databases = $self->databases;
227 my $dir = $self->upgrade_directory;
228 my $sqltargs = $self->sqltargs;
d54b8d69 229 my $version = $schema->schema_version;
230
2e68a8e1 231 unless( -d $dir ) {
232 carp "Upgrade directory $dir does not exist, using ./\n";
d54b8d69 233 $dir = './';
2e68a8e1 234 }
235
2e68a8e1 236
9600776d 237 my $sqlt = SQL::Translator->new({
d54b8d69 238 add_drop_table => 1,
2e68a8e1 239 ignore_constraint_names => 1,
d54b8d69 240 ignore_index_names => 1,
241 parser => 'SQL::Translator::Parser::DBIx::Class',
2e68a8e1 242 %{$sqltargs || {}}
9600776d 243 });
2e68a8e1 244
2e68a8e1 245 my $sqlt_schema = $sqlt->translate({ data => $schema })
246 or $self->throw_exception ($sqlt->error);
247
248 foreach my $db (@$databases) {
249 $sqlt->reset;
250 $sqlt->{schema} = $sqlt_schema;
251 $sqlt->producer($db);
252
76d311e7 253 my $filename = $self->_ddl_schema_produce_filename($db, $version, $dir);
9600776d 254 if (-e $filename ) {
2e68a8e1 255 carp "Overwriting existing DDL file - $filename";
256 unlink $filename;
257 }
258
259 my $output = $sqlt->translate;
260 if(!$output) {
261 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
262 next;
263 }
264 my $file;
265 unless( open $file, q(>), $filename ) {
266 $self->throw_exception("Can't open $filename for writing ($!)");
267 next;
268 }
269 print {$file} $output;
270 close $file;
271 }
272}
273
a41a04e5 274sub prepare_upgrade {
9600776d 275 my ($self, $from_version, $to_version, $version_set) = @_;
276
277 $from_version ||= $self->db_version;
278 $to_version ||= $self->schema_version;
279
63e95f13 280 # for updates prepared automatically (rob's stuff)
281 # one would want to explicitly set $version_set to
282 # [$to_version]
9600776d 283 $version_set ||= [$from_version, $to_version];
76d311e7 284
285 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up');
286}
287
288sub prepare_downgrade {
289 my ($self, $from_version, $to_version, $version_set) = @_;
290
291 $from_version ||= $self->db_version;
292 $to_version ||= $self->schema_version;
293
294 # for updates prepared automatically (rob's stuff)
295 # one would want to explicitly set $version_set to
296 # [$to_version]
297 $version_set ||= [$from_version, $to_version];
298
299 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'down');
300}
301
302method _prepare_changegrade($from_version, $to_version, $version_set, $direction) {
2e68a8e1 303 my $schema = $self->schema;
304 my $databases = $self->databases;
305 my $dir = $self->upgrade_directory;
306 my $sqltargs = $self->sqltargs;
307
9600776d 308 my $schema_version = $schema->schema_version;
2e68a8e1 309
310 $sqltargs = {
311 add_drop_table => 1,
312 ignore_constraint_names => 1,
313 ignore_index_names => 1,
314 %{$sqltargs}
315 };
316
317 my $sqlt = SQL::Translator->new( $sqltargs );
318
319 $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
320 my $sqlt_schema = $sqlt->translate({ data => $schema })
321 or $self->throw_exception ($sqlt->error);
322
323 foreach my $db (@$databases) {
324 $sqlt->reset;
325 $sqlt->{schema} = $sqlt_schema;
326 $sqlt->producer($db);
327
76d311e7 328 my $prefilename = $self->_ddl_schema_produce_filename($db, $from_version, $dir);
2e68a8e1 329 unless(-e $prefilename) {
330 carp("No previous schema file found ($prefilename)");
331 next;
332 }
76d311e7 333 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
334 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
2e68a8e1 335 if(-e $diff_file) {
76d311e7 336 carp("Overwriting existing $direction-diff file - $diff_file");
2e68a8e1 337 unlink $diff_file;
338 }
339
340 my $source_schema;
341 {
342 my $t = SQL::Translator->new({
343 %{$sqltargs},
344 debug => 0,
345 trace => 0,
346 });
347
348 $t->parser( $db ) # could this really throw an exception?
349 or $self->throw_exception ($t->error);
350
351 my $out = $t->translate( $prefilename )
352 or $self->throw_exception ($t->error);
353
354 $source_schema = $t->schema;
355
356 $source_schema->name( $prefilename )
357 unless $source_schema->name;
358 }
359
360 # The "new" style of producers have sane normalization and can support
361 # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
362 # And we have to diff parsed SQL against parsed SQL.
363 my $dest_schema = $sqlt_schema;
364
365 unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) {
366 my $t = SQL::Translator->new({
367 %{$sqltargs},
368 debug => 0,
369 trace => 0,
370 });
371
372 $t->parser( $db ) # could this really throw an exception?
373 or $self->throw_exception ($t->error);
374
76d311e7 375 my $filename = $self->_ddl_schema_produce_filename($db, $to_version, $dir);
2e68a8e1 376 my $out = $t->translate( $filename )
377 or $self->throw_exception ($t->error);
378
379 $dest_schema = $t->schema;
380
381 $dest_schema->name( $filename )
382 unless $dest_schema->name;
383 }
384
385 my $diff = SQL::Translator::Diff::schema_diff(
386 $source_schema, $db,
387 $dest_schema, $db,
388 $sqltargs
389 );
390 my $file;
391 unless(open $file, q(>), $diff_file) {
392 $self->throw_exception("Can't write to $diff_file ($!)");
393 next;
394 }
395 print {$file} $diff;
396 close $file;
397 }
398}
399
334bced5 400method _read_sql_file($file) {
401 return unless $file;
402
403 open my $fh, '<', $file or carp("Can't open upgrade file, $file ($!)");
404 my @data = split /\n/, join '', <$fh>;
405 close $fh;
406
407 @data = grep {
408 $_ &&
409 !/^--/ &&
410 !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/m
411 } split /;/,
412 join '', @data;
413
414 return \@data;
415}
416
76d311e7 417# these are exactly the same for now
418sub _downgrade_single_step {
419 my $self = shift;
420 my @version_set = @{ shift @_ };
421 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
422 $self->storage->sqlt_type,
423 \@version_set,
424 )};
425
426 for my $upgrade_file (@upgrade_files) {
427 unless (-f $upgrade_file) {
428 # croak?
429 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
430 return;
431 }
432
433 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
434 $self->schema->txn_do(sub { $self->_do_upgrade });
435 }
436}
437
7521a845 438sub _upgrade_single_step {
439 my $self = shift;
24f4524b 440 my @version_set = @{ shift @_ };
76d311e7 441 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
334bced5 442 $self->storage->sqlt_type,
24f4524b 443 \@version_set,
3c1b5ee8 444 )};
334bced5 445
3c1b5ee8 446 for my $upgrade_file (@upgrade_files) {
447 unless (-f $upgrade_file) {
448 # croak?
449 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
450 return;
451 }
334bced5 452
3c1b5ee8 453 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
454 $self->schema->txn_do(sub { $self->_do_upgrade });
455 }
334bced5 456}
457
8a7847f1 458method _do_upgrade { $self->_run_upgrade(qr/.*?/) }
334bced5 459
8a7847f1 460method _run_upgrade($stm) {
334bced5 461 return unless $self->_filedata;
462 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
463
464 for (@statements) {
465 $self->storage->debugobj->query_start($_) if $self->storage->debug;
8a7847f1 466 $self->_apply_statement($_);
334bced5 467 $self->storage->debugobj->query_end($_) if $self->storage->debug;
468 }
469}
470
8a7847f1 471method _apply_statement($statement) {
334bced5 472 # croak?
473 $self->storage->dbh->do($_) or carp "SQL was: $_"
474}
475
2e68a8e1 4761;
e051bb00 477
478__END__
479
2eaf903b 480vim: ts=2 sw=2 expandtab