summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2015-03-18 17:10:48 +0400
committerAlexander Barkov <bar@mariadb.org>2015-03-18 17:10:48 +0400
commite28a241907aa7a511b65b196703efaeea71e1dc4 (patch)
tree9456d75e69127d0416592f8036441b17cbe43724 /sql
parenta471b7098f50e21565ce4c86efcb05d8310e1d62 (diff)
downloadmariadb-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.cc39
-rw-r--r--sql/item.h15
-rw-r--r--sql/item_strfunc.cc15
-rw-r--r--sql/item_timefunc.cc166
-rw-r--r--sql/item_timefunc.h5
-rw-r--r--sql/share/errmsg-utf8.txt2
-rw-r--r--sql/sql_string.h12
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();