insert ddl into database; fix tests to ingore that
[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
566925df 151 my @sql = map @{$self->_read_sql_file($_)}, @{$self->_ddl_schema_consume_filenames(
0d19af1d 152 $self->storage->sqlt_type,
153 $self->schema_version
566925df 154 )};
155
156 foreach my $line (@sql) {
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;
566925df 170 return join "\n", @sql;
2e68a8e1 171}
172
7521a845 173sub prepare_install {
174 my $self = shift;
2e68a8e1 175 my $schema = $self->schema;
176 my $databases = $self->databases;
177 my $dir = $self->upgrade_directory;
178 my $sqltargs = $self->sqltargs;
d54b8d69 179 my $version = $schema->schema_version;
180
9600776d 181 my $sqlt = SQL::Translator->new({
d54b8d69 182 add_drop_table => 1,
2e68a8e1 183 ignore_constraint_names => 1,
d54b8d69 184 ignore_index_names => 1,
185 parser => 'SQL::Translator::Parser::DBIx::Class',
3aaf766f 186 %{$sqltargs}
9600776d 187 });
2e68a8e1 188
d53e0bfc 189 my $sqlt_schema = $sqlt->translate( data => $schema )
3aaf766f 190 or $self->throw_exception($sqlt->error);
2e68a8e1 191
192 foreach my $db (@$databases) {
193 $sqlt->reset;
194 $sqlt->{schema} = $sqlt_schema;
195 $sqlt->producer($db);
196
76d311e7 197 my $filename = $self->_ddl_schema_produce_filename($db, $version, $dir);
9600776d 198 if (-e $filename ) {
2e68a8e1 199 carp "Overwriting existing DDL file - $filename";
200 unlink $filename;
201 }
202
203 my $output = $sqlt->translate;
204 if(!$output) {
205 carp("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
206 next;
207 }
208 my $file;
209 unless( open $file, q(>), $filename ) {
210 $self->throw_exception("Can't open $filename for writing ($!)");
211 next;
212 }
213 print {$file} $output;
214 close $file;
215 }
216}
217
a41a04e5 218sub prepare_upgrade {
9600776d 219 my ($self, $from_version, $to_version, $version_set) = @_;
220
221 $from_version ||= $self->db_version;
222 $to_version ||= $self->schema_version;
223
63e95f13 224 # for updates prepared automatically (rob's stuff)
225 # one would want to explicitly set $version_set to
226 # [$to_version]
9600776d 227 $version_set ||= [$from_version, $to_version];
76d311e7 228
229 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'up');
230}
231
232sub prepare_downgrade {
233 my ($self, $from_version, $to_version, $version_set) = @_;
234
235 $from_version ||= $self->db_version;
236 $to_version ||= $self->schema_version;
237
238 # for updates prepared automatically (rob's stuff)
239 # one would want to explicitly set $version_set to
240 # [$to_version]
241 $version_set ||= [$from_version, $to_version];
242
243 $self->_prepare_changegrade($from_version, $to_version, $version_set, 'down');
244}
245
246method _prepare_changegrade($from_version, $to_version, $version_set, $direction) {
2e68a8e1 247 my $schema = $self->schema;
248 my $databases = $self->databases;
249 my $dir = $self->upgrade_directory;
250 my $sqltargs = $self->sqltargs;
251
9600776d 252 my $schema_version = $schema->schema_version;
2e68a8e1 253
254 $sqltargs = {
255 add_drop_table => 1,
256 ignore_constraint_names => 1,
257 ignore_index_names => 1,
258 %{$sqltargs}
259 };
260
261 my $sqlt = SQL::Translator->new( $sqltargs );
262
263 $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
d53e0bfc 264 my $sqlt_schema = $sqlt->translate( data => $schema )
2e68a8e1 265 or $self->throw_exception ($sqlt->error);
266
267 foreach my $db (@$databases) {
268 $sqlt->reset;
269 $sqlt->{schema} = $sqlt_schema;
270 $sqlt->producer($db);
271
76d311e7 272 my $prefilename = $self->_ddl_schema_produce_filename($db, $from_version, $dir);
2e68a8e1 273 unless(-e $prefilename) {
274 carp("No previous schema file found ($prefilename)");
275 next;
276 }
76d311e7 277 my $diff_file_method = "_ddl_schema_${direction}_produce_filename";
278 my $diff_file = $self->$diff_file_method($db, $version_set, $dir );
2e68a8e1 279 if(-e $diff_file) {
76d311e7 280 carp("Overwriting existing $direction-diff file - $diff_file");
2e68a8e1 281 unlink $diff_file;
282 }
283
284 my $source_schema;
285 {
286 my $t = SQL::Translator->new({
287 %{$sqltargs},
288 debug => 0,
289 trace => 0,
290 });
291
292 $t->parser( $db ) # could this really throw an exception?
293 or $self->throw_exception ($t->error);
294
295 my $out = $t->translate( $prefilename )
296 or $self->throw_exception ($t->error);
297
298 $source_schema = $t->schema;
299
300 $source_schema->name( $prefilename )
301 unless $source_schema->name;
302 }
303
304 # The "new" style of producers have sane normalization and can support
305 # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
306 # And we have to diff parsed SQL against parsed SQL.
307 my $dest_schema = $sqlt_schema;
308
309 unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) {
310 my $t = SQL::Translator->new({
311 %{$sqltargs},
312 debug => 0,
313 trace => 0,
314 });
315
316 $t->parser( $db ) # could this really throw an exception?
317 or $self->throw_exception ($t->error);
318
76d311e7 319 my $filename = $self->_ddl_schema_produce_filename($db, $to_version, $dir);
2e68a8e1 320 my $out = $t->translate( $filename )
321 or $self->throw_exception ($t->error);
322
323 $dest_schema = $t->schema;
324
325 $dest_schema->name( $filename )
326 unless $dest_schema->name;
327 }
328
329 my $diff = SQL::Translator::Diff::schema_diff(
330 $source_schema, $db,
331 $dest_schema, $db,
332 $sqltargs
333 );
334 my $file;
335 unless(open $file, q(>), $diff_file) {
336 $self->throw_exception("Can't write to $diff_file ($!)");
337 next;
338 }
339 print {$file} $diff;
340 close $file;
341 }
342}
343
334bced5 344method _read_sql_file($file) {
345 return unless $file;
346
0d19af1d 347 open my $fh, '<', $file or carp("Can't open sql file, $file ($!)");
348 my @data = split /;\n/, join '', <$fh>;
334bced5 349 close $fh;
350
351 @data = grep {
0d19af1d 352 $_ && # remove blank lines
353 !/^(BEGIN|BEGIN TRANSACTION|COMMIT)/ # strip txn's
354 } map {
355 s/^\s+//; s/\s+$//; # trim whitespace
356 join '', grep { !/^--/ } split /\n/ # remove comments
357 } @data;
334bced5 358
359 return \@data;
360}
361
76d311e7 362# these are exactly the same for now
363sub _downgrade_single_step {
364 my $self = shift;
365 my @version_set = @{ shift @_ };
366 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
367 $self->storage->sqlt_type,
368 \@version_set,
369 )};
370
371 for my $upgrade_file (@upgrade_files) {
372 unless (-f $upgrade_file) {
373 # croak?
374 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
375 return;
376 }
377
378 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
3249629f 379
380 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
381 $self->_do_upgrade;
382 $guard->commit if $self->txn_wrap;
76d311e7 383 }
384}
385
7521a845 386sub _upgrade_single_step {
387 my $self = shift;
24f4524b 388 my @version_set = @{ shift @_ };
76d311e7 389 my @upgrade_files = @{$self->_ddl_schema_up_consume_filenames(
334bced5 390 $self->storage->sqlt_type,
24f4524b 391 \@version_set,
3c1b5ee8 392 )};
334bced5 393
3c1b5ee8 394 for my $upgrade_file (@upgrade_files) {
395 unless (-f $upgrade_file) {
396 # croak?
397 carp "Upgrade not possible, no upgrade file found ($upgrade_file), please create one\n";
398 return;
399 }
334bced5 400
3c1b5ee8 401 $self->_filedata($self->_read_sql_file($upgrade_file)); # I don't like this --fREW 2010-02-22
3249629f 402 my $guard = $self->schema->txn_scope_guard if $self->txn_wrap;
403 $self->_do_upgrade;
404 $guard->commit if $self->txn_wrap;
3c1b5ee8 405 }
334bced5 406}
407
8a7847f1 408method _do_upgrade { $self->_run_upgrade(qr/.*?/) }
334bced5 409
8a7847f1 410method _run_upgrade($stm) {
334bced5 411 return unless $self->_filedata;
412 my @statements = grep { $_ =~ $stm } @{$self->_filedata};
413
414 for (@statements) {
415 $self->storage->debugobj->query_start($_) if $self->storage->debug;
8a7847f1 416 $self->_apply_statement($_);
334bced5 417 $self->storage->debugobj->query_end($_) if $self->storage->debug;
418 }
419}
420
8a7847f1 421method _apply_statement($statement) {
334bced5 422 # croak?
423 $self->storage->dbh->do($_) or carp "SQL was: $_"
424}
425
2e68a8e1 4261;
e051bb00 427
428__END__
429
2eaf903b 430vim: ts=2 sw=2 expandtab