Applied patch from Ryan to uniqify index names sanely for the mysql producer
Jess Robinson [Tue, 3 Jun 2008 20:26:30 +0000 (20:26 +0000)]
Build.PL
Changes
lib/SQL/Translator.pm
lib/SQL/Translator/Producer/MySQL.pm
lib/SQL/Translator/Utils.pm
t/38-mysql-producer.t

index 5102d04..c3f85d6 100644 (file)
--- a/Build.PL
+++ b/Build.PL
@@ -24,6 +24,7 @@ my $builder = Module::Build->new(
         'Class::Base'              => 0,
         'Class::Data::Inheritable' => 0.02,
         'Class::MakeMethods'       => 0,
+        'Digest::SHA1'             => 2.00,
         'IO::Dir'                  => 0,
         'Log::Log4perl'            => 0,
         'Parse::RecDescent'        => 1.94,
diff --git a/Changes b/Changes
index ca1afd0..e8741f4 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 
 * Added support for proper booleans in the mysql producer, when a mysql version of at least 4.x is supplied
 * Added support for proper enums under pg (as of 8.3), with pg version check, and deferrable constraints
+* Added support to truncate long constraint and index names in the mysql producer, because of a change to DBIx::Class to produce such long names in some cases.
 
 # ----------------------------------------------------------
 # 0.0900 2008-02-25
index 4e34d0a..a4b6933 100644 (file)
@@ -1306,6 +1306,8 @@ The following people have contributed to the SQLFairy project:
 
 =item * Daniel Ruoso <daniel@ruoso.com>
 
+=item * Ryan D Johnson <ryan@innerfence.com>
+
 =back
 
 If you would like to contribute to the project, you can send patches
index e3cbd3f..307ef6a 100644 (file)
@@ -96,9 +96,14 @@ use vars qw[ $VERSION $DEBUG %used_names ];
 $VERSION = sprintf "%d.%02d", q$Revision: 1.54 $ =~ /(\d+)\.(\d+)/;
 $DEBUG   = 0 unless defined $DEBUG;
 
+# Maximum length for most identifiers is 64, according to:
+#   http://dev.mysql.com/doc/refman/4.1/en/identifiers.html
+#   http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
+my $DEFAULT_MAX_ID_LENGTH = 64;
+
 use Data::Dumper;
 use SQL::Translator::Schema::Constants;
-use SQL::Translator::Utils qw(debug header_comment);
+use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely);
 
 #
 # Use only lowercase for the keys (e.g. "long" and not "LONG")
@@ -248,6 +253,7 @@ sub produce {
     my $show_warnings  = $translator->show_warnings || 0;
     my $producer_args  = $translator->producer_args;
     my $mysql_version  = $producer_args->{mysql_version} || 0;
+    my $max_id_length  = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
 
     my ($qt, $qf, $qc) = ('','', '');
     $qt = '`' if $translator->quote_table_names;
@@ -275,6 +281,7 @@ sub produce {
                                          no_comments       => $no_comments,
                                          quote_table_names => $qt,
                                          quote_field_names => $qf,
+                                         max_id_length     => $max_id_length,
                                          mysql_version     => $mysql_version
                                          });
     }
@@ -517,7 +524,7 @@ sub create_index
 
     return join( ' ', 
                  lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
-                 $index->name,
+                 truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
                  '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
                  );
 
