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/field.cc | |
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/field.cc')
-rw-r--r-- | sql/field.cc | 435 |
1 files changed, 239 insertions, 196 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; } |