diff options
author | Alexander Barkov <bar@mariadb.org> | 2015-03-18 17:10:48 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2015-03-18 17:10:48 +0400 |
commit | e28a241907aa7a511b65b196703efaeea71e1dc4 (patch) | |
tree | 9456d75e69127d0416592f8036441b17cbe43724 /sql | |
parent | a471b7098f50e21565ce4c86efcb05d8310e1d62 (diff) | |
download | mariadb-git-e28a241907aa7a511b65b196703efaeea71e1dc4.tar.gz |
MDEV-7661 Unexpected result for: CAST(0xHHHH AS CHAR CHARACTER SET xxx)
for incorrect byte sequences
Diffstat (limited to 'sql')
-rw-r--r-- | sql/item.cc | 39 | ||||
-rw-r--r-- | sql/item.h | 15 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 15 | ||||
-rw-r--r-- | sql/item_timefunc.cc | 166 | ||||
-rw-r--r-- | sql/item_timefunc.h | 5 | ||||
-rw-r--r-- | sql/share/errmsg-utf8.txt | 2 | ||||
-rw-r--r-- | sql/sql_string.h | 12 |
7 files changed, 170 insertions, 84 deletions
diff --git a/sql/item.cc b/sql/item.cc index 85279b2abf1..c8a9164fd92 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5508,6 +5508,45 @@ String *Item::check_well_formed_result(String *str, bool send_error) return str; } + +/** + Copy a string with optional character set conversion. +*/ +bool +String_copier_for_item::copy_with_warn(CHARSET_INFO *dstcs, String *dst, + CHARSET_INFO *srccs, const char *src, + uint32 src_length, uint32 nchars) +{ + if ((dst->copy(dstcs, srccs, src, src_length, nchars, this))) + return true; // EOM + if (const char *pos= well_formed_error_pos()) + { + ErrConvString err(pos, src_length - (pos - src), &my_charset_bin); + push_warning_printf(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_CHARACTER_STRING, + ER(ER_INVALID_CHARACTER_STRING), + srccs == &my_charset_bin ? + dstcs->csname : srccs->csname, + err.ptr()); + return m_thd->is_strict_mode(); + } + if (const char *pos= cannot_convert_error_pos()) + { + char buf[16]; + int mblen= srccs->cset->charlen(srccs, (const uchar *) pos, + (const uchar *) src + src_length); + DBUG_ASSERT(mblen > 0 && mblen * 2 + 1 <= (int) sizeof(buf)); + octet2hex(buf, pos, mblen); + push_warning_printf(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANNOT_CONVERT_CHARACTER, + ER(ER_CANNOT_CONVERT_CHARACTER), + srccs->csname, buf, dstcs->csname); + return m_thd->is_strict_mode(); + } + return false; +} + + /* Compare two items using a given collation diff --git a/sql/item.h b/sql/item.h index db5a94fdb01..42468593dd8 100644 --- a/sql/item.h +++ b/sql/item.h @@ -531,6 +531,21 @@ class st_select_lex_unit; class Item_func_not; class Item_splocal; +/** + String_copier that honors the current sql_mode (strict vs non strict) + and can send warnings. +*/ +class String_copier_for_item: public String_copier +{ + THD *m_thd; +public: + bool copy_with_warn(CHARSET_INFO *dstcs, String *dst, + CHARSET_INFO *srccs, const char *src, + uint32 src_length, uint32 nchars); + String_copier_for_item(THD *thd): m_thd(thd) { } +}; + + class Item { Item(const Item &); /* Prevent use of these */ void operator=(Item &); diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 54fc8d555c8..32b6d6348ac 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -3378,15 +3378,12 @@ String *Item_func_conv_charset::val_str(String *str) if (use_cached_value) return null_value ? 0 : &str_value; String *arg= args[0]->val_str(str); - uint dummy_errors; - if (args[0]->null_value) - { - null_value=1; - return 0; - } - null_value= tmp_value.copy(arg->ptr(), arg->length(), arg->charset(), - conv_charset, &dummy_errors); - return null_value ? 0 : check_well_formed_result(&tmp_value); + String_copier_for_item copier(current_thd); + return ((null_value= args[0]->null_value || + copier.copy_with_warn(conv_charset, &tmp_value, + arg->charset(), arg->ptr(), + arg->length(), arg->length()))) ? + 0 : &tmp_value; } void Item_func_conv_charset::fix_length_and_dec() diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 171750363ed..d82e4aabb35 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -2356,105 +2356,121 @@ void Item_char_typecast::print(String *str, enum_query_type query_type) str->append(')'); } + +void Item_char_typecast::check_truncation_with_warn(String *src, uint dstlen) +{ + if (dstlen < src->length()) + { + char char_type[40]; + my_snprintf(char_type, sizeof(char_type), "%s(%lu)", + cast_cs == &my_charset_bin ? "BINARY" : "CHAR", + (ulong) cast_length); + ErrConvString err(src); + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER(ER_TRUNCATED_WRONG_VALUE), char_type, + err.ptr()); + } +} + + +String *Item_char_typecast::reuse(String *src, uint32 length) +{ + DBUG_ASSERT(length <= src->length()); + check_truncation_with_warn(src, length); + tmp_value.set(src->ptr(), length, cast_cs); + return &tmp_value; +} + + +/* + Make a copy, to handle conversion or fix bad bytes. +*/ +String *Item_char_typecast::copy(String *str, CHARSET_INFO *strcs) +{ + String_copier_for_item copier(current_thd); + if (copier.copy_with_warn(cast_cs, &tmp_value, strcs, + str->ptr(), str->length(), cast_length)) + { + null_value= 1; // In strict mode: malformed data or could not convert + return 0; + } + check_truncation_with_warn(str, copier.source_end_pos() - str->ptr()); + return &tmp_value; +} + + +uint Item_char_typecast::adjusted_length_with_warn(uint length) +{ + if (length <= current_thd->variables.max_allowed_packet) + return length; + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), + cast_cs == &my_charset_bin ? + "cast_as_binary" : func_name(), + current_thd->variables.max_allowed_packet); + return current_thd->variables.max_allowed_packet; +} + + String *Item_char_typecast::val_str(String *str) { DBUG_ASSERT(fixed == 1); String *res; - uint32 length; - if (cast_length != ~0U && - cast_length > current_thd->variables.max_allowed_packet) + if (has_explicit_length()) + cast_length= adjusted_length_with_warn(cast_length); + + if (!(res= args[0]->val_str(str))) { - push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - cast_cs == &my_charset_bin ? - "cast_as_binary" : func_name(), - current_thd->variables.max_allowed_packet); - cast_length= current_thd->variables.max_allowed_packet; + null_value= 1; + return 0; } - if (!charset_conversion) + if (cast_cs == &my_charset_bin && + has_explicit_length() && + cast_length > res->length()) { - if (!(res= args[0]->val_str(str))) + // Special case: pad binary value with trailing 0x00 + DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet); + if (res->alloced_length() < cast_length) { - null_value= 1; - return 0; + str_value.alloc(cast_length); + str_value.copy(*res); + res= &str_value; } + bzero((char*) res->ptr() + res->length(), cast_length - res->length()); + res->length(cast_length); + res->set_charset(&my_charset_bin); } else { /* - Convert character set if differ from_cs is 0 in the case where the result set may vary between calls, for example with dynamic columns. */ - uint dummy_errors; - if (!(res= args[0]->val_str(str)) || - tmp_value.copy(res->ptr(), res->length(), - from_cs ? from_cs : res->charset(), - cast_cs, &dummy_errors)) - { - null_value= 1; - return 0; - } - res= &tmp_value; - } - - res->set_charset(cast_cs); - - /* - Cut the tail if cast with length - and the result is longer than cast length, e.g. - CAST('string' AS CHAR(1)) - */ - if (cast_length != ~0U) - { - if (res->length() > (length= (uint32) res->charpos(cast_length))) - { // Safe even if const arg - char char_type[40]; - my_snprintf(char_type, sizeof(char_type), "%s(%lu)", - cast_cs == &my_charset_bin ? "BINARY" : "CHAR", - (ulong) length); - - if (!res->alloced_length()) - { // Don't change const str - str_value= *res; // Not malloced string - res= &str_value; - } - ErrConvString err(res); - push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), char_type, - err.ptr()); - res->length((uint) length); - } - else if (cast_cs == &my_charset_bin && res->length() < cast_length) + CHARSET_INFO *cs= from_cs ? from_cs : res->charset(); + if (!charset_conversion) { - if (res->alloced_length() < cast_length) + // Try to reuse the original string (if well formed). + MY_STRCOPY_STATUS status; + cs->cset->well_formed_char_length(cs, res->ptr(), res->end(), + cast_length, &status); + if (!status.m_well_formed_error_pos) { - str_value.alloc(cast_length); - str_value.copy(*res); - res= &str_value; + res= reuse(res, status.m_source_end_pos - res->ptr()); } - bzero((char*) res->ptr() + res->length(), cast_length - res->length()); - res->length(cast_length); + goto end; } + // Character set conversion, or bad bytes were found. + if (!(res= copy(res, cs))) + return 0; } - null_value= 0; - if (res->length() > current_thd->variables.max_allowed_packet) - { - push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - cast_cs == &my_charset_bin ? - "cast_as_binary" : func_name(), - current_thd->variables.max_allowed_packet); - null_value= 1; - return 0; - } - return res; +end: + return ((null_value= (res->length() > + adjusted_length_with_warn(res->length())))) ? 0 : res; } diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 8438119ddc6..e7a37ed7bb1 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -886,6 +886,11 @@ class Item_char_typecast :public Item_str_func CHARSET_INFO *cast_cs, *from_cs; bool charset_conversion; String tmp_value; + bool has_explicit_length() const { return cast_length != ~0U; } + String *reuse(String *src, uint32 length); + String *copy(String *src, CHARSET_INFO *cs); + uint adjusted_length_with_warn(uint length); + void check_truncation_with_warn(String *src, uint dstlen); public: Item_char_typecast(Item *a, uint length_arg, CHARSET_INFO *cs_arg) :Item_str_func(a), cast_length(length_arg), cast_cs(cs_arg) {} diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 6b09b4cf769..9dff16b2be1 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7125,3 +7125,5 @@ ER_ROLE_CREATE_EXISTS eng "Can't create role '%-.64s'; it already exists" ER_ROLE_DROP_EXISTS eng "Can't drop role '%-.64s'; it doesn't exist" +ER_CANNOT_CONVERT_CHARACTER + eng "Cannot convert '%s' character 0x%-.64s to '%s'" diff --git a/sql/sql_string.h b/sql/sql_string.h index 4a23d65d6a8..518b8e5ba4b 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -200,6 +200,7 @@ public: inline bool is_empty() const { return (str_length == 0); } inline void mark_as_const() { Alloced_length= 0;} inline const char *ptr() const { return Ptr; } + inline const char *end() const { return Ptr + str_length; } inline char *c_ptr() { DBUG_ASSERT(!alloced || !Ptr || !Alloced_length || @@ -423,6 +424,17 @@ public: { return copy(str->ptr(), str->length(), str->charset(), tocs, errors); } + bool copy(CHARSET_INFO *tocs, + CHARSET_INFO *fromcs, const char *src, uint32 src_length, + uint32 nchars, String_copier *copier) + { + if (alloc(tocs->mbmaxlen * src_length)) + return true; + str_length= copier->well_formed_copy(tocs, Ptr, Alloced_length, + fromcs, src, src_length, nchars); + str_charset= tocs; + return false; + } void move(String &s) { free(); |