some code style cleanup
[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;
76d08d08 9use File::Path 'mkpath';
41863428 10use DBIx::Class::DeploymentHandler::Types;
76d08d08 11use File::Spec::Functions;
41863428 12
2e68a8e1 13
7521a845 14with 'DBIx::Class::DeploymentHandler::HandlesDeploy';
3c1b5ee8 15
2e68a8e1 16use Carp 'carp';
17
d54b8d69 18has schema => (
19 isa => 'DBIx::Class::Schema',
20 is => 'ro',
21 required => 1,
22 handles => [qw( schema_version )],
23);
24
334bced5 25has storage => (
26 isa => 'DBIx::Class::Storage',
27 is => 'ro',
28 lazy_build => 1,
29);
30
2eaf903b 31method _build_storage {
32 my $s = $self->schema->storage;
33 $s->_determine_driver;
34 $s
35}
36
334bced5 37has sqltargs => (
38 isa => 'HashRef',
39 is => 'ro',
40 default => sub { {} },
41);
42has upgrade_directory => (
43 isa => 'Str',
44 is => 'ro',
45 required => 1,
46 default => 'sql',
47);
48
334bced5 49has databases => (
50 coerce => 1,
51 isa => 'DBIx::Class::DeploymentHandler::Databases',
52 is => 'ro',
53 default => sub { [qw( MySQL SQLite PostgreSQL )] },
54);
55
334bced5 56has _filedata => (
57 isa => 'ArrayRef[Str]',
58 is => 'rw',
59);
60
a7d53deb 61has txn_wrap => (
62 is => 'ro',
63 isa => 'Bool',
64 default => 1,
65);
66
76d311e7 67method __ddl_consume_with_prefix($type, $versions, $prefix) {
262166c1 68 my $base_dir = $self->upgrade_directory;
69
76d08d08 70 my $main = catfile( $base_dir, $type );
71 my $generic = catfile( $base_dir, '_generic' );
72 my $common =
73 catfile( $base_dir, '_common', $prefix, join q(-), @{$versions} );
262166c1 74
75 my $dir;
76 if (-d $main) {
76d08d08 77 $dir = catfile($main, $prefix, join q(-), @{$versions})
262166c1 78 } elsif (-d $generic) {
76d08d08 79 $dir = catfile($main, $prefix, join q(-), @{$versions})
262166c1 80 } else {
81 die 'PREPARE TO SQL'
82 }
83
84 opendir my($dh), $dir;
85 my %files = map { $_ => "$dir/$_" } grep { /\.sql$/ && -f "$dir/$_" } readdir($dh);
86 closedir $dh;
87
88 if (-d $common) {
89 opendir my($dh), $common;
90 for my $filename (grep { /\.sql$/ && -f "$common/$_" } readdir($dh)) {
91 unless ($files{$filename}) {
92 $files{$filename} = "$common/$_";
93 }
94 }
95 closedir $dh;
96 }
97
98 return [@files{sort keys %files}]
99}
3c1b5ee8 100
76d311e7 101method _ddl_schema_consume_filenames($type, $version) {
102 $self->__ddl_consume_with_prefix($type, [ $version ], 'schema')
3c1b5ee8 103}
104
76d311e7 105method _ddl_schema_produce_filename($type, $version) {
76d08d08 106 my $dirname = catfile( $self->upgrade_directory, $type, 'schema', $version );
107 mkpath($dirname) unless -d $dirname;
d54b8d69 108
76d08d08 109 return catfile( $dirname, '001-auto.sql' );
d54b8d69 110}
111
76d311e7 112method _ddl_schema_up_consume_filenames($type, $versions) {
113 $self->__ddl_consume_with_prefix($type, $versions, 'up')
3c1b5ee8 114}
115
76d311e7 116method _ddl_schema_down_consume_filenames($type, $versions) {
117 $self->__ddl_consume_with_prefix($type, $versions, 'down')
a41a04e5 118}
119
76d311e7 120method _ddl_schema_up_produce_filename($type, $versions) {
121 my $dir = $self->upgrade_directory;
122
76d08d08 123 my $dirname = catfile( $dir, $type, 'up', join q(-), @{$versions});
124 mkpath($dirname) unless -d $dirname;
a41a04e5 125
76d08d08 126 return catfile( $dirname, '001-auto.sql'
a41a04e5 127 );
128}
129
76d311e7 130method _ddl_schema_down_produce_filename($type, $versions, $dir) {
76d08d08 131 my $dirname = catfile( $dir, $type, 'down', join q(-), @{$versions} );
132 mkpath($dirname) unless -d $dirname;
24f4524b 133
76d08d08 134 return catfile( $dirname, '001-auto.sql');
24f4524b 135}
136
7521a845 137sub _deploy {
138 my $self = shift;
2e68a8e1 139 my $storage = $self->storage;
140
a7d53deb 141 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
142
566925df 143 my @sql = map @{$self->_read_sql_file($_)}, @{$self->_ddl_schema_consume_filenames(
0d19af1d 144 $self->storage->sqlt_type,
145 $self->schema_version
566925df 146 )};
147
148 foreach my $line (@sql) {
2e68a8e1 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);
2e68a8e1 159 }
a7d53deb 160
161 $guard->commit if $self->txn_wrap;
566925df 162 return join "\n", @sql;
2e68a8e1 163}
164
7521a845 165sub prepare_install {
166 my $self = shift;
2e68a8e1 167 my $schema = $self->schema;
168 my $databases = $self->databases;
169 my $dir = $self->upgrade_directory;
170 my $sqltargs = $self->sqltargs;
d54b8d69 171 my $version = $schema->schema_version;
172
9600776d 173 my $sqlt = SQL::Translator->new({
d54b8d69 174 add_drop_table => 1,
2e68a8e1 175 ignore_constraint_names => 1,
d54b8d69 176 ignore_index_names => 1,
177 parser => 'SQL::Translator::Parser::DBIx::Class',
3aaf766f 178 %{$sqltargs}
9600776d 179 });
2e68a8e1 180
d53e0bfc 181 my $sqlt_schema = $sqlt->translate( data => $schema )
3aaf766f 182 or $self->throw_exception($sqlt->error);
2e68a8e1 183
184 foreach my $db (@$databases) {
185 $sqlt->reset;
186 $sqlt->{schema} = $sqlt_schema;
187 $sqlt->producer($db);
188
76d311e7 189 my $filename = $self->_ddl_schema_produce_filename($db, $version, $dir);
9600776d 190 if (-e $filename ) {
2e68a8e1 191 carp "Overwriting existing DDL file - $filename";
192 unlink $filename;
193 }
194
195 my $output = $sqlt->translate;
196 if(!$output) {
197 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
198 next;
199 }
200 my $file;
201 unless( open $file, q(>), $filename ) {
202 $self->throw_exception("Can't open $filename for writing ($!)");
203 next;
204 }
205 print {$file} $output;
206 close $file;
207 }
208}
209
a41a04e5 210sub prepare_upgrade {
9600776d 211 my ($self, $from_version, $to_version, $version_set) = @_;
212
213 $from_version ||= $self->db_version;
214 $to_version ||= $self->schema_version;
215
63e95f13 216 # for updates prepared automatically (rob's stuff)
217 # one would want to explicitly set $version_set to
218 # [$to_version]
9600776d 219 $version_set ||= [$from_version, $to_version];
76d311e7 220
221 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up');
222}
223
224sub prepare_downgrade {
225 my ($self, $from_version, $to_version, $version_set) = @_;
226
227 $from_version ||= $self->db_version;
228 $to_version ||= $self->schema_version;
229
230 # for updates prepared automatically (rob's stuff)
231 # one would want to explicitly set $version_set to
232 # [$to_version]
233 $version_set ||= [$from_version, $to_version];
234
235 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'down');
236}
237
238method _prepare_changegrade($from_version, $to_version, $version_set, $direction) {
2e68a8e1 239 my $schema = $self->schema;
240 my $databases = $self->databases;
241 my $dir = $self->upgrade_directory;
242 my $sqltargs = $self->sqltargs;
243
9600776d 244 my $schema_version = $schema->schema_version;
2e68a8e1 245
246 $sqltargs = {
247 add_drop_table => 1,
248 ignore_constraint_names => 1,
249 ignore_index_names => 1,
250 %{$sqltargs}
251 };
252
253 my $sqlt = SQL::Translator->new( $sqltargs );
254
255 $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
d53e0bfc 256 my $sqlt_schema = $sqlt->translate( data => $schema )
2e68a8e1 257 or $self->throw_exception ($sqlt->error);
258
259 foreach my $db (@$databases) {
260 $sqlt->reset;
261 $sqlt->{schema} = $sqlt_schema;
262 $sqlt->producer($db);
263
76d311e7 264 my $prefilename = $self->_ddl_schema_produce_filename($db, $from_version, $dir);
2e68a8e1 265 unless(-e $prefilename) {
266 carp("No previous schema file found ($prefilename)");
267 next;
268 }
76d311e7 269 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
270 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
2e68a8e1 271 if(-e $diff_file) {
76d311e7 272 carp("Overwriting existing $direction-diff file - $diff_file");
2e68a8e1 273 unlink $diff_file;
274 }
275
276 my $source_schema;
277 {
278 my $t = SQL::Translator->new({
279 %{$sqltargs},
280 debug => 0,
281 trace => 0,
282 });
283
284 $t->parser( $db ) # could this really throw an exception?
285 or $self->throw_exception ($t->error);
286
287 my $out = $t->translate( $prefilename )
288 or $self->throw_exception ($t->error);
289
290 $source_schema = $t->schema;
291
292 $source_schema->name( $prefilename )
293 unless $source_schema->name;
294 }
295
296 # The "new" style of producers have sane normalization and can support
297 # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
298 # And we have to diff parsed SQL against parsed SQL.
299 my $dest_schema = $sqlt_schema;
300
301 unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) {
302 my $t = SQL::Translator->new({
303 %{$sqltargs},
304 debug => 0,
305 trace => 0,
306 });
307
308 $t->parser( $db ) # could this really throw an exception?
309 or $self->throw_exception ($t->error);
310
76d311e7 311 my $filename = $self->_ddl_schema_produce_filename($db, $to_version, $dir);
2e68a8e1 312 my $out = $t->translate( $filename )
313 or $self->throw_exception ($t->error);
314
315 $dest_schema = $t->schema;
316
317 $dest_schema->name( $filename )
318 unless $dest_schema->name;
319 }
320
321 my $diff = SQL::Translator::Diff::schema_diff(
322 $source_schema, $db,
323 $dest_schema, $db,
324 $sqltargs
325 );
326 my $file;
327 unless(open $file, q(>), $diff_file) {
328 $self->throw_exception("Can't write to $diff_file ($!)");
329 next;
330 }
331 print {$file} $diff;
332 close $file;
333 }
334}
335
334bced5 336method _read_sql_file($file) {
337 return unless $file;
338
0d19af1d 339 open my $fh, '<', $file or carp("Can't open sql file, $file ($!)");
340 my @data = split /;\n/, join '', <$fh>;
334bced5 341 close $fh;
342
343 @data = grep {
0d19af1d 344 $_ && # remove blank lines
345 !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/ # strip txn's
346 } map {
347 s/^\s+//; s/\s+$//; # trim whitespace
348 join '', grep { !/^--/ } split /\n/ # remove comments
349 } @data;
334bced5 350
351 return \@data;
352}
353
76d311e7 354# these are exactly the same for now
355sub _downgrade_single_step {
356 my $self = shift;
357 my @version_set = @{ shift @_ };
358 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
359 $self->storage->sqlt_type,
360 \@version_set,
361 )};
362
363 for my $upgrade_file (@upgrade_files) {
364 unless (-f $upgrade_file) {
365 # croak?
366 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
367 return;
368 }
369
370 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
3249629f 371
372 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
373 $self->_do_upgrade;
374 $guard->commit if $self->txn_wrap;
76d311e7 375 }
376}
377
7521a845 378sub _upgrade_single_step {
379 my $self = shift;
24f4524b 380 my @version_set = @{ shift @_ };
76d311e7 381 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
334bced5 382 $self->storage->sqlt_type,
24f4524b 383 \@version_set,
3c1b5ee8 384 )};
334bced5 385
3c1b5ee8 386 for my $upgrade_file (@upgrade_files) {
387 unless (-f $upgrade_file) {
388 # croak?
389 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
390 return;
391 }
334bced5 392
3c1b5ee8 393 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
3249629f 394 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
395 $self->_do_upgrade;
396 $guard->commit if $self->txn_wrap;
3c1b5ee8 397 }
334bced5 398}
399
8a7847f1 400method _do_upgrade { $self->_run_upgrade(qr/.*?/) }
334bced5 401
8a7847f1 402method _run_upgrade($stm) {
334bced5 403 return unless $self->_filedata;
404 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
405
406 for (@statements) {
407 $self->storage->debugobj->query_start($_) if $self->storage->debug;
8a7847f1 408 $self->_apply_statement($_);
334bced5 409 $self->storage->debugobj->query_end($_) if $self->storage->debug;
410 }
411}
412
8a7847f1 413method _apply_statement($statement) {
334bced5 414 # croak?
415 $self->storage->dbh->do($_) or carp "SQL was: $_"
416}
417
2e68a8e1 4181;
e051bb00 419
420__END__
421
2eaf903b 422vim: ts=2 sw=2 expandtab