From 86010101e294559d83d0c99def8160eddcf4ce55 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Mar 2007 09:08:30 +0500 Subject: [PATCH] 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. --- mysql-test/r/strict.result | 41 ++++ mysql-test/r/type_date.result | 2 +- mysql-test/r/type_year.result | 12 ++ mysql-test/t/strict.test | 50 +++++ mysql-test/t/type_year.test | 10 +- sql/field.cc | 371 +++++++++++++--------------------- sql/field.h | 7 +- 7 files changed, 263 insertions(+), 230 deletions(-) diff --git a/mysql-test/r/strict.result b/mysql-test/r/strict.result index 702fc68bb25..93ce4fb8f7c 100644 --- a/mysql-test/r/strict.result +++ b/mysql-test/r/strict.result @@ -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 diff --git a/mysql-test/r/type_date.result b/mysql-test/r/type_date.result index ed15293bb45..644d4d971c6 100644 --- a/mysql-test/r/type_date.result +++ b/mysql-test/r/type_date.result @@ -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 diff --git a/mysql-test/r/type_year.result b/mysql-test/r/type_year.result index 84b688429db..e52947455c8 100644 --- a/mysql-test/r/type_year.result +++ b/mysql-test/r/type_year.result @@ -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 diff --git a/mysql-test/t/strict.test b/mysql-test/t/strict.test index 224a7422de1..53b3bc03ff7 100644 --- a/mysql-test/t/strict.test +++ b/mysql-test/t/strict.test @@ -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 diff --git a/mysql-test/t/type_year.test b/mysql-test/t/type_year.test index 9744da24c02..0e174a556d6 100644 --- a/mysql-test/t/type_year.test +++ b/mysql-test/t/type_year.test @@ -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 diff --git a/sql/field.cc b/sql/field.cc index a95d0069985..352f08e40ce 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -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,65 +1076,114 @@ 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 - - /* Allow end .0000 */ - if (*str == '.') + /* Test if we have garbage at the end of the given string. */ + if (test_if_important_data(cs, int_end, str + length)) { - for (str++ ; str != end && *str == '0'; str++) - ; - } - /* Allow end space */ - for ( ; str != end ; str++) - { - if (!my_isspace(cs,*str)) - { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); - return 1; - } + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + return 2; } return 0; } +/* + 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) + { + + if (((ulonglong) *rnd > unsigned_max) && (*rnd= (longlong) unsigned_max) || + error == MY_ERRNO_ERANGE) + { + goto out_of_range; + } + } + else + { + if (*rnd < signed_min) + { + *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 truncation. @@ -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) diff --git a/sql/field.h b/sql/field.h index 524380800f3..32553573433 100644 --- a/sql/field.h +++ b/sql/field.h @@ -306,8 +306,6 @@ public: 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 @@ public: 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); };