0.0899_01 diffing fixes
Jess Robinson [Wed, 24 Oct 2007 10:58:35 +0000 (10:58 +0000)]
16 files changed:
Build.PL
Changes
MANIFEST
META.yml
bin/sqlt-diff
bin/sqlt-diff-old [new file with mode: 0755]
lib/SQL/Translator.pm
lib/SQL/Translator/Diff.pm
lib/SQL/Translator/Producer/MySQL.pm
lib/SQL/Translator/Schema.pm
lib/SQL/Translator/Schema/Field.pm
lib/SQL/Translator/Schema/Index.pm
lib/SQL/Translator/Schema/Table.pm
t/30sqlt-diff.t
t/30sqlt-new-diff.t [new file with mode: 0644]
t/38-mysql-producer.t

index 670d37f..da75416 100644 (file)
--- 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 (file)
--- 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
 # ----------------------------------------------------------
index ce779cd..0b5fa3b 100644 (file)
--- 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
index 7c39b69..b67b2df 100644 (file)
--- 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 <kclark@cpan.org>'
 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:
index 5e8167b..1cd80ca 100755 (executable)
@@ -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<are not> 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 (executable)
index 0000000..342b705
--- /dev/null
@@ -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 <table_name> 
+    [DROP <field_name>] 
+    [CHANGE <field_name> <datatype> (<size>)] ;
+
+=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 <index_name> ON <table_name> ;
+
+An index of a different type or on different fields will be reported as a 
+new index as such:
+
+  CREATE [<index_type>] INDEX [<index_name>] ON <table_name> 
+    ( <field_name>[,<field_name>] ) ;
+
+=back
+
+"ALTER/DROP TABLE" and "CREATE INDEX" statements B<are not> 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( <<END
+DECLARE \@defname VARCHAR(100), \@cmd VARCHAR(1000)
+SET \@defname = 
+(SELECT name 
+FROM sysobjects so JOIN sysconstraints sc
+ON so.id = sc.constid 
+WHERE object_name(so.parent_obj) = '%s' 
+AND so.xtype = 'D'
+AND sc.colid = 
+ (SELECT colid FROM syscolumns 
+ WHERE id = object_id('%s') AND 
+ name = '%s'))
+SET \@cmd = 'ALTER TABLE %s DROP CONSTRAINT '
++ \@defname
+EXEC(\@cmd)
+END
+                                       , $t1_name, $t1_name, $f1_name, $t1_name,
+                   );
+            }
+            next;
+        }
+
+        my $f2_type = $t2_field->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 E<lt>kclark@cpan.orgE<gt>.
+
+=head1 SEE ALSO
+
+SQL::Translator, L<http://sqlfairy.sourceforge.net>.
+
+=cut
index 88df169..89e7e29 100644 (file)
@@ -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    = "";
 
index 43b688b..91b45e1 100644 (file)
@@ -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
-                        ( <<END
-DECLARE \@defname VARCHAR(100), \@cmd VARCHAR(1000)
-SET \@defname = 
-(SELECT name 
- FROM sysobjects so JOIN sysconstraints sc
-  ON so.id = sc.constid 
- WHERE object_name(so.parent_obj) = '%s' 
-   AND so.xtype = 'D'
-   AND sc.colid = 
-    (SELECT colid FROM syscolumns 
-     WHERE id = object_id('%s') AND 
-         name = '%s'))
-SET \@cmd = 'ALTER TABLE %s DROP CONSTRAINT '
-+ \@defname
-EXEC(\@cmd)
-END
-                         , $tar_table_name, $tar_table_name, $f_tar_name, $tar_table_name,
-                        );
-                  }
-                next;
-              }
+          my $al_field = $producer_class->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;
index 52adf89..69bcddc 100644 (file)
@@ -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;
 
 # -------------------------------------------------------------------
index d102834..03e658a 100644 (file)
@@ -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;
     
index 2385f99..5b3a189 100644 (file)
@@ -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;
index bae02de..583a2d9 100644 (file)
@@ -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;
index 4a8cdbd..e1bac58 100644 (file)
@@ -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<SQL::Translator::Schema::Index> 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;
 }
index 177b7d2..8a34d4a 100644 (file)
@@ -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 (file)
index 0000000..094a919
--- /dev/null
@@ -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");
+
index 03d9c60..b00db3b 100644 (file)
@@ -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"