From: Jess Robinson Date: Thu, 28 Aug 2008 21:41:28 +0000 (+0000) Subject: Enormous patch from Peter Rabbitson making mysql version parsing saner and adding... X-Git-Tag: v0.11008~301 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=5d666b31769e40325f3513299a58cdac631ced41;p=dbsrgits%2FSQL-Translator.git Enormous patch from Peter Rabbitson making mysql version parsing saner and adding tests for it. --- diff --git a/lib/SQL/Translator/Parser/MySQL.pm b/lib/SQL/Translator/Parser/MySQL.pm index 6890eab..d657475 100644 --- a/lib/SQL/Translator/Parser/MySQL.pm +++ b/lib/SQL/Translator/Parser/MySQL.pm @@ -134,6 +134,17 @@ A subset of INSERT that we ignore: INSERT anything +=head1 ARGUMENTS + +This parser takes a single optional parser_arg C, 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 are listed L + +More information about the MySQL comment-syntax: L + + =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); diff --git a/lib/SQL/Translator/Producer/MySQL.pm b/lib/SQL/Translator/Producer/MySQL.pm index 0e2917d..a9b8301 100644 --- a/lib/SQL/Translator/Producer/MySQL.pm +++ b/lib/SQL/Translator/Producer/MySQL.pm @@ -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, 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 are listed L + =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 { diff --git a/lib/SQL/Translator/Utils.pm b/lib/SQL/Translator/Utils.pm index e85000a..466e850 100644 --- a/lib/SQL/Translator/Utils.pm +++ b/lib/SQL/Translator/Utils.pm @@ -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. +=head2 parse_mysql_version + +Used by both L and +L 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 Edarren@cpan.orgE, Ken Y. Clark Ekclark@cpan.orgE. =cut - -=cut diff --git a/t/02mysql-parser.t b/t/02mysql-parser.t index e172b90..585bd05 100644 --- a/t/02mysql-parser.t +++ b/t/02mysql-parser.t @@ -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'); diff --git a/t/38-mysql-producer.t b/t/38-mysql-producer.t index 50eb682..3c95bde 100644 --- a/t/38-mysql-producer.t +++ b/t/38-mysql-producer.t @@ -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'); } - -