X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FTranslator%2FDiff.pm;h=91b45e17919b7298b2e514569b73c9482903d175;hb=f8ce4e88a16065368bbd8855b40fd0a56d7baa21;hp=9b608035533e2455194e77e0d59c2d70130b4261;hpb=99b0dca667d0de07e60f5fe49eb01d6fdbd6a9a0;p=dbsrgits%2FSQL-Translator.git diff --git a/lib/SQL/Translator/Diff.pm b/lib/SQL/Translator/Diff.pm index 9b60803..91b45e1 100644 --- a/lib/SQL/Translator/Diff.pm +++ b/lib/SQL/Translator/Diff.pm @@ -2,389 +2,237 @@ package SQL::Translator::Diff; ## SQLT schema diffing code use strict; use warnings; +use Data::Dumper; use SQL::Translator::Schema::Constants; sub schema_diff -{ -# use Data::Dumper; + { + # use Data::Dumper; + ## we are getting instructions on how to turn the source into the target + ## source == original, target == new (hmm, if I need to comment this, should I rename the vars again ??) + ## _schema isa SQL::Translator::Schema + ## _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) = @_; -# print Data::Dumper::Dumper($target_schema); - my $caseopt = $options->{caseopt} || 0; + # print Data::Dumper::Dumper($target_schema); + + my $producer_class = "SQL::Translator::Producer::$source_db"; + eval "require $producer_class"; + + my $case_insensitive = $options->{caseopt} || 0; my $debug = $options->{debug} || 0; my $trace = $options->{trace} || 0; - - my $case_insensitive = $source_db =~ /SQLServer/ || $caseopt; + my $ignore_index_names = $options->{ignore_index_names} || 0; + my $ignore_constraint_names = $options->{ignore_constraint_names} || 0; + my $ignore_view_sql = $options->{ignore_view_sql} || 0; + my $ignore_proc_sql = $options->{ignore_proc_sql} || 0; + my $output_db = $options->{output_db} || $source_db; my $tar_name = $target_schema->name; my $src_name = $source_schema->name; - my ( @new_tables, @diffs , @diffs_at_end); + + my ( @diffs_new_tables, @diffs_at_end, @new_tables, @diffs_index_drops, @diffs_constraint_drops, @diffs_table_drops, @diffs_table_adds, @diffs_index_creates, @diffs_constraint_creates, @diffs_table_options ); + ## do original/source tables exist in target? for my $tar_table ( $target_schema->get_tables ) { - my $tar_table_name = $tar_table->name; - my $src_table = $source_schema->get_table( $tar_table_name, $case_insensitive ); + my $tar_table_name = $tar_table->name; + my $src_table = $source_schema->get_table( $tar_table_name, $case_insensitive ); + + warn "TABLE '$tar_name.$tar_table_name'\n" if $debug; + unless ( $src_table ) { + warn "Couldn't find table '$tar_name.$tar_table_name' in '$src_name'\n" + if $debug; + ## table is new + ## add table(s) later. + my $cr_table = $producer_class->can('create_table') || die "$producer_class does not support create_table"; + my $new_table_sql = $cr_table->($tar_table, { leave_name => 1 }); + push (@diffs_new_tables, $new_table_sql); + push (@new_tables, $tar_table); + next; + } - warn "TABLE '$tar_name.$tar_table_name'\n" if $debug; - unless ( $src_table ) { - warn "Couldn't find table '$tar_name.$tar_table_name' in '$src_name'\n" - if $debug; - if ( $source_db =~ /(SQLServer|Oracle)/ ) { - for my $constraint ( $tar_table->get_constraints ) { - next if $constraint->type ne FOREIGN_KEY; - push @diffs_at_end, "ALTER TABLE $tar_table_name ADD ". - constraint_to_string($constraint, $source_db, $target_schema).";"; - $tar_table->drop_constraint($constraint); - } + # Go through our options + my $options_different = 0; + my %checkedOptions; + OPTION: + for my $tar_table_option_ref ( $tar_table->options ) { + my($key_tar, $value_tar) = %{$tar_table_option_ref}; + for my $src_table_option_ref ( $src_table->options ) { + my($key_src, $value_src) = %{$src_table_option_ref}; + if ( $key_tar eq $key_src ) { + if ( defined $value_tar != defined $value_src ) { + $options_different = 1; + last OPTION; } - push @new_tables, $tar_table; - next; - } - - # Go through our options - my $options_different = 0; - my %checkedOptions; - OPTION: - for my $tar_table_option_ref ( $tar_table->options ) { - my($key_tar, $value_tar) = %{$tar_table_option_ref}; - for my $src_table_option_ref ( $src_table->options ) { - my($key_src, $value_src) = %{$src_table_option_ref}; - if ( $key_tar eq $key_src ) { - if ( defined $value_tar != defined $value_src ) { - $options_different = 1; - last OPTION; - } - if ( defined $value_tar && $value_tar ne $value_src ) { - $options_different = 1; - last OPTION; - } - $checkedOptions{$key_tar} = 1; - next OPTION; - } - } - $options_different = 1; - last OPTION; - } - # Go through the other table's options - unless ( $options_different ) { - for my $src_table_option_ref ( $src_table->options ) { - my($key, $value) = %{$src_table_option_ref}; - next if $checkedOptions{$key}; - $options_different = 1; - last; + if ( defined $value_tar && $value_tar ne $value_src ) { + $options_different = 1; + last OPTION; } + $checkedOptions{$key_tar} = 1; + next OPTION; + } } - # If there's a difference, just re-set all the options - my @diffs_table_options; - if ( $options_different ) { - my @options = (); - foreach my $option_ref ( $tar_table->options ) { - my($key, $value) = %{$option_ref}; - push(@options, defined $value ? "$key=$value" : $key); - } - my $options = join(' ', @options); - @diffs_table_options = ("ALTER TABLE $tar_table_name $options;"); + $options_different = 1; + last OPTION; + } + # Go through the other table's options + unless ( $options_different ) { + for my $src_table_option_ref ( $src_table->options ) { + my($key, $value) = %{$src_table_option_ref}; + next if $checkedOptions{$key}; + $options_different = 1; + last; } + } + # If there's a difference, just re-set all the options + if ( $options_different ) { + my $al_table = $producer_class->can('alter_table') || die "$producer_class does not support alter_table"; + my $alter_sql = $al_table->( $tar_table ) . ';'; + @diffs_table_options = ("$alter_sql"); + } - my $src_table_name = $src_table->name; - my(@diffs_table_adds, @diffs_table_changes); - for my $tar_table_field ( $tar_table->get_fields ) { - my $f_tar_type = $tar_table_field->data_type; - my $f_tar_size = $tar_table_field->size; - my $f_tar_name = $tar_table_field->name; - my $f_tar_nullable = $tar_table_field->is_nullable; - my $f_tar_default = $tar_table_field->default_value; - my $f_tar_auto_inc = $tar_table_field->is_auto_increment; - my $src_table_field = $src_table->get_field( $f_tar_name, $case_insensitive ); - my $f_tar_full_name = "$tar_name.$tar_table_name.$tar_table_name"; - warn "FIELD '$f_tar_full_name'\n" if $debug; + my $src_table_name = $src_table->name; + ## Compare fields, their types, defaults, sizes etc etc + for my $tar_table_field ( $tar_table->get_fields ) { + my $f_tar_type = $tar_table_field->data_type; + my $f_tar_size = $tar_table_field->size; + my $f_tar_name = $tar_table_field->name; + my $f_tar_nullable = $tar_table_field->is_nullable; + my $f_tar_default = $tar_table_field->default_value; + my $f_tar_auto_inc = $tar_table_field->is_auto_increment; + my $src_table_field = $src_table->get_field( $f_tar_name, $case_insensitive ); + my $f_tar_full_name = "$tar_name.$tar_table_name.$f_tar_name"; + warn "FIELD '$f_tar_full_name'\n" if $debug; + + my $f_src_full_name = "$src_name.$src_table_name.$f_tar_name"; + + unless ( $src_table_field ) { + warn "Couldn't find field '$f_src_full_name' in '$src_table_name'\n" + if $debug; + + my $add_field = $producer_class->can('add_field') || die "$producer_class does not support add_field"; + my $alter_add_sql = $add_field->( $tar_table_field ) . ';'; + push (@diffs_table_adds, $alter_add_sql); + next; + } - my $f_src_full_name = "$src_name.$src_table_name.$f_tar_name"; + ## field exists, so what changed? + ## (do we care? just call equals to see IF) + if ( !$tar_table_field->equals($src_table_field, $case_insensitive) ) { + ## throw all this junk away and call producer->alter_field + ## check output same, etc etc - unless ( $src_table_field ) { - warn "Couldn't find field '$f_src_full_name' in '$src_table_name'\n" - if $debug; - my $temp_default_value = 0; - if ( $source_db =~ /SQLServer/ && - !$f_tar_nullable && - !defined $f_tar_default ) { - # SQL Server doesn't allow adding non-nullable, non-default columns - # so we add it with a default value, then remove the default value - $temp_default_value = 1; - my(@numeric_types) = qw(decimal numeric float real int bigint smallint tinyint); - $f_tar_default = grep($_ eq $f_tar_type, @numeric_types) ? 0 : ''; - } - push @diffs_table_adds, sprintf - ( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", - $tar_table_name, $source_db =~ /Oracle/ ? '(' : '', - $f_tar_name, $f_tar_type, - ($f_tar_size && $f_tar_type !~ /(blob|text)$/) ? "($f_tar_size)" : '', - !defined $f_tar_default ? '' - : uc $f_tar_default eq 'NULL' ? ' DEFAULT NULL' - : uc $f_tar_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' - : " DEFAULT '$f_tar_default'", - $f_tar_nullable ? '' : ' NOT NULL', - $f_tar_auto_inc ? ' AUTO_INCREMENT' : '', - $source_db =~ /Oracle/ ? ')' : '', - ); - if ( $temp_default_value ) { - undef $f_tar_default; - push @diffs_table_adds, sprintf - ( <can('alter_field') || die "$producer_class does not support alter_field"; + my $alter_field_sql = $al_field->( $src_table_field, $tar_table_field ) . ';'; + push (@diffs_table_adds, $alter_field_sql); + next; + } + } - my $f_src_type = $src_table_field->data_type; - my $f_src_size = $src_table_field->size || ''; - my $f_src_nullable = $src_table_field->is_nullable; - my $f_src_default = $src_table_field->default_value; - my $f_src_auto_inc = $src_table_field->is_auto_increment; - if ( !$tar_table_field->equals($src_table_field, $case_insensitive) ) { - # SQLServer timestamp fields can't be altered, so we drop and add instead - if ( $source_db =~ /SQLServer/ && $f_src_type eq "timestamp" ) { - push @diffs_table_changes, "ALTER TABLE $tar_table_name DROP COLUMN $f_tar_name;"; - push @diffs_table_changes, sprintf - ( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", - $tar_table_name, $source_db =~ /Oracle/ ? '(' : '', - $f_tar_name, $f_tar_type, - ($f_tar_size && $f_tar_type !~ /(blob|text)$/) ? "($f_tar_size)" : '', - !defined $f_tar_default ? '' - : uc $f_tar_default eq 'NULL' ? ' DEFAULT NULL' - : uc $f_tar_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' - : " DEFAULT '$f_tar_default'", - $f_tar_nullable ? '' : ' NOT NULL', - $f_tar_auto_inc ? ' AUTO_INCREMENT' : '', - $source_db =~ /Oracle/ ? ')' : '', - ); - next; - } + for my $src_table_field ( $src_table->get_fields ) { + my $f_src_name = $src_table_field->name; + my $tar_table_field = $tar_table->get_field( $f_src_name, $case_insensitive ); + my $f_src_full_name = "$tar_name.$tar_table_name.$f_src_name"; - my $changeText = $source_db =~ /SQLServer/ ? 'ALTER COLUMN' : - $source_db =~ /Oracle/ ? 'MODIFY (' : 'CHANGE'; - my $nullText = $f_tar_nullable ? '' : ' NOT NULL'; - $nullText = '' if $source_db =~ /Oracle/ && $f_tar_nullable == $f_src_nullable; - push @diffs_table_changes, sprintf - ( "ALTER TABLE %s %s %s%s %s%s%s%s%s%s;", - $tar_table_name, $changeText, - $f_tar_name, $source_db =~ /MySQL/ ? " $f_tar_name" : '', - $f_tar_type, ($f_tar_size && $f_tar_type !~ /(blob|text)$/) ? "($f_tar_size)" : '', - $nullText, - !defined $f_tar_default || $source_db =~ /SQLServer/ ? '' - : uc $f_tar_default eq 'NULL' ? ' DEFAULT NULL' - : uc $f_tar_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' - : " DEFAULT '$f_tar_default'", - $f_tar_auto_inc ? ' AUTO_INCREMENT' : '', - $source_db =~ /Oracle/ ? ')' : '', - ); - if ( defined $f_tar_default && $source_db =~ /SQLServer/ ) { - # Adding a column with a default value for SQL Server means adding a - # constraint and setting existing NULLs to the default value - push @diffs_table_changes, sprintf - ( "ALTER TABLE %s ADD CONSTRAINT DF_%s_%s %s FOR %s;", - $tar_table_name, $tar_table_name, $f_tar_name, uc $f_tar_default eq 'NULL' ? 'DEFAULT NULL' - : uc $f_tar_default eq 'CURRENT_TIMESTAMP' ? 'DEFAULT CURRENT_TIMESTAMP' - : "DEFAULT '$f_tar_default'", $f_tar_name, - ); - push @diffs_table_changes, sprintf - ( "UPDATE %s SET %s = %s WHERE %s IS NULL;", - $tar_table_name, $f_tar_name, uc $f_tar_default eq 'NULL' ? 'NULL' - : uc $f_tar_default eq 'CURRENT_TIMESTAMP' ? 'CURRENT_TIMESTAMP' - : "'$f_tar_default'", $f_tar_name, - ); - } - } - } + unless ( $tar_table_field ) { + warn "Couldn't find field '$f_src_full_name' in '$src_table_name'\n" + if $debug; - my(%checked_indices, @diffs_index_creates, @diffs_index_drops); - INDEX: - for my $i_tar ( $tar_table->get_indices ) { - for my $i_src ( $src_table->get_indices ) { - if ( $i_tar->equals($i_src, $case_insensitive) ) { - $checked_indices{$i_src} = 1; - next INDEX; - } - } - push @diffs_index_creates, sprintf - ( "CREATE %sINDEX%s ON %s (%s);", - $i_tar->type eq NORMAL ? '' : $i_tar->type." ", - $i_tar->name ? " ".$i_tar->name : '', - $tar_table_name, - join(",", $i_tar->fields), - ); + my $dr_field = $producer_class->can('drop_field') || die "$producer_class does not support drop_field"; + my $alter_drop_sql = $dr_field->( $src_table_field ) . ';'; + push (@diffs_table_drops, $alter_drop_sql); + next; } - INDEX2: + } + + my (%checked_indices); + INDEX_CREATE: + for my $i_tar ( $tar_table->get_indices ) { for my $i_src ( $src_table->get_indices ) { - next if $checked_indices{$i_src}; - for my $i_tar ( $tar_table->get_indices ) { - next INDEX2 if $i_src->equals($i_tar, $case_insensitive); + if ( $i_tar->equals($i_src, $case_insensitive, $ignore_index_names) ) { + $checked_indices{$i_src} = 1; + next INDEX_CREATE; } - $source_db =~ /SQLServer/ - ? push @diffs_index_drops, "DROP INDEX $tar_table_name.".$i_src->name.";" - : push @diffs_index_drops, "DROP INDEX ".$i_src->name." on $tar_table_name;"; } + my $al_cr_index = $producer_class->can('alter_create_index') || die "$producer_class does not support alter_create_index"; + my $create_index_sql = $al_cr_index->( $i_tar ) . ';'; + push ( @diffs_index_creates, $create_index_sql ); + } + INDEX_DROP: + for my $i_src ( $src_table->get_indices ) { + next if !$ignore_index_names && $checked_indices{$i_src}; + for my $i_tar ( $tar_table->get_indices ) { + next INDEX_DROP if $i_src->equals($i_tar, $case_insensitive, $ignore_index_names); + } + my $al_dr_index = $producer_class->can('alter_drop_index') || die "$producer_class does not support alter_drop_index"; + my $drop_index_sql = $al_dr_index->( $i_src ) . ';'; + push ( @diffs_index_drops, $drop_index_sql ); + } - my(%checked_constraints, @diffs_constraint_drops); - CONSTRAINT: + my(%checked_constraints); + CONSTRAINT_CREATE: for my $c_tar ( $tar_table->get_constraints ) { - next if $target_db =~ /Oracle/ && - $c_tar->type eq UNIQUE && $c_tar->name =~ /^SYS_/i; for my $c_src ( $src_table->get_constraints ) { - if ( $c_tar->equals($c_src, $case_insensitive) ) { + if ( $c_tar->equals($c_src, $case_insensitive, $ignore_constraint_names) ) { $checked_constraints{$c_src} = 1; - next CONSTRAINT; - } + next CONSTRAINT_CREATE; + } } - push @diffs_at_end, "ALTER TABLE $tar_table_name ADD ". - constraint_to_string($c_tar, $source_db, $target_schema).";"; + my $al_cr_const = $producer_class->can('alter_create_constraint') || die "$producer_class does not support alter_create_constraint"; + my $create_constraint_sql = $al_cr_const->( $c_tar, { leave_name => 1 }) . ';'; + push ( @diffs_constraint_creates, $create_constraint_sql ); } - CONSTRAINT2: + + CONSTRAINT_DROP: for my $c_src ( $src_table->get_constraints ) { - next if $source_db =~ /Oracle/ && - $c_src->type eq UNIQUE && $c_src->name =~ /^SYS_/i; - next if $checked_constraints{$c_src}; + next if !$ignore_constraint_names && $checked_constraints{$c_src}; for my $c_tar ( $tar_table->get_constraints ) { - next CONSTRAINT2 if $c_src->equals($c_tar, $case_insensitive); - } - if ( $c_src->type eq UNIQUE ) { - push @diffs_constraint_drops, "ALTER TABLE $tar_table_name DROP INDEX ". - $c_src->name.";"; - } elsif ( $source_db =~ /SQLServer/ ) { - push @diffs_constraint_drops, "ALTER TABLE $tar_table_name DROP ".$c_src->name.";"; - } else { - push @diffs_constraint_drops, "ALTER TABLE $tar_table_name DROP ".$c_src->type. - ($c_src->type eq FOREIGN_KEY ? " ".$c_src->name : '').";"; + next CONSTRAINT_DROP if $c_src->equals($c_tar, $case_insensitive, $ignore_constraint_names); } - } - push @diffs, @diffs_index_drops, @diffs_constraint_drops, - @diffs_table_options, @diffs_table_adds, - @diffs_table_changes, @diffs_index_creates; - } + my $al_dr_const = $producer_class->can('alter_drop_constraint') || die "$producer_class does not support alter_drop_constraint"; + my $drop_constraint_sql = $al_dr_const->( $c_src ) . ';'; + push ( @diffs_constraint_drops, $drop_constraint_sql ); + } + } + my @diffs_dropped_tables; for my $src_table ( $source_schema->get_tables ) { my $src_table_name = $src_table->name; - my $tar_table = $target_schema->get_table( $src_table_name, $source_db =~ /SQLServer/ ); + my $tar_table = $target_schema->get_table( $src_table_name, $case_insensitive ); unless ( $tar_table ) { - if ( $source_db =~ /SQLServer/ ) { - for my $constraint ( $src_table->get_constraints ) { - next if $constraint->type eq PRIMARY_KEY; - push @diffs, "ALTER TABLE $src_table_name DROP ".$constraint->name.";"; - } - } - push @diffs_at_end, "DROP TABLE $src_table_name;"; - next; - } - - for my $src_table_field ( $src_table->get_fields ) { - my $f_src_name = $src_table_field->name; - my $tar_table_field = $tar_table->get_field( $f_src_name ); - unless ( $tar_table_field ) { - my $modifier = $source_db =~ /SQLServer/ ? "COLUMN " : ''; - push @diffs, "ALTER TABLE $src_table_name DROP $modifier$f_src_name;"; + for my $c_src ( $src_table->get_constraints ) { + my $al_dr_const = $producer_class->can('alter_drop_constraint') || die "$producer_class does not support alter_drop_constraint"; + my $drop_constraint_sql = $al_dr_const->( $c_src ) . ';'; + push ( @diffs_constraint_drops, $drop_constraint_sql ); } - } - } - - if ( @new_tables ) { - my $dummytr = SQL::Translator->new; - $dummytr->schema->add_table( $_ ) for @new_tables; - my $producer = $dummytr->producer( $source_db ); - unshift @diffs, $producer->( $dummytr ); - } - push(@diffs, @diffs_at_end); - - if ( @diffs ) { - return join( "\n", - "-- Convert schema '$src_name' to '$tar_name':\n", @diffs, "\n" - ); - } - return undef; -} -sub constraint_to_string { - my $c = shift; - my $source_db = shift; - my $schema = shift or die "No schema given"; - my @fields = $c->field_names or return ''; - - if ( $c->type eq PRIMARY_KEY ) { - if ( $source_db =~ /Oracle/ ) { - return (defined $c->name ? 'CONSTRAINT '.$c->name.' ' : '') . - 'PRIMARY KEY (' . join(', ', @fields). ')'; - } else { - return 'PRIMARY KEY (' . join(', ', @fields). ')'; - } - } - elsif ( $c->type eq UNIQUE ) { - if ( $source_db =~ /Oracle/ ) { - return (defined $c->name ? 'CONSTRAINT '.$c->name.' ' : '') . - 'UNIQUE (' . join(', ', @fields). ')'; - } else { - return 'UNIQUE '. - (defined $c->name ? $c->name.' ' : ''). - '(' . join(', ', @fields). ')'; - } - } - elsif ( $c->type eq FOREIGN_KEY ) { - my $def = join(' ', - map { $_ || () } 'CONSTRAINT', $c->name, 'FOREIGN KEY' - ); - - $def .= ' (' . join( ', ', @fields ) . ')'; - - $def .= ' REFERENCES ' . $c->reference_table; - - my @rfields = map { $_ || () } $c->reference_fields; - unless ( @rfields ) { - my $rtable_name = $c->reference_table; - if ( my $ref_table = $schema->get_table( $rtable_name ) ) { - push @rfields, $ref_table->primary_key; - } - else { - warn "Can't find reference table '$rtable_name' " . - "in schema\n"; + push @diffs_dropped_tables, "DROP TABLE $src_table_name;"; + next; } } - if ( @rfields ) { - $def .= ' (' . join( ', ', @rfields ) . ')'; - } - else { - warn "FK constraint on " . 'some table' . '.' . - join('', @fields) . " has no reference fields\n"; - } - - if ( $c->match_type ) { - $def .= ' MATCH ' . - ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL'; - } + my @diffs; + push ( @diffs, @diffs_constraint_drops, @diffs_index_drops, @diffs_table_drops, @diffs_table_adds, @diffs_index_creates, @diffs_constraint_creates, @diffs_table_options ); + unshift (@diffs, "SET foreign_key_checks=0;\n\n", @diffs_new_tables, "SET foreign_key_checks=1;\n\n" ); + push (@diffs, @diffs_dropped_tables); - if ( $c->on_delete ) { - $def .= ' ON DELETE '.join( ' ', $c->on_delete ); + if(@diffs_constraint_drops+@diffs_index_drops+@diffs_table_drops+@diffs_table_adds+@diffs_index_creates+@diffs_constraint_creates+@diffs_table_options+@diffs_new_tables+@diffs_dropped_tables == 0 ) + { + @diffs = ('No differences found'); } - if ( $c->on_update ) { - $def .= ' ON UPDATE '.join( ' ', $c->on_update ); + if ( @diffs ) { +# if ( $target_db !~ /^(MySQL|SQLServer|Oracle)$/ ) { + if ( $target_db !~ /^(MySQL)$/ ) { + unshift(@diffs, "-- Target database $target_db is untested/unsupported!!!"); + } + return join( "\n", + "-- Convert schema '$src_name' to '$tar_name':\n", @diffs, "\n" + ); } - - return $def; + return undef; } -} 1;