-# $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';
script_files => [
'bin/sqlt-diagram',
'bin/sqlt-diff',
+ 'bin/sqlt-diff-old',
'bin/sqlt-dumper',
'bin/sqlt-graph',
'bin/sqlt',
'Template' => 2.10,
'GD' => 0,
'GraphViz' => 0,
+ 'Graph::Directed' => 0,
'IO::File' => 0,
'IO::Scalar' => 0,
'Spreadsheet::ParseExcel' => 0.2602,
+
+# -----------------------------------------------------------
+# 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
# ----------------------------------------------------------
bin/sqlt
bin/sqlt-diagram
bin/sqlt-diff
+bin/sqlt-diff-old
bin/sqlt-dumper
bin/sqlt-graph
bin/sqlt.cgi
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
---
name: SQL-Translator
-version: 0.08_01
+version: 0.0899_01
author:
- 'Ken Y. Clark <kclark@cpan.org>'
abstract: SQL DDL transformations and more
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:
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
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
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
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
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
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
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
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
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
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:
# 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
#
=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
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,
}
}
+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;
--- /dev/null
+#!/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
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
#
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 = "";
## 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;
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;
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
#
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;
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;
$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
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 { " $_" }
# 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) = @_;
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) = @_;
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;
# 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;
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) = @_;
}
+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;
# -------------------------------------------------------------------
# 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
#
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/);
=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;
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
#
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
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;
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
#
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,
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;
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
#
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
$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;
}
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);
: catfile($Bin, "t", @create2);
BEGIN {
- maybe_plan(23,
+ maybe_plan(21,
'SQL::Translator::Parser::SQLite',
'SQL::Translator::Parser::MySQL',
'SQL::Translator::Parser::Oracle',
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");
@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);
"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);
--- /dev/null
+#!/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");
+
`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",
`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"