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