@@ -583,7 +590,7 @@ sub create_constraint
     elsif ( $c->type eq UNIQUE ) {
         return
         'UNIQUE '. 
-            (defined $c->name ? $qf.$c->name.$qf.' ' : '').
+            (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
             '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
     }
     elsif ( $c->type eq FOREIGN_KEY ) {
@@ -592,7 +599,7 @@ sub create_constraint
         #
 
         my $table = $c->table;
-        my $c_name = $c->name;
+        my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
 
         my $def = join(' ', 
                        map { $_ || () } 
index cefa284..e85000a 100644 (file)
@@ -24,12 +24,14 @@ use strict;
 use base qw(Exporter);
 use vars qw($VERSION $DEFAULT_COMMENT @EXPORT_OK);
 
+use Digest::SHA1 qw( sha1_hex );
+
 use Exporter;
 
 $VERSION = sprintf "%d.%02d", q$Revision: 1.12 $ =~ /(\d+)\.(\d+)/;
 $DEFAULT_COMMENT = '-- ';
 @EXPORT_OK = qw(
-    debug normalize_name header_comment parse_list_arg $DEFAULT_COMMENT
+    debug normalize_name header_comment parse_list_arg truncate_id_uniquely $DEFAULT_COMMENT
 );
 
 # ----------------------------------------------------------------------
@@ -143,6 +145,32 @@ sub parse_list_arg {
     }
 }
 
+# ----------------------------------------------------------------------
+# truncate_id_uniquely( $desired_name, $max_symbol_length )
+#
+# Truncates the name $desired_name to the $max_symbol_length by
+# including part of the hash of the full name at the end of the
+# truncated name, giving a high probability that the symbol will be
+# unique.
+# ----------------------------------------------------------------------
+my $COLLISION_TAG_LENGTH = 8;
+sub truncate_id_uniquely {
+    my ( $desired_name, $max_symbol_length ) = @_;
+
+    return $desired_name unless defined $desired_name && length $desired_name > $max_symbol_length;
+
+    my $truncated_name = substr $desired_name, 0, $max_symbol_length - $COLLISION_TAG_LENGTH - 1;
+
+    # Hex isn't the most space-efficient, but it skirts around allowed
+    # charset issues
+    my $digest = sha1_hex($desired_name);
+    my $collision_tag = substr $digest, 0, $COLLISION_TAG_LENGTH;
+
+    return $truncated_name
+         . '_'
+         . $collision_tag;
+}
+
 1;
 
 # ----------------------------------------------------------------------
@@ -250,6 +278,23 @@ All of the following will return equivalent values:
   parse_list_arg( [ 'id', 'name' ] );
   parse_list_arg( qw[ id name ] );
 
+=head2 truncate_id_uniquely
+
+Takes a string ($desired_name) and int ($max_symbol_length). Truncates
+$desired_name to $max_symbol_length by including part of the hash of
+the full name at the end of the truncated name, giving a high
+probability that the symbol will be unique. For example,
+
+  truncate_id_uniquely( 'a' x 100, 64 )
+  truncate_id_uniquely( 'a' x 99 . 'b', 64 );
+  truncate_id_uniquely( 'a' x 99,  64 )
+
+Will give three different results; specifically:
+
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_7f900025
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_6191e39a
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_8cd96af2
+
 =head2 $DEFAULT_COMMENT
 
 This is the default comment string, '-- ' by default.  Useful for
index ce5bbdd..b909e93 100644 (file)
@@ -102,7 +102,7 @@ schema:
         - type: NORMAL
           fields: 
             - id
-          name: index_2
+          name: really_long_name_bigger_than_64_chars_aaaaaaaaaaaaaaaaaaaaaaaaaaa
       constraints:
         - type: PRIMARY_KEY
           fields:
@@ -126,8 +126,8 @@ my @stmts = (
 "CREATE TABLE `thing` (
   `id` unsigned int auto_increment,
   `name` varchar(32),
-  `swedish_name` varchar(32) CHARACTER SET swe7,
-  `description` text CHARACTER SET utf8 COLLATE utf8_general_ci,
+  `swedish_name` varchar(32) character set swe7,
+  `description` text character set utf8 collate utf8_general_ci,
   PRIMARY KEY (`id`),
   UNIQUE `idx_unique_name` (`name`)
 ) ENGINE=InnoDB DEFAULT CHARACTER SET latin1 COLLATE latin1_danish_ci;\n\n",
@@ -138,7 +138,7 @@ my @stmts = (
   `foo` integer,
   `foo2` integer,
   INDEX index_1 (`id`),
-  INDEX index_2 (`id`),
+  INDEX really_long_name_bigger_than_64_chars_aaaaaaaaaaaaaaaaa_aed44c47 (`id`),
   INDEX (`foo`),
   INDEX (`foo2`),
   PRIMARY KEY (`id`, `foo`),