Enormous patch from Peter Rabbitson making mysql version parsing saner and adding...
Jess Robinson [Thu, 28 Aug 2008 21:41:28 +0000 (21:41 +0000)]
lib/SQL/Translator/Parser/MySQL.pm
lib/SQL/Translator/Producer/MySQL.pm
lib/SQL/Translator/Utils.pm
t/02mysql-parser.t
t/38-mysql-producer.t

index 6890eab..d657475 100644 (file)
@@ -134,6 +134,17 @@ A subset of INSERT that we ignore:
 
   INSERT anything
 
+=head1 ARGUMENTS
+
+This parser takes a single optional parser_arg C<mysql_parser_version>, which
+provides the desired version for the target database. Any statement in the processed
+dump file, that is commented with a version higher than the one supplied, will be stripped.
+
+Valid version specifiers for C<mysql_parser_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
+
+More information about the MySQL comment-syntax: L<http://dev.mysql.com/doc/refman/5.0/en/comments.html>
+
+
 =cut
 
 use strict;
@@ -148,6 +159,8 @@ use Storable qw(dclone);
 use DBI qw(:sql_types);
 use base qw(Exporter);
 
+use SQL::Translator::Utils qw/parse_mysql_version/;
+
 our %type_mapping = (
 );
 
@@ -751,7 +764,9 @@ sub parse {
     }
     
     # Preprocess for MySQL-specific and not-before-version comments from mysqldump
-    my $parser_version = $translator->parser_args->{mysql_parser_version} || DEFAULT_PARSER_VERSION;
+    my $parser_version = 
+        parse_mysql_version ($translator->parser_args->{mysql_parser_version}, 'mysql') 
+        || DEFAULT_PARSER_VERSION;
     while ( $data =~ s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es ) {}
 
     my $result = $parser->startrule($data);
index 0e2917d..a9b8301 100644 (file)
@@ -40,6 +40,15 @@ There are still some issues to be worked out with syntax differences
 between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
 for fields, etc.).
 
+=head1 ARGUMENTS 
+
+This producer takes a single optional producer_arg C<mysql_version>, which 
+provides the desired version for the target database. By default MySQL v3 is
+assumed, and statements pertaining to any features introduced in later versions
+(e.g. CREATE VIEW) are not produced.
+
+Valid version specifiers for C<mysql_parser_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version> 
+
 =head2 Table Types
 
 Normally the tables will be created without any explicit table type given and
@@ -103,7 +112,7 @@ my $DEFAULT_MAX_ID_LENGTH = 64;
 
 use Data::Dumper;
 use SQL::Translator::Schema::Constants;
-use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely);
+use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely parse_mysql_version);
 
 #
 # Use only lowercase for the keys (e.g. "long" and not "LONG")
@@ -252,7 +261,7 @@ sub produce {
     my $schema         = $translator->schema;
     my $show_warnings  = $translator->show_warnings || 0;
     my $producer_args  = $translator->producer_args;
-    my $mysql_version  = $producer_args->{mysql_version} || 0;
+    my $mysql_version  = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
     my $max_id_length  = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
 
     my ($qt, $qf, $qc) = ('','', '');
@@ -469,6 +478,7 @@ sub create_field
     my $charset = $extra{'mysql_charset'};
     my $collate = $extra{'mysql_collate'};
 
+    my $mysql_version = $options->{mysql_version} || 0;
     #
     # Oracle "number" type -- figure best MySQL type
     #
@@ -489,13 +499,15 @@ sub create_field
     }
     #
     # Convert a large Oracle varchar to "text"
+    # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
     #
     elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
-        $data_type = 'text';
-        @size      = ();
+        unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
+            $data_type = 'text';
+            @size      = ();
+        }
     }
     elsif ( $data_type =~ /boolean/i ) {
-        my $mysql_version = $options->{mysql_version} || 0;
         if ($mysql_version >= 4) {
             $data_type = 'boolean';
         } else {
index e85000a..466e850 100644 (file)
@@ -31,7 +31,7 @@ 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 truncate_id_uniquely $DEFAULT_COMMENT
+    debug normalize_name header_comment parse_list_arg truncate_id_uniquely $DEFAULT_COMMENT parse_mysql_version
 );
 
 # ----------------------------------------------------------------------
@@ -171,6 +171,55 @@ sub truncate_id_uniquely {
          . $collision_tag;
 }
 
+
+#---------------------------------------------------------------------
+# parse_mysql_version ( $version_string, $result_target)
+#
+# Attempts to parse an arbitrary string as a mysql version number. 
+# Returns either a floating point perl style string, or a mysql style
+# 5 digit string, depending on the supplied $result_target
+#---------------------------------------------------------------------
+sub parse_mysql_version {
+    my ($v, $target) = @_;
+
+    return undef unless $v;
+
+    $target ||= 'perl';
+
+    my @vers;
+
+    # X.Y.Z style 
+    if ( $v =~ / ^ (\d+) \. (\d{1,3}) (?: \. (\d{1,3}) )? $ /x ) {
+        push @vers, $1, $2, $3;
+    }
+
+    # XYYZZ (mysql) style 
+    elsif ( $v =~ / ^ (\d) (\d{2}) (\d{2}) $ /x ) {
+        push @vers, $1, $2, $3;
+    }
+
+    # XX.YYYZZZ (perl) style or simply X 
+    elsif ( $v =~ / ^ (\d+) (?: \. (\d{3}) (\d{3}) )? $ /x ) {
+        push @vers, $1, $2, $3;
+    }
+    else {
+        #how do I croak sanely here?
+        die "Unparseable MySQL version '$v'";
+    }
+
+    if ($target eq 'perl') {
+        return sprintf ('%d.%03d%03d', map { $_ || 0 } (@vers) );
+    }
+    elsif ($target eq 'mysql') {
+        return sprintf ('%d%02d%02d', map { $_ || 0 } (@vers) );
+    }
+    else {
+        #how do I croak sanely here?
+        die "Unknown version target '$target'";
+    }
+}
+
+
 1;
 
 # ----------------------------------------------------------------------
@@ -300,11 +349,24 @@ Will give three different results; specifically:
 This is the default comment string, '-- ' by default.  Useful for
 C<header_comment>.
 
+=head2 parse_mysql_version
+
+Used by both L<Parser::MySQL|SQL::Translator::Parser::MySQL> and 
+L<Producer::MySQL|SQL::Translator::Producer::MySQL> in order to provide a
+consistent format for both C<< parser_args->{mysql_parser_version} >> and
+C<< producer_args->{mysql_version} >> respectively. Takes any of the following
+version specifications:
+
+  5.0.3
+  4.1
+  3.23.2
+  5
+  5.001005  (perl style)
+  30201     (mysql style)
+
 =head1 AUTHORS
 
 Darren Chamberlain E<lt>darren@cpan.orgE<gt>,
 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
 
 =cut
-
-=cut
index e172b90..585bd05 100644 (file)
@@ -7,10 +7,11 @@ use strict;
 use Test::More;
 use SQL::Translator;
 use SQL::Translator::Schema::Constants;
+use SQL::Translator::Utils qw//;
 use Test::SQL::Translator qw(maybe_plan);
 
 BEGIN {
-    maybe_plan(233, "SQL::Translator::Parser::MySQL");
+    maybe_plan(244, "SQL::Translator::Parser::MySQL");
     SQL::Translator::Parser::MySQL->import('parse');
 }
 
@@ -627,3 +628,35 @@ BEGIN {
     is($collate, 'latin1_bin', "Collate found");
     is($charset, 'latin1', "Character set found");
 }
+
+# Test the mysql version parser (probably needs to migrate to t/utils.t)
+my $parse_as = {
+    perl => {
+        '3.23.2'    => 3.023002,
+        '4'         => 4.000000,
+        '50003'     => 5.000003,
+        '5.01.0'    => 5.001000,
+        '5.1'       => 5.001000,
+    },
+    mysql => {
+        '3.23.2'    => 32302,
+        '4'         => 40000,
+        '50003'     => 50003,
+        '5.01.0'    => 50100,
+        '5.1'       => 50100,
+    },
+};
+
+for my $target (keys %$parse_as) {
+    for my $str (keys %{$parse_as->{$target}}) {
+        cmp_ok (
+            SQL::Translator::Utils::parse_mysql_version ($str, $target),
+            '==',
+            $parse_as->{$target}{$str},
+            "'$str' parsed as $target version '$parse_as->{$target}{$str}'",
+        );
+    }
+}
+
+eval { SQL::Translator::Utils::parse_mysql_version ('bogus5.1') };
+ok ($@, 'Exception thrown on invalid version string');
index 50eb682..3c95bde 100644 (file)
@@ -19,7 +19,7 @@ use FindBin qw/$Bin/;
 #=============================================================================
 
 BEGIN {
-    maybe_plan(16,
+    maybe_plan(32,
         'YAML',
         'SQL::Translator::Producer::MySQL',
         'Test::Differences',
@@ -262,6 +262,111 @@ is($field3_sql, "myfield enum('0','1') NOT NULL", 'For Mysql < 4, use enum for b
 $field3_sql = SQL::Translator::Producer::MySQL::create_field($field3,);
 is($field3_sql, "myfield enum('0','1') NOT NULL", 'When no version specified, use enum for boolean type');
 
+my $number_sizes = {
+    '3, 2' => 'double',
+    12 => 'bigint',
+    1 => 'tinyint',
+    4 => 'int',
+};
+for my $size (keys %$number_sizes) {
+    my $expected = $number_sizes->{$size};
+    my $number_field = SQL::Translator::Schema::Field->new( 
+        name => "numberfield_$size",
+        table => $table,
+        data_type => 'number',
+        size => $size,
+        is_nullable => 1,
+        is_foreign_key => 0,
+        is_unique => 0 
+    );
+
+    is(
+        SQL::Translator::Producer::MySQL::create_field($number_field),
+        "numberfield_$size $expected($size)",
+        "Use $expected for NUMBER types of size $size"
+    );
+}
+
+my $varchars;
+for my $size (qw/255 256 65535 65536/) {
+    $varchars->{$size} = SQL::Translator::Schema::Field->new( 
+        name => "vch_$size",
+        table => $table,
+        data_type => 'varchar',
+        size => $size,
+        is_nullable => 1,
+    );
+}
+
+
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{255}, { mysql_version => 5.000003 }),
+    'vch_255 varchar(255)', 
+    'VARCHAR(255) is not substituted with TEXT for Mysql >= 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{255}, { mysql_version => 5.0 }),
+    'vch_255 varchar(255)', 
+    'VARCHAR(255) is not substituted with TEXT for Mysql < 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{255}),
+    'vch_255 varchar(255)', 
+    'VARCHAR(255) is not substituted with TEXT when no version specified',
+);
+
+
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{256}, { mysql_version => 5.000003 }),
+    'vch_256 varchar(256)', 
+    'VARCHAR(256) is not substituted with TEXT for Mysql >= 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{256}, { mysql_version => 5.0 }),
+    'vch_256 text', 
+    'VARCHAR(256) is substituted with TEXT for Mysql < 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{256}),
+    'vch_256 text', 
+    'VARCHAR(256) is substituted with TEXT when no version specified',
+);
+
+
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65535}, { mysql_version => 5.000003 }),
+    'vch_65535 varchar(65535)', 
+    'VARCHAR(65535) is not substituted with TEXT for Mysql >= 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65535}, { mysql_version => 5.0 }),
+    'vch_65535 text', 
+    'VARCHAR(65535) is substituted with TEXT for Mysql < 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65535}),
+    'vch_65535 text', 
+    'VARCHAR(65535) is substituted with TEXT when no version specified',
+);
+
+
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65536}, { mysql_version => 5.000003 }),
+    'vch_65536 text', 
+    'VARCHAR(65536) is substituted with TEXT for Mysql >= 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65536}, { mysql_version => 5.0 }),
+    'vch_65536 text', 
+    'VARCHAR(65536) is substituted with TEXT for Mysql < 5.0.3'
+);
+is (
+    SQL::Translator::Producer::MySQL::create_field($varchars->{65536}),
+    'vch_65536 text', 
+    'VARCHAR(65536) is substituted with TEXT when no version specified',
+);
+
+
 {
   my $view1 = SQL::Translator::Schema::View->new( name => 'view_foo',
                                                   fields => [qw/id name/],
@@ -295,5 +400,3 @@ is($field3_sql, "myfield enum('0','1') NOT NULL", 'When no version specified, us
   );\n\n";
   is($view1_sql2, $view_sql_noreplace, 'correct "CREATE VIEW" SQL');
 }
-
-