package SQL::Translator::Diff;
-
## SQLT schema diffing code
use strict;
use warnings;
-
use Data::Dumper;
use SQL::Translator::Schema::Constants;
use base 'Class::Accessor::Fast';
+use vars qw[ $VERSION ];
+
+$VERSION = 1.60;
+
# Input/option accessors
__PACKAGE__->mk_accessors(qw/
ignore_index_names ignore_constraint_names ignore_view_sql
- ignore_proc_sql output_db source_schema source_db target_schema target_db
- case_insensitive no_batch_alters ignore_missing_methods
+ ignore_proc_sql output_db source_schema target_schema
+ case_insensitive no_batch_alters ignore_missing_methods producer_options
/);
my @diff_arrays = qw/
## _db is the name of the producer/db it came out of/into
## results are formatted to the source preferences
- my ($source_schema, $source_db, $target_schema, $target_db, $options) = @_;
+ my ($source_schema, $source_db, $target_schema, $output_db, $options) = @_;
$options ||= {};
my $obj = SQL::Translator::Diff->new( {
%$options,
source_schema => $source_schema,
- source_db => $source_db,
target_schema => $target_schema,
- target_db => $target_db
+ output_db => $output_db
} );
$obj->compute_differences->produce_diff_sql;
$values->{$_} ||= [] foreach @diff_arrays;
$values->{table_diff_hash} = {};
+ $values->{producer_options} ||= {};
$values->{output_db} ||= $values->{source_db};
return $class->SUPER::new($values);
}
die $@ if $@;
if (my $preprocess = $producer_class->can('preprocess_schema')) {
- $producer_class->$preprocess($source_schema);
- $producer_class->$preprocess($target_schema);
+ $preprocess->($source_schema);
+ $preprocess->($target_schema);
}
my %src_tables_checked = ();
{ map {
$func_map{$_} => $self->table_diff_hash->{$table}{$_}
} keys %func_map
- }
+ },
+ $self->producer_options
);
}
} else {
if (@{ $flattened_diffs{$_} || [] }) {
my $meth = $producer_class->can($_);
- $meth ? map { my $sql = $meth->(ref $_ eq 'ARRAY' ? @$_ : $_); $sql ? ("$sql;") : () } @{ $flattened_diffs{$_} }
+ $meth ? map {
+ my $sql = $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $self->producer_options );
+ $sql ? ("$sql") : ();
+ } @{ $flattened_diffs{$_} }
: $self->ignore_missing_methods
? "-- $producer_class cant $_"
: die "$producer_class cant $_";
add_drop_table => 0,
no_comments => 1,
# TODO: sort out options
- quote_table_names => 0,
- quote_field_names => 0,
+ %{ $self->producer_options }
);
+ $translator->producer_args->{no_transaction} = 1;
my $schema = $translator->schema;
$schema->add_table($_) for @tables;
unshift @diffs,
# Remove begin/commit here, since we wrap everything in one.
- grep { $_ !~ /^(?:COMMIT|START(?: TRANSACTION)?|BEGIN(?: TRANSACTION)?);/ } $producer_class->can('produce')->($translator);
+ grep { $_ !~ /^(?:COMMIT|START(?: TRANSACTION)?|BEGIN(?: TRANSACTION)?)/ } $producer_class->can('produce')->($translator);
}
if (my @tables_to_drop = @{ $self->{tables_to_drop} || []} ) {
my $meth = $producer_class->can('drop_table');
- push @diffs, $meth ? map( { $meth->($_) } @tables_to_drop )
+ push @diffs, $meth ? ( map { $meth->($_, $self->producer_options) } @tables_to_drop)
: $self->ignore_missing_methods
? "-- $producer_class cant drop_table"
: die "$producer_class cant drop_table";
}
if (@diffs) {
- unshift @diffs, "BEGIN TRANSACTION;\n";
- push @diffs, "\nCOMMIT;\n";
+ unshift @diffs, "BEGIN";
+ push @diffs, "\nCOMMIT";
} else {
- @diffs = ("-- No differences found\n\n");
+ @diffs = ("-- No differences found");
}
if ( @diffs ) {
- if ( $self->target_db !~ /^(?:MySQL|SQLite)$/ ) {
- unshift(@diffs, "-- Target database @{[$self->target_db]} is untested/unsupported!!!");
+ if ( $self->output_db !~ /^(?:MySQL|SQLite|PostgreSQL)$/ ) {
+ unshift(@diffs, "-- Output database @{[$self->output_db]} is untested/unsupported!!!");
}
- return join( "\n", "-- Convert schema '$src_name' to '$tar_name':\n", @diffs);
+
+ my @return =
+ map { $_ ? ( $_ =~ /;$/xms ? $_ : "$_;\n\n" ) : "\n" }
+ ("-- Convert schema '$src_name' to '$tar_name':", @diffs);
+
+ return wantarray ? @return : join('', @return);
}
return undef;
sub diff_table_options {
my ($self, $src_table, $tar_table) = @_;
+ my $cmp = sub {
+ my ($a_name, undef, $b_name, undef) = ( %$a, %$b );
+ $a_name cmp $b_name;
+ };
+ # Need to sort the options so we dont get supruious diffs.
+ my (@src_opts, @tar_opts);
+ @src_opts = sort $cmp $src_table->options;
+ @tar_opts = sort $cmp $tar_table->options;
+
# If there's a difference, just re-set all the options
push @{ $self->table_diff_hash->{$tar_table}{table_options} }, $tar_table
- unless $src_table->_compare_objects( scalar $src_table->options, scalar $tar_table->options );
+ unless $src_table->_compare_objects( \@src_opts, \@tar_opts );
}
1;
=item * C<batch_alter_table($table, $hash)> (optional)
-
-=back
-
If the producer supports C<batch_alter_table>, it will be called with the
table to alter and a hash, the keys of which will be the method names listed
above; values will be arrays of fields or constraints to operate on. In the
alter_field => [ [$old_field, $new_field] ]
}
+
+=item * C<preprocess_schema($class, $schema)> (optional)
+
+C<preprocess_schema> is called by the Diff code to allow the producer to
+normalize any data it needs to first. For example, the MySQL producer uses
+this method to ensure that FK contraint names are unique.
+
+Basicaly any changes that need to be made to produce the SQL file for the
+schema should be done here, so that a diff between a parsed SQL file and (say)
+a parsed DBIx::Class::Schema object will be sane.
+
+(As an aside, DBIx::Class, for instance, uses the presence of a
+C<preprocess_schema> function on the producer to know that it can diff between
+the previous SQL file and its own internal representation. Without this method
+on th producer it will diff the two SQL files which is slower, but known to
+work better on old-style producers.)
+
+=back
+
+
=head1 AUTHOR
Original Author(s) unknown.
-Refactor and more comprehensive tests by Ash Berlin C<< ash@cpan.org >>.
+Refactor/re-write and more comprehensive tests by Ash Berlin C<< ash@cpan.org >>.
Redevelopment sponsored by Takkle Inc.