diff options
author | Alexander Barkov <bar@mariadb.org> | 2015-09-17 11:05:07 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2015-09-17 11:05:07 +0400 |
commit | d9b25ae3db8584bde809c0ab3230cbe151fa489b (patch) | |
tree | cb0ae8c91d4f1bcd614c3c1b2d7847f3ef7a130f /sql | |
parent | c69cf93bfb3a221d9106f3695aa16e11f7e8b7fb (diff) | |
download | mariadb-git-d9b25ae3db8584bde809c0ab3230cbe151fa489b.tar.gz |
MDEV-8466 CAST works differently for DECIMAL/INT vs DOUBLE for empty strings
MDEV-8468 CAST and INSERT work differently for DECIMAL/INT vs DOUBLE for a string with trailing spaces
Diffstat (limited to 'sql')
-rw-r--r-- | sql/field.cc | 435 | ||||
-rw-r--r-- | sql/field.h | 262 | ||||
-rw-r--r-- | sql/item.cc | 69 | ||||
-rw-r--r-- | sql/item.h | 7 | ||||
-rw-r--r-- | sql/item_func.cc | 25 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 6 | ||||
-rw-r--r-- | sql/my_decimal.cc | 21 | ||||
-rw-r--r-- | sql/my_decimal.h | 14 | ||||
-rw-r--r-- | sql/set_var.h | 2 |
9 files changed, 523 insertions, 318 deletions
diff --git a/sql/field.cc b/sql/field.cc index 0d38ac1a633..bacab1f79f4 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1055,37 +1055,6 @@ Item_result Field::result_merge_type(enum_field_types field_type) *****************************************************************************/ /** - Output a warning for erroneous conversion of strings to numerical - values. For use with ER_TRUNCATED_WRONG_VALUE[_FOR_FIELD] - - @param thd THD object - @param str pointer to string that failed to be converted - @param length length of string - @param cs charset for string - @param typestr string describing type converted to - @param error error value to output - @param field_name (for *_FOR_FIELD) name of field - @param row_num (for *_FOR_FIELD) row number - */ -static void push_numerical_conversion_warning(THD* thd, const char* str, - uint length, CHARSET_INFO* cs, - const char* typestr, int error, - const char* field_name="UNKNOWN", - ulong row_num=0) -{ - char buf[MY_MAX(MY_MAX(DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE, - LONGLONG_TO_STRING_CONVERSION_BUFFER_SIZE), - DECIMAL_TO_STRING_CONVERSION_BUFFER_SIZE)]; - - String tmp(buf, sizeof(buf), cs); - tmp.copy(str, length, cs); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - error, ER_THD(thd, error), typestr, tmp.c_ptr(), - field_name, row_num); -} - - -/** Check whether a field type can be partially indexed by a key. This is a static method, rather than a virtual function, because we need @@ -1389,43 +1358,132 @@ Item *Field_num::get_equal_zerofill_const_item(THD *thd, const Context &ctx, /** - Test if given number is a int. + Contruct warning parameters using thd->no_errors + to determine whether to generate or suppress warnings. + We can get here in a query like this: + SELECT COUNT(@@basedir); + from Item_func_get_system_var::update_null_value(). +*/ +Value_source::Warn_filter::Warn_filter(const THD *thd) + :m_want_warning_edom(!thd->no_errors), + m_want_note_truncated_spaces(!thd->no_errors) +{ } - @todo - Make this multi-byte-character safe - @param str String to test +/** + Check string-to-number conversion and produce a warning if + - could not convert any digits (EDOM-alike error) + - found garbage at the end of the string + - found trailing spaces (a note) + See also Field_num::check_edom_and_truncation() for a similar function. + + @param thd - the thread + @param filter - which warnings/notes are allowed + @param type - name of the data type (e.g. "INTEGER", "DECIMAL", "DOUBLE") + @param cs - character set of the original string + @param str - the original string + @param end - the end of the string + + Unlike Field_num::check_edom_and_truncation(), this function does not + distinguish between EDOM and truncation and reports the same warning for + both cases. Perhaps we should eventually print different warnings, to make + the explicit CAST work closer to the implicit cast in Field_xxx::store(). +*/ +void +Value_source::Converter_string_to_number::check_edom_and_truncation(THD *thd, + Warn_filter filter, + const char *type, + CHARSET_INFO *cs, + const char *str, + size_t length) const +{ + DBUG_ASSERT(str <= m_end_of_num); + DBUG_ASSERT(m_end_of_num <= str + length); + if (m_edom || (m_end_of_num < str + length && + !check_if_only_end_space(cs, m_end_of_num, str + length))) + { + // EDOM or important trailing data truncation + if (filter.want_warning_edom()) + { + /* + We can use err.ptr() here as ErrConvString is guranteed to put an + end \0 here. + */ + THD *wthd= thd ? thd : current_thd; + push_warning_printf(wthd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(wthd, ER_TRUNCATED_WRONG_VALUE), type, + ErrConvString(str, length, cs).ptr()); + } + } + else if (m_end_of_num < str + length) + { + // Unimportant trailing data (spaces) truncation + if (filter.want_note_truncated_spaces()) + { + THD *wthd= thd ? thd : current_thd; + push_warning_printf(wthd, Sql_condition::WARN_LEVEL_NOTE, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(wthd, ER_TRUNCATED_WRONG_VALUE), type, + ErrConvString(str, length, cs).ptr()); + } + } +} + + +/** + Check a string-to-number conversion routine result and generate warnings + in case when it: + - could not convert any digits + - found garbage at the end of the string. + + @param type Data type name (e.g. "decimal", "integer", "double") + @param edom Indicates that the string-to-number routine retuned + an error code equivalent to EDOM (value out of domain), + i.e. the string fully consisted of garbage and the + conversion routine could not get any digits from it. + @param str The original string @param length Length of 'str' - @param int_end Pointer to char after last used digit - @param cs Character set + @param cs Character set + @param end Pointer to char after last used digit @note - This is called after one has called strntoull10rnd() function. + This is called after one has called one of the following functions: + - strntoull10rnd() + - my_strntod() + - str2my_decimal() @retval - 0 OK + 0 OK @retval - 1 error: empty string or wrong integer. + 1 error: could not scan any digits (EDOM), + e.g. empty string, or garbage. @retval - 2 error: garbage at the end of string. + 2 error: scanned some digits, + but then found garbage at the end of the string. */ -int Field_num::check_int(CHARSET_INFO *cs, const char *str, int length, - const char *int_end, int error) + +int Field_num::check_edom_and_truncation(const char *type, bool edom, + CHARSET_INFO *cs, + const char *str, uint length, + const char *end) { - /* Test if we get an empty string or wrong integer */ - if (str == int_end || error == MY_ERRNO_EDOM) + /* Test if we get an empty string or garbage */ + if (edom) { ErrConvString err(str, length, cs); - set_warning_truncated_wrong_value("integer", err.ptr()); + set_warning_truncated_wrong_value(type, err.ptr()); return 1; } /* Test if we have garbage at the end of the given string. */ - if (test_if_important_data(cs, int_end, str + length)) + if (test_if_important_data(cs, end, str + length)) { set_warning(WARN_DATA_TRUNCATED, 1); return 2; } + if (end < str + length) + set_note(WARN_DATA_TRUNCATED, 1); return 0; } @@ -1497,6 +1555,24 @@ out_of_range: } +double Field_real::get_double(const char *str, uint length, CHARSET_INFO *cs, + int *error) +{ + char *end; + double nr= my_strntod(cs,(char*) str, length, &end, error); + if (*error) + { + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); + *error= 1; + } + else if (get_thd()->count_cuted_fields && + check_edom_and_truncation("double", str == end, + cs, str, length, end)) + *error= 1; + return nr; +} + + /** Process decimal library return codes and issue warnings for overflow and truncation. @@ -2962,36 +3038,60 @@ int Field_new_decimal::store(const char *from, uint length, CHARSET_INFO *charset_arg) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; - int err; my_decimal decimal_value; + THD *thd= get_thd(); DBUG_ENTER("Field_new_decimal::store(char*)"); - if ((err= str2my_decimal(E_DEC_FATAL_ERROR & - ~(E_DEC_OVERFLOW | E_DEC_BAD_NUM), + const char *end; + int err= str2my_decimal(E_DEC_FATAL_ERROR & + ~(E_DEC_OVERFLOW | E_DEC_BAD_NUM), from, length, charset_arg, - &decimal_value)) && - get_thd()->abort_on_warning) + &decimal_value, &end); + + if (err == E_DEC_OVERFLOW) // Too many digits (>81) in the integer part { - ErrConvString errmsg(from, length, charset_arg); - set_warning_truncated_wrong_value("decimal", errmsg.ptr()); - DBUG_RETURN(err); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); + if (!thd->abort_on_warning) + { + set_value_on_overflow(&decimal_value, decimal_value.sign()); + store_decimal(&decimal_value); + } + DBUG_RETURN(1); } - switch (err) { - case E_DEC_TRUNCATED: - set_note(WARN_DATA_TRUNCATED, 1); - break; - case E_DEC_OVERFLOW: - set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); - set_value_on_overflow(&decimal_value, decimal_value.sign()); - break; - case E_DEC_BAD_NUM: + if (thd->count_cuted_fields) + { + if (check_edom_and_truncation("decimal", + err && err != E_DEC_TRUNCATED, + charset_arg, from, length, end)) { - ErrConvString errmsg(from, length, charset_arg); - set_warning_truncated_wrong_value("decimal", errmsg.ptr()); - my_decimal_set_zero(&decimal_value); - break; + if (!thd->abort_on_warning) + { + if (err && err != E_DEC_TRUNCATED) + { + /* + If check_decimal() failed because of EDOM-alike error, + (e.g. E_DEC_BAD_NUM), we have to initialize decimal_value to zero. + Note: if check_decimal() failed because of truncation, + decimal_value is alreay properly initialized. + */ + my_decimal_set_zero(&decimal_value); + /* + TODO: check str2my_decimal() with HF. It seems to do + decimal_make_zero() on fatal errors, so my_decimal_set_zero() + is probably not needed here. + */ + } + store_decimal(&decimal_value); + } + DBUG_RETURN(1); } + /* + E_DEC_TRUNCATED means minor truncation '1e-1000000000000' -> 0.0 + A note should be enough. + */ + if (err == E_DEC_TRUNCATED) + set_note(WARN_DATA_TRUNCATED, 1); } #ifndef DBUG_OFF @@ -3000,7 +3100,7 @@ int Field_new_decimal::store(const char *from, uint length, dbug_decimal_as_string(dbug_buff, &decimal_value))); #endif store_value(&decimal_value); - DBUG_RETURN(err); + DBUG_RETURN(0); } @@ -4212,15 +4312,7 @@ void Field_longlong::sql_type(String &res) const int Field_float::store(const char *from,uint len,CHARSET_INFO *cs) { int error; - char *end; - double nr= my_strntod(cs,(char*) from,len,&end,&error); - if (error || (!len || ((uint) (end-from) != len && - get_thd()->count_cuted_fields))) - { - set_warning(error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED, 1); - error= error ? 1 : 2; - } - Field_float::store(nr); + Field_float::store(get_double(from, len, cs, &error)); return error; } @@ -4399,15 +4491,7 @@ void Field_float::sql_type(String &res) const int Field_double::store(const char *from,uint len,CHARSET_INFO *cs) { int error; - char *end; - double nr= my_strntod(cs,(char*) from, len, &end, &error); - if (error || (!len || ((uint) (end-from) != len && - get_thd()->count_cuted_fields))) - { - set_warning(error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED, 1); - error= error ? 1 : 2; - } - Field_double::store(nr); + Field_double::store(get_double(from, len, cs, &error)); return error; } @@ -6853,53 +6937,41 @@ bool Field_longstr::can_optimize_group_min_max(const Item_bool_func *cond, } +/** + This overrides the default behavior of the parent constructor + Warn_filter(thd) to suppress notes about trailing spaces in case of CHAR(N), + as they are truncated during val_str(). + We still do want truncation notes in case of BINARY(N), + as trailing spaces are not truncated in val_str(). +*/ +Field_string::Warn_filter_string::Warn_filter_string(const THD *thd, + const Field_string *field) + :Warn_filter(!thd->no_errors, + !thd->no_errors && + field->Field_string::charset() == &my_charset_bin) +{ } + + double Field_string::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - double result; THD *thd= get_thd(); - - result= my_strntod(cs,(char*) ptr,field_length,&end,&error); - if (!thd->no_errors && - (error || (field_length != (uint32)(end - (char*) ptr) && - !check_if_only_end_space(cs, end, - (char*) ptr + field_length)))) - { - ErrConvString err((char*) ptr, field_length, cs); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "DOUBLE", - err.ptr()); - } - return result; + return Converter_strntod_with_warn(get_thd(), + Warn_filter_string(thd, this), + Field_string::charset(), + (const char *) ptr, + field_length).result(); } longlong Field_string::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - longlong result; THD *thd= get_thd(); - - result= my_strntoll(cs, (char*) ptr,field_length,10,&end,&error); - if (!thd->no_errors && - (error || (field_length != (uint32)(end - (char*) ptr) && - !check_if_only_end_space(cs, end, - (char*) ptr + field_length)))) - { - ErrConvString err((char*) ptr, field_length, cs); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), - "INTEGER", err.ptr()); - } - return result; + return Converter_strntoll_with_warn(thd, Warn_filter_string(thd, this), + Field_string::charset(), + (const char *) ptr, + field_length).result(); } @@ -6922,30 +6994,17 @@ String *Field_string::val_str(String *val_buffer __attribute__((unused)), } -my_decimal *Field_longstr::val_decimal_from_str(const char *str, - uint length, - CHARSET_INFO *cs, - my_decimal *decimal_value) -{ - THD *thd; - int err= str2my_decimal(E_DEC_FATAL_ERROR, str, length, cs, decimal_value); - if (err && !(thd= get_thd())->no_errors) - { - ErrConvString errmsg(str, length, cs); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), - "DECIMAL", errmsg.ptr()); - } - return decimal_value; -} - - my_decimal *Field_string::val_decimal(my_decimal *decimal_value) { ASSERT_COLUMN_MARKED_FOR_READ; - return val_decimal_from_str((const char *) ptr, field_length, - Field_string::charset(), decimal_value); + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, + Warn_filter_string(thd, this), + E_DEC_FATAL_ERROR, + Field_string::charset(), + (const char *) ptr, + field_length, decimal_value); + return decimal_value; } @@ -7310,54 +7369,30 @@ int Field_varstring::store(longlong nr, bool unsigned_val) double Field_varstring::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - double result; - CHARSET_INFO* cs= charset(); - - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - result= my_strntod(cs, (char*)ptr+length_bytes, length, &end, &error); - - if (!get_thd()->no_errors && - (error || (length != (uint)(end - (char*)ptr+length_bytes) && - !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length)))) - { - push_numerical_conversion_warning(get_thd(), (char*)ptr+length_bytes, - length, cs,"DOUBLE", - ER_TRUNCATED_WRONG_VALUE); - } - return result; + THD *thd= get_thd(); + return Converter_strntod_with_warn(thd, Warn_filter(thd), + Field_varstring::charset(), + (const char *) get_data(), + get_length()).result(); } longlong Field_varstring::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - longlong result= my_strntoll(cs, (char*) ptr+length_bytes, length, 10, - &end, &error); - - if (!get_thd()->no_errors && - (error || (length != (uint)(end - (char*)ptr+length_bytes) && - !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length)))) - { - push_numerical_conversion_warning(get_thd(), (char*)ptr+length_bytes, - length, cs, "INTEGER", - ER_TRUNCATED_WRONG_VALUE); - } - return result; + THD *thd= get_thd(); + return Converter_strntoll_with_warn(thd, Warn_filter(thd), + Field_varstring::charset(), + (const char *) get_data(), + get_length()).result(); } + String *Field_varstring::val_str(String *val_buffer __attribute__((unused)), String *val_ptr) { ASSERT_COLUMN_MARKED_FOR_READ; - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - val_ptr->set((const char*) ptr+length_bytes, length, field_charset); + val_ptr->set((const char*) get_data(), get_length(), field_charset); return val_ptr; } @@ -7365,9 +7400,14 @@ String *Field_varstring::val_str(String *val_buffer __attribute__((unused)), my_decimal *Field_varstring::val_decimal(my_decimal *decimal_value) { ASSERT_COLUMN_MARKED_FOR_READ; - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - return val_decimal_from_str((const char *) ptr + length_bytes, length, - Field_varstring::charset(), decimal_value); + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, Warn_filter(thd), + E_DEC_FATAL_ERROR, + Field_varstring::charset(), + (const char *) get_data(), + get_length(), decimal_value); + return decimal_value; + } @@ -7821,32 +7861,31 @@ int Field_blob::store(longlong nr, bool unsigned_val) double Field_blob::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int not_used; - char *end_not_used, *blob; - uint32 length; - CHARSET_INFO *cs; - + char *blob; memcpy(&blob, ptr+packlength, sizeof(char*)); if (!blob) return 0.0; - length= get_length(ptr); - cs= charset(); - return my_strntod(cs, blob, length, &end_not_used, ¬_used); + THD *thd= get_thd(); + return Converter_strntod_with_warn(thd, Warn_filter(thd), + Field_blob::charset(), + blob, get_length(ptr)).result(); } longlong Field_blob::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int not_used; char *blob; memcpy(&blob, ptr+packlength, sizeof(char*)); if (!blob) return 0; - uint32 length=get_length(ptr); - return my_strntoll(charset(),blob,length,10,NULL,¬_used); + THD *thd= get_thd(); + return Converter_strntoll_with_warn(thd, Warn_filter(thd), + Field_blob::charset(), + blob, get_length(ptr)).result(); } + String *Field_blob::val_str(String *val_buffer __attribute__((unused)), String *val_ptr) { @@ -7875,8 +7914,12 @@ my_decimal *Field_blob::val_decimal(my_decimal *decimal_value) else length= get_length(ptr); - return val_decimal_from_str(blob, length, - Field_blob::charset(), decimal_value); + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, Warn_filter(thd), + E_DEC_FATAL_ERROR, + Field_blob::charset(), + blob, length, decimal_value); + return decimal_value; } diff --git a/sql/field.h b/sql/field.h index cd6c476671b..7b76512bc69 100644 --- a/sql/field.h +++ b/sql/field.h @@ -56,6 +56,231 @@ enum enum_check_fields */ class Value_source { +protected: + + // Parameters for warning and note generation + class Warn_filter + { + bool m_want_warning_edom; + bool m_want_note_truncated_spaces; + public: + Warn_filter(bool want_warning_edom, bool want_note_truncated_spaces) : + m_want_warning_edom(want_warning_edom), + m_want_note_truncated_spaces(want_note_truncated_spaces) + { } + Warn_filter(const THD *thd); + bool want_warning_edom() const + { return m_want_warning_edom; } + bool want_note_truncated_spaces() const + { return m_want_note_truncated_spaces; } + }; + class Warn_filter_all: public Warn_filter + { + public: + Warn_filter_all() :Warn_filter(true, true) { } + }; + + + // String-to-number converters + class Converter_string_to_number + { + protected: + char *m_end_of_num; // Where the low-level conversion routine stopped + int m_error; // The error code returned by the low-level routine + bool m_edom; // If EDOM-alike error happened during conversion + /** + Check string-to-number conversion and produce a warning if + - could not convert any digits (EDOM-alike error) + - found garbage at the end of the string + - found extra spaces at the end (a note) + See also Field_num::check_edom_and_truncation() for a similar function. + + @param thd - the thread that will be used to generate warnings. + Can be NULL (which means current_thd will be used + if a warning is really necessary). + @param type - name of the data type + (e.g. "INTEGER", "DECIMAL", "DOUBLE") + @param cs - character set of the original string + @param str - the original string + @param end - the end of the string + @param allow_notes - tells if trailing space notes should be displayed + or suppressed. + + Unlike Field_num::check_edom_and_truncation(), this function does not + distinguish between EDOM and truncation and reports the same warning for + both cases. Perhaps we should eventually print different warnings, + to make the explicit CAST work closer to the implicit cast in + Field_xxx::store(). + */ + void check_edom_and_truncation(THD *thd, Warn_filter filter, + const char *type, + CHARSET_INFO *cs, + const char *str, + size_t length) const; + public: + int error() const { return m_error; } + }; + + class Converter_strntod: public Converter_string_to_number + { + double m_result; + public: + Converter_strntod(CHARSET_INFO *cs, const char *str, size_t length) + { + m_result= my_strntod(cs, (char *) str, length, &m_end_of_num, &m_error); + // strntod() does not set an error if the input string was empty + m_edom= m_error !=0 || str == m_end_of_num; + } + double result() const { return m_result; } + }; + + class Converter_string_to_longlong: public Converter_string_to_number + { + protected: + longlong m_result; + public: + longlong result() const { return m_result; } + }; + + class Converter_strntoll: public Converter_string_to_longlong + { + public: + Converter_strntoll(CHARSET_INFO *cs, const char *str, size_t length) + { + m_result= my_strntoll(cs, str, length, 10, &m_end_of_num, &m_error); + /* + All non-zero errors means EDOM error. + strntoll() does not set an error if the input string was empty. + Check it here. + Notice the different with the same condition in Converter_strntoll10. + */ + m_edom= m_error != 0 || str == m_end_of_num; + } + }; + + class Converter_strtoll10: public Converter_string_to_longlong + { + public: + Converter_strtoll10(CHARSET_INFO *cs, const char *str, size_t length) + { + m_end_of_num= (char *) str + length; + m_result= (*(cs->cset->strtoll10))(cs, str, &m_end_of_num, &m_error); + /* + Negative error means "good negative number". + Only a positive m_error value means a real error. + strtoll10() sets error to MY_ERRNO_EDOM in case of an empty string, + so we don't have to additionally catch empty strings here. + */ + m_edom= m_error > 0; + } + }; + + class Converter_str2my_decimal: public Converter_string_to_number + { + public: + Converter_str2my_decimal(uint mask, + CHARSET_INFO *cs, const char *str, size_t length, + my_decimal *buf) + { + m_error= str2my_decimal(mask, str, length, cs, + buf, (const char **) &m_end_of_num); + // E_DEC_TRUNCATED means a very minor truncation: '1e-100' -> 0 + m_edom= m_error && m_error != E_DEC_TRUNCATED; + } + }; + + + // String-to-number converters with automatic warning generation + class Converter_strntod_with_warn: public Converter_strntod + { + public: + Converter_strntod_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strntod(cs, str, length) + { + check_edom_and_truncation(thd, filter, "DOUBLE", cs, str, length); + } + }; + + class Converter_strntoll_with_warn: public Converter_strntoll + { + public: + Converter_strntoll_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strntoll(cs, str, length) + { + check_edom_and_truncation(thd, filter, "INTEGER", cs, str, length); + } + }; + + class Converter_strtoll10_with_warn: public Converter_strtoll10 + { + public: + Converter_strtoll10_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strtoll10(cs, str, length) + { + check_edom_and_truncation(thd, filter, "INTEGER", cs, str, length); + } + }; + + class Converter_str2my_decimal_with_warn: public Converter_str2my_decimal + { + public: + Converter_str2my_decimal_with_warn(THD *thd, Warn_filter filter, + uint mask, CHARSET_INFO *cs, + const char *str, size_t length, + my_decimal *buf) + :Converter_str2my_decimal(mask, cs, str, length, buf) + { + check_edom_and_truncation(thd, filter, "DECIMAL", cs, str, length); + } + }; + + + // String-to-number convertion methods for the old code compatibility + longlong longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, + const char *end) const + { + /* + TODO: Give error if we wanted a signed integer and we got an unsigned + one + + Notice, longlong_from_string_with_check() honors thd->no_error, because + it's used to handle queries like this: + SELECT COUNT(@@basedir); + and is called when Item_func_get_system_var::update_null_value() + suppresses warnings and then calls val_int(). + The other methods {double|decimal}_from_string_with_check() ignore + thd->no_errors, because they are not used for update_null_value() + and they always allow all kind of warnings. + */ + THD *thd= current_thd; + return Converter_strtoll10_with_warn(thd, Warn_filter(thd), + cs, cptr, end - cptr).result(); + } + + double double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, + const char *end) const + { + return Converter_strntod_with_warn(NULL, Warn_filter_all(), + cs, cptr, end - cptr).result(); + } + my_decimal *decimal_from_string_with_check(my_decimal *decimal_value, + CHARSET_INFO *cs, + const char *cptr, + const char *end) + { + Converter_str2my_decimal_with_warn(NULL, Warn_filter_all(), + E_DEC_FATAL_ERROR & ~E_DEC_BAD_NUM, + cs, cptr, end - cptr, decimal_value); + return decimal_value; + } + // End of String-to-number conversion methods + public: /* The enumeration Subst_constraint is currently used only in implementations @@ -1207,6 +1432,20 @@ protected: class Field_num :public Field { protected: + int check_edom_and_truncation(const char *type, bool edom, + CHARSET_INFO *cs, + const char *str, uint length, + const char *end_of_num); + int check_int(CHARSET_INFO *cs, const char *str, uint length, + const char *int_end, int error) + { + return check_edom_and_truncation("integer", + error == MY_ERRNO_EDOM || str == int_end, + cs, str, length, int_end); + } + bool get_int(CHARSET_INFO *cs, const char *from, uint len, + longlong *rnd, ulonglong unsigned_max, + longlong signed_min, longlong signed_max); void prepend_zeros(String *value) const; Item *get_equal_zerofill_const_item(THD *thd, const Context &ctx, Item *const_item); @@ -1244,11 +1483,6 @@ public: return length; } int store_time_dec(MYSQL_TIME *ltime, uint dec); - 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); double pos_in_interval(Field *min, Field *max) { return pos_in_interval_val_real(min, max); @@ -1318,9 +1552,6 @@ protected: const Item *item) const; bool cmp_to_string_with_stricter_collation(const Item_bool_func *cond, const Item *item) const; - my_decimal *val_decimal_from_str(const char *str, uint length, - CHARSET_INFO *cs, - my_decimal *decimal_value); public: Field_longstr(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, @@ -1342,6 +1573,8 @@ public: /* base class for float and double and decimal (old one) */ class Field_real :public Field_num { +protected: + double get_double(const char *str, uint length, CHARSET_INFO *cs, int *err); public: bool not_fixed; @@ -2485,6 +2718,11 @@ new_Field_datetime(MEM_ROOT *root, uchar *ptr, uchar *null_ptr, uchar null_bit, } class Field_string :public Field_longstr { + class Warn_filter_string: public Warn_filter + { + public: + Warn_filter_string(const THD *thd, const Field_string *field); + }; public: bool can_alter_field_type; Field_string(uchar *ptr_arg, uint32 len_arg,uchar *null_ptr_arg, @@ -2558,6 +2796,14 @@ private: class Field_varstring :public Field_longstr { + uchar *get_data() const + { + return ptr + length_bytes; + } + uint get_length() const + { + return length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); + } public: /* The maximum space available in a Field_varstring, in bytes. See diff --git a/sql/item.cc b/sql/item.cc index 6ae6fc87bf7..55159cb9df0 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -317,18 +317,8 @@ my_decimal *Item::val_decimal_from_string(my_decimal *decimal_value) if (!(res= val_str(&str_value))) return 0; - if (str2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_BAD_NUM, - res->ptr(), res->length(), res->charset(), - decimal_value) & E_DEC_BAD_NUM) - { - THD *thd= current_thd; - ErrConvString err(res); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "DECIMAL", - err.ptr()); - } - return decimal_value; + return decimal_from_string_with_check(decimal_value, + res->charset(), res->ptr(), res->end()); } @@ -3028,33 +3018,6 @@ void Item_string::print(String *str, enum_query_type query_type) } -double -double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end) -{ - int error; - char *end_of_num= (char*) end; - double tmp; - - tmp= my_strntod(cs, (char*) cptr, end - cptr, &end_of_num, &error); - if (error || (end != end_of_num && - !check_if_only_end_space(cs, end_of_num, end))) - { - THD *thd= current_thd; - ErrConvString err(cptr, end - cptr, cs); - /* - We can use err.ptr() here as ErrConvString is guranteed to put an - end \0 here. - */ - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "DOUBLE", - err.ptr()); - } - return tmp; -} - - double Item_string::val_real() { DBUG_ASSERT(fixed == 1); @@ -3065,34 +3028,6 @@ double Item_string::val_real() } -longlong -longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end) -{ - int err; - longlong tmp; - char *end_of_num= (char*) end; - THD *thd= current_thd; - - tmp= (*(cs->cset->strtoll10))(cs, cptr, &end_of_num, &err); - /* - TODO: Give error if we wanted a signed integer and we got an unsigned - one - */ - if (!thd->no_errors && - (err > 0 || - (end != end_of_num && !check_if_only_end_space(cs, end_of_num, end)))) - { - ErrConvString err_str(cptr, end - cptr, cs); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "INTEGER", - err_str.ptr()); - } - return tmp; -} - - /** @todo Give error if we wanted a signed integer and we got an unsigned one diff --git a/sql/item.h b/sql/item.h index 6a4e5481b36..2b845064dce 100644 --- a/sql/item.h +++ b/sql/item.h @@ -3043,13 +3043,6 @@ public: }; -longlong -longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end); -double -double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end); - class Item_static_string_func :public Item_string { const char *func_name; diff --git a/sql/item_func.cc b/sql/item_func.cc index bc4a39b577b..e0cfa037780 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1137,11 +1137,8 @@ void Item_func_signed::print(String *str, enum_query_type query_type) longlong Item_func_signed::val_int_from_str(int *error) { - char buff[MAX_FIELD_WIDTH], *end, *start; - uint32 length; + char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff), &my_charset_bin), *res; - longlong value; - CHARSET_INFO *cs; /* For a string result, we must first get the string and then convert it @@ -1155,22 +1152,10 @@ longlong Item_func_signed::val_int_from_str(int *error) return 0; } null_value= 0; - start= (char *)res->ptr(); - length= res->length(); - cs= res->charset(); - - end= start + length; - value= cs->cset->strtoll10(cs, start, &end, error); - if (*error > 0 || end != start+ length) - { - THD *thd= current_thd; - ErrConvString err(res); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "INTEGER", - err.ptr()); - } - return value; + Converter_strtoll10_with_warn cnv(NULL, Warn_filter_all(), + res->charset(), res->ptr(), res->length()); + *error= cnv.error(); + return cnv.result(); } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 7f466e86afb..ef2e96e8234 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -5081,13 +5081,15 @@ my_decimal *Item_dyncol_get::val_decimal(my_decimal *decimal_value) break; case DYN_COL_STRING: { + const char *end; int rc; rc= str2my_decimal(0, val.x.string.value.str, val.x.string.value.length, - val.x.string.charset, decimal_value); + val.x.string.charset, decimal_value, &end); char buff[80]; strmake(buff, val.x.string.value.str, MY_MIN(sizeof(buff)-1, val.x.string.value.length)); - if (rc != E_DEC_OK) + if (rc != E_DEC_OK || + end != val.x.string.value.str + val.x.string.value.length) { THD *thd= current_thd; push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, diff --git a/sql/my_decimal.cc b/sql/my_decimal.cc index 5ec3fe7ff28..91455c7cb84 100644 --- a/sql/my_decimal.cc +++ b/sql/my_decimal.cc @@ -239,9 +239,9 @@ int my_decimal2binary(uint mask, const my_decimal *d, uchar *bin, int prec, */ int str2my_decimal(uint mask, const char *from, uint length, - CHARSET_INFO *charset, my_decimal *decimal_value) + CHARSET_INFO *charset, my_decimal *decimal_value, + const char **end_ptr) { - char *end, *from_end; int err; char buff[STRING_BUFFER_USUAL_SIZE]; String tmp(buff, sizeof(buff), &my_charset_bin); @@ -253,20 +253,11 @@ int str2my_decimal(uint mask, const char *from, uint length, length= tmp.length(); charset= &my_charset_bin; } - from_end= end= (char*) from+length; + char *end= (char*) from + length; err= string2decimal((char *)from, (decimal_t*) decimal_value, &end); - if (end != from_end && !err) - { - /* Give warning if there is something other than end space */ - for ( ; end < from_end; end++) - { - if (!my_isspace(&my_charset_latin1, *end)) - { - err= E_DEC_TRUNCATED; - break; - } - } - } + if (charset->mbminlen > 1) + end= (char *) from + charset->mbminlen * (size_t) (end - buff); + *end_ptr= end; check_result_and_overflow(mask, err, decimal_value); return err; } diff --git a/sql/my_decimal.h b/sql/my_decimal.h index a2cce862f1a..78c71d54b6d 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -366,13 +366,23 @@ int str2my_decimal(uint mask, const char *str, my_decimal *d, char **end) int str2my_decimal(uint mask, const char *from, uint length, - CHARSET_INFO *charset, my_decimal *decimal_value); + CHARSET_INFO *charset, my_decimal *decimal_value, + const char **end); + +inline int str2my_decimal(uint mask, const char *from, uint length, + CHARSET_INFO *charset, my_decimal *decimal_value) +{ + const char *end; + return str2my_decimal(mask, from, length, charset, decimal_value, &end); +} #if defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) inline int string2my_decimal(uint mask, const String *str, my_decimal *d) { - return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), d); + const char *end; + return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), + d, &end); } diff --git a/sql/set_var.h b/sql/set_var.h index 43ad7f509d8..b8192e67ca9 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -55,7 +55,7 @@ int mysql_del_sys_var_chain(sys_var *chain); optionally it can be assigned to, optionally it can have a command-line counterpart with the same name. */ -class sys_var +class sys_var: protected Value_source // for double_from_string_with_check { public: sys_var *next; |