Commit e152fc4d authored by unknown's avatar unknown

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.


mysql-test/r/strict.result:
  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
    - test result.
mysql-test/r/type_date.result:
  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
    - test result.
mysql-test/r/type_year.result:
  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
    - test result.
mysql-test/t/strict.test:
  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
    - test case.
mysql-test/t/type_year.test:
  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
sql/field.cc:
  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
    - Field_num::get_int() method introduced. It converts a string to integer
      then check errors and bounds.
    - similar Field_tiny::store(const char...),  Field_short::store(const char...),
      Field_medium::store(const char...), Field_long::store(const char...)
      rewritten, now they just call Field_num::get_int() then store value returned.
    - Field_num::check_int() simplified.
    - Field_year::store(const char...) now uses strntoull10rnd() and properly checks
      errors returned.
sql/field.h:
  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
   - check_int() moved to Field_num.
   - get_int() introduced.
parent aa0aa0c9
......@@ -1352,3 +1352,44 @@ t1 CREATE TABLE `t1` (
`i` int(11) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='123456789*123456789*123456789*123456789*123456789*123456789*'
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;
CREATE TABLE t1 (y YEAR);
INSERT INTO t1 VALUES ('abc');
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;
y
0000
......
......@@ -34,3 +34,15 @@ select if(y = now(), 1, 0) from t1;
if(y = now(), 1, 0)
1
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)
comment '123456789*123456789*123456789*123456789*123456789*123456789*';
show create 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());
select if(y = now(), 1, 0) from 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]=
};
/*
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
......@@ -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
test_if_int()
Field_num::check_int
cs Character set
str String to test
end Pointer to char after last used digit
cs Character set
length String length
error Error returned by strntoull10rnd()
NOTES
This is called after one has called my_strntol() or similar function.
This is only used to give warnings in ALTER TABLE or LOAD DATA...
TODO
Make this multi-byte-character safe
NOTE
This is called after one has called strntoull10rnd() function.
RETURN
0 OK
1 error. A warning is pushed if field_name != 0
0 ok
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,
CHARSET_INFO *cs)
int Field_num::check_int(CHARSET_INFO *cs, const char *str, int length,
const char *int_end, int error)
{
const char *end;
if (str == int_end)
/* Test if we get an empty string or wrong integer */
if (str == int_end || error == MY_ERRNO_EDOM)
{
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);
push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
"integer", tmp.c_ptr(), field_name,
(ulong) table->in_use->row_count);
return 1; // Empty string
return 1;
}
end= str+length;
if ((str= int_end) == end)
return 0; // OK; All digits was used
/* Test if we have garbage at the end of the given string. */
if (test_if_important_data(cs, int_end, str + length))
{
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 */
for ( ; str != end ; str++)
else
{
if (!my_isspace(cs,*str))
if (*rnd < signed_min)
{
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
return 1;
*rnd= signed_min;
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;
}
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
......@@ -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)
{
char *end;
int error;
if (unsigned_flag)
{
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error);
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;
}
longlong rnd;
error= get_int(cs, from, len, &rnd, 255, -128, 127);
ptr[0]= unsigned_flag ? (char) (ulonglong) rnd : (char) rnd;
return error;
}
......@@ -2708,59 +2748,20 @@ void Field_tiny::sql_type(String &res) const
int Field_short::store(const char *from,uint len,CHARSET_INFO *cs)
{
char *end;
int store_tmp;
int error;
if (unsigned_flag)
{
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error);
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;
longlong rnd;
error= get_int(cs, from, len, &rnd, UINT_MAX16, INT_MIN16, INT_MAX16);
store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
#ifdef WORDS_BIGENDIAN
if (table->s->db_low_byte_first)
{
int2store(ptr,tmp);
}
else
#endif
shortstore(ptr,(short) tmp);
if (table->s->db_low_byte_first)
{
int2store(ptr, store_tmp);
}
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
shortstore(ptr,(short) tmp);
}
shortstore(ptr, (short) store_tmp);
return error;
}
......@@ -2988,45 +2989,13 @@ void Field_short::sql_type(String &res) const
int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs)
{
char *end;
int store_tmp;
int error;
if (unsigned_flag)
{
ulonglong tmp= cs->cset->strntoull10rnd(cs, from, len, 1, &end, &error);
if (error == MY_ERRNO_ERANGE || tmp > UINT_MAX24)
{
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);
}
longlong rnd;
error= get_int(cs, from, len, &rnd, UINT_MAX24, INT_MIN24, INT_MAX24);
store_tmp= unsigned_flag ? (int) (ulonglong) rnd : (int) rnd;
int3store(ptr, store_tmp);
return error;
}
......@@ -3205,45 +3174,10 @@ int Field_long::store(const char *from,uint len,CHARSET_INFO *cs)
{
long store_tmp;
int error;
char *end;
if (unsigned_flag)
{
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;
}
longlong rnd;
error= get_int(cs, from, len, &rnd, UINT_MAX32, INT_MIN32, INT_MAX32);
store_tmp= unsigned_flag ? (long) (ulonglong) rnd : (long) rnd;
#ifdef WORDS_BIGENDIAN
if (table->s->db_low_byte_first)
{
......@@ -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);
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;
else
error= 0;
......@@ -5007,16 +4942,25 @@ int Field_year::store(const char *from, uint len,CHARSET_INFO *cs)
{
char *end;
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;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 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;
}
if (nr != 0 || len != 4)
{
......@@ -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 */
int Field_string::store(const char *from,uint length,CHARSET_INFO *cs)
......
......@@ -306,8 +306,6 @@ class Field
virtual void set_derivation(enum Derivation derivation_arg) { }
bool set_warning(MYSQL_ERROR::enum_warning_level, unsigned int code,
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,
const char *str, uint str_len,
timestamp_type ts_type, int cuted_increment);
......@@ -369,6 +367,11 @@ class Field_num :public Field {
bool eq_def(Field *field);
int store_decimal(const 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