Fix for bugs

#27176: Assigning a string to an year column has unexpected results
#26359: Strings becoming truncated and converted to numbers under STRICT mode

Problems: 
1. storing a string to an integer field we don't check 
   if strntoull10rnd() returns MY_ERRNO_EDOM error.
   Fix: check for MY_ERRNO_EDOM.
2. storing a string to an year field we use my_strntol() function.
   Fix: use strntoull10rnd() instead.
parent 433d3e6c
...@@ -1352,3 +1352,44 @@ t1 CREATE TABLE `t1` ( ...@@ -1352,3 +1352,44 @@ t1 CREATE TABLE `t1` (
`i` int(11) default NULL `i` int(11) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='123456789*123456789*123456789*123456789*123456789*123456789*' ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='123456789*123456789*123456789*123456789*123456789*123456789*'
drop table t1; drop table t1;
set sql_mode= 'traditional';
create table t1(col1 tinyint, col2 tinyint unsigned,
col3 smallint, col4 smallint unsigned,
col5 mediumint, col6 mediumint unsigned,
col7 int, col8 int unsigned,
col9 bigint, col10 bigint unsigned);
insert into t1(col1) values('-');
ERROR HY000: Incorrect integer value: '-' for column 'col1' at row 1
insert into t1(col2) values('+');
ERROR HY000: Incorrect integer value: '+' for column 'col2' at row 1
insert into t1(col3) values('-');
ERROR HY000: Incorrect integer value: '-' for column 'col3' at row 1
insert into t1(col4) values('+');
ERROR HY000: Incorrect integer value: '+' for column 'col4' at row 1
insert into t1(col5) values('-');
ERROR HY000: Incorrect integer value: '-' for column 'col5' at row 1
insert into t1(col6) values('+');
ERROR HY000: Incorrect integer value: '+' for column 'col6' at row 1
insert into t1(col7) values('-');
ERROR HY000: Incorrect integer value: '-' for column 'col7' at row 1
insert into t1(col8) values('+');
ERROR HY000: Incorrect integer value: '+' for column 'col8' at row 1
insert into t1(col9) values('-');
ERROR HY000: Incorrect integer value: '-' for column 'col9' at row 1
insert into t1(col10) values('+');
ERROR HY000: Incorrect integer value: '+' for column 'col10' at row 1
drop table t1;
set sql_mode='traditional';
create table t1(a year);
insert into t1 values ('-');
ERROR HY000: Incorrect integer value: '-' for column 'a' at row 1
insert into t1 values ('+');
ERROR HY000: Incorrect integer value: '+' for column 'a' at row 1
insert into t1 values ('');
ERROR HY000: Incorrect integer value: '' for column 'a' at row 1
insert into t1 values ('2000a');
ERROR 01000: Data truncated for column 'a' at row 1
insert into t1 values ('2E3x');
ERROR 01000: Data truncated for column 'a' at row 1
drop table t1;
End of 5.0 tests
...@@ -99,7 +99,7 @@ DROP TABLE t1, t2, t3; ...@@ -99,7 +99,7 @@ DROP TABLE t1, t2, t3;
CREATE TABLE t1 (y YEAR); CREATE TABLE t1 (y YEAR);
INSERT INTO t1 VALUES ('abc'); INSERT INTO t1 VALUES ('abc');
Warnings: Warnings:
Warning 1264 Out of range value adjusted for column 'y' at row 1 Warning 1366 Incorrect integer value: 'abc' for column 'y' at row 1
SELECT * FROM t1; SELECT * FROM t1;
y y
0000 0000
......
...@@ -34,3 +34,15 @@ select if(y = now(), 1, 0) from t1; ...@@ -34,3 +34,15 @@ select if(y = now(), 1, 0) from t1;
if(y = now(), 1, 0) if(y = now(), 1, 0)
1 1
drop table t1; drop table t1;
create table t1(a year);
insert into t1 values (2000.5), ('2000.5'), ('2001a'), ('2.001E3');
Warnings:
Warning 1265 Data truncated for column 'a' at row 3
select * from t1;
a
2001
2001
2001
2001
drop table t1;
End of 5.0 tests
...@@ -1208,3 +1208,53 @@ create table t1 (i int) ...@@ -1208,3 +1208,53 @@ create table t1 (i int)
comment '123456789*123456789*123456789*123456789*123456789*123456789*'; comment '123456789*123456789*123456789*123456789*123456789*123456789*';
show create table t1; show create table t1;
drop table t1; drop table t1;
#
# Bug #26359: Strings becoming truncated and converted to numbers under STRICT mode
#
set sql_mode= 'traditional';
create table t1(col1 tinyint, col2 tinyint unsigned,
col3 smallint, col4 smallint unsigned,
col5 mediumint, col6 mediumint unsigned,
col7 int, col8 int unsigned,
col9 bigint, col10 bigint unsigned);
--error 1366
insert into t1(col1) values('-');
--error 1366
insert into t1(col2) values('+');
--error 1366
insert into t1(col3) values('-');
--error 1366
insert into t1(col4) values('+');
--error 1366
insert into t1(col5) values('-');
--error 1366
insert into t1(col6) values('+');
--error 1366
insert into t1(col7) values('-');
--error 1366
insert into t1(col8) values('+');
--error 1366
insert into t1(col9) values('-');
--error 1366
insert into t1(col10) values('+');
drop table t1;
#
# Bug #27176: Assigning a string to an year column has unexpected results
#
set sql_mode='traditional';
create table t1(a year);
--error 1366
insert into t1 values ('-');
--error 1366
insert into t1 values ('+');
--error 1366
insert into t1 values ('');
--error 1265
insert into t1 values ('2000a');
--error 1265
insert into t1 values ('2E3x');
drop table t1;
--echo End of 5.0 tests
...@@ -21,4 +21,12 @@ insert into t1 values (now()); ...@@ -21,4 +21,12 @@ insert into t1 values (now());
select if(y = now(), 1, 0) from t1; select if(y = now(), 1, 0) from t1;
drop table t1; drop table t1;
# End of 4.1 tests #
# Bug #27176: Assigning a string to an year column has unexpected results
#
create table t1(a year);
insert into t1 values (2000.5), ('2000.5'), ('2001a'), ('2.001E3');
select * from t1;
drop table t1;
--echo End of 5.0 tests
...@@ -963,6 +963,31 @@ static Item_result field_types_result_type [FIELDTYPE_NUM]= ...@@ -963,6 +963,31 @@ static Item_result field_types_result_type [FIELDTYPE_NUM]=
}; };
/*
Test if the given string contains important data:
not spaces for character string,
or any data for binary string.
SYNOPSIS
test_if_important_data()
cs Character set
str String to test
strend String end
RETURN
FALSE - If string does not have important data
TRUE - If string has some important data
*/
static bool
test_if_important_data(CHARSET_INFO *cs, const char *str, const char *strend)
{
if (cs != &my_charset_bin)
str+= cs->cset->scan(cs, str, strend, MY_SEQ_SPACES);
return (str < strend);
}
/* /*
Detect Item_result by given field type of UNION merge result Detect Item_result by given field type of UNION merge result
...@@ -1051,64 +1076,113 @@ void Field_num::prepend_zeros(String *value) ...@@ -1051,64 +1076,113 @@ void Field_num::prepend_zeros(String *value)
} }
/* /*
Test if given number is a int (or a fixed format float with .000) Test if given number is a int.
SYNOPSIS SYNOPSIS
test_if_int() Field_num::check_int
cs Character set
str String to test str String to test
end Pointer to char after last used digit end Pointer to char after last used digit
cs Character set length String length
error Error returned by strntoull10rnd()
NOTES NOTE
This is called after one has called my_strntol() or similar function. This is called after one has called strntoull10rnd() function.
This is only used to give warnings in ALTER TABLE or LOAD DATA...
TODO
Make this multi-byte-character safe
RETURN RETURN
0 OK 0 ok
1 error. A warning is pushed if field_name != 0 1 error: empty string or wrong integer.
2 error: garbage at the end of string.
*/ */
bool Field::check_int(const char *str, int length, const char *int_end, int Field_num::check_int(CHARSET_INFO *cs, const char *str, int length,
CHARSET_INFO *cs) const char *int_end, int error)
{ {
const char *end; /* Test if we get an empty string or wrong integer */
if (str == int_end) if (str == int_end || error == MY_ERRNO_EDOM)
{ {
char buff[128]; char buff[128];
String tmp(buff,(uint32) sizeof(buff), system_charset_info); String tmp(buff, (uint32) sizeof(buff), system_charset_info);
tmp.copy(str, length, system_charset_info); tmp.copy(str, length, system_charset_info);
push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN, push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
"integer", tmp.c_ptr(), field_name, "integer", tmp.c_ptr(), field_name,
(ulong) table->in_use->row_count); (ulong) table->in_use->row_count);
return 1; // Empty string return 1;
} }
end= str+length; /* Test if we have garbage at the end of the given string. */
if ((str= int_end) == end) if (test_if_important_data(cs, int_end, str + length))
return 0; // OK; All digits was used {
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
return 2;
}
return 0;
}
/* Allow end .0000 */
if (*str == '.') /*
Conver a string to an integer then check bounds.
SYNOPSIS
Field_num::get_int
cs Character set
from String to convert
len Length of the string
rnd OUT longlong value
unsigned_max max unsigned value
signed_min min signed value
signed_max max signed value
DESCRIPTION
The function calls strntoull10rnd() to get an integer value then
check bounds and errors returned. In case of any error a warning
is raised.
RETURN
0 ok
1 error
*/
bool Field_num::get_int(CHARSET_INFO *cs, const char *from, uint len,
longlong *rnd, ulonglong unsigned_max,
longlong signed_min, longlong signed_max)
{
char *end;
int error;
*rnd= (longlong) cs->cset->strntoull10rnd(cs, from, len, unsigned_flag, &end,
&error);
if (unsigned_flag)
{ {
for (str++ ; str != end && *str == '0'; str++)
; if (((ulonglong) *rnd > unsigned_max) && (*rnd= (longlong) unsigned_max) ||
error == MY_ERRNO_ERANGE)
{
goto out_of_range;
}
} }
/* Allow end space */ else
for ( ; str != end ; str++)
{ {
if (!my_isspace(cs,*str)) if (*rnd < signed_min)
{ {
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); *rnd= signed_min;
return 1; goto out_of_range;
}
else if (*rnd > signed_max)
{
*rnd= signed_max;
goto out_of_range;
} }
} }
if (table->in_use->count_cuted_fields && check_int(cs, from, len, end, error))
return 1;
return 0; return 0;
}
out_of_range:
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
return 1;
}
/* /*
Process decimal library return codes and issue warnings for overflow and Process decimal library return codes and issue warnings for overflow and
...@@ -2505,45 +2579,11 @@ void Field_new_decimal::sql_type(String &str) const ...@@ -2505,45 +2579,11 @@ void Field_new_decimal::sql_type(String &str) const
int Field_tiny::store(const char *from,uint len,CHARSET_INFO *cs) int Field_tiny::store(const char *from,uint len,CHARSET_INFO *cs)
{ {
char *end;
int error; int error;
longlong rnd;
if (unsigned_flag)
{ error= get_int(cs, from, len, &rnd, 255, -128, 127);
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error); ptr[0]= unsigned_flag ? (char) (ulonglong) rnd : (char) rnd;
if (error == MY_ERRNO_ERANGE || tmp > 255)
{
set_if_smaller(tmp, 255);
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
ptr[0]= (char) tmp;
}
else
{
longlong tmp= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
if (tmp < -128)
{
tmp= -128;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (tmp >= 128)
{
tmp= 127;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
ptr[0]= (char) tmp;
}
return error; return error;
} }
...@@ -2708,59 +2748,20 @@ void Field_tiny::sql_type(String &res) const ...@@ -2708,59 +2748,20 @@ void Field_tiny::sql_type(String &res) const
int Field_short::store(const char *from,uint len,CHARSET_INFO *cs) int Field_short::store(const char *from,uint len,CHARSET_INFO *cs)
{ {
char *end; int store_tmp;
int error; int error;
longlong rnd;
if (unsigned_flag)
{ error= get_int(cs, from, len, &rnd, UINT_MAX16, INT_MIN16, INT_MAX16);
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error); store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
if (error == MY_ERRNO_ERANGE || tmp > UINT_MAX16)
{
set_if_smaller(tmp, UINT_MAX16);
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
if (table->s->db_low_byte_first) if (table->s->db_low_byte_first)
{ {
int2store(ptr,tmp); int2store(ptr, store_tmp);
}
else
#endif
shortstore(ptr,(short) tmp);
} }
else else
{
longlong tmp= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
if (tmp < INT_MIN16)
{
tmp= INT_MIN16;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (tmp > INT_MAX16)
{
tmp=INT_MAX16;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
#ifdef WORDS_BIGENDIAN
if (table->s->db_low_byte_first)
{
int2store(ptr,tmp);
}
else
#endif #endif
shortstore(ptr,(short) tmp); shortstore(ptr, (short) store_tmp);
}
return error; return error;
} }
...@@ -2988,45 +2989,13 @@ void Field_short::sql_type(String &res) const ...@@ -2988,45 +2989,13 @@ void Field_short::sql_type(String &res) const
int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs) int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs)
{ {
char *end; int store_tmp;
int error; int error;
longlong rnd;
if (unsigned_flag)
{ error= get_int(cs, from, len, &rnd, UINT_MAX24, INT_MIN24, INT_MAX24);
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error); store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
if (error == MY_ERRNO_ERANGE || tmp > UINT_MAX24) int3store(ptr, store_tmp);
{
set_if_smaller(tmp, UINT_MAX24);
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
int3store(ptr,tmp);
}
else
{
longlong tmp= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
if (tmp < INT_MIN24)
{
tmp= INT_MIN24;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (tmp > INT_MAX24)
{
tmp=INT_MAX24;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
int3store(ptr,tmp);
}
return error; return error;
} }
...@@ -3205,45 +3174,10 @@ int Field_long::store(const char *from,uint len,CHARSET_INFO *cs) ...@@ -3205,45 +3174,10 @@ int Field_long::store(const char *from,uint len,CHARSET_INFO *cs)
{ {
long store_tmp; long store_tmp;
int error; int error;
char *end; longlong rnd;
if (unsigned_flag) error= get_int(cs, from, len, &rnd, UINT_MAX32, INT_MIN32, INT_MAX32);
{ store_tmp= unsigned_flag ? (long) (ulonglong) rnd : (long) rnd;
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error);
if (error == MY_ERRNO_ERANGE || tmp > (ulonglong) UINT_MAX32)
{
set_if_smaller(tmp, (ulonglong) UINT_MAX32);
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
store_tmp= (long) tmp;
}
else
{
longlong tmp= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
if (tmp < INT_MIN32)
{
tmp= INT_MIN32;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (tmp > INT_MAX32)
{
tmp=INT_MAX32;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1;
}
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs))
error= 1;
else
error= 0;
store_tmp= (long) tmp;
}
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
if (table->s->db_low_byte_first) if (table->s->db_low_byte_first)
{ {
...@@ -3489,7 +3423,8 @@ int Field_longlong::store(const char *from,uint len,CHARSET_INFO *cs) ...@@ -3489,7 +3423,8 @@ int Field_longlong::store(const char *from,uint len,CHARSET_INFO *cs)
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
error= 1; error= 1;
} }
else if (table->in_use->count_cuted_fields && check_int(from,len,end,cs)) else if (table->in_use->count_cuted_fields &&
check_int(cs, from, len, end, error))
error= 1; error= 1;
else else
error= 0; error= 0;
...@@ -5007,16 +4942,25 @@ int Field_year::store(const char *from, uint len,CHARSET_INFO *cs) ...@@ -5007,16 +4942,25 @@ int Field_year::store(const char *from, uint len,CHARSET_INFO *cs)
{ {
char *end; char *end;
int error; int error;
long nr= my_strntol(cs, from, len, 10, &end, &error); longlong nr= cs->cset->strntoull10rnd(cs, from, len, 0, &end, &error);
if (nr < 0 || nr >= 100 && nr <= 1900 || nr > 2155 || error) if (nr < 0 || nr >= 100 && nr <= 1900 || nr > 2155 ||
error == MY_ERRNO_ERANGE)
{ {
*ptr=0; *ptr=0;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
return 1; return 1;
} }
if (table->in_use->count_cuted_fields && check_int(from,len,end,cs)) if (table->in_use->count_cuted_fields &&
(error= check_int(cs, from, len, end, error)))
{
if (error == 1) /* empty or incorrect string */
{
*ptr= 0;
return 1;
}
error= 1; error= 1;
}
if (nr != 0 || len != 4) if (nr != 0 || len != 4)
{ {
...@@ -5906,31 +5850,6 @@ report_data_too_long(Field_str *field) ...@@ -5906,31 +5850,6 @@ report_data_too_long(Field_str *field)
} }
/*
Test if the given string contains important data:
not spaces for character string,
or any data for binary string.
SYNOPSIS
test_if_important_data()
cs Character set
str String to test
strend String end
RETURN
FALSE - If string does not have important data
TRUE - If string has some important data
*/
static bool
test_if_important_data(CHARSET_INFO *cs, const char *str, const char *strend)
{
if (cs != &my_charset_bin)
str+= cs->cset->scan(cs, str, strend, MY_SEQ_SPACES);
return (str < strend);
}
/* Copy a string and fill with space */ /* Copy a string and fill with space */
int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) int Field_string::store(const char *from,uint length,CHARSET_INFO *cs)
......
...@@ -306,8 +306,6 @@ class Field ...@@ -306,8 +306,6 @@ class Field
virtual void set_derivation(enum Derivation derivation_arg) { } virtual void set_derivation(enum Derivation derivation_arg) { }
bool set_warning(MYSQL_ERROR::enum_warning_level, unsigned int code, bool set_warning(MYSQL_ERROR::enum_warning_level, unsigned int code,
int cuted_increment); int cuted_increment);
bool check_int(const char *str, int length, const char *int_end,
CHARSET_INFO *cs);
void set_datetime_warning(MYSQL_ERROR::enum_warning_level, uint code, void set_datetime_warning(MYSQL_ERROR::enum_warning_level, uint code,
const char *str, uint str_len, const char *str, uint str_len,
timestamp_type ts_type, int cuted_increment); timestamp_type ts_type, int cuted_increment);
...@@ -369,6 +367,11 @@ class Field_num :public Field { ...@@ -369,6 +367,11 @@ class Field_num :public Field {
bool eq_def(Field *field); bool eq_def(Field *field);
int store_decimal(const my_decimal *); int store_decimal(const my_decimal *);
my_decimal *val_decimal(my_decimal *); my_decimal *val_decimal(my_decimal *);
int check_int(CHARSET_INFO *cs, const char *str, int length,
const char *int_end, int error);
bool get_int(CHARSET_INFO *cs, const char *from, uint len,
longlong *rnd, ulonglong unsigned_max,
longlong signed_min, longlong signed_max);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment