maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #09135
Re: MDEV-8844 Unreadable control characters printed as is in warnings
Hi Sergei,
Please review the second version, according to our discussion on IRC.
Thanks.
On 12/17/2015 10:15 PM, Sergei Golubchik wrote:
Hi, Alexander!
On Dec 16, Alexander Barkov wrote:
Hi Sergei,
Please review a patch for MDEV-8844.
Thanks!
diff --git a/sql/sql_error.cc b/sql/sql_error.cc
index b72d642..1ed3547 100644
--- a/sql/sql_error.cc
+++ b/sql/sql_error.cc
@@ -931,7 +931,7 @@ char *err_conv(char *buff, uint to_length, const char *from,
else
{
uint errors;
- res= copy_and_convert(to, to_length, system_charset_info,
+ res= copy_and_convert(to, to_length, &my_charset_errmsg,
from, from_length, from_cs, &errors);
I'm not sure I like this approach. True, when all you have is a hammer -
everything looks like a nail. But we don't do *all* string conversions
with charsets, do we? For example, escaping/unescaping is done with an
explicit loop. I don't think my_charset_errmsg qualifies is a *charset*,
it's only a helper to do map unprintable characters, it's even less
charset than filename.
So I'd prefer a loop here, instead of copy_and_convert. Or, if you want
to use CHARSET_INFO, then, at least
* make it hidden and not user-selectable
* all properties and methods not directly related to err_conv() call
should be NULL - my_charset_errmsg should not be used anywhere else.
And still, I think I'd prefer an explicit loop here.
Regards,
Sergei
diff --git a/include/m_ctype.h b/include/m_ctype.h
index a552226..1e7a47a 100644
--- a/include/m_ctype.h
+++ b/include/m_ctype.h
@@ -46,6 +46,19 @@ extern "C" {
#define MY_CS_REPLACEMENT_CHARACTER 0xFFFD
+/**
+ Maximum character length of a string produced by wc_to_printable().
+ Note, wc_to_printable() is currently limited to BMP.
+ One non-printable or non-convertable character can produce a string
+ with at most 5 characters: \hhhh.
+ If we ever modify wc_to_printable() to support supplementary characters,
+ e.g. \+hhhhhh, this constant should be changed to 8.
+ Note, maximum octet length of a wc_to_printable() result can be calculated
+ as: (MY_CS_PRINTABLE_CHAR_LENGTH*cs->mbminlen).
+*/
+#define MY_CS_PRINTABLE_CHAR_LENGTH 5
+
+
/*
On i386 we store Unicode->CS conversion tables for
some character sets using Big-endian order,
@@ -529,10 +542,12 @@ struct my_charset_handler_st
cs->cset->native_to_mb() rather than cs->cset->wc_mb().
*/
my_charset_conv_wc_mb native_to_mb;
+ my_charset_conv_wc_mb wc_to_printable;
};
extern MY_CHARSET_HANDLER my_charset_8bit_handler;
extern MY_CHARSET_HANDLER my_charset_ucs2_handler;
+extern MY_CHARSET_HANDLER my_charset_utf8_handler;
/*
@@ -684,6 +699,9 @@ int my_mb_wc_8bit(CHARSET_INFO *cs,my_wc_t *wc, const uchar *s,const uchar *e);
int my_wc_mb_8bit(CHARSET_INFO *cs,my_wc_t wc, uchar *s, uchar *e);
int my_wc_mb_bin(CHARSET_INFO *cs,my_wc_t wc, uchar *s, uchar *e);
+int my_wc_to_printable_8bit(CHARSET_INFO *cs, my_wc_t wc, uchar *s, uchar *e);
+int my_wc_to_printable_mb(CHARSET_INFO *cs, my_wc_t wc, uchar *s, uchar *e);
+
int my_mb_ctype_8bit(CHARSET_INFO *,int *, const uchar *,const uchar *);
int my_mb_ctype_mb(CHARSET_INFO *,int *, const uchar *,const uchar *);
@@ -889,6 +907,18 @@ uint32 my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
const char *from, uint32 from_length,
CHARSET_INFO *from_cs, uint *errors);
+/**
+ An extended version of my_convert(), to pass non-default mb_wc() and wc_mb().
+ For example, String::copy_printable() which is used in
+ Protocol::store_warning() uses this to escape control
+ and non-convertable characters.
+*/
+uint32 my_convert_using_func(char *to, uint32 to_length, CHARSET_INFO *to_cs,
+ my_charset_conv_wc_mb mb_wc,
+ const char *from, uint32 from_length,
+ CHARSET_INFO *from_cs,
+ my_charset_conv_mb_wc wc_mb,
+ uint *errors);
/*
Convert a string between two character sets.
Bad byte sequences as well as characters that cannot be
diff --git a/mysql-test/r/ctype_latin1.result b/mysql-test/r/ctype_latin1.result
index 4847592..271822d 100644
--- a/mysql-test/r/ctype_latin1.result
+++ b/mysql-test/r/ctype_latin1.result
@@ -8181,5 +8181,53 @@ Warnings:
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t1`.`c` AS `c`,`test`.`t1`.`d` AS `d` from `test`.`t1` where ((coalesce(`test`.`t1`.`c`,0) = '3 ') and (coalesce(`test`.`t1`.`d`,0) = '3 '))
DROP TABLE t1;
#
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES latin1;
+SELECT CAST(_latin1 0x610062 AS INT);
+CAST(_latin1 0x610062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_latin1 0x610162 AS INT);
+CAST(_latin1 0x610162 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_latin1 0x611F62 AS INT);
+CAST(_latin1 0x611F62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\001Fb'
+SELECT CAST(_latin1 0x617F62 AS INT);
+CAST(_latin1 0x617F62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\007Fb'
+SELECT CAST(_latin1 0x612062 AS INT);
+CAST(_latin1 0x612062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a b'
+SELECT CAST(_latin1 0x617E62 AS INT);
+CAST(_latin1 0x617E62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a~b'
+SELECT CAST(_latin1 0x61FF62 AS INT);
+CAST(_latin1 0x61FF62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'aÿb'
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET latin1, UNIQUE(a));
+INSERT INTO t1 VALUES (0x61000162FF);
+INSERT INTO t1 VALUES (0x61000162FF);
+ERROR 23000: Duplicate entry 'a\0000\0001bÿ' for key 'a'
+INSERT IGNORE INTO t1 VALUES (0x61000162FF);
+Warnings:
+Warning 1062 Duplicate entry 'a\0000\0001bÿ' for key 'a'
+DROP TABLE t1;
+#
# End of 10.1 tests
#
diff --git a/mysql-test/r/ctype_ucs.result b/mysql-test/r/ctype_ucs.result
index 5617431..82e4784 100644
--- a/mysql-test/r/ctype_ucs.result
+++ b/mysql-test/r/ctype_ucs.result
@@ -5649,5 +5649,38 @@ CAST(CONVERT('1IJ3' USING ucs2) AS SIGNED)
Warnings:
Warning 1292 Truncated incorrect INTEGER value: '1IJ3'
#
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SELECT CAST(_ucs2 0x006100000062 AS INT);
+CAST(_ucs2 0x006100000062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_ucs2 0x006100010062 AS INT);
+CAST(_ucs2 0x006100010062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_ucs2 0x0061D8000062 AS INT);
+CAST(_ucs2 0x0061D8000062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\D800b'
+SELECT CAST(_ucs2 0x0061DFFF0062 AS INT);
+CAST(_ucs2 0x0061DFFF0062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\DFFFb'
+SELECT CAST(_ucs2 0x0061D7000062 AS INT);
+CAST(_ucs2 0x0061D7000062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'aíb'
+SELECT CAST(_ucs2 0x0061E0030062 AS INT);
+CAST(_ucs2 0x0061E0030062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'aîb'
+#
# End of 10.1 tests
#
diff --git a/mysql-test/r/ctype_utf16.result b/mysql-test/r/ctype_utf16.result
index 3bd3725..303fa48 100644
--- a/mysql-test/r/ctype_utf16.result
+++ b/mysql-test/r/ctype_utf16.result
@@ -2199,5 +2199,14 @@ CAST(CONVERT('1IJ3' USING utf16) AS SIGNED)
Warnings:
Warning 1292 Truncated incorrect INTEGER value: '1IJ3'
#
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES utf8;
+SELECT CAST(_utf16 0x0061D83DDE0E0062 AS INT);
+CAST(_utf16 0x0061D83DDE0E0062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a?b'
+#
# End of 10.1 tests
#
diff --git a/mysql-test/r/ctype_utf8.result b/mysql-test/r/ctype_utf8.result
index 66db7df..27886a2 100644
--- a/mysql-test/r/ctype_utf8.result
+++ b/mysql-test/r/ctype_utf8.result
@@ -5382,13 +5382,13 @@ EXPLAIN EXTENDED SELECT 'abcdÃÃÃÃÃ
', _latin1'abcdÃÃÃÃÃ
', _utf8'abcdÃ
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
-Note 1003 select 'abcdÃÃÃÃÃ
' AS `abcdÃÃÃÃÃ
`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃÃÃÃÃ
`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `u`
+Note 1003 select 'abcdÃ\0081ÃÃÃÃ
' AS `abcdÃ\0081ÃÃÃÃ
`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃ\0081ÃÃÃÃ
`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `u`
SET NAMES utf8;
EXPLAIN EXTENDED SELECT 'abcdÃÃÃÃÃ
', _latin1'abcdÃÃÃÃÃ
', _utf8'abcdÃÃÃÃÃ
';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
-Note 1003 select 'abcdÃÃÃÃÃ
' AS `abcdÃÃÃÃÃ
`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃÂÃâÃÆÃâÃâ¦`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃÃÃÃÃ
`
+Note 1003 select 'abcdÃÃÃÃÃ
' AS `abcdÃÃÃÃÃ
`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃ\0081ÃâÃÆÃâÃâ¦`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcdÃÃÃÃÃ
`
#
# Bug#11750518 41090: ORDER BY TRUNCATES GROUP_CONCAT RESULT
#
@@ -10213,5 +10213,78 @@ Warnings:
Note 1003 select `test`.`t1`.`c` AS `c` from `test`.`t1` where (`test`.`t1`.`c` = 'A')
DROP TABLE t1;
#
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES utf8;
+SELECT CAST(_utf8 0x610062 AS INT);
+CAST(_utf8 0x610062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_utf8 0x610162 AS INT);
+CAST(_utf8 0x610162 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_utf8 0x611F62 AS INT);
+CAST(_utf8 0x611F62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\001Fb'
+SELECT CAST(_utf8 0x617F62 AS INT);
+CAST(_utf8 0x617F62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\007Fb'
+SELECT CAST(_utf8 0x61C28062 AS INT);
+CAST(_utf8 0x61C28062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\0080b'
+SELECT CAST(_utf8 0x61C29F62 AS INT);
+CAST(_utf8 0x61C29F62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a\009Fb'
+SELECT CAST(_utf8 0x612062 AS INT);
+CAST(_utf8 0x612062 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a b'
+SELECT CAST(_utf8 0x617E62 AS INT);
+CAST(_utf8 0x617E62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a~b'
+SELECT CAST(_utf8 0x61C2BF62 AS INT);
+CAST(_utf8 0x61C2BF62 AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'a¿b'
+SELECT CAST(_utf8 'ëëë' AS INT);
+CAST(_utf8 'ëëë' AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'ëëë'
+SELECT CAST(_utf8 'ÅÅÅ' AS INT);
+CAST(_utf8 'ÅÅÅ' AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'ÅÅÅ'
+SELECT CAST(_utf8 'ÑÑÑ' AS INT);
+CAST(_utf8 'ÑÑÑ' AS INT)
+0
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: 'ÑÑÑ'
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET utf8, UNIQUE(a));
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+ERROR 23000: Duplicate entry 'a\0000\0001bÿ' for key 'a'
+INSERT IGNORE INTO t1 VALUES (_latin1 0x61000162FF);
+Warnings:
+Warning 1062 Duplicate entry 'a\0000\0001bÿ' for key 'a'
+DROP TABLE t1;
+#
# End of 10.1 tests
#
diff --git a/mysql-test/r/func_str.result b/mysql-test/r/func_str.result
index 9bc8061..57e62d9 100644
--- a/mysql-test/r/func_str.result
+++ b/mysql-test/r/func_str.result
@@ -939,17 +939,17 @@ explain extended select length('\n\t\r\b\0\_\%\\');
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
-Note 1003 select length('\n \r\0\\_\\%\\') AS `length('\n\t\r\b\0\_\%\\')`
+Note 1003 select length('\n \r\0008\0\\_\\%\\') AS `length('\n\t\r\b\0\_\%\\')`
explain extended select bit_length('\n\t\r\b\0\_\%\\');
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
-Note 1003 select bit_length('\n \r\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
+Note 1003 select bit_length('\n \r\0008\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
explain extended select bit_length('\n\t\r\b\0\_\%\\');
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
-Note 1003 select bit_length('\n \r\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
+Note 1003 select bit_length('\n \r\0008\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
explain extended select concat('monty',' was here ','again');
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
diff --git a/mysql-test/r/get_diagnostics.result b/mysql-test/r/get_diagnostics.result
index 623d48c..e196c47 100644
--- a/mysql-test/r/get_diagnostics.result
+++ b/mysql-test/r/get_diagnostics.result
@@ -510,7 +510,7 @@ BEGIN
SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='ÃÃÃÃ
Ã';
END|
CALL p1();
-ERROR 77777: ÃÃÃÃ
Ã
+ERROR 77777: Ã\0081ÃÃÃ
Ã
GET DIAGNOSTICS CONDITION 1
@mysql_errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT,
@returned_sqlstate = RETURNED_SQLSTATE, @class_origin = CLASS_ORIGIN;
@@ -520,6 +520,15 @@ SELECT @mysql_errno, @message_text, @returned_sqlstate, @class_origin;
@returned_sqlstate 77777
@class_origin
DROP PROCEDURE p1;
+SET NAMES utf8;
+CREATE PROCEDURE p1()
+BEGIN
+SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='ÃÃÃÃ
Ã';
+END|
+CALL p1();
+ERROR 77777: ÃÃÃÃ
Ã
+DROP PROCEDURE p1;
+SET NAMES latin1;
CREATE PROCEDURE p1()
BEGIN
DECLARE cond CONDITION FOR SQLSTATE '12345';
diff --git a/mysql-test/r/not_embedded_server.result b/mysql-test/r/not_embedded_server.result
index 1cd54b1..50999ec 100644
--- a/mysql-test/r/not_embedded_server.result
+++ b/mysql-test/r/not_embedded_server.result
@@ -66,7 +66,7 @@ ERROR HY000: Operation CREATE USER failed for 'user\"s_12601974'@'localhost'
DROP USER 'user\"s_12601974'@'localhost';
CREATE USER 'user\bs_12601974'@'localhost';
CREATE USER 'user\bs_12601974'@'localhost';
-ERROR HY000: Operation CREATE USER failed for 'users_12601974'@'localhost'
+ERROR HY000: Operation CREATE USER failed for 'user\0008s_12601974'@'localhost'
DROP USER 'user\bs_12601974'@'localhost';
CREATE USER 'user\ns_12601974'@'localhost';
CREATE USER 'user\ns_12601974'@'localhost';
diff --git a/mysql-test/t/ctype_latin1.test b/mysql-test/t/ctype_latin1.test
index a30c7ae..cceaa84 100644
--- a/mysql-test/t/ctype_latin1.test
+++ b/mysql-test/t/ctype_latin1.test
@@ -374,5 +374,27 @@ SELECT * FROM t1 WHERE COALESCE(c,0)='3 ' AND COALESCE(d,0)=COALESCE(c,0);
DROP TABLE t1;
--echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES latin1;
+# control
+SELECT CAST(_latin1 0x610062 AS INT);
+SELECT CAST(_latin1 0x610162 AS INT);
+SELECT CAST(_latin1 0x611F62 AS INT);
+SELECT CAST(_latin1 0x617F62 AS INT);
+# normal characters
+SELECT CAST(_latin1 0x612062 AS INT);
+SELECT CAST(_latin1 0x617E62 AS INT);
+SELECT CAST(_latin1 0x61FF62 AS INT);
+
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET latin1, UNIQUE(a));
+INSERT INTO t1 VALUES (0x61000162FF);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (0x61000162FF);
+INSERT IGNORE INTO t1 VALUES (0x61000162FF);
+DROP TABLE t1;
+
+--echo #
--echo # End of 10.1 tests
--echo #
diff --git a/mysql-test/t/ctype_ucs.test b/mysql-test/t/ctype_ucs.test
index 2f48062..d6341fb 100644
--- a/mysql-test/t/ctype_ucs.test
+++ b/mysql-test/t/ctype_ucs.test
@@ -955,5 +955,18 @@ SET NAMES utf8;
SELECT CAST(CONVERT('1IJ3' USING ucs2) AS SIGNED);
--echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+# control
+SELECT CAST(_ucs2 0x006100000062 AS INT);
+SELECT CAST(_ucs2 0x006100010062 AS INT);
+# surrogate halfs
+SELECT CAST(_ucs2 0x0061D8000062 AS INT);
+SELECT CAST(_ucs2 0x0061DFFF0062 AS INT);
+# normal characters
+SELECT CAST(_ucs2 0x0061D7000062 AS INT);
+SELECT CAST(_ucs2 0x0061E0030062 AS INT);
+
+--echo #
--echo # End of 10.1 tests
--echo #
diff --git a/mysql-test/t/ctype_utf16.test b/mysql-test/t/ctype_utf16.test
index bb7eb8c..9e15961 100644
--- a/mysql-test/t/ctype_utf16.test
+++ b/mysql-test/t/ctype_utf16.test
@@ -893,5 +893,14 @@ SELECT CAST(CONVERT('1IJ3' USING utf16) AS SIGNED);
--echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES utf8;
+# Make sure surrogate halfs (when a part of a full utf16 character)
+# are not escaped and the entire utf16 character consisting of two
+# surrogate pairs is replaced to a single question mark.
+SELECT CAST(_utf16 0x0061D83DDE0E0062 AS INT);
+
+--echo #
--echo # End of 10.1 tests
--echo #
diff --git a/mysql-test/t/ctype_utf8.test b/mysql-test/t/ctype_utf8.test
index 639f6d4..a9fa0f6 100644
--- a/mysql-test/t/ctype_utf8.test
+++ b/mysql-test/t/ctype_utf8.test
@@ -1843,6 +1843,33 @@ EXPLAIN EXTENDED
SELECT * FROM t1 WHERE c>=_utf8'a' COLLATE utf8_general_ci AND c='A';
DROP TABLE t1;
+--echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES utf8;
+# control, part1
+SELECT CAST(_utf8 0x610062 AS INT);
+SELECT CAST(_utf8 0x610162 AS INT);
+SELECT CAST(_utf8 0x611F62 AS INT);
+# control, part2: U+0080..U+009F
+SELECT CAST(_utf8 0x617F62 AS INT);
+SELECT CAST(_utf8 0x61C28062 AS INT);
+SELECT CAST(_utf8 0x61C29F62 AS INT);
+# normal characters
+SELECT CAST(_utf8 0x612062 AS INT);
+SELECT CAST(_utf8 0x617E62 AS INT);
+SELECT CAST(_utf8 0x61C2BF62 AS INT);
+SELECT CAST(_utf8 'ëëë' AS INT);
+SELECT CAST(_utf8 'ÅÅÅ' AS INT);
+SELECT CAST(_utf8 'ÑÑÑ' AS INT);
+
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET utf8, UNIQUE(a));
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+INSERT IGNORE INTO t1 VALUES (_latin1 0x61000162FF);
+DROP TABLE t1;
--echo #
--echo # End of 10.1 tests
diff --git a/mysql-test/t/get_diagnostics.test b/mysql-test/t/get_diagnostics.test
index 0b0d8a7..8c9a169 100644
--- a/mysql-test/t/get_diagnostics.test
+++ b/mysql-test/t/get_diagnostics.test
@@ -556,6 +556,7 @@ DELIMITER ;|
--error 1000
CALL p1();
+
GET DIAGNOSTICS CONDITION 1
@mysql_errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT,
@returned_sqlstate = RETURNED_SQLSTATE, @class_origin = CLASS_ORIGIN;
@@ -566,6 +567,18 @@ SELECT @mysql_errno, @message_text, @returned_sqlstate, @class_origin;
DROP PROCEDURE p1;
+SET NAMES utf8;
+DELIMITER |;
+CREATE PROCEDURE p1()
+BEGIN
+ SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='ÃÃÃÃ
Ã';
+END|
+DELIMITER ;|
+--error 1000
+CALL p1();
+DROP PROCEDURE p1;
+SET NAMES latin1;
+
DELIMITER |;
CREATE PROCEDURE p1()
BEGIN
diff --git a/sql/protocol.cc b/sql/protocol.cc
index fa42915..91b78be 100644
--- a/sql/protocol.cc
+++ b/sql/protocol.cc
@@ -1019,6 +1019,18 @@ bool Protocol::store_string_aux(const char *from, size_t length,
}
+bool Protocol::store_warning(const char *from, size_t length)
+{
+ StringBuffer<MYSQL_ERRMSG_SIZE> tmp;
+ CHARSET_INFO *cs= thd->variables.character_set_results;
+ if (cs == &my_charset_bin)
+ cs= system_charset_info;
+ if (tmp.copy_printable_hhhh(cs, system_charset_info, from, length))
+ return net_store_data((const uchar*)"", 0);
+ return net_store_data((const uchar *) tmp.ptr(), tmp.length());
+}
+
+
bool Protocol_text::store(const char *from, size_t length,
CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
{
diff --git a/sql/protocol.h b/sql/protocol.h
index ea33c6b..e1179f5 100644
--- a/sql/protocol.h
+++ b/sql/protocol.h
@@ -81,6 +81,7 @@ class Protocol
bool store(I_List<i_string> *str_list);
bool store(const char *from, CHARSET_INFO *cs);
+ bool store_warning(const char *from, size_t length);
String *storage_packet() { return packet; }
inline void free() { packet->free(); }
virtual bool write();
diff --git a/sql/sql_error.cc b/sql/sql_error.cc
index b72d642..8af0e71 100644
--- a/sql/sql_error.cc
+++ b/sql/sql_error.cc
@@ -859,9 +859,8 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
warning_level_names[err->get_level()].length,
system_charset_info);
protocol->store((uint32) err->get_sql_errno());
- protocol->store(err->get_message_text(),
- err->get_message_octet_length(),
- system_charset_info);
+ protocol->store_warning(err->get_message_text(),
+ err->get_message_octet_length());
if (protocol->write())
DBUG_RETURN(TRUE);
}
@@ -874,6 +873,26 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
/**
+ This replaces U+0000 to '\0000', so the result error message string:
+ - is a good null-terminated string
+ - presents the entire data
+ For example:
+ SELECT CAST(_latin1 0x610062 AS SIGNED);
+ returns a warning:
+ Truncated incorrect INTEGER value: 'a\0000b'
+ Notice, the 0x00 byte is replaced to a 5-byte long string '\0000',
+ while 'a' and 'b' are printed as is.
+*/
+extern "C" int my_wc_mb_utf8_null_terminated(CHARSET_INFO *cs,
+ my_wc_t wc, uchar *r, uchar *e)
+{
+ return wc == '\0' ?
+ my_charset_utf8_handler.wc_to_printable(cs, wc, r, e) :
+ my_charset_utf8_handler.wc_mb(cs, wc, r, e);
+}
+
+
+/**
Convert value for dispatch to error message(see WL#751).
@param to buffer for converted string
@@ -931,8 +950,11 @@ char *err_conv(char *buff, uint to_length, const char *from,
else
{
uint errors;
- res= copy_and_convert(to, to_length, system_charset_info,
- from, from_length, from_cs, &errors);
+ res= my_convert_using_func(to, to_length, system_charset_info,
+ my_wc_mb_utf8_null_terminated,
+ from, from_length,
+ from_cs, from_cs->cset->mb_wc,
+ &errors);
to[res]= 0;
}
return buff;
@@ -958,64 +980,19 @@ uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs,
const char *from, uint32 from_length,
CHARSET_INFO *from_cs, uint *errors)
{
- int cnvres;
- my_wc_t wc;
- const uchar *from_end= (const uchar*) from+from_length;
- char *to_start= to;
- uchar *to_end;
- my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
- my_charset_conv_wc_mb wc_mb;
- uint error_count= 0;
- uint length;
-
DBUG_ASSERT(to_length > 0);
/* Make room for the null terminator. */
to_length--;
- to_end= (uchar*) (to + to_length);
-
- if (!to_cs || from_cs == to_cs || to_cs == &my_charset_bin)
- {
- length= MY_MIN(to_length, from_length);
- memmove(to, from, length);
- to[length]= 0;
- return length;
- }
-
- wc_mb= to_cs->cset->wc_mb;
- while (1)
- {
- if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0)
- {
- if (!wc)
- break;
- from+= cnvres;
- }
- else if (cnvres == MY_CS_ILSEQ)
- {
- wc= (ulong) (uchar) *from;
- from+=1;
- }
- else
- break;
-
- if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0)
- to+= cnvres;
- else if (cnvres == MY_CS_ILUNI)
- {
- length= (wc <= 0xFFFF) ? 6/* '\1234' format*/ : 9 /* '\+123456' format*/;
- if ((uchar*)(to + length) >= to_end)
- break;
- cnvres= my_snprintf(to, 9,
- (wc <= 0xFFFF) ? "\\%04X" : "\\+%06X", (uint) wc);
- to+= cnvres;
- }
- else
- break;
- }
-
- *to= 0;
- *errors= error_count;
- return (uint32) (to - to_start);
+ if (to_cs == &my_charset_bin)
+ to_cs= system_charset_info;
+ uint32 cnv_length= my_convert_using_func(to, to_length,
+ to_cs, to_cs->cset->wc_to_printable,
+ from, from_length,
+ from_cs, from_cs->cset->mb_wc,
+ errors);
+ DBUG_ASSERT(to_length >= cnv_length);
+ to[cnv_length]= '\0';
+ return cnv_length;
}
diff --git a/sql/sql_string.h b/sql/sql_string.h
index 51a11c7..446caed 100644
--- a/sql/sql_string.h
+++ b/sql/sql_string.h
@@ -417,6 +417,31 @@ class String
str_charset= tocs;
return false;
}
+ /**
+ Convert a string to a printable format.
+ All non-convertable and control characters are replaced to 5-character
+ sequences '\hhhh'.
+ */
+ bool copy_printable_hhhh(CHARSET_INFO *to_cs,
+ CHARSET_INFO *from_cs,
+ const char *from, uint32 from_length)
+ {
+ uint errors;
+ uint one_escaped_char_length= MY_CS_PRINTABLE_CHAR_LENGTH * to_cs->mbminlen;
+ uint one_char_length= MY_MAX(one_escaped_char_length, to_cs->mbmaxlen);
+ uint32 bytes_needed= from_length * one_char_length;
+ if (alloc(bytes_needed))
+ return true;
+ str_charset= to_cs;
+ str_length= my_convert_using_func(Ptr, Alloced_length, to_cs,
+ to_cs->cset->wc_to_printable,
+ from, from_length,
+ from_cs,
+ from_cs->cset->mb_wc,
+ &errors);
+ return false;
+ }
+
void move(String &s)
{
free();
diff --git a/strings/ctype-big5.c b/strings/ctype-big5.c
index d6a9695..9103fe5 100644
--- a/strings/ctype-big5.c
+++ b/strings/ctype-big5.c
@@ -6848,6 +6848,7 @@ static MY_CHARSET_HANDLER my_charset_big5_handler=
my_well_formed_char_length_big5,
my_copy_fix_mb,
my_native_to_mb_big5,
+ my_wc_to_printable_8bit,
};
struct charset_info_st my_charset_big5_chinese_ci=
diff --git a/strings/ctype-bin.c b/strings/ctype-bin.c
index 0be6ae9..dd6c40e 100644
--- a/strings/ctype-bin.c
+++ b/strings/ctype-bin.c
@@ -551,6 +551,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_8bit,
my_copy_8bit,
my_wc_mb_bin,
+ my_wc_mb_bin,
};
diff --git a/strings/ctype-cp932.c b/strings/ctype-cp932.c
index 9bf206f..af60059 100644
--- a/strings/ctype-cp932.c
+++ b/strings/ctype-cp932.c
@@ -34723,6 +34723,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_cp932,
my_copy_fix_mb,
my_native_to_mb_cp932,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-euc_kr.c b/strings/ctype-euc_kr.c
index 1f13ab6..e4fa6f8 100644
--- a/strings/ctype-euc_kr.c
+++ b/strings/ctype-euc_kr.c
@@ -10017,6 +10017,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_euckr,
my_copy_fix_mb,
my_native_to_mb_euckr,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-eucjpms.c b/strings/ctype-eucjpms.c
index 82c4bb5..d1b498a 100644
--- a/strings/ctype-eucjpms.c
+++ b/strings/ctype-eucjpms.c
@@ -67550,6 +67550,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_eucjpms,
my_copy_fix_mb,
my_native_to_mb_eucjpms,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-gb2312.c b/strings/ctype-gb2312.c
index b0e275f..873dacc 100644
--- a/strings/ctype-gb2312.c
+++ b/strings/ctype-gb2312.c
@@ -6421,6 +6421,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_gb2312,
my_copy_fix_mb,
my_native_to_mb_gb2312,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-gbk.c b/strings/ctype-gbk.c
index 37b003f..9cd8f32 100644
--- a/strings/ctype-gbk.c
+++ b/strings/ctype-gbk.c
@@ -10733,6 +10733,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_gbk,
my_copy_fix_mb,
my_native_to_mb_gbk,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-latin1.c b/strings/ctype-latin1.c
index 26c66d6..76216b5 100644
--- a/strings/ctype-latin1.c
+++ b/strings/ctype-latin1.c
@@ -426,6 +426,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_8bit,
my_copy_8bit,
my_wc_mb_bin, /* native_to_mb */
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-mb.c b/strings/ctype-mb.c
index eef283d..79ddf10 100644
--- a/strings/ctype-mb.c
+++ b/strings/ctype-mb.c
@@ -1543,4 +1543,116 @@ int my_mb_ctype_mb(CHARSET_INFO *cs, int *ctype,
}
+/**
+ Detect if a Unicode code point is printable.
+*/
+static inline my_bool
+my_is_printable(my_wc_t wc)
+{
+ /*
+ Blocks:
+ U+0000 .. U+001F control
+ U+0020 .. U+007E printable
+ U+007F .. U+009F control
+ U+00A0 .. U+00FF printable
+ U+0100 .. U+10FFFF As of Unicode-6.1.0, this range does not have any
+ characters of the "Cc" (Other, control) category.
+ Should be mostly safe to print.
+ Except for the surrogate halfs,
+ which are encoding components, not real characters.
+ */
+ if (wc >= 0x20 && wc <= 0x7E) /* Quickly detect ASCII printable */
+ return TRUE;
+ if (wc <= 0x9F) /* The rest of U+0000..U+009F are control characters */
+ {
+ /* NL, CR, TAB are Ok */
+ return (wc == '\r' || wc == '\n' || wc == '\t');
+ }
+ /*
+ Surrogate halfs (when alone) print badly in terminals:
+ SELECT _ucs2 0xD800;
+ Let's escape them as well.
+ */
+ if (wc >= 0xD800 && wc <= 0xDFFF)
+ return FALSE;
+ return TRUE;
+}
+
+
+static uint to_printable_8bit(uchar *dst, my_wc_t wc)
+{
+ /*
+ This function is used only in context of error messages for now.
+ All non-BMP characters are currently replaced to question marks
+ when a message is put into diagnostics area.
+ */
+ DBUG_ASSERT(wc < 0x10000);
+ *dst++= '\\';
+ *dst++= _dig_vec_upper[(wc >> 12) & 0x0F];
+ *dst++= _dig_vec_upper[(wc >> 8) & 0x0F];
+ *dst++= _dig_vec_upper[(wc >> 4) & 0x0F];
+ *dst++= _dig_vec_upper[wc & 0x0F];
+ return MY_CS_PRINTABLE_CHAR_LENGTH;
+}
+
+
+/**
+ Encode an Unicode character "wc" into a printable string.
+ Non-convertable and non-printable code points are printed as \hhhh.
+ Convertable printable code points are encoded normally.
+ This function is suitable for 8-bit character sets,
+ as well as for ASCII-compatible multi-byte character sets, e.g. sjis, utf8.
+*/
+int
+my_wc_to_printable_8bit(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end)
+{
+ if (my_is_printable(wc))
+ {
+ int mblen= cs->cset->wc_mb(cs, wc, str, end);
+ if (mblen > 0)
+ return mblen;
+ }
+ if (str + MY_CS_PRINTABLE_CHAR_LENGTH > end)
+ return MY_CS_TOOSMALLN(MY_CS_PRINTABLE_CHAR_LENGTH);
+ return to_printable_8bit(str, wc);
+}
+
+
+/**
+ Encode an Unicode character "wc" into a printable string.
+ This function is suitable for any character set, including
+ ASCII-incompatible multi-byte character sets, e.g. ucs2, utf16, utf32.
+*/
+int
+my_wc_to_printable_mb(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end)
+{
+ uchar *str0;
+ uint i, length;
+ uchar tmp[MY_CS_PRINTABLE_CHAR_LENGTH];
+
+ if (my_is_printable(wc))
+ {
+ int mblen= cs->cset->wc_mb(cs, wc, str, end);
+ if (mblen > 0)
+ return mblen;
+ }
+
+ if (str + MY_CS_PRINTABLE_CHAR_LENGTH * cs->mbminlen > end)
+ return MY_CS_TOOSMALLN(MY_CS_PRINTABLE_CHAR_LENGTH * cs->mbminlen);
+
+ length= to_printable_8bit(tmp, wc);
+ str0= str;
+ for (i= 0; i < length; i++)
+ {
+ if (cs->cset->wc_mb(cs, tmp[i], str, end) != (int) cs->mbminlen)
+ {
+ DBUG_ASSERT(0);
+ return MY_CS_ILSEQ;
+ }
+ str+= cs->mbminlen;
+ }
+ return str - str0;
+}
+
+
#endif
diff --git a/strings/ctype-simple.c b/strings/ctype-simple.c
index 288f5fd..31393d6 100644
--- a/strings/ctype-simple.c
+++ b/strings/ctype-simple.c
@@ -1956,6 +1956,7 @@ MY_CHARSET_HANDLER my_charset_8bit_handler=
my_well_formed_char_length_8bit,
my_copy_8bit,
my_wc_mb_bin, /* native_to_mb */
+ my_wc_to_printable_8bit,
};
MY_COLLATION_HANDLER my_collation_8bit_simple_ci_handler =
diff --git a/strings/ctype-sjis.c b/strings/ctype-sjis.c
index 629e1cd..c0bcf8b 100644
--- a/strings/ctype-sjis.c
+++ b/strings/ctype-sjis.c
@@ -34102,6 +34102,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_sjis,
my_copy_fix_mb,
my_native_to_mb_sjis,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-tis620.c b/strings/ctype-tis620.c
index a1ca320..6cef472 100644
--- a/strings/ctype-tis620.c
+++ b/strings/ctype-tis620.c
@@ -890,6 +890,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_8bit,
my_copy_8bit,
my_wc_mb_bin, /* native_to_mb */
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-ucs2.c b/strings/ctype-ucs2.c
index cae85f3..c5cbdb7 100644
--- a/strings/ctype-ucs2.c
+++ b/strings/ctype-ucs2.c
@@ -1606,6 +1606,7 @@ MY_CHARSET_HANDLER my_charset_utf16_handler=
my_well_formed_char_length_utf16,
my_copy_fix_mb2_or_mb4,
my_uni_utf16,
+ my_wc_to_printable_mb,
};
@@ -1829,6 +1830,7 @@ static MY_CHARSET_HANDLER my_charset_utf16le_handler=
my_well_formed_char_length_utf16,
my_copy_fix_mb2_or_mb4,
my_uni_utf16le,
+ my_wc_to_printable_mb,
};
@@ -2575,6 +2577,7 @@ MY_CHARSET_HANDLER my_charset_utf32_handler=
my_well_formed_char_length_utf32,
my_copy_fix_mb2_or_mb4,
my_uni_utf32,
+ my_wc_to_printable_mb,
};
@@ -3062,6 +3065,7 @@ MY_CHARSET_HANDLER my_charset_ucs2_handler=
my_well_formed_char_length_ucs2,
my_copy_fix_mb2_or_mb4,
my_uni_ucs2,
+ my_wc_to_printable_mb,
};
diff --git a/strings/ctype-ujis.c b/strings/ctype-ujis.c
index 308f5f0..432a330 100644
--- a/strings/ctype-ujis.c
+++ b/strings/ctype-ujis.c
@@ -67294,6 +67294,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
my_well_formed_char_length_ujis,
my_copy_fix_mb,
my_native_to_mb_ujis,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype-utf8.c b/strings/ctype-utf8.c
index 3c2c812..5201c53 100644
--- a/strings/ctype-utf8.c
+++ b/strings/ctype-utf8.c
@@ -5527,6 +5527,7 @@ MY_CHARSET_HANDLER my_charset_utf8_handler=
my_well_formed_char_length_utf8,
my_copy_fix_mb,
my_uni_utf8,
+ my_wc_to_printable_8bit,
};
@@ -7111,6 +7112,7 @@ static MY_CHARSET_HANDLER my_charset_filename_handler=
my_well_formed_char_length_filename,
my_copy_fix_mb,
my_wc_mb_filename,
+ my_wc_mb_filename, /* Is already printable */
};
@@ -7882,6 +7884,7 @@ MY_CHARSET_HANDLER my_charset_utf8mb4_handler=
my_well_formed_char_length_utf8mb4,
my_copy_fix_mb,
my_wc_mb_utf8mb4,
+ my_wc_to_printable_8bit,
};
diff --git a/strings/ctype.c b/strings/ctype.c
index f871a21..620c7e1 100644
--- a/strings/ctype.c
+++ b/strings/ctype.c
@@ -1030,19 +1030,18 @@ my_charset_is_ascii_compatible(CHARSET_INFO *cs)
@return Number of bytes copied to 'to' string
*/
-static uint32
-my_convert_internal(char *to, uint32 to_length,
- CHARSET_INFO *to_cs,
- const char *from, uint32 from_length,
- CHARSET_INFO *from_cs, uint *errors)
+uint32
+my_convert_using_func(char *to, uint32 to_length,
+ CHARSET_INFO *to_cs, my_charset_conv_wc_mb wc_mb,
+ const char *from, uint32 from_length,
+ CHARSET_INFO *from_cs, my_charset_conv_mb_wc mb_wc,
+ uint *errors)
{
int cnvres;
my_wc_t wc;
const uchar *from_end= (const uchar*) from + from_length;
char *to_start= to;
uchar *to_end= (uchar*) to + to_length;
- my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
- my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
uint error_count= 0;
while (1)
@@ -1119,8 +1118,11 @@ my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
immediately switch to slow mb_wc->wc_mb method.
*/
if ((to_cs->state | from_cs->state) & MY_CS_NONASCII)
- return my_convert_internal(to, to_length, to_cs,
- from, from_length, from_cs, errors);
+ return my_convert_using_func(to, to_length,
+ to_cs, to_cs->cset->wc_mb,
+ from, from_length,
+ from_cs, from_cs->cset->mb_wc,
+ errors);
length= length2= MY_MIN(to_length, from_length);
@@ -1152,9 +1154,11 @@ my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
uint32 copied_length= length2 - length;
to_length-= copied_length;
from_length-= copied_length;
- return copied_length + my_convert_internal(to, to_length, to_cs,
- from, from_length, from_cs,
- errors);
+ return copied_length + my_convert_using_func(to, to_length, to_cs,
+ to_cs->cset->wc_mb,
+ from, from_length, from_cs,
+ from_cs->cset->mb_wc,
+ errors);
}
}
References