From: Jess Robinson Date: Wed, 24 Oct 2007 10:58:35 +0000 (+0000) Subject: 0.0899_01 diffing fixes X-Git-Tag: v0.11008~357 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=da5a1bae10b18456fedc2707f0361274e6c68a17;p=dbsrgits%2FSQL-Translator.git 0.0899_01 diffing fixes --- diff --git a/Build.PL b/Build.PL index 670d37f..da75416 100644 --- a/Build.PL +++ b/Build.PL @@ -1,4 +1,4 @@ -# $Id: Build.PL,v 1.12 2006-07-23 14:14:56 schiffbruechige Exp $ +# $Id: Build.PL,v 1.13 2007-10-24 10:55:44 schiffbruechige Exp $ use strict; use lib './lib'; @@ -15,6 +15,7 @@ my $builder = Module::Build->new( script_files => [ 'bin/sqlt-diagram', 'bin/sqlt-diff', + 'bin/sqlt-diff-old', 'bin/sqlt-dumper', 'bin/sqlt-graph', 'bin/sqlt', @@ -32,6 +33,7 @@ my $builder = Module::Build->new( 'Template' => 2.10, 'GD' => 0, 'GraphViz' => 0, + 'Graph::Directed' => 0, 'IO::File' => 0, 'IO::Scalar' => 0, 'Spreadsheet::ParseExcel' => 0.2602, diff --git a/Changes b/Changes index 2398ef2..4ca32f2 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,12 @@ + +# ----------------------------------------------------------- +# 0.0899_01 2007-10-21 +# ---------------------------------------------------------- + +* SQL::Translator::Diff now uses the ::Producer modules to create diffs + This *will* break back-compatibility + Use sqlt-diff-old for the previous one, and fix producers! + # ----------------------------------------------------------- # 0.08001 2007-09-26 # ---------------------------------------------------------- diff --git a/MANIFEST b/MANIFEST index ce779cd..0b5fa3b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -2,6 +2,7 @@ AUTHORS bin/sqlt bin/sqlt-diagram bin/sqlt-diff +bin/sqlt-diff-old bin/sqlt-dumper bin/sqlt-graph bin/sqlt.cgi @@ -114,6 +115,7 @@ t/26sybase.t t/27sqlite-parser.t t/29html.t t/30sqlt-diff.t +t/30sqlt-new-diff.t t/31dumper.t t/32schema-lookups.t t/33tt-table-producer.t diff --git a/META.yml b/META.yml index 7c39b69..b67b2df 100644 --- a/META.yml +++ b/META.yml @@ -1,6 +1,6 @@ --- name: SQL-Translator -version: 0.08_01 +version: 0.0899_01 author: - 'Ken Y. Clark ' abstract: SQL DDL transformations and more @@ -38,7 +38,7 @@ provides: file: lib/SQL/Translator/Parser/DB2/Grammar.pm SQL::Translator: file: lib/SQL/Translator.pm - version: 0.08_01 + version: 0.0899_01 SQL::Translator::Diff: file: lib/SQL/Translator/Diff.pm SQL::Translator::Filter::DefaultExtra: @@ -73,10 +73,10 @@ provides: version: 1.02 SQL::Translator::Parser::DBI::PostgreSQL: file: lib/SQL/Translator/Parser/DBI/PostgreSQL.pm - version: 1.09 + version: 1.10 SQL::Translator::Parser::DBI::SQLServer: file: lib/SQL/Translator/Parser/DBI/SQLServer.pm - version: 1.03 + version: 1.06 SQL::Translator::Parser::DBI::SQLite: file: lib/SQL/Translator/Parser/DBI/SQLite.pm version: 1.05 @@ -88,19 +88,19 @@ provides: version: 1.14 SQL::Translator::Parser::MySQL: file: lib/SQL/Translator/Parser/MySQL.pm - version: 1.54 + version: 1.58 SQL::Translator::Parser::Oracle: file: lib/SQL/Translator/Parser/Oracle.pm - version: 1.26 + version: 1.29 SQL::Translator::Parser::PostgreSQL: file: lib/SQL/Translator/Parser/PostgreSQL.pm - version: 1.47 + version: 1.48 SQL::Translator::Parser::SQLServer: file: lib/SQL/Translator/Parser/SQLServer.pm - version: 1.04 + version: 1.06 SQL::Translator::Parser::SQLite: file: lib/SQL/Translator/Parser/SQLite.pm - version: 1.11 + version: 1.12 SQL::Translator::Parser::Storable: file: lib/SQL/Translator/Parser/Storable.pm version: 1.05 @@ -124,7 +124,7 @@ provides: version: 1.08 SQL::Translator::Producer::DB2: file: lib/SQL/Translator/Producer/DB2.pm - version: 1.03 + version: 1.05 SQL::Translator::Producer::DiaUml: file: lib/SQL/Translator/Producer/DiaUml.pm version: 1.02 @@ -136,7 +136,7 @@ provides: version: 1.09 SQL::Translator::Producer::GraphViz: file: lib/SQL/Translator/Producer/GraphViz.pm - version: 1.12 + version: 1.14 SQL::Translator::Producer::HTML: file: lib/SQL/Translator/Producer/HTML.pm version: 1.15 @@ -145,7 +145,7 @@ provides: version: 1.01 SQL::Translator::Producer::MySQL: file: lib/SQL/Translator/Producer/MySQL.pm - version: 1.49 + version: 1.52 SQL::Translator::Producer::Oracle: file: lib/SQL/Translator/Producer/Oracle.pm version: 1.34 @@ -154,13 +154,13 @@ provides: version: 1.05 SQL::Translator::Producer::PostgreSQL: file: lib/SQL/Translator/Producer/PostgreSQL.pm - version: 1.24 + version: 1.29 SQL::Translator::Producer::SQLServer: file: lib/SQL/Translator/Producer/SQLServer.pm - version: 1.05 + version: 1.07 SQL::Translator::Producer::SQLite: file: lib/SQL/Translator/Producer/SQLite.pm - version: 1.14 + version: 1.15 SQL::Translator::Producer::Storable: file: lib/SQL/Translator/Producer/Storable.pm version: 1.05 @@ -175,7 +175,7 @@ provides: version: 1.03 SQL::Translator::Producer::TTSchema: file: lib/SQL/Translator/Producer/TTSchema.pm - version: 1.10 + version: 1.11 SQL::Translator::Producer::XML: file: lib/SQL/Translator/Producer/XML.pm version: 1.16 @@ -193,7 +193,7 @@ provides: version: 1.43 SQL::Translator::Schema::Constraint: file: lib/SQL/Translator/Schema/Constraint.pm - version: 1.19 + version: 1.21 SQL::Translator::Schema::Field: file: lib/SQL/Translator/Schema/Field.pm version: 1.26 @@ -211,13 +211,13 @@ provides: file: lib/SQL/Translator/Schema/Graph/Port.pm SQL::Translator::Schema::Index: file: lib/SQL/Translator/Schema/Index.pm - version: 1.14 + version: 1.17 SQL::Translator::Schema::Object: file: lib/SQL/Translator/Schema/Object.pm version: 1.08 SQL::Translator::Schema::Procedure: file: lib/SQL/Translator/Schema/Procedure.pm - version: 1.06 + version: 1.08 SQL::Translator::Schema::Table: file: lib/SQL/Translator/Schema/Table.pm version: 1.36 @@ -226,7 +226,7 @@ provides: version: 1.09 SQL::Translator::Schema::View: file: lib/SQL/Translator/Schema/View.pm - version: 1.12 + version: 1.14 SQL::Translator::Shell: file: lib/SQL/Translator/Shell.pm SQL::Translator::Utils: diff --git a/bin/sqlt-diff b/bin/sqlt-diff index 5e8167b..1cd80ca 100755 --- a/bin/sqlt-diff +++ b/bin/sqlt-diff @@ -2,7 +2,7 @@ # vim: set ft=perl: # ------------------------------------------------------------------- -# $Id: sqlt-diff,v 1.18 2007-03-21 15:20:50 duality72 Exp $ +# $Id: sqlt-diff,v 1.19 2007-10-24 10:55:44 schiffbruechige Exp $ # ------------------------------------------------------------------- # Copyright (C) 2002-4 The SQLFairy Authors # @@ -93,9 +93,10 @@ new index as such: =back -"ALTER/DROP TABLE" and "CREATE INDEX" statements B generated by -the Producer, unfortunately, and may require massaging before being passed to -your target database. +ALTER, CREATE, DROP statements are created by +SQL::Translator::Producer::*, see there for support/problems. + +Currently (v0.0900), only MySQL is supported by this code. =cut @@ -109,7 +110,7 @@ use SQL::Translator::Diff; use SQL::Translator::Schema::Constants; use vars qw( $VERSION ); -$VERSION = sprintf "%d.%02d", q$Revision: 1.18 $ =~ /(\d+)\.(\d+)/; +$VERSION = sprintf "%d.%02d", q$Revision: 1.19 $ =~ /(\d+)\.(\d+)/; my ( @input, $list, $help, $debug, $trace, $caseopt, $ignore_index_names, $ignore_constraint_names, $output_db, $mysql_parser_version, @@ -156,6 +157,8 @@ for my $arg ( @ARGV ) { } } +print STDERR "This code is experimental, currently the new code only supports MySQL diffing.\n To add support for other databases, please patch the relevant SQL::Translator::Producer:: module.\n If you need compatibility with the old sqlt-diff, please use sqlt-diff-old, and look into helping us make this one work for you.\n"; + pod2usage(1) if $help || !@ARGV; pod2usage('Please specify only two schemas to diff') if scalar @input > 2; diff --git a/bin/sqlt-diff-old b/bin/sqlt-diff-old new file mode 100755 index 0000000..342b705 --- /dev/null +++ b/bin/sqlt-diff-old @@ -0,0 +1,552 @@ +#!/usr/bin/perl -w +# vim: set ft=perl: + +# ------------------------------------------------------------------- +# $Id: sqlt-diff-old,v 1.1 2007-10-24 10:55:44 schiffbruechige Exp $ +# ------------------------------------------------------------------- +# Copyright (C) 2002-4 The SQLFairy Authors +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA +# ------------------------------------------------------------------- + +=head1 NAME + +sqlt-diff - find the differences b/w two schemas + +=head1 SYNOPSIS + +For help: + + sqlt-diff -h|--help + +For a list of all valid parsers: + + sqlt -l|--list + +To diff two schemas: + + sqlt-diff [options] file_name1=parser file_name2=parser + +Options: + + -d|--debug Show debugging info + +=head1 DESCRIPTION + +sqlt-diff is a utility for creating a file of SQL commands necessary to +transform the first schema provided to the second. While not yet +exhaustive in its ability to mutate the entire schema, it will report the +following + +=over + +=item * New tables + +Using the Producer class of the target (second) schema, any tables missing +in the first schema will be generated in their entirety (fields, constraints, +indices). + +=item * Missing/altered fields + +Any fields missing or altered between the two schemas will be reported +as: + + ALTER TABLE + [DROP ] + [CHANGE ()] ; + +=item * Missing/altered indices + +Any indices missing or of a different type or on different fields will be +indicated. Indices that should be dropped will be reported as such: + + DROP INDEX ON ; + +An index of a different type or on different fields will be reported as a +new index as such: + + CREATE [] INDEX [] ON + ( [,] ) ; + +=back + +"ALTER/DROP TABLE" and "CREATE INDEX" statements B generated by +the Producer, unfortunately, and may require massaging before being passed to +your target database. + +=cut + +# ------------------------------------------------------------------- + +use strict; +use Pod::Usage; +use Data::Dumper; +use SQL::Translator; +use SQL::Translator::Schema::Constants; + +use vars qw( $VERSION ); +$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/; + +my ( @input, $list, $help, $debug ); +for my $arg ( @ARGV ) { + if ( $arg =~ m/^-?-l(ist)?$/ ) { + $list = 1; + } + elsif ( $arg =~ m/^-?-h(elp)?$/ ) { + $help = 1; + } + elsif ( $arg =~ m/^-?-d(ebug)?$/ ) { + $debug = 1; + } + elsif ( $arg =~ m/^([^=]+)=(.+)$/ ) { + push @input, { file => $1, parser => $2 }; + } + else { + pod2usage( msg => "Unknown argument '$arg'" ); + } +} + +pod2usage(1) if $help; +pod2usage('Please specify only two schemas to diff') if scalar @input > 2; + +my $tr = SQL::Translator->new; +my @parsers = $tr->list_parsers; +my %valid_parsers = map { $_, 1 } @parsers; + +if ( $list ) { + print "\nParsers:\n", map { "\t$_\n" } sort @parsers; + print "\n"; + exit(0); +} + +pod2usage( msg => 'Too many file args' ) if @input > 2; + +my ( $source_schema, $source_db, $target_schema, $target_db ); + +my $i = 2; +for my $in ( @input ) { + my $file = $in->{'file'}; + my $parser = $in->{'parser'}; + + die "Unable to read file '$file'\n" unless -r $file; + die "'$parser' is an invalid parser\n" unless $valid_parsers{ $parser }; + + my $t = SQL::Translator->new; + $t->debug( $debug ); + $t->parser( $parser ) or die $tr->error; + my $out = $t->translate( $file ) or die $tr->error; + my $schema = $t->schema; + unless ( $schema->name ) { + $schema->name( $file ); + } + + if ( $i == 1 ) { + $source_schema = $schema; + $source_db = $parser; + } + else { + $target_schema = $schema; + $target_db = $parser; + } + $i--; +} +my $case_insensitive = $target_db =~ /SQLServer/; + +my $s1_name = $source_schema->name; +my $s2_name = $target_schema->name; +my ( @new_tables, @diffs , @diffs_at_end); +for my $t1 ( $source_schema->get_tables ) { + my $t1_name = $t1->name; + my $t2 = $target_schema->get_table( $t1_name, $case_insensitive ); + + warn "TABLE '$s1_name.$t1_name'\n" if $debug; + unless ( $t2 ) { + warn "Couldn't find table '$s1_name.$t1_name' in '$s2_name'\n" + if $debug; + if ( $target_db =~ /(SQLServer|Oracle)/ ) { + for my $constraint ( $t1->get_constraints ) { + next if $constraint->type ne FOREIGN_KEY; + push @diffs_at_end, "ALTER TABLE $t1_name ADD ". + constraint_to_string($constraint, $source_schema).";"; + $t1->drop_constraint($constraint); + } + } + push @new_tables, $t1; + next; + } + + # Go through our options + my $options_different = 0; + my %checkedOptions; +OPTION: + for my $t1_option_ref ( $t1->options ) { + my($key1, $value1) = %{$t1_option_ref}; + for my $t2_option_ref ( $t2->options ) { + my($key2, $value2) = %{$t2_option_ref}; + if ( $key1 eq $key2 ) { + if ( defined $value1 != defined $value2 ) { + $options_different = 1; + last OPTION; + } + if ( defined $value1 && $value1 ne $value2 ) { + $options_different = 1; + last OPTION; + } + $checkedOptions{$key1} = 1; + next OPTION; + } + } + $options_different = 1; + last OPTION; + } + # Go through the other table's options + unless ( $options_different ) { + for my $t2_option_ref ( $t2->options ) { + my($key, $value) = %{$t2_option_ref}; + next if $checkedOptions{$key}; + $options_different = 1; + last; + } + } + # If there's a difference, just re-set all the options + my @diffs_table_options; + if ( $options_different ) { + my @options = (); + foreach my $option_ref ( $t1->options ) { + my($key, $value) = %{$option_ref}; + push(@options, defined $value ? "$key=$value" : $key); + } + my $options = join(' ', @options); + @diffs_table_options = ("ALTER TABLE $t1_name $options;"); + } + + my $t2_name = $t2->name; + my(@diffs_table_adds, @diffs_table_changes); + for my $t1_field ( $t1->get_fields ) { + my $f1_type = $t1_field->data_type; + my $f1_size = $t1_field->size; + my $f1_name = $t1_field->name; + my $f1_nullable = $t1_field->is_nullable; + my $f1_default = $t1_field->default_value; + my $f1_auto_inc = $t1_field->is_auto_increment; + my $t2_field = $t2->get_field( $f1_name, $case_insensitive ); + my $f1_full_name = "$s1_name.$t1_name.$t1_name"; + warn "FIELD '$f1_full_name'\n" if $debug; + + my $f2_full_name = "$s2_name.$t2_name.$f1_name"; + + unless ( $t2_field ) { + warn "Couldn't find field '$f2_full_name' in '$t2_name'\n" + if $debug; + my $temp_default_value = 0; + if ( $target_db =~ /SQLServer/ && !$f1_nullable && !defined $f1_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); + $f1_default = grep($_ eq $f1_type, @numeric_types) ? 0 : ''; + } + push @diffs_table_adds, sprintf( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", + $t1_name, $target_db =~ /Oracle/ ? '(' : '', + $f1_name, $f1_type, + ($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', + !defined $f1_default ? '' + : uc $f1_default eq 'NULL' ? ' DEFAULT NULL' + : uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' + : " DEFAULT '$f1_default'", + $f1_nullable ? '' : ' NOT NULL', + $f1_auto_inc ? ' AUTO_INCREMENT' : '', + $target_db =~ /Oracle/ ? ')' : '', + ); + if ( $temp_default_value ) { + undef $f1_default; + push @diffs_table_adds, sprintf( <data_type; + my $f2_size = $t2_field->size || ''; + my $f2_nullable = $t2_field->is_nullable; + my $f2_default = $t2_field->default_value; + my $f2_auto_inc = $t2_field->is_auto_increment; + if ( !$t1_field->equals($t2_field, $case_insensitive) ) { + # SQLServer timestamp fields can't be altered, so we drop and add instead + if ( $target_db =~ /SQLServer/ && $f2_type eq "timestamp" ) { + push @diffs_table_changes, "ALTER TABLE $t1_name DROP COLUMN $f1_name;"; + push @diffs_table_changes, sprintf( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", + $t1_name, $target_db =~ /Oracle/ ? '(' : '', + $f1_name, $f1_type, + ($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', + !defined $f1_default ? '' + : uc $f1_default eq 'NULL' ? ' DEFAULT NULL' + : uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' + : " DEFAULT '$f1_default'", + $f1_nullable ? '' : ' NOT NULL', + $f1_auto_inc ? ' AUTO_INCREMENT' : '', + $target_db =~ /Oracle/ ? ')' : '', + ); + next; + } + + my $changeText = $target_db =~ /SQLServer/ ? 'ALTER COLUMN' : + $target_db =~ /Oracle/ ? 'MODIFY (' : 'CHANGE'; + my $nullText = $f1_nullable ? '' : ' NOT NULL'; + $nullText = '' if $target_db =~ /Oracle/ && $f1_nullable == $f2_nullable; + push @diffs_table_changes, sprintf( "ALTER TABLE %s %s %s%s %s%s%s%s%s%s;", + $t1_name, $changeText, + $f1_name, $target_db =~ /MySQL/ ? " $f1_name" : '', + $f1_type, ($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', + $nullText, + !defined $f1_default || $target_db =~ /SQLServer/ ? '' + : uc $f1_default eq 'NULL' ? ' DEFAULT NULL' + : uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' + : " DEFAULT '$f1_default'", + $f1_auto_inc ? ' AUTO_INCREMENT' : '', + $target_db =~ /Oracle/ ? ')' : '', + ); + if ( defined $f1_default && $target_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;", + $t1_name, $t1_name, $f1_name, uc $f1_default eq 'NULL' ? 'DEFAULT NULL' + : uc $f1_default eq 'CURRENT_TIMESTAMP' ? 'DEFAULT CURRENT_TIMESTAMP' + : "DEFAULT '$f1_default'", $f1_name, + ); + push @diffs_table_changes, sprintf( "UPDATE %s SET %s = %s WHERE %s IS NULL;", + $t1_name, $f1_name, uc $f1_default eq 'NULL' ? 'NULL' + : uc $f1_default eq 'CURRENT_TIMESTAMP' ? 'CURRENT_TIMESTAMP' + : "'$f1_default'", $f1_name, + ); + } + } + } + + my(%checked_indices, @diffs_index_creates, @diffs_index_drops); +INDEX: + for my $i1 ( $t1->get_indices ) { + for my $i2 ( $t2->get_indices ) { + if ( $i1->equals($i2, $case_insensitive) ) { + $checked_indices{$i2} = 1; + next INDEX; + } + } + push @diffs_index_creates, sprintf( + "CREATE %sINDEX%s ON %s (%s);", + $i1->type eq NORMAL ? '' : $i1->type." ", + $i1->name ? " ".$i1->name : '', + $t1_name, + join(",", $i1->fields), + ); + } +INDEX2: + for my $i2 ( $t2->get_indices ) { + next if $checked_indices{$i2}; + for my $i1 ( $t1->get_indices ) { + next INDEX2 if $i2->equals($i1, $case_insensitive); + } + $target_db =~ /SQLServer/ + ? push @diffs_index_drops, "DROP INDEX $t1_name.".$i2->name.";" + : push @diffs_index_drops, "DROP INDEX ".$i2->name." on $t1_name;"; + } + + my(%checked_constraints, @diffs_constraint_drops); +CONSTRAINT: + for my $c1 ( $t1->get_constraints ) { + next if $source_db =~ /Oracle/ && $c1->type eq UNIQUE && $c1->name =~ /^SYS_/i; + for my $c2 ( $t2->get_constraints ) { + if ( $c1->equals($c2, $case_insensitive) ) { + $checked_constraints{$c2} = 1; + next CONSTRAINT; + } + } + push @diffs_at_end, "ALTER TABLE $t1_name ADD ". + constraint_to_string($c1, $source_schema).";"; + } +CONSTRAINT2: + for my $c2 ( $t2->get_constraints ) { + next if $checked_constraints{$c2}; + for my $c1 ( $t1->get_constraints ) { + next CONSTRAINT2 if $c2->equals($c1, $case_insensitive); + } + if ( $c2->type eq UNIQUE ) { + push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP INDEX ". + $c2->name.";"; + } elsif ( $target_db =~ /SQLServer/ ) { + push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP ".$c2->name.";"; + } else { + push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP ".$c2->type. + ($c2->type eq FOREIGN_KEY ? " ".$c2->name : '').";"; + } + } + + push @diffs, @diffs_index_drops, @diffs_constraint_drops, + @diffs_table_options, @diffs_table_adds, + @diffs_table_changes, @diffs_index_creates; +} + +for my $t2 ( $target_schema->get_tables ) { + my $t2_name = $t2->name; + my $t1 = $source_schema->get_table( $t2_name, $target_db =~ /SQLServer/ ); + + unless ( $t1 ) { + if ( $target_db =~ /SQLServer/ ) { + for my $constraint ( $t2->get_constraints ) { + next if $constraint->type eq PRIMARY_KEY; + push @diffs, "ALTER TABLE $t2_name DROP ".$constraint->name.";"; + } + } + push @diffs_at_end, "DROP TABLE $t2_name;"; + next; + } + + for my $t2_field ( $t2->get_fields ) { + my $f2_name = $t2_field->name; + my $t1_field = $t1->get_field( $f2_name ); + unless ( $t1_field ) { + my $modifier = $target_db =~ /SQLServer/ ? "COLUMN " : ''; + push @diffs, "ALTER TABLE $t2_name DROP $modifier$f2_name;"; + } + } +} + +if ( @new_tables ) { + my $dummy_tr = SQL::Translator->new; + $dummy_tr->schema->add_table( $_ ) for @new_tables; + my $producer = $dummy_tr->producer( $target_db ); + unshift @diffs, $producer->( $dummy_tr ); +} +push(@diffs, @diffs_at_end); + +if ( @diffs ) { + if ( $source_db !~ /^(MySQL|SQLServer|Oracle)$/ ) { + unshift(@diffs, "-- Target database $target_db is untested/unsupported!!!"); + } +} + +print STDERR "sqlt-diff-old is deprecated, please try and use sqlt-diff, and tell us about any problems or patch SQL::Translator::Diff\n"; + +if ( @diffs ) { + print join( "\n", + "-- Convert schema '$s2_name' to '$s1_name':\n", @diffs, "\n" + ); + exit(1); +} +else { + print "There were no differences.\n"; +} + +sub constraint_to_string { + my $c = shift; + my $schema = shift or die "No schema given"; + my @fields = $c->field_names or return ''; + + if ( $c->type eq PRIMARY_KEY ) { + if ( $target_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 ( $target_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"; + } + } + + 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'; + } + + if ( $c->on_delete ) { + $def .= ' ON DELETE '.join( ' ', $c->on_delete ); + } + + if ( $c->on_update ) { + $def .= ' ON UPDATE '.join( ' ', $c->on_update ); + } + + return $def; + } +} + +# ------------------------------------------------------------------- +# Bring out number weight & measure in a year of dearth. +# William Blake +# ------------------------------------------------------------------- + +=pod + +=head1 AUTHOR + +Ken Y. Clark Ekclark@cpan.orgE. + +=head1 SEE ALSO + +SQL::Translator, L. + +=cut diff --git a/lib/SQL/Translator.pm b/lib/SQL/Translator.pm index 88df169..89e7e29 100644 --- a/lib/SQL/Translator.pm +++ b/lib/SQL/Translator.pm @@ -1,7 +1,7 @@ package SQL::Translator; # ---------------------------------------------------------------------- -# $Id: Translator.pm,v 1.72 2007-09-26 13:20:09 schiffbruechige Exp $ +# $Id: Translator.pm,v 1.73 2007-10-24 10:55:45 schiffbruechige Exp $ # ---------------------------------------------------------------------- # Copyright (C) 2002-4 The SQLFairy Authors # @@ -26,8 +26,8 @@ use base 'Class::Base'; require 5.004; -$VERSION = '0.08001'; -$REVISION = sprintf "%d.%02d", q$Revision: 1.72 $ =~ /(\d+)\.(\d+)/; +$VERSION = '0.0899_01'; +$REVISION = sprintf "%d.%02d", q$Revision: 1.73 $ =~ /(\d+)\.(\d+)/; $DEBUG = 0 unless defined $DEBUG; $ERROR = ""; diff --git a/lib/SQL/Translator/Diff.pm b/lib/SQL/Translator/Diff.pm index 43b688b..91b45e1 100644 --- a/lib/SQL/Translator/Diff.pm +++ b/lib/SQL/Translator/Diff.pm @@ -2,13 +2,24 @@ 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); + # 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; @@ -20,420 +31,208 @@ sub schema_diff my $tar_name = $target_schema->name; my $src_name = $source_schema->name; - my ( @new_tables, @diffs , @diffs_at_end); - 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 ); - 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 ( $output_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, $output_db, $target_schema).";"; - $tar_table->drop_constraint($constraint); - } - } - push @new_tables, $tar_table; - next; - } + 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 ); + + 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; + } - # 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; - } + # 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; } - $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.$f_tar_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 ( $output_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, $output_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' : '', - $output_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 ( $output_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, $output_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' : '', - $output_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 = $output_db =~ /SQLServer/ ? 'ALTER COLUMN' : - $output_db =~ /Oracle/ ? 'MODIFY (' : 'CHANGE'; - my $nullText = $f_tar_nullable ? '' : ' NOT NULL'; - $nullText = '' if $output_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, $output_db =~ /MySQL/ ? " $f_tar_name" : '', - $f_tar_type, ($f_tar_size && $f_tar_type !~ /(blob|text)$/) ? "($f_tar_size)" : '', - $nullText, - !defined $f_tar_default || $output_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' : '', - $output_db =~ /Oracle/ ? ')' : '', - ); - if ( defined $f_tar_default && $output_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, $ignore_index_names) ) { - $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 !$ignore_index_names && $checked_indices{$i_src}; - for my $i_tar ( $tar_table->get_indices ) { - next INDEX2 if $i_src->equals($i_tar, $case_insensitive, $ignore_index_names); + if ( $i_tar->equals($i_src, $case_insensitive, $ignore_index_names) ) { + $checked_indices{$i_src} = 1; + next INDEX_CREATE; } - $output_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 ) { for my $c_src ( $src_table->get_constraints ) { - if ( $c_tar->equals($c_src, $case_insensitive, $ignore_constraint_names) ) { + 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, $output_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 !$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, $ignore_constraint_names); - } - if ( $c_src->type eq UNIQUE ) { - push @diffs_constraint_drops, "ALTER TABLE $tar_table_name DROP INDEX ". - $c_src->name.";"; - } elsif ( $output_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, $case_insensitive ); unless ( $tar_table ) { - if ( $output_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, $case_insensitive ); - unless ( $tar_table_field ) { - my $modifier = $output_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( $output_db ); - unshift @diffs, $producer->( $dummytr ); - } - push(@diffs, @diffs_at_end); - - # Procedures - my(%checked_procs, @diffs_proc_creates, @diffs_proc_drops); - PROC: - for my $p_tar ( $target_schema->get_procedures ) { - for my $p_src ( $source_schema->get_procedures ) { - if ( $p_tar->equals($p_src, $case_insensitive, $ignore_proc_sql) ) { - $checked_procs{$p_src} = 1; - next PROC; - } - } - push @diffs_proc_creates, $p_tar->sql; - } - PROC2: - for my $p_src ( $source_schema->get_procedures ) { - next if $checked_procs{$p_src}; - for my $p_tar ( $target_schema->get_procedures ) { - next PROC2 if $p_src->equals($p_tar, $case_insensitive, $ignore_proc_sql); - } - my $proc_ident = $p_src->owner ? sprintf("[%s].%s", $p_src->owner, $p_src->name) : $p_src->name; - push @diffs_proc_drops, "DROP PROCEDURE $proc_ident;\nGO\n"; - } - - # Views - my(%checked_views, @diffs_view_creates, @diffs_view_drops); - VIEW: - for my $v_tar ( $target_schema->get_views ) { - for my $v_src ( $source_schema->get_views ) { - if ( $v_tar->equals($v_src, $case_insensitive, $ignore_view_sql) ) { - $checked_views{$v_src} = 1; - next VIEW; - } - } - push @diffs_view_creates, $v_tar->sql; - } - VIEW2: - for my $v_src ( $source_schema->get_views ) { - next if $checked_views{$v_src}; - for my $v_tar ( $target_schema->get_views ) { - next VIEW2 if $v_src->equals($v_tar, $case_insensitive, $ignore_view_sql); + push @diffs_dropped_tables, "DROP TABLE $src_table_name;"; + next; } - my $view_ident = $v_src->name; - push @diffs_view_drops, "DROP VIEW $view_ident;\nGO\n"; } - push @diffs, @diffs_view_drops, @diffs_proc_drops, - @diffs_view_creates, @diffs_proc_creates; + 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 ( @diffs ) { - if ( $target_db !~ /^(MySQL|SQLServer|Oracle)$/ ) { - unshift(@diffs, "-- Target database $target_db is untested/unsupported!!!"); - } - return join( "\n", - "-- Convert schema '$src_name' to '$tar_name':\n", @diffs, "\n" - ); + 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'); } - 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"; + 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" + ); } - - 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'; - } - - if ( $c->on_delete ) { - $def .= ' ON DELETE '.join( ' ', $c->on_delete ); - } - - if ( $c->on_update ) { - $def .= ' ON UPDATE '.join( ' ', $c->on_update ); - } - - return $def; + return undef; } -} 1; diff --git a/lib/SQL/Translator/Producer/MySQL.pm b/lib/SQL/Translator/Producer/MySQL.pm index 52adf89..69bcddc 100644 --- a/lib/SQL/Translator/Producer/MySQL.pm +++ b/lib/SQL/Translator/Producer/MySQL.pm @@ -1,7 +1,7 @@ package SQL::Translator::Producer::MySQL; # ------------------------------------------------------------------- -# $Id: MySQL.pm,v 1.52 2006-11-27 19:28:04 schiffbruechige Exp $ +# $Id: MySQL.pm,v 1.53 2007-10-24 10:55:44 schiffbruechige Exp $ # ------------------------------------------------------------------- # Copyright (C) 2002-4 SQLFairy Authors # @@ -92,8 +92,8 @@ Set the fields charater set and collation order. use strict; use warnings; -use vars qw[ $VERSION $DEBUG ]; -$VERSION = sprintf "%d.%02d", q$Revision: 1.52 $ =~ /(\d+)\.(\d+)/; +use vars qw[ $VERSION $DEBUG %used_names ]; +$VERSION = sprintf "%d.%02d", q$Revision: 1.53 $ =~ /(\d+)\.(\d+)/; $DEBUG = 0 unless defined $DEBUG; use Data::Dumper; @@ -131,6 +131,7 @@ my %translate = ( sub produce { my $translator = shift; local $DEBUG = $translator->debug; + local %used_names; my $no_comments = $translator->no_comments; my $add_drop_table = $translator->add_drop_table; my $schema = $translator->schema; @@ -141,7 +142,7 @@ sub produce { $qf = '`' if $translator->quote_field_names; debug("PKG: Beginning production\n"); - + %used_names = (); my $create; $create .= header_comment unless ($no_comments); # \todo Don't set if MySQL 3.x is set on command line @@ -227,10 +228,10 @@ sub create_table my $constr = create_constraint($c, $options); push @constraint_defs, $constr if($constr); - unless ( $indexed_fields{ ($c->fields())[0] } ) { - push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)"; - $indexed_fields{ ($c->fields())[0] } = 1; - } + unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) { + push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)"; + $indexed_fields{ ($c->fields())[0] } = 1; + } } $create .= join(",\n", map { " $_" } @@ -241,33 +242,42 @@ sub create_table # Footer # $create .= "\n)"; - my $table_type_defined = 0; - for my $t1_option_ref ( $table->options ) { - my($key, $value) = %{$t1_option_ref}; - $table_type_defined = 1 - if uc $key eq 'ENGINE' or uc $key eq 'TYPE'; - $create .= " $key=$value"; - } - my $mysql_table_type = $table->extra('mysql_table_type'); - #my $charset = $table->extra('mysql_character_set'); - #my $collate = $table->extra('mysql_collate'); - #$create .= " Type=$mysql_table_type" if $mysql_table_type; - #$create .= " DEFAULT CHARACTER SET $charset" if $charset; - #$create .= " COLLATE $collate" if $collate; - $create .= " Type=$mysql_table_type" - if $mysql_table_type && !$table_type_defined; - my $charset = $table->extra('mysql_charset'); - my $collate = $table->extra('mysql_collate'); - my $comments = $table->comments; - - $create .= " DEFAULT CHARACTER SET $charset" if $charset; - $create .= " COLLATE $collate" if $collate; - $create .= qq[ comment='$comments'] if $comments; + $create .= generate_table_options($table) || ''; $create .= ";\n\n"; return $drop ? ($drop,$create) : $create; } +sub generate_table_options +{ + my ($table) = @_; + my $create; + + my $table_type_defined = 0; + for my $t1_option_ref ( $table->options ) { + my($key, $value) = %{$t1_option_ref}; + $table_type_defined = 1 + if uc $key eq 'ENGINE' or uc $key eq 'TYPE'; + $create .= " $key=$value"; + } + my $mysql_table_type = $table->extra('mysql_table_type'); + #my $charset = $table->extra('mysql_character_set'); + #my $collate = $table->extra('mysql_collate'); + #$create .= " Type=$mysql_table_type" if $mysql_table_type; + #$create .= " DEFAULT CHARACTER SET $charset" if $charset; + #$create .= " COLLATE $collate" if $collate; + $create .= " Type=$mysql_table_type" + if $mysql_table_type && !$table_type_defined; + my $charset = $table->extra('mysql_charset'); + my $collate = $table->extra('mysql_collate'); + my $comments = $table->comments; + + $create .= " DEFAULT CHARACTER SET $charset" if $charset; + $create .= " COLLATE $collate" if $collate; + $create .= qq[ comment='$comments'] if $comments; + return $create; +} + sub create_field { my ($field, $options) = @_; @@ -376,6 +386,21 @@ sub create_field return $field_def; } +sub alter_create_index +{ + my ($index, $options) = @_; + + my $qt = $options->{quote_table_names} || ''; + my $qf = $options->{quote_field_names} || ''; + + return join( ' ', + 'ALTER TABLE', + $qt.$index->table->name.$qt, + 'ADD', + create_index(@_) + ); +} + sub create_index { my ($index, $options) = @_; @@ -383,19 +408,64 @@ sub create_index my $qf = $options->{quote_field_names} || ''; return join( ' ', - lc $index->type eq 'normal' ? 'INDEX' : $index->type, + lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX', $index->name, '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')' ); } +sub alter_drop_index +{ + my ($index, $options) = @_; + + my $qt = $options->{quote_table_names} || ''; + my $qf = $options->{quote_field_names} || ''; + + return join( ' ', + 'ALTER TABLE', + $qt.$index->table->name.$qt, + 'DROP', + 'INDEX', + $index->name || $index->fields + ); + +} + +sub alter_drop_constraint +{ + my ($c, $options) = @_; + + my $qt = $options->{quote_table_names} || ''; + my $qc = $options->{quote_constraint_names} || ''; + + my $out = sprintf('ALTER TABLE %s DROP %s %s', + $c->table->name, + $c->type, + $qc . $c->name . $qc ); + + return $out; +} + +sub alter_create_constraint +{ + my ($index, $options) = @_; + + my $qt = $options->{quote_table_names} || ''; + return join( ' ', + 'ALTER TABLE', + $qt.$index->table->name.$qt, + 'ADD', + create_constraint(@_) ); +} + sub create_constraint { my ($c, $options) = @_; my $qf = $options->{quote_field_names} || ''; my $qt = $options->{quote_table_names} || ''; + my $leave_name = $options->{leave_name} || undef; my $counter = ($options->{fk_name_counter} ||= {}); my @fields = $c->fields or next; @@ -414,17 +484,17 @@ sub create_constraint # Make sure FK field is indexed or MySQL complains. # + my $c_name = $c->name; $counter->{$c->table} ||= {}; my $def = join(' ', map { $_ || () } 'CONSTRAINT', - $qt . join('_', $c->table, - $c->name, - ($counter->{$c->table}{$c->name}++ || ()) + $qt . join('_', next_unused_name($c_name) ) . $qt, 'FOREIGN KEY' ); + $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')'; $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt; @@ -468,6 +538,20 @@ sub create_constraint return undef; } +sub alter_table +{ + my ($to_table, $options) = @_; + + my $qt = $options->{quote_table_name} || ''; + + my $table_options = generate_table_options($to_table) || ''; + my $out = sprintf('ALTER TABLE %s%s', + $qt . $to_table->name . $qt, + $table_options); + + return $out; +} + sub alter_field { my ($from_field, $to_field, $options) = @_; @@ -512,6 +596,22 @@ sub drop_field } +sub next_unused_name { + my $name = shift || ''; + if ( !defined($used_names{$name}) ) { + $used_names{$name} = $name; + return $name; + } + + my $i = 1; + while ( defined($used_names{$name . '_' . $i}) ) { + ++$i; + } + $name .= '_' . $i; + $used_names{$name} = $name; + return $name; +} + 1; # ------------------------------------------------------------------- diff --git a/lib/SQL/Translator/Schema.pm b/lib/SQL/Translator/Schema.pm index d102834..03e658a 100644 --- a/lib/SQL/Translator/Schema.pm +++ b/lib/SQL/Translator/Schema.pm @@ -3,7 +3,7 @@ package SQL::Translator::Schema; # vim: sw=4: ts=4: # ---------------------------------------------------------------------- -# $Id: Schema.pm,v 1.26 2006-06-07 16:43:41 schiffbruechige Exp $ +# $Id: Schema.pm,v 1.27 2007-10-24 10:58:35 schiffbruechige Exp $ # ---------------------------------------------------------------------- # Copyright (C) 2002-4 SQLFairy Authors # @@ -60,7 +60,7 @@ use SQL::Translator::Utils 'parse_list_arg'; use base 'SQL::Translator::Schema::Object'; use vars qw[ $VERSION $TABLE_ORDER $VIEW_ORDER $TRIGGER_ORDER $PROC_ORDER ]; -$VERSION = sprintf "%d.%02d", q$Revision: 1.26 $ =~ /(\d+)\.(\d+)/; +$VERSION = sprintf "%d.%02d", q$Revision: 1.27 $ =~ /(\d+)\.(\d+)/; __PACKAGE__->_attributes(qw/name database translator/); @@ -85,12 +85,14 @@ sub as_graph_pm { =pod -=head2 as_grap_pmh +=head2 as_graph_pm Returns a Graph::Directed object with the table names for nodes. =cut + require Graph::Directed; + my $self = shift; my $g = Graph::Directed->new; diff --git a/lib/SQL/Translator/Schema/Field.pm b/lib/SQL/Translator/Schema/Field.pm index 2385f99..5b3a189 100644 --- a/lib/SQL/Translator/Schema/Field.pm +++ b/lib/SQL/Translator/Schema/Field.pm @@ -1,7 +1,7 @@ package SQL::Translator::Schema::Field; # ---------------------------------------------------------------------- -# $Id: Field.pm,v 1.26 2005-08-10 16:44:17 duality72 Exp $ +# $Id: Field.pm,v 1.27 2007-10-24 10:55:44 schiffbruechige Exp $ # ---------------------------------------------------------------------- # Copyright (C) 2002-4 SQLFairy Authors # @@ -50,7 +50,7 @@ use base 'SQL::Translator::Schema::Object'; use vars qw($VERSION $TABLE_COUNT $VIEW_COUNT); -$VERSION = sprintf "%d.%02d", q$Revision: 1.26 $ =~ /(\d+)\.(\d+)/; +$VERSION = sprintf "%d.%02d", q$Revision: 1.27 $ =~ /(\d+)\.(\d+)/; # Stringify to our name, being careful not to pass any args through so we don't # accidentally set it to undef. We also have to tweak bool so the object is @@ -569,7 +569,7 @@ Determines if this field is the same as another return 0 unless $case_insensitive ? uc($self->name) eq uc($other->name) : $self->name eq $other->name; return 0 unless lc($self->data_type) eq lc($other->data_type); return 0 unless $self->size eq $other->size; - return 0 unless defined $self->default_value eq defined $other->default_value; + return 0 unless (!defined $self->default_value || $self->default_value eq 'NULL') eq (!defined $other->default_value || $other->default_value eq 'NULL'); return 0 if defined $self->default_value && $self->default_value ne $other->default_value; return 0 unless $self->is_nullable eq $other->is_nullable; # return 0 unless $self->is_unique eq $other->is_unique; diff --git a/lib/SQL/Translator/Schema/Index.pm b/lib/SQL/Translator/Schema/Index.pm index bae02de..583a2d9 100644 --- a/lib/SQL/Translator/Schema/Index.pm +++ b/lib/SQL/Translator/Schema/Index.pm @@ -1,7 +1,7 @@ package SQL::Translator::Schema::Index; # ---------------------------------------------------------------------- -# $Id: Index.pm,v 1.17 2007-03-06 23:50:23 duality72 Exp $ +# $Id: Index.pm,v 1.18 2007-10-24 10:55:44 schiffbruechige Exp $ # ---------------------------------------------------------------------- # Copyright (C) 2002-4 SQLFairy Authors # @@ -53,7 +53,7 @@ use base 'SQL::Translator::Schema::Object'; use vars qw($VERSION $TABLE_COUNT $VIEW_COUNT); -$VERSION = sprintf "%d.%02d", q$Revision: 1.17 $ =~ /(\d+)\.(\d+)/; +$VERSION = sprintf "%d.%02d", q$Revision: 1.18 $ =~ /(\d+)\.(\d+)/; my %VALID_INDEX_TYPE = ( UNIQUE, 1, @@ -252,8 +252,12 @@ Determines if this index is the same as another my $ignore_index_names = shift; return 0 unless $self->SUPER::equals($other); + unless ($ignore_index_names) { + unless ((!$self->name && ($other->name eq $other->fields->[0])) || + (!$other->name && ($self->name eq $self->fields->[0]))) { return 0 unless $case_insensitive ? uc($self->name) eq uc($other->name) : $self->name eq $other->name; + } } #return 0 unless $self->is_valid eq $other->is_valid; return 0 unless $self->type eq $other->type; diff --git a/lib/SQL/Translator/Schema/Table.pm b/lib/SQL/Translator/Schema/Table.pm index 4a8cdbd..e1bac58 100644 --- a/lib/SQL/Translator/Schema/Table.pm +++ b/lib/SQL/Translator/Schema/Table.pm @@ -1,7 +1,7 @@ package SQL::Translator::Schema::Table; # ---------------------------------------------------------------------- -# $Id: Table.pm,v 1.36 2005-08-10 16:45:40 duality72 Exp $ +# $Id: Table.pm,v 1.37 2007-10-24 10:55:44 schiffbruechige Exp $ # ---------------------------------------------------------------------- # Copyright (C) 2002-4 SQLFairy Authors # @@ -51,7 +51,7 @@ use base 'SQL::Translator::Schema::Object'; use vars qw( $VERSION $FIELD_ORDER ); -$VERSION = sprintf "%d.%02d", q$Revision: 1.36 $ =~ /(\d+)\.(\d+)/; +$VERSION = sprintf "%d.%02d", q$Revision: 1.37 $ =~ /(\d+)\.(\d+)/; # Stringify to our name, being careful not to pass any args through so we don't @@ -238,7 +238,9 @@ C object. $index = $index_class->new( \%args ) or return $self->error( $index_class->error ); } - + foreach my $ex_index ($self->get_indices) { + return if ($ex_index->equals($index)); + } push @{ $self->{'indices'} }, $index; return $index; } diff --git a/t/30sqlt-diff.t b/t/30sqlt-diff.t index 177b7d2..8a34d4a 100644 --- a/t/30sqlt-diff.t +++ b/t/30sqlt-diff.t @@ -8,7 +8,7 @@ use FindBin qw($Bin); use Test::More; use Test::SQL::Translator qw(maybe_plan); -my @script = qw(blib script sqlt-diff); +my @script = qw(blib script sqlt-diff-old); my @create1 = qw(data sqlite create.sql); my @create2 = qw(data sqlite create2.sql); @@ -25,7 +25,7 @@ my $create2 = (-d "t") : catfile($Bin, "t", @create2); BEGIN { - maybe_plan(23, + maybe_plan(21, 'SQL::Translator::Parser::SQLite', 'SQL::Translator::Parser::MySQL', 'SQL::Translator::Parser::Oracle', @@ -34,7 +34,6 @@ BEGIN { ok(-e $sqlt_diff, 'Found sqlt-diff script'); my @cmd = ($sqlt_diff, "$create1=SQLite", "$create2=SQLite"); - my $out = `@cmd`; like($out, qr/-- Target database SQLite is untested/, "Detected 'untested' comment"); @@ -45,7 +44,7 @@ like($out, qr/ALTER TABLE person ADD is_rock_star/, @cmd = ($sqlt_diff, "$create1=SQLite", "$create1=SQLite"); $out = `@cmd`; -like($out, qr/No differences found/, "Properly detected no differences"); +like($out, qr/There were no differences/, "Properly detected no differences"); my @mysql_create1 = qw(data mysql create.sql); my @mysql_create2 = qw(data mysql create2.sql); @@ -81,21 +80,11 @@ like($out, qr/ALTER TABLE employee ADD CONSTRAINT FK5302D47D93FE702E_diff/, "Detected add constraint"); unlike($out, qr/ALTER TABLE employee ADD PRIMARY KEY/, "Primary key looks different when it shouldn't"); -# Test ignore parameters -@cmd = ($sqlt_diff, "--ignore-index-names", "--ignore-constraint-names", - "$mysql_create1=MySQL", "$mysql_create2=MySQL"); -$out = `@cmd`; - -unlike($out, qr/CREATE UNIQUE INDEX unique_name/, - "Detected unique index with different name"); -unlike($out, qr/ALTER TABLE employee ADD CONSTRAINT FK5302D47D93FE702E_diff/, - "Detected add constraint"); - # Test for sameness @cmd = ($sqlt_diff, "$mysql_create1=MySQL", "$mysql_create1=MySQL"); $out = `@cmd`; -like($out, qr/No differences found/, "Properly detected no differences"); +like($out, qr/There were no differences/, "Properly detected no differences"); my @oracle_create1 = qw(data oracle create.sql); my @oracle_create2 = qw(data oracle create2.sql); diff --git a/t/30sqlt-new-diff.t b/t/30sqlt-new-diff.t new file mode 100644 index 0000000..094a919 --- /dev/null +++ b/t/30sqlt-new-diff.t @@ -0,0 +1,83 @@ +#!/usr/bin/perl +# vim: set ft=perl: + +use strict; + +use File::Spec::Functions qw(catfile updir tmpdir); +use FindBin qw($Bin); +use Test::More; +use Test::SQL::Translator qw(maybe_plan); + +my @script = qw(blib script sqlt-diff); +my @create1 = qw(data sqlite create.sql); +my @create2 = qw(data sqlite create2.sql); + +my $sqlt_diff = (-d "blib") + ? catfile($Bin, updir, @script) + : catfile($Bin, @script); + +my $create1 = (-d "t") + ? catfile($Bin, @create1) + : catfile($Bin, "t", @create1); + +my $create2 = (-d "t") + ? catfile($Bin, @create2) + : catfile($Bin, "t", @create2); + +BEGIN { + maybe_plan(16, 'SQL::Translator::Parser::MySQL', + ); +} + +ok(-e $sqlt_diff, 'Found sqlt-diff script'); + +my @mysql_create1 = qw(data mysql create.sql); +my @mysql_create2 = qw(data mysql create2.sql); + +my $mysql_create1 = (-d "t") + ? catfile($Bin, @mysql_create1) + : catfile($Bin, "t", @mysql_create1); + +my $mysql_create2 = (-d "t") + ? catfile($Bin, @mysql_create2) + : catfile($Bin, "t", @mysql_create2); + +# Test for differences +my @cmd = ($sqlt_diff, "$mysql_create1=MySQL", "$mysql_create2=MySQL"); +my $out = `@cmd`; + +unlike($out, qr/-- Target database MySQL is untested/, "Did not detect 'untested' comment"); +like($out, qr/ALTER TABLE person CHANGE COLUMN person_id/, "Detected altered 'person_id' field"); +like($out, qr/ALTER TABLE person CHANGE COLUMN iq/, "Detected altered 'iq' field"); +like($out, qr/ALTER TABLE person CHANGE COLUMN name/, "Detected altered 'name' field"); +like($out, qr/ALTER TABLE person CHANGE COLUMN age/, "Detected altered 'age' field"); +like($out, qr/ALTER TABLE person ADD COLUMN is_rock_star/, + "Detected missing rock star field"); +like($out, qr/ALTER TABLE person ADD UNIQUE UC_person_id/, + "Detected missing unique constraint"); +like($out, qr/ALTER TABLE person ADD UNIQUE INDEX unique_name/, + "Detected unique index with different name"); +like($out, qr/ALTER TABLE person ENGINE=InnoDB;/, + "Detected altered table option"); +like($out, qr/ALTER TABLE employee DROP FOREIGN KEY FK5302D47D93FE702E/, + "Detected drop foreign key"); +like($out, qr/ALTER TABLE employee ADD CONSTRAINT FK5302D47D93FE702E_diff/, + "Detected add constraint"); +unlike($out, qr/ALTER TABLE employee ADD PRIMARY KEY/, "Primary key looks different when it shouldn't"); + +# Test ignore parameters +@cmd = ($sqlt_diff, "--ignore-index-names", "--ignore-constraint-names", + "$mysql_create1=MySQL", "$mysql_create2=MySQL"); +$out = `@cmd`; + +unlike($out, qr/CREATE UNIQUE INDEX unique_name/, + "Detected unique index with different name"); +unlike($out, qr/ALTER TABLE employee ADD CONSTRAINT employee_FK5302D47D93FE702E_diff/, + "Detected add constraint"); + +# Test for sameness +@cmd = ($sqlt_diff, "$mysql_create1=MySQL", "$mysql_create1=MySQL"); +$out = `@cmd`; + +like($out, qr/No differences found/, "Properly detected no differences"); + diff --git a/t/38-mysql-producer.t b/t/38-mysql-producer.t index 03d9c60..b00db3b 100644 --- a/t/38-mysql-producer.t +++ b/t/38-mysql-producer.t @@ -118,8 +118,6 @@ my @stmts = ( `name` varchar(32), `swedish_name` varchar(32) CHARACTER SET swe7, `description` text CHARACTER SET utf8 COLLATE utf8_general_ci, - INDEX (`id`), - INDEX (`name`), PRIMARY KEY (`id`), UNIQUE `idx_unique_name` (`name`) ) Type=InnoDB DEFAULT CHARACTER SET latin1 COLLATE latin1_danish_ci;\n\n", @@ -129,12 +127,11 @@ my @stmts = ( `id` integer, `foo` integer, `foo2` integer, - INDEX (`id`), INDEX (`foo`), INDEX (`foo2`), PRIMARY KEY (`id`, `foo`), - CONSTRAINT `thing2_fk_thing` FOREIGN KEY (`foo`) REFERENCES `thing` (`id`), - CONSTRAINT `thing2_fk_thing_1` FOREIGN KEY (`foo2`) REFERENCES `thing` (`id`) + CONSTRAINT `fk_thing` FOREIGN KEY (`foo`) REFERENCES `thing` (`id`), + CONSTRAINT `fk_thing_1` FOREIGN KEY (`foo2`) REFERENCES `thing` (`id`) ) Type=InnoDB;\n\n", "SET foreign_key_checks=1;\n\n"