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