diff --git a/Changes b/Changes index b15529cc..921a2253 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,7 @@ DBI/DBD community (4.048) * Fix corrupted META.json so cpan installations work as expected. https://github.com/perl5-dbi/DBD-mysql/issues/263 +* Fix issue 251: unable to insert "." with bind_type_guessing on 2018-09-08 Daniƫl van Eeden, Patrick Galbraith, DBI/DBD community (4.047) * Add options needed for public key based security. diff --git a/dbdimp.c b/dbdimp.c index 2f6930a1..478c18ec 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -5665,6 +5665,22 @@ int mysql_db_async_ready(SV* h) } } +/* + * this funtion tries to parse numbers from the passed string value + * for emulated prepared statements being bound + * * finds the beginning and end address of the string + * * loops through the string + * * skips past any spaces + * * if it sees a -, ., +, or e, sets a flag + * * if all subsequent values are numbers, is a digit + * * otherwise, a string + * * if any character that's not a digit is found, it's a string + * * if only numbers are found, it's a number + * * `end` is set to the address of the last member in the iteration + * and used in knowing where the value is when building the SQL + * statement + * +*/ static int parse_number(char *string, STRLEN len, char **end) { int seen_neg; @@ -5672,7 +5688,7 @@ static int parse_number(char *string, STRLEN len, char **end) int seen_e; int seen_plus; int seen_digit; - char *cp; + char *cp,*str_end; seen_neg= seen_dec= seen_e= seen_plus= seen_digit= 0; @@ -5681,6 +5697,7 @@ static int parse_number(char *string, STRLEN len, char **end) } cp= string; + str_end= string + len-1; /* Skip leading whitespace */ while (*cp && isspace(*cp)) @@ -5688,7 +5705,7 @@ static int parse_number(char *string, STRLEN len, char **end) for ( ; *cp; cp++) { - if ('-' == *cp) + if (*cp == '-') { if (seen_neg >= 2) { @@ -5699,27 +5716,35 @@ static int parse_number(char *string, STRLEN len, char **end) } seen_neg += 1; } - else if ('.' == *cp) + else if (*cp == '.') { - if (seen_dec) + if (seen_dec || cp == str_end) { /* second '.' */ break; } seen_dec= 1; } - else if ('e' == *cp) + else if (*cp == 'e') { - if (seen_e) + /* + * this is the case where the value for example, e2, + * which should be a varchar + */ + if (cp == str_end -1) + { + break; + } + if (seen_e || cp == str_end) { /* second 'e' */ break; } seen_e= 1; } - else if ('+' == *cp) + else if (*cp == '+') { - if (seen_plus) + if (seen_plus || cp == str_end) { /* second '+' */ break; @@ -5732,6 +5757,7 @@ static int parse_number(char *string, STRLEN len, char **end) /* seen_digit= 1; */ break; } + /*printf("else: cp is a digit: %d\n", *cp); */ } *end= cp; diff --git a/dbdimp.h b/dbdimp.h index db0045a0..b7f5d0f4 100644 --- a/dbdimp.h +++ b/dbdimp.h @@ -17,6 +17,7 @@ /* * Header files we use */ +#include #include /* installed by the DBI module */ #include /* Comes with MySQL-devel */ #include /* Comes MySQL */ diff --git a/t/35limit.t b/t/35limit.t index 881d61d1..9a98896e 100644 --- a/t/35limit.t +++ b/t/35limit.t @@ -15,7 +15,11 @@ require 'lib.pl'; my $dbh; eval {$dbh= DBI->connect($test_dsn, $test_user, $test_password, - { RaiseError => 1, PrintError => 1, AutoCommit => 0 });}; + { mysql_bind_type_guessing => 1, + RaiseError => 1, + PrintError => 1, + AutoCommit => 1 });}; + if ($@) { plan skip_all => "no database connection"; } diff --git a/t/51bind_type_guessing.t b/t/51bind_type_guessing.t index 8e4826fe..60b09f45 100644 --- a/t/51bind_type_guessing.t +++ b/t/51bind_type_guessing.t @@ -4,20 +4,22 @@ use warnings; use DBI; use DBI::Const::GetInfoType; use Test::More; +select(($|=1,select(STDERR),$|=1)[1]); use lib 't', '.'; require 'lib.pl'; use vars qw($test_dsn $test_user $test_password); -my $dbh; +my ($dbh, $t); eval {$dbh= DBI->connect($test_dsn, $test_user, $test_password, - { RaiseError => 1, PrintError => 1, AutoCommit => 0 });}; + { RaiseError => 1, PrintError => 1, AutoCommit => 1 });}; if ($@) { plan skip_all => "no database connection"; } -plan tests => 26; +plan tests => 98; -ok $dbh->do("DROP TABLE IF EXISTS dbd_mysql_t51bind_type_guessing"), "drop table if exists dbd_mysql_t51bind_type_guessing"; +ok $dbh->do("DROP TABLE IF EXISTS dbd_mysql_t51bind_type_guessing"), + "drop table if exists dbd_mysql_t51bind_type_guessing"; my $create= <<"EOTABLE"; create table dbd_mysql_t51bind_type_guessing ( @@ -25,7 +27,6 @@ create table dbd_mysql_t51bind_type_guessing ( ) EOTABLE - ok $dbh->do($create), "creating table"; my $statement= "insert into dbd_mysql_t51bind_type_guessing (id) values (?)"; @@ -54,7 +55,8 @@ ok $sth2= $dbh->prepare($statement); ok $rows= $sth2->execute('9999999999999996', '9999999999999997'); my $retref; -ok $retref= $dbh->selectall_arrayref("select * from dbd_mysql_t51bind_type_guessing"); +ok $retref= $dbh->selectall_arrayref( + "select * from dbd_mysql_t51bind_type_guessing"); cmp_ok $retref->[0][0], '==', 9999999999999998; cmp_ok $retref->[1][0], '==', 9999999999999996; @@ -62,21 +64,143 @@ cmp_ok $retref->[1][0], '==', 9999999999999996; # checking varchars/empty strings/misidentification: $create= <<"EOTABLE"; create table dbd_mysql_t51bind_type_guessing ( + id bigint default 0 not null, + nn bigint default 0, + dd double(12,4), str varchar(80), - num bigint - ) + primary key (id) + ) engine=innodb EOTABLE + ok $dbh->do("DROP TABLE IF EXISTS dbd_mysql_t51bind_type_guessing"), "drop table if exists dbd_mysql_t51bind_type_guessing"; -ok $dbh->do($create), "creating table w/ varchar"; + +ok $dbh->do($create), "creating table with int, double, and varchar"; + +my @sts; +$t= "prepare insert integer col nn into dbd_mysql_t51bind_type_guessing"; +ok $sts[0] = $dbh->prepare("insert into dbd_mysql_t51bind_type_guessing (id,nn) values (?,?)"), $t; +$t= "prepare update double col dd dbd_mysql_t51bind_type_guessing"; +ok $sts[1] = $dbh->prepare("update dbd_mysql_t51bind_type_guessing set dd = ? where id = ?"), $t; +$t= "prepare update string col str dbd_mysql_t51bind_type_guessing"; +ok $sts[2] = $dbh->prepare("update dbd_mysql_t51bind_type_guessing set str = ? where id = ?"), $t; + +# various values to try including issue 251 +my @vals = ( 52.3, + ' 77.7777', + '.1', + '5e3', + +1, + -1, + undef, + '5e', + '1+', + '+', + '.', + 'e5', +); + +my $val; +# the tests for 'like' are when values fail to be inserted/updated +for my $i (0 .. 11) { + $val = $vals[$i]; + if (defined $val) { + $t= "insert int val $val id $i" + } + else { + $t= "insert undef into int id $i"; + } + if ($i >= 8) { + eval { + $rows= $sts[0]->execute($i, $val); + }; + if ($i == 8) { + like ($@, qr{Data truncated for column}, $t); + } + else { + like ($@, qr{Incorrect integer value}, $t); + } + $rows= $sts[0]->execute($i, 0); + } + else { + ok $rows= $sts[0]->execute($i, $val),$t; + } + + if (defined $val) { + $t= "update double val $val id $i"; + } + else { + $t= "update double val undefined id $i"; + } + if ($i >= 7) { + eval { + $rows = $sts[1]->execute($val, $i); + }; + like ($@, qr{Data truncated for column}, $t); + $rows= $sts[1]->execute(0, $i); + } + else { + ok $rows= $sts[1]->execute($val,$i),$t; + } + + if (defined $val) { + $t= "update string val $val id $i"; + } + else { + $t= "update string val undef id $i"; + } + ok $rows = $sts[2]->execute($val,$i),$t; +} + +for my $i (0 .. 2) { + $sts[$i]->finish(); +} + +# expected results +my $res= [ + [ 0, 52, '52.3', '52.3' ], + [ 1, 78, '77.7777', '77.7777' ], + [ 2, 0, '0.1', '0.1' ], + [ 3, 5000, '5000', '5e3' ], + [ 4, 1, '1', '1' ], + [ 5, -1, '-1', '-1' ], + [ 6, undef, undef, undef ], + [ 7, 5, '0', '5e' ], + [ 8, 0, '0', '1+' ], + [ 9, 0, '0', '+' ], + [ 10, 0, '0', '.' ], + [ 11, 0, '0', 'e5' ] + ]; + +$t= "Select all values"; +my $query= "select * from dbd_mysql_t51bind_type_guessing"; + +ok $retref = $dbh->selectall_arrayref($query), $t; + +for my $i (0 .. $#$res) { + if ($i == 6) { + is($retref->[$i][1], undef, "$i: nn undefined as expected"); + is($retref->[$i][2], undef, "$i: dd undefined as expected"); + is($retref->[$i][3], undef, "$i: str undefined as expected"); + } + else { + cmp_ok $retref->[$i][1], '==', $res->[$i][1], + "test: " . "$retref->[$i][1], '==', $res->[$i][1]"; + cmp_ok $retref->[$i][2], 'eq', $res->[$i][2], + "test: " . "$retref->[$i][2], '==', $res->[$i][2]"; + cmp_ok $retref->[$i][3], 'eq', $res->[$i][3], + "test: " . "$retref->[$i][2], '==', $res->[$i][2]"; + } +} + my $sth3; -ok $sth3= $dbh->prepare("insert into dbd_mysql_t51bind_type_guessing (str, num) values (?, ?)"); -ok $rows= $sth3->execute(52.3, 44); -ok $rows= $sth3->execute('', ' 77'); -ok $rows= $sth3->execute(undef, undef); - -ok $sth3= $dbh->prepare("select * from dbd_mysql_t51bind_type_guessing limit ?"); -ok $rows= $sth3->execute(1); -ok $rows= $sth3->execute(' 1'); +$t = "Prepare limit statement"; +ok $sth3= $dbh->prepare("select * from dbd_mysql_t51bind_type_guessing limit ?"), $t; +$val = 1; +$t = "select with limit $val statement"; +ok $rows= $sth3->execute($val), $t; +$val = ' 1'; +$t = "select with limit $val statement"; +ok $rows= $sth3->execute($val), $t; $sth3->finish(); ok $dbh->do("DROP TABLE dbd_mysql_t51bind_type_guessing");