Fix some more normalization problems
Ash Berlin [Thu, 20 Dec 2007 16:54:56 +0000 (16:54 +0000)]
lib/SQL/Translator/Diff.pm
lib/SQL/Translator/Parser/MySQL.pm
lib/SQL/Translator/Producer/MySQL.pm
lib/SQL/Translator/Schema/Field.pm
t/38-mysql-producer.t

index 2638e83..ac49b57 100644 (file)
@@ -73,10 +73,21 @@ sub compute_differences {
     my $target_schema = $self->target_schema;
     my $source_schema = $self->source_schema;
 
+    my $producer_class = "SQL::Translator::Producer::@{[$self->output_db]}";
+    eval "require $producer_class";
+    die $@ if $@;
+
+    if (my $preprocess = $producer_class->can('preprocess_schema')) {
+      $producer_class->$preprocess($source_schema);
+      $DB::single = 1;
+      $producer_class->$preprocess($target_schema);
+    }
+
     my @tar_tables = sort { $a->name cmp $b->name } $target_schema->get_tables;
     ## do original/source tables exist in target?
     for my $tar_table ( @tar_tables ) {
       my $tar_table_name = $tar_table->name;
+      $DB::single = 1 if $tar_table_name eq 'admin_contest';
       my $src_table      = $source_schema->get_table( $tar_table_name, $self->case_insensitive );
 
       unless ( $src_table ) {
@@ -296,6 +307,7 @@ sub diff_table_fields {
   for my $tar_table_field ( $tar_table->get_fields ) {
     my $f_tar_name      = $tar_table_field->name;
 
+    $DB::single = 1 if $f_tar_name eq 'invite_type';
     if (my $old_name = $tar_table_field->extra->{renamed_from}) {
       my $src_table_field = $src_table->get_field( $old_name, $self->case_insensitive );
       die qq#Renamed cant find "@{[$src_table->name]}.$old_name" for renamed column\n# unless $src_table_field;
@@ -344,45 +356,9 @@ sub diff_table_options {
   my ($self, $src_table, $tar_table) = @_;
 
 
-  # Go through our options
-  my $options_different = 0;
-  my %checkedOptions;
-
-  OPTION:
-  for my $tar_table_option_ref ( $tar_table->options ) {
-    my($key_tar, $value_tar) = %{$tar_table_option_ref};
-    for my $src_table_option_ref ( $src_table->options ) {
-      my($key_src, $value_src) = %{$src_table_option_ref};
-      if ( $key_tar eq $key_src ) {
-        if ( defined $value_tar != defined $value_src ) {
-          $options_different = 1;
-          last OPTION;
-        }
-        if ( defined $value_tar && $value_tar ne $value_src ) {
-          $options_different = 1;
-          last OPTION;
-        }
-        $checkedOptions{$key_tar} = 1;
-        next OPTION;
-      }
-    }
-    $options_different = 1;
-    last OPTION;
-  }
-
-  # Go through the other table's options
-  unless ( $options_different ) {
-    for my $src_table_option_ref ( $src_table->options ) {
-      my($key, $value) = %{$src_table_option_ref};
-      next if $checkedOptions{$key};
-      $options_different = 1;
-      last;
-    }
-  }
-
   # If there's a difference, just re-set all the options
   push @{ $self->table_diff_hash->{$tar_table}{table_options} }, $tar_table
-    if ( $options_different );
+    unless $src_table->_compare_objects( scalar $src_table->options, scalar $tar_table->options );
 }
 
 1;
index 410fe23..fda11d7 100644 (file)
@@ -145,49 +145,6 @@ use DBI qw(:sql_types);
 use base qw(Exporter);
 
 our %type_mapping = (
-  int => SQL_INTEGER,
-  integer => SQL_INTEGER,
-
-  tinyint => SQL_TINYINT,
-  smallint => SQL_SMALLINT,
-  mediumint,
-  bigint,
-
-  float => SQL_FLOAT, # Precision 0..23
-  double => SQL_DOUBLE, # Precision 24..53
-  "double precision" => SQL_DOUBLE,
-  real => SQL_DOUBLE,
-
-  # all these are the same.
-  decimal => SQL_DECIMAL,
-  numeric => SQL_NUMERIC,
-  dec => SQL_DECIMAL,
-  # fixed: does this exist
-
-  bit => SQL_BIT
-
-  date => SQL_DATE,
-  datetime => SQL_DATETIME,
-  timestamp => SQL_TIMESTAMP,
-  time => SQL_TIME,
-  year
-
-
-  char => SQL_CHAR,
-  varchar => SQL_VARCHAR,
-  binary => SQL_BINARY,
-  varbinary => SQL_VARBINARY,
-  tinyblob => SQL_BLOB,
-  tinytext => 
-  blob => SQL_BLOB,
-  text => SQL_LONGVARCHAR
-  mediumblob => SQL_BLOB,
-  mediumtext => SQL_LONGVARCHAR
-  longblob => SQL_BLOB
-  longtext => SQL_LONGVARCHAR
-
-  enum
-  set
 );
 
 @EXPORT_OK = qw(parse);
@@ -852,15 +809,6 @@ sub parse {
                 ) or die $table->error;
             }
 
-            if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
-                my %extra = $field->extra;
-                my $longest = 0;
-                for my $len ( map { length } @{ $extra{'list'} || [] } ) {
-                    $longest = $len if $len > $longest;
-                }
-                $field->size( $longest ) if $longest;
-            }
-
             for my $cdata ( @{ $fdata->{'constraints'} } ) {
                 next unless $cdata->{'type'} eq 'foreign_key';
                 $cdata->{'fields'} ||= [ $field->name ];
@@ -976,7 +924,17 @@ sub normalize_field {
         $changed = $size != 4_294_967_295;
         $size = 4_294_967_295;
     }
-    $DB::single = 1 if $field->name eq 'employee_id';
+    if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
+        my %extra = $field->extra;
+        my $longest = 0;
+        for my $len ( map { length } @{ $extra{'list'} || [] } ) {
+            $longest = $len if $len > $longest;
+        }
+        $changed = 1;
+        $size = $longest if $longest;
+    }
+
+
     if ($changed) {
       # We only want to clone the field, not *everything*
       { local $field->{table} = undef;
@@ -985,7 +943,7 @@ sub normalize_field {
       }
       $field->size($size);
       $field->data_type($type);
-      $field->sql_data_type( $type_mapping{lc $type} || SQL_UNKNOWN_TYPE );
+      $field->sql_data_type( $type_mapping{lc $type} ) if exists $type_mapping{lc $type};
       $field->extra->{list} = $list if @$list;
     }
 }
index 945829f..f3f53a4 100644 (file)
@@ -124,6 +124,68 @@ my %translate  = (
     'datetime'     => 'datetime',
 );
 
+
+sub preprocess_schema {
+    my ($class, $schema) = @_;
+
+    # extra->{mysql_table_type} used to be the type. It belongs in options, so
+    # move it if we find it. Return Engine type if found in extra or options
+    my $mysql_table_type_to_options = sub {
+      my ($table) = @_;
+
+      my $extra = $table->extra;
+
+      my $extra_type = delete $extra->{mysql_table_type};
+
+      # Now just to find if there is already an Engine or Type option...
+      # and lets normalize it to ENGINE since:
+      #
+      # The ENGINE table option specifies the storage engine for the table. 
+      # TYPE is a synonym, but ENGINE is the preferred option name.
+      #
+
+      # We have to use the hash directly here since otherwise there is no way 
+      # to remove options.
+      my $options = ( $table->{options} ||= []);
+
+      # This assumes that there isn't both a Type and an Engine option.
+      for my $idx ( 0..$#{$options} ) {
+        my ($key, $value) = %{ $options->[$idx] };
+
+        next unless uc $key eq 'ENGINE' || uc $key eq 'TYPE';
+
+        # if the extra.mysql_table_type is given, use that
+        delete $options->[$idx]{$key};
+        return $options->[$idx]{ENGINE} = $value || $extra_type;
+
+      }
+  
+      if ($extra_type) {
+        push @$options, { ENGINE => $extra_type };
+        return $extra_type;
+      }
+
+    };
+
+    #
+    # Work out which tables need to be InnoDB to support foreign key
+    # constraints. We do this first as we need InnoDB at both ends.
+    #
+    foreach my $table ( $schema->get_tables ) {
+       
+        $mysql_table_type_to_options->($table);
+
+        foreach ( $table->get_constraints ) {
+            next unless $_->type eq FOREIGN_KEY;
+            for my $meth (qw/table reference_table/) {
+                my $table = $schema->get_table($_->$meth) || next;
+                next if $mysql_table_type_to_options->($table);
+                $table->options( { 'ENGINE' => 'InnoDB' } );
+            }
+        }
+    }
+}
+
 sub produce {
     my $translator     = shift;
     local $DEBUG       = $translator->debug;
@@ -144,18 +206,7 @@ sub produce {
     # \todo Don't set if MySQL 3.x is set on command line
     $create .= "SET foreign_key_checks=0;\n\n";
 
-    #
-    # Work out which tables need to be InnoDB to support foreign key
-    # constraints. We do this first as we need InnoDB at both ends.
-    #
-    foreach ( map { $_->get_constraints } $schema->get_tables ) {
-        next unless $_->type eq FOREIGN_KEY;
-        foreach my $meth (qw/table reference_table/) {
-            my $table = $schema->get_table($_->$meth) || next;
-            next if $table->extra('mysql_table_type');
-            $table->extra( 'mysql_table_type' => 'InnoDB');
-        }
-    }
+    __PACKAGE__->preprocess_schema($schema);
 
     #
     # Generate sql
@@ -257,8 +308,9 @@ sub generate_table_options
       if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
     $create .= " $key=$value";
   }
+
   my $mysql_table_type = $table->extra('mysql_table_type');
-  $create .= " Type=$mysql_table_type"
+  $create .= " ENGINE=$mysql_table_type"
     if $mysql_table_type && !$table_type_defined;
   my $charset          = $table->extra('mysql_charset');
   my $collate          = $table->extra('mysql_collate');
index 2f20ee5..93d25e2 100644 (file)
@@ -61,6 +61,38 @@ use overload
     fallback => 1,
 ;
 
+use DBI qw(:sql_types);
+
+# Mapping from string to sql contstant
+our %type_mapping = (
+  integer => SQL_INTEGER,
+  int     => SQL_INTEGER,
+
+  smallint => SQL_SMALLINT,
+  bigint => 9999, # DBI doesn't export a constatn for this. Le suck
+
+  double => SQL_DOUBLE,
+
+  decimal => SQL_DECIMAL,
+  numeric => SQL_NUMERIC,
+  dec => SQL_DECIMAL,
+
+  bit => SQL_BIT,
+
+  date => SQL_DATE,
+  datetime => SQL_DATETIME,
+  timestamp => SQL_TIMESTAMP,
+  time => SQL_TIME,
+
+  char => SQL_CHAR,
+  varchar => SQL_VARCHAR,
+  binary => SQL_BINARY,
+  varbinary => SQL_VARBINARY,
+  tinyblob => SQL_BLOB,
+  blob => SQL_BLOB,
+  text => SQL_LONGVARCHAR
+
+);
 # ----------------------------------------------------------------------
 
 __PACKAGE__->_attributes( qw/
@@ -132,7 +164,10 @@ Get or set the field's data type.
 =cut
 
     my $self = shift;
-    $self->{'data_type'} = shift if @_;
+    if (@_) {
+      $self->{'data_type'} = $_[0];
+      $self->{'sql_data_type'} = $type_mapping{lc $_[0]} || SQL_UNKNOWN_TYPE unless exists $self->{sql_data_type};
+    }
     return $self->{'data_type'} || '';
 }
 
index 25937b8..3f0a507 100644 (file)
@@ -130,7 +130,7 @@ my @stmts = (
   `description` text CHARACTER SET utf8 COLLATE utf8_general_ci,
   PRIMARY KEY (`id`),
   UNIQUE `idx_unique_name` (`name`)
-) Type=InnoDB DEFAULT CHARACTER SET latin1 COLLATE latin1_danish_ci;\n\n",
+) ENGINE=InnoDB DEFAULT CHARACTER SET latin1 COLLATE latin1_danish_ci;\n\n",
 
 "DROP TABLE IF EXISTS `thing2`;\n",
 "CREATE TABLE `thing2` (
@@ -144,7 +144,7 @@ my @stmts = (
   PRIMARY KEY (`id`, `foo`),
   CONSTRAINT `fk_thing` FOREIGN KEY (`foo`) REFERENCES `thing` (`id`),
   CONSTRAINT `fk_thing_1` FOREIGN KEY (`foo2`) REFERENCES `thing` (`id`)
-) Type=InnoDB;\n\n",
+) ENGINE=InnoDB;\n\n",
 
 "SET foreign_key_checks=1;\n\n"