summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2015-09-17 11:05:07 +0400
committerAlexander Barkov <bar@mariadb.org>2015-09-17 11:05:07 +0400
commitd9b25ae3db8584bde809c0ab3230cbe151fa489b (patch)
treecb0ae8c91d4f1bcd614c3c1b2d7847f3ef7a130f /sql
parentc69cf93bfb3a221d9106f3695aa16e11f7e8b7fb (diff)
downloadmariadb-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.cc435
-rw-r--r--sql/field.h262
-rw-r--r--sql/item.cc69
-rw-r--r--sql/item.h7
-rw-r--r--sql/item_func.cc25
-rw-r--r--sql/item_strfunc.cc6
-rw-r--r--sql/my_decimal.cc21
-rw-r--r--sql/my_decimal.h14
-rw-r--r--sql/set_var.h2
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, &not_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,&not_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;