Included patch does the following:
- rename parse_number to is_number
- fixes is_number so it more closesly follows what MySQL expects
- exposes it as DBD::mysql::_is_number
- adds a test for all sorts of valid and invalid numbers
Note that this patch still thinks the string "12345e6" is a number. Debate could go either way on whether it should be one or not. The argument against is that the string "12e3" is the number 12 in Perl, not 12000 as it is in MySQL.
diff -rub DBD-mysql-4.032_01-orig/dbdimp.c DBD-mysql-4.032_01-PG/dbdimp.c
--- DBD-mysql-4.032_01-orig/dbdimp.c 2015-04-16 17:04:02.000000000 -0400
+++ DBD-mysql-4.032_01-PG/dbdimp.c 2015-05-26 12:15:03.942881366 -0400
@@ -43,8 +43,6 @@
# define ASYNC_CHECK_RETURN(h, value)
#endif
-static int parse_number(char *string, STRLEN len, char **end);
-
DBISTATE_DECLARE;
typedef struct sql_type_info_s
@@ -583,7 +581,7 @@
valbuf= SvPV(ph->value, vallen);
ph->type= SQL_INTEGER;
- if (parse_number(valbuf, vallen, &end) != 0)
+ if (mysql_is_number(valbuf, vallen, &end) != 0)
{
ph->type= SQL_VARCHAR;
}
@@ -754,7 +752,7 @@
}
/* (note this sets *end, which we use if is_num) */
- if ( parse_number(valbuf, vallen, &end) != 0 && is_num)
+ if ( mysql_is_number(valbuf, vallen, &end) != 0 && is_num)
{
if (bind_type_guessing) {
/* .. not a number, so apparerently we guessed wrong */
@@ -5320,19 +5318,54 @@
}
#endif
-static int parse_number(char *string, STRLEN len, char **end)
+/*
+ * Are numbers:
+ * 0
+ * -0
+ * 1234
+ * -1234
+ * 1234.0
+ * 1234.0110
+ * .15
+ * -.15
+ * -1234.123
+ * +1234.888
+ * 1e2
+ * 1e-2
+ * 1e+2
+ * +1e+2
+ * -1e2
+ * -123e-3
+
+ * Aren't numbers:
+ * 2015-10-03
+ * 1.2.3
+ * 1e2.3
+ * 1e-+3
+*/
+
+
+int mysql_is_number(char *string, STRLEN len, char **end)
{
int seen_neg;
+ int may_neg;
int seen_dec;
+ int may_dec;
int seen_e;
+ int may_e;
int seen_plus;
+ int may_plus;
int seen_digit;
char *cp;
seen_neg= seen_dec= seen_e= seen_plus= seen_digit= 0;
+ may_e= 0;
+ may_neg= may_plus= may_dec= 1;
+
if (len <= 0) {
len= strlen(string);
+ /* fprintf( stderr, "len=%i string=%s\n", (int)len, string ); */
}
cp= string;
@@ -5345,6 +5378,7 @@
{
if ('-' == *cp)
{
+ if( !may_neg ) break; /* not allow a - here */
if (seen_neg >= 2)
{
/*
@@ -5353,48 +5387,82 @@
break;
}
seen_neg += 1;
+ if( seen_e ) {
+ may_dec= 0; /* 1e-3.3 invalid */
+ may_neg= 0; /* 1e--3 invalid */
+ may_plus= 0; /* 1e-+3 invalid */
+ may_e= 0; /* 1ee3 invalid */
+ }
+ else {
+ may_dec= 1; /* -.3 valid */
+ may_neg= 0; /* --3 invalid */
+ may_plus= 0; /* -+3 invalid */
+ may_e= 0; /* -e3 invalid */
+ }
}
else if ('.' == *cp)
{
+ if( !may_dec ) break; /* not allow a . here */
+ if( seen_e ) break; /* 1e2.3 invalid */
if (seen_dec)
{
/* second '.' */
break;
}
seen_dec= 1;
+
+ may_dec= 0; /* 3..3 invalid */
+ may_neg= 0; /* .-3 invalid */
+ may_plus= 0; /* .+3 invalid */
+ may_e= 1; /* 1.e3 invalid */
}
else if ('e' == *cp)
{
+ if( !may_e ) break; /* not allow a e here */
if (seen_e)
{
/* second 'e' */
break;
}
seen_e= 1;
+ may_dec= 0; /* 1e3.3 invalid */
+ may_neg= 1; /* 1e-3 valid */
+ may_plus= 1; /* 1e+3 valid */
+ may_e= 0; /* 1ee3 invalid */
}
else if ('+' == *cp)
{
+ if( !may_plus ) break; /* not allow a + here */
if (seen_plus)
{
/* second '+' */
break;
}
seen_plus= 1;
+ may_dec= 1; /* +.3 valid */
+ may_neg= 0; /* +-3 invalid (it's math, not string) */
+ may_plus= 0; /* ++3 invalid (it's math, not string) */
+ may_e= 0; /* +e3 invalid */
}
else if (!isdigit(*cp))
{
- /* Not sure why this was changed */
- /* seen_digit= 1; */
break;
}
+ else {
+ seen_digit += 1;
+ may_dec= 1; /* 1. valid */
+ may_neg= 0; /* 1- invalid */
+ may_plus= 0; /* 1+ invalid */
+ may_e= 1; /* 1e2 valid */
+ }
}
*end= cp;
/* length 0 -> not a number */
- /* Need to revisit this */
- /*if (len == 0 || cp - string < (int) len || seen_digit == 0) {*/
- if (len == 0 || cp - string < (int) len) {
+ int seen = cp - string;
+ /* fprintf( stderr, "len=%i seen=%i digits=%i\n", (int)len, seen, seen_digit ); */
+ if (len == 0 || seen < (int) len || seen_digit == 0) {
return -1;
}
Only in DBD-mysql-4.032_01-PG: dbdimp.c~
diff -rub DBD-mysql-4.032_01-orig/dbdimp.h DBD-mysql-4.032_01-PG/dbdimp.h
--- DBD-mysql-4.032_01-orig/dbdimp.h 2015-01-26 22:02:15.000000000 -0500
+++ DBD-mysql-4.032_01-PG/dbdimp.h 2015-05-26 11:46:40.636953219 -0400
@@ -373,6 +373,8 @@
extern int mysql_db_reconnect(SV*);
int mysql_st_free_result_sets (SV * sth, imp_sth_t * imp_sth);
+int mysql_is_number(char *string, STRLEN len, char **end);
+
#if MYSQL_ASYNC
int mysql_db_async_result(SV* h, MYSQL_RES** resp);
int mysql_db_async_ready(SV* h);
diff -rub DBD-mysql-4.032_01-orig/mysql.xs DBD-mysql-4.032_01-PG/mysql.xs
--- DBD-mysql-4.032_01-orig/mysql.xs 2015-01-26 22:02:15.000000000 -0500
+++ DBD-mysql-4.032_01-PG/mysql.xs 2015-05-26 12:21:27.795315748 -0400
@@ -44,6 +44,24 @@
OUTPUT:
RETVAL
+SV *
+_is_number(str)
+ char *str;
+ PROTOTYPE: $
+ CODE:
+ {
+ /* this function is exposed so we can easily write a test for it */
+ int retval;
+ char *bidon;
+ retval = mysql_is_number(str, 0, &bidon);
+ /* warn( "retvat=%i", retval ); */
+ RETVAL = boolSV(retval==0);
+ }
+ OUTPUT:
+ RETVAL
+
+
+
MODULE = DBD::mysql PACKAGE = DBD::mysql::dr
diff -rub DBD-mysql-4.032_01-orig/t/rt62458-is_number.t DBD-mysql-4.032_01-PG/t/rt62458-is_number.t
--- DBD-mysql-4.032_01-orig/t/rt62458-is_number.t 2015-05-26 12:18:26.595110614 -0400
+++ DBD-mysql-4.032_01-PG/t/rt62458-is_number.t 2015-05-26 12:20:55.539279136 -0400
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use DBI;
+use Test::More ( tests => 29 );
+
+my $drh = DBI->install_driver('mysql');
+
+ok DBD::mysql::_is_number( "0" ), "0 number";
+ok DBD::mysql::_is_number( "1" ), "1 number";
+ok DBD::mysql::_is_number( "-1" ), "-1 number";
+ok DBD::mysql::_is_number( "+1" ), "+1 number";
+
+# For the next 4, SELECT returns what you expect
+# but they are math, not numbers.
+ok !DBD::mysql::_is_number( "--1" ), "--1 not a number";
+ok !DBD::mysql::_is_number( "++1" ), "++1 not a number";
+ok !DBD::mysql::_is_number( "-+1" ), "-+1 not a number";
+ok !DBD::mysql::_is_number( "+-1" ), "+-1 not a number";
+
+ok DBD::mysql::_is_number( "1.1" ), "1.1 number";
+ok DBD::mysql::_is_number( "-1.1" ), "-1.1 number";
+ok DBD::mysql::_is_number( "+1.1" ), "+1.1 number";
+ok DBD::mysql::_is_number( ".1" ), ".1 number";
+ok DBD::mysql::_is_number( "-.1" ), "-.1 number";
+ok DBD::mysql::_is_number( "+.1" ), "+.1 number";
+ok DBD::mysql::_is_number( "0.1" ), "0.1 number";
+
+ok DBD::mysql::_is_number( "1e1" ), "1e1 number";
+ok DBD::mysql::_is_number( "-1e1" ), "-1e1 number";
+ok DBD::mysql::_is_number( "+1e1" ), "+1e1 number";
+ok DBD::mysql::_is_number( "1e-1" ), "1e-1 number";
+ok DBD::mysql::_is_number( "-1e-1" ), "-1e-1 number";
+
+# everything else is not a number
+ok !DBD::mysql::_is_number( "-1e.1" ), "-1e.1 not a number";
+ok !DBD::mysql::_is_number( "hello" ), "hello not a number";
+ok !DBD::mysql::_is_number( "1e.1" ), "1e.1 not a number";
+ok !DBD::mysql::_is_number( "e.1" ), "e.1 not a number";
+ok !DBD::mysql::_is_number( "e1" ), "e1 not a number";
+ok !DBD::mysql::_is_number( "2015-12-02" ), "2015-12-02 not a number";
+ok !DBD::mysql::_is_number( "0-1" ), "0-1 not a number";
+ok !DBD::mysql::_is_number( "1+1" ), "1+1 not a number";
+ok !DBD::mysql::_is_number( "" ), "empty string not a number";