From de70be9da6eb65875a42cf9143aa391adca44a11 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 3 Oct 2006 14:11:57 +0500 Subject: [PATCH 1/9] Bug#22052 Trailing spaces are not removed from UNICODE fields in an index Fix: using charset-aware functions cs->cset->lengthsp() and cs->cset->fill() instead of single byte code which is not UCS2 compatible. myisam/mi_key.c: Using character-set aware functions to trim and append spaces. myisam/mi_open.c: Initialize charset for BINARY/VARBINARY to my_charset_bin, instead of NULL mysql-test/r/ctype_ucs.result: Adding test case mysql-test/t/ctype_ucs.test: Adding test case --- myisam/mi_key.c | 15 ++++++++------- myisam/mi_open.c | 2 ++ mysql-test/r/ctype_ucs.result | 22 ++++++++++++++++++++++ mysql-test/t/ctype_ucs.test | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/myisam/mi_key.c b/myisam/mi_key.c index eaa854b1a37..9d538d58817 100644 --- a/myisam/mi_key.c +++ b/myisam/mi_key.c @@ -42,7 +42,7 @@ static int _mi_put_key_in_record(MI_INFO *info,uint keynr,byte *record); uint _mi_make_key(register MI_INFO *info, uint keynr, uchar *key, const byte *record, my_off_t filepos) { - byte *pos,*end; + byte *pos; uchar *start; reg1 HA_KEYSEG *keyseg; my_bool is_ft= info->s->keyinfo[keynr].flag & HA_FULLTEXT; @@ -84,18 +84,17 @@ uint _mi_make_key(register MI_INFO *info, uint keynr, uchar *key, pos= (byte*) record+keyseg->start; if (keyseg->flag & HA_SPACE_PACK) { - end= pos + length; if (type != HA_KEYTYPE_NUM) { - while (end > pos && end[-1] == ' ') - end--; + length= cs->cset->lengthsp(cs, pos, length); } else { + byte *end= pos + length; while (pos < end && pos[0] == ' ') pos++; + length=(uint) (end-pos); } - length=(uint) (end-pos); FIX_LENGTH(cs, pos, length, char_length); store_key_length_inc(key,char_length); memcpy((byte*) key,(byte*) pos,(size_t) char_length); @@ -358,8 +357,10 @@ static int _mi_put_key_in_record(register MI_INFO *info, uint keynr, pos= record+keyseg->start; if (keyseg->type != (int) HA_KEYTYPE_NUM) { - memcpy(pos,key,(size_t) length); - bfill(pos+length,keyseg->length-length,' '); + memcpy(pos,key,(size_t) length); + keyseg->charset->cset->fill(keyseg->charset, + pos + length, keyseg->length - length, + ' '); } else { diff --git a/myisam/mi_open.c b/myisam/mi_open.c index a5b303f86d4..174890d57b6 100644 --- a/myisam/mi_open.c +++ b/myisam/mi_open.c @@ -323,6 +323,8 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) goto err; } } + else if (pos->type == HA_KEYTYPE_BINARY) + pos->charset= &my_charset_bin; } if (share->keyinfo[i].flag & HA_SPATIAL) { diff --git a/mysql-test/r/ctype_ucs.result b/mysql-test/r/ctype_ucs.result index 642a0fc13b5..bf827209795 100644 --- a/mysql-test/r/ctype_ucs.result +++ b/mysql-test/r/ctype_ucs.result @@ -715,6 +715,28 @@ lily river drop table t1; deallocate prepare stmt; +create table t1 ( +a char(10) unicode not null, +index a (a) +) engine=myisam; +insert into t1 values (repeat(0x201f, 10)); +insert into t1 values (repeat(0x2020, 10)); +insert into t1 values (repeat(0x2021, 10)); +explain select hex(a) from t1 order by a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL a 20 NULL 3 Using index +select hex(a) from t1 order by a; +hex(a) +201F201F201F201F201F201F201F201F201F201F +2020202020202020202020202020202020202020 +2021202120212021202120212021202120212021 +alter table t1 drop index a; +select hex(a) from t1 order by a; +hex(a) +201F201F201F201F201F201F201F201F201F201F +2020202020202020202020202020202020202020 +2021202120212021202120212021202120212021 +drop table t1; CREATE TABLE t1 (id int, s char(5) CHARACTER SET ucs2 COLLATE ucs2_unicode_ci); INSERT INTO t1 VALUES (1, 'ZZZZZ'), (1, 'ZZZ'), (2, 'ZZZ'), (2, 'ZZZZZ'); SELECT id, MIN(s) FROM t1 GROUP BY id; diff --git a/mysql-test/t/ctype_ucs.test b/mysql-test/t/ctype_ucs.test index 641080f48ab..10559d33eb3 100644 --- a/mysql-test/t/ctype_ucs.test +++ b/mysql-test/t/ctype_ucs.test @@ -452,6 +452,24 @@ select utext from t1 where utext like '%%'; drop table t1; deallocate prepare stmt; +# +# Bug#22052 Trailing spaces are not removed from UNICODE fields in an index +# +create table t1 ( + a char(10) unicode not null, + index a (a) +) engine=myisam; +insert into t1 values (repeat(0x201f, 10)); +insert into t1 values (repeat(0x2020, 10)); +insert into t1 values (repeat(0x2021, 10)); +# make sure "index read" is used +explain select hex(a) from t1 order by a; +select hex(a) from t1 order by a; +alter table t1 drop index a; +select hex(a) from t1 order by a; +drop table t1; + + # # Bug #20076: server crashes for a query with GROUP BY if MIN/MAX aggregation # over a 'ucs2' field uses a temporary table From d6bc2d60e5a658d4f1e646f4f94664d815de683e Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Oct 2006 10:16:37 +0300 Subject: [PATCH 2/9] BUG#20697 slave fails to rollback replicated transaction hang over innodb_lock_wait_timeou Transaction on the slave sql thread got blocked against a slave's mysqld local ta's lock. Since the default, slave-transaction-retries=10, there was replaying of the replicated ta. That failed because of a new started from 5.0.13 policy not to rollback a timed-out transaction. Effectively the first round of a timed-out ta becomes committed by the replaying's first "BEGIN". It was decided to backport already existed method working in 5.1 implemented in bug #16228 for handling symmetrical deadlock problem. That patch introduced end_trans execution whenever a replicated ta deadlocks or timed-out. Note, that this solution can be practically suboptimal - in the light of the changed behavior due to timeout we still could replay only the last statement - only with a high rate of timeouting replicated transactions. mysql-test/r/rpl_deadlock.result: results changed mysql-test/t/rpl_deadlock.test: Refining the timeout part of the test to display that the timed-out transaction is rolled back prior its replaying by slave sql. Non-zero select's count would mean the first round work became persistent - wrong. sql/slave.cc: applying bug#16228 fix, approbated for deadlock use case in 5.1, almost verbatim. Another alternative to replay only the offending statement requires significant efforts, incl design work. --- mysql-test/r/rpl_deadlock.result | 125 +++++++++++++++++++++++++++---- mysql-test/t/rpl_deadlock.test | 16 ++-- sql/slave.cc | 23 ++++-- 3 files changed, 136 insertions(+), 28 deletions(-) diff --git a/mysql-test/r/rpl_deadlock.result b/mysql-test/r/rpl_deadlock.result index 541e12b806f..19f97987cc6 100644 --- a/mysql-test/r/rpl_deadlock.result +++ b/mysql-test/r/rpl_deadlock.result @@ -6,7 +6,7 @@ drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; start slave; create table t1 (a int not null, key(a)) engine=innodb; create table t2 (a int not null, key(a)) engine=innodb; -create table t3 (a int) engine=innodb; +create table t3 (a int unique) engine=innodb; create table t4 (a int) engine=innodb; show variables like 'slave_transaction_retries'; Variable_name Value @@ -35,14 +35,14 @@ begin; select * from t1 for update; a start slave; -insert into t2 values(22); +insert into t2 values(201); commit; select * from t1; a 1 select * from t2; a -22 +201 show slave status; Slave_IO_State # Master_Host 127.0.0.1 @@ -50,7 +50,7 @@ Master_User root Master_Port MASTER_MYPORT Connect_Retry 1 Master_Log_File master-bin.000001 -Read_Master_Log_Pos 18911 +Read_Master_Log_Pos 18918 Relay_Log_File # Relay_Log_Pos # Relay_Master_Log_File master-bin.000001 @@ -65,7 +65,7 @@ Replicate_Wild_Ignore_Table Last_Errno 0 Last_Error Skip_Counter 0 -Exec_Master_Log_Pos 18911 +Exec_Master_Log_Pos 18918 Relay_Log_Space # Until_Condition None Until_Log_File @@ -78,12 +78,16 @@ Master_SSL_Cipher Master_SSL_Key Seconds_Behind_Master # stop slave; -change master to master_log_pos=532; +delete from t3; +change master to master_log_pos=539; begin; select * from t2 for update; a -22 +201 start slave; +select count(*) from t3 /* must be zero */; +count(*) +0 commit; select * from t1; a @@ -91,7 +95,7 @@ a 1 select * from t2; a -22 +201 show slave status; Slave_IO_State # Master_Host 127.0.0.1 @@ -99,7 +103,7 @@ Master_User root Master_Port MASTER_MYPORT Connect_Retry 1 Master_Log_File master-bin.000001 -Read_Master_Log_Pos 18911 +Read_Master_Log_Pos 18918 Relay_Log_File # Relay_Log_Pos # Relay_Master_Log_File master-bin.000001 @@ -114,7 +118,7 @@ Replicate_Wild_Ignore_Table Last_Errno 0 Last_Error Skip_Counter 0 -Exec_Master_Log_Pos 18911 +Exec_Master_Log_Pos 18918 Relay_Log_Space # Until_Condition None Until_Log_File @@ -128,12 +132,16 @@ Master_SSL_Key Seconds_Behind_Master # set global max_relay_log_size=0; stop slave; -change master to master_log_pos=532; +delete from t3; +change master to master_log_pos=539; begin; select * from t2 for update; a -22 +201 start slave; +select count(*) from t3 /* must be zero */; +count(*) +0 commit; select * from t1; a @@ -142,7 +150,7 @@ a 1 select * from t2; a -22 +201 show slave status; Slave_IO_State # Master_Host 127.0.0.1 @@ -150,7 +158,7 @@ Master_User root Master_Port MASTER_MYPORT Connect_Retry 1 Master_Log_File master-bin.000001 -Read_Master_Log_Pos 18911 +Read_Master_Log_Pos 18918 Relay_Log_File # Relay_Log_Pos # Relay_Master_Log_File master-bin.000001 @@ -165,7 +173,7 @@ Replicate_Wild_Ignore_Table Last_Errno 0 Last_Error Skip_Counter 0 -Exec_Master_Log_Pos 18911 +Exec_Master_Log_Pos 18918 Relay_Log_Space # Until_Condition None Until_Log_File @@ -177,4 +185,91 @@ Master_SSL_Cert Master_SSL_Cipher Master_SSL_Key Seconds_Behind_Master # +drop table if exists t1; +create table t1 (f int unique) engine=innodb; +insert into t1 values (0); +begin; +select * from t1 where f = 0 for update; +f +0 +begin; +insert into t1 values (1); +update t1 set f=-1 where f = 0; +commit; +insert into t1 values (2); +show slave status; +Slave_IO_State # +Master_Host 127.0.0.1 +Master_User root +Master_Port MASTER_MYPORT +Connect_Retry 1 +Master_Log_File master-bin.000001 +Read_Master_Log_Pos 19618 +Relay_Log_File # +Relay_Log_Pos # +Relay_Master_Log_File master-bin.000001 +Slave_IO_Running # +Slave_SQL_Running No +Replicate_Do_DB +Replicate_Ignore_DB +Replicate_Do_Table +Replicate_Ignore_Table +Replicate_Wild_Do_Table +Replicate_Wild_Ignore_Table +Last_Errno 1205 +Last_Error Error 'Lock wait timeout exceeded; try restarting transaction' on query. Default database: 'test'. Query: 'update t1 set f=-1 where f = 0' +Skip_Counter 0 +Exec_Master_Log_Pos 19227 +Relay_Log_Space # +Until_Condition None +Until_Log_File +Until_Log_Pos 0 +Master_SSL_Allowed No +Master_SSL_CA_File +Master_SSL_CA_Path +Master_SSL_Cert +Master_SSL_Cipher +Master_SSL_Key +Seconds_Behind_Master # +set @@global.sql_slave_skip_counter = 4; +start slave; +show slave status; +Slave_IO_State # +Master_Host 127.0.0.1 +Master_User root +Master_Port MASTER_MYPORT +Connect_Retry 1 +Master_Log_File master-bin.000001 +Read_Master_Log_Pos 19618 +Relay_Log_File # +Relay_Log_Pos # +Relay_Master_Log_File master-bin.000001 +Slave_IO_Running # +Slave_SQL_Running Yes +Replicate_Do_DB +Replicate_Ignore_DB +Replicate_Do_Table +Replicate_Ignore_Table +Replicate_Wild_Do_Table +Replicate_Wild_Ignore_Table +Last_Errno 0 +Last_Error +Skip_Counter 0 +Exec_Master_Log_Pos 19618 +Relay_Log_Space # +Until_Condition None +Until_Log_File +Until_Log_Pos 0 +Master_SSL_Allowed No +Master_SSL_CA_File +Master_SSL_CA_Path +Master_SSL_Cert +Master_SSL_Cipher +Master_SSL_Key +Seconds_Behind_Master # +select * from t1; +f +0 +2 +commit; drop table t1,t2,t3,t4; diff --git a/mysql-test/t/rpl_deadlock.test b/mysql-test/t/rpl_deadlock.test index 684cb54611c..6c5f942cec9 100644 --- a/mysql-test/t/rpl_deadlock.test +++ b/mysql-test/t/rpl_deadlock.test @@ -16,7 +16,8 @@ source include/master-slave.inc; connection master; create table t1 (a int not null, key(a)) engine=innodb; create table t2 (a int not null, key(a)) engine=innodb; -create table t3 (a int) engine=innodb; +# requiring 'unique' for the timeout part of the test +create table t3 (a int unique) engine=innodb; create table t4 (a int) engine=innodb; show variables like 'slave_transaction_retries'; sync_slave_with_master; @@ -31,8 +32,7 @@ stop slave; connection master; begin; # Let's keep BEGIN and the locked statement in two different relay logs. -let $1=200; -disable_query_log; +let $1=200;disable_query_log; while ($1) { eval insert into t3 values( $1 ); @@ -59,7 +59,7 @@ enable_query_log; select * from t1 for update; start slave; --real_sleep 3 # hope that slave is blocked now -insert into t2 values(22); # provoke deadlock, slave should be victim +insert into t2 values(201); # provoke deadlock, slave should be victim commit; sync_with_master; select * from t1; # check that slave succeeded finally @@ -74,11 +74,13 @@ show slave status; # 2) Test lock wait timeout stop slave; -change master to master_log_pos=532; # the BEGIN log event +delete from t3; +change master to master_log_pos=539; # the BEGIN log event begin; select * from t2 for update; # hold lock start slave; --real_sleep 10 # slave should have blocked, and be retrying +select count(*) from t3 /* must be zero */; # replaying begins after rollback commit; sync_with_master; select * from t1; # check that slave succeeded finally @@ -97,11 +99,13 @@ set global max_relay_log_size=0; # This is really copy-paste of 2) of above stop slave; -change master to master_log_pos=532; +delete from t3; +change master to master_log_pos=539; begin; select * from t2 for update; start slave; --real_sleep 10 +select count(*) from t3 /* must be zero */; # replaying begins after rollback commit; sync_with_master; select * from t1; diff --git a/sql/slave.cc b/sql/slave.cc index 55cff94a179..244c5dfbcc4 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -3345,9 +3345,9 @@ static int exec_relay_log_event(THD* thd, RELAY_LOG_INFO* rli) const char *errmsg; /* We were in a transaction which has been rolled back because of a - deadlock (currently, InnoDB deadlock detected by InnoDB) or lock - wait timeout (innodb_lock_wait_timeout exceeded); let's seek back to - BEGIN log event and retry it all again. + Sonera deadlock. if lock wait timeout (innodb_lock_wait_timeout exceeded) + there is no rollback since 5.0.13 (ref: manual). + let's seek back to BEGIN log event and retry it all again. We have to not only seek but also a) init_master_info(), to seek back to hot relay log's start for later (for when we will come back to this hot log after re-processing the @@ -3369,6 +3369,7 @@ static int exec_relay_log_event(THD* thd, RELAY_LOG_INFO* rli) else { exec_res= 0; + end_trans(thd, ROLLBACK); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); @@ -3386,9 +3387,17 @@ static int exec_relay_log_event(THD* thd, RELAY_LOG_INFO* rli) "the slave_transaction_retries variable.", slave_trans_retries); } - if (!((thd->options & OPTION_BEGIN) && opt_using_transactions)) - rli->trans_retries= 0; // restart from fresh - } + else if (!((thd->options & OPTION_BEGIN) && opt_using_transactions)) + { + /* + Only reset the retry counter if the event succeeded or + failed with a non-transient error. On a successful event, + the execution will proceed as usual; in the case of a + non-transient error, the slave will stop with an error. + */ + rli->trans_retries= 0; // restart from fresh + } + } return exec_res; } else @@ -4613,7 +4622,7 @@ static int connect_to_master(THD* thd, MYSQL* mysql, MASTER_INFO* mi, suppress_warnings= 0; sql_print_error("Slave I/O thread: error %s to master \ '%s@%s:%d': \ -Error: '%s' errno: %d retry-time: %d retries: %d", +Error: '%s' errno: %d retry-time: %d retries: %lu", (reconnect ? "reconnecting" : "connecting"), mi->user,mi->host,mi->port, mysql_error(mysql), last_errno, From 5d7fb619849b6864b075a289bb5bf68ebfa99237 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Oct 2006 11:07:20 +0300 Subject: [PATCH 3/9] bug#20697 - results in sync with the test --- mysql-test/r/rpl_deadlock.result | 87 -------------------------------- 1 file changed, 87 deletions(-) diff --git a/mysql-test/r/rpl_deadlock.result b/mysql-test/r/rpl_deadlock.result index 19f97987cc6..bea7fd81616 100644 --- a/mysql-test/r/rpl_deadlock.result +++ b/mysql-test/r/rpl_deadlock.result @@ -185,91 +185,4 @@ Master_SSL_Cert Master_SSL_Cipher Master_SSL_Key Seconds_Behind_Master # -drop table if exists t1; -create table t1 (f int unique) engine=innodb; -insert into t1 values (0); -begin; -select * from t1 where f = 0 for update; -f -0 -begin; -insert into t1 values (1); -update t1 set f=-1 where f = 0; -commit; -insert into t1 values (2); -show slave status; -Slave_IO_State # -Master_Host 127.0.0.1 -Master_User root -Master_Port MASTER_MYPORT -Connect_Retry 1 -Master_Log_File master-bin.000001 -Read_Master_Log_Pos 19618 -Relay_Log_File # -Relay_Log_Pos # -Relay_Master_Log_File master-bin.000001 -Slave_IO_Running # -Slave_SQL_Running No -Replicate_Do_DB -Replicate_Ignore_DB -Replicate_Do_Table -Replicate_Ignore_Table -Replicate_Wild_Do_Table -Replicate_Wild_Ignore_Table -Last_Errno 1205 -Last_Error Error 'Lock wait timeout exceeded; try restarting transaction' on query. Default database: 'test'. Query: 'update t1 set f=-1 where f = 0' -Skip_Counter 0 -Exec_Master_Log_Pos 19227 -Relay_Log_Space # -Until_Condition None -Until_Log_File -Until_Log_Pos 0 -Master_SSL_Allowed No -Master_SSL_CA_File -Master_SSL_CA_Path -Master_SSL_Cert -Master_SSL_Cipher -Master_SSL_Key -Seconds_Behind_Master # -set @@global.sql_slave_skip_counter = 4; -start slave; -show slave status; -Slave_IO_State # -Master_Host 127.0.0.1 -Master_User root -Master_Port MASTER_MYPORT -Connect_Retry 1 -Master_Log_File master-bin.000001 -Read_Master_Log_Pos 19618 -Relay_Log_File # -Relay_Log_Pos # -Relay_Master_Log_File master-bin.000001 -Slave_IO_Running # -Slave_SQL_Running Yes -Replicate_Do_DB -Replicate_Ignore_DB -Replicate_Do_Table -Replicate_Ignore_Table -Replicate_Wild_Do_Table -Replicate_Wild_Ignore_Table -Last_Errno 0 -Last_Error -Skip_Counter 0 -Exec_Master_Log_Pos 19618 -Relay_Log_Space # -Until_Condition None -Until_Log_File -Until_Log_Pos 0 -Master_SSL_Allowed No -Master_SSL_CA_File -Master_SSL_CA_Path -Master_SSL_Cert -Master_SSL_Cipher -Master_SSL_Key -Seconds_Behind_Master # -select * from t1; -f -0 -2 -commit; drop table t1,t2,t3,t4; From 00c1c19e9410a942ed7b2b49e7dc7916a9d8010f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 30 Oct 2006 10:14:03 +0400 Subject: [PATCH 4/9] Bug#18908: ERROR 1406 (22001): Data too long for column :: using utf8 Problem: Too confusing error message when cannot convert between string and column character sets on INSERT and UPDATE. Fix: producing a better error message, instead of "Data too long" in such cases Additional changes: Adding "DROP TABLE IF EXISTS" into several tests to be safe against failures in previous tests. mysql-test/include/strict_autoinc.inc: Adding DROP TABLE to be safe against previous tests failure. mysql-test/r/ctype_recoding.result: Fixing test results mysql-test/r/ctype_utf8.result: Fixing test results mysql-test/r/fulltext.result: Fixing test results mysql-test/r/strict_autoinc_1myisam.result: Adding DROP TABLE to be safe against previous tests failure. mysql-test/r/strict_autoinc_2innodb.result: Adding DROP TABLE to be safe against previous tests failure. mysql-test/r/strict_autoinc_3heap.result: Adding DROP TABLE to be safe against previous tests failure. mysql-test/r/strict_autoinc_4bdb.result: Adding DROP TABLE to be safe against previous tests failure. mysql-test/r/strict_autoinc_5ndb.result: Adding DROP TABLE to be safe against previous tests failure. sql/field.cc: - producing better error messages than "DATA TRUNCATED" or "DATA TOO LONG" (in strict mode) in case of "not well formed source" and "cannot convert to field character set" - Performance improvements: copying directly to the target, instead of using an intermediate String. - Moving duplicate code into report_data_too_long() function. sql/sql_string.cc: Adding a new function to convert strings between character sets, but not more than "nchar" characters - a helper function for Field_string::store(), Field_varstring::store() and Field_blob::store(). sql/sql_string.h: Adding new function prototype. --- BitKeeper/etc/collapsed | 1 + mysql-test/include/strict_autoinc.inc | 4 + mysql-test/r/ctype_recoding.result | 4 +- mysql-test/r/ctype_utf8.result | 6 +- mysql-test/r/fulltext.result | 8 +- mysql-test/r/strict_autoinc_1myisam.result | 1 + mysql-test/r/strict_autoinc_2innodb.result | 1 + mysql-test/r/strict_autoinc_3heap.result | 1 + mysql-test/r/strict_autoinc_4bdb.result | 1 + mysql-test/r/strict_autoinc_5ndb.result | 1 + sql/field.cc | 357 +++++++++++++-------- sql/sql_string.cc | 156 +++++++++ sql/sql_string.h | 8 + 13 files changed, 402 insertions(+), 147 deletions(-) diff --git a/BitKeeper/etc/collapsed b/BitKeeper/etc/collapsed index d4d681937a2..073379cd917 100644 --- a/BitKeeper/etc/collapsed +++ b/BitKeeper/etc/collapsed @@ -2,3 +2,4 @@ 44ec850ac2k4y2Omgr92GiWPBAVKGQ 44edb86b1iE5knJ97MbliK_3lCiAXA 44f33f3aj5KW5qweQeekY1LU0E9ZCg +452c6c6dAjuNghfc1ObZ_UQ5SCl85g diff --git a/mysql-test/include/strict_autoinc.inc b/mysql-test/include/strict_autoinc.inc index 6960440f3a7..823efcc2040 100644 --- a/mysql-test/include/strict_autoinc.inc +++ b/mysql-test/include/strict_autoinc.inc @@ -2,6 +2,10 @@ # Test for strict-mode autoincrement # +--disable_warnings +drop table if exists t1; +--enable_warnings + set @org_mode=@@sql_mode; eval create table t1 ( diff --git a/mysql-test/r/ctype_recoding.result b/mysql-test/r/ctype_recoding.result index 4e145346081..125e0edd1f9 100644 --- a/mysql-test/r/ctype_recoding.result +++ b/mysql-test/r/ctype_recoding.result @@ -171,8 +171,8 @@ create table t1 (a char(10) character set koi8r, b text character set koi8r); insert into t1 values ('test','test'); insert into t1 values ('ÊÃÕË','ÊÃÕË'); Warnings: -Warning 1265 Data truncated for column 'a' at row 1 -Warning 1265 Data truncated for column 'b' at row 1 +Warning 1366 Incorrect string value: '\xCA\xC3\xD5\xCB' for column 'a' at row 1 +Warning 1366 Incorrect string value: '\xCA\xC3\xD5\xCB' for column 'b' at row 1 drop table t1; set names koi8r; create table t1 (a char(10) character set cp1251); diff --git a/mysql-test/r/ctype_utf8.result b/mysql-test/r/ctype_utf8.result index 51f361349e6..a0b6d233a5c 100644 --- a/mysql-test/r/ctype_utf8.result +++ b/mysql-test/r/ctype_utf8.result @@ -197,7 +197,7 @@ drop table t1; create table t1 (s1 char(10) character set utf8); insert into t1 values (0x41FF); Warnings: -Warning 1265 Data truncated for column 's1' at row 1 +Warning 1366 Incorrect string value: '\xFF' for column 's1' at row 1 select hex(s1) from t1; hex(s1) 41 @@ -205,7 +205,7 @@ drop table t1; create table t1 (s1 varchar(10) character set utf8); insert into t1 values (0x41FF); Warnings: -Warning 1265 Data truncated for column 's1' at row 1 +Warning 1366 Incorrect string value: '\xFF' for column 's1' at row 1 select hex(s1) from t1; hex(s1) 41 @@ -213,7 +213,7 @@ drop table t1; create table t1 (s1 text character set utf8); insert into t1 values (0x41FF); Warnings: -Warning 1265 Data truncated for column 's1' at row 1 +Warning 1366 Incorrect string value: '\xFF' for column 's1' at row 1 select hex(s1) from t1; hex(s1) 41 diff --git a/mysql-test/r/fulltext.result b/mysql-test/r/fulltext.result index 85894d5cb0e..7c9069791fa 100644 --- a/mysql-test/r/fulltext.result +++ b/mysql-test/r/fulltext.result @@ -372,10 +372,10 @@ t collation(t) FORMAT(MATCH t AGAINST ('Osnabruck'),6) aus Osnabrück utf8_general_ci 1.591140 alter table t1 modify t varchar(200) collate latin1_german2_ci not null; Warnings: -Warning 1265 Data truncated for column 't' at row 3 -Warning 1265 Data truncated for column 't' at row 4 -Warning 1265 Data truncated for column 't' at row 5 -Warning 1265 Data truncated for column 't' at row 6 +Warning 1366 Incorrect string value: '\xD0\xAD\xD1\x82\xD0\xBE...' for column 't' at row 3 +Warning 1366 Incorrect string value: '\xD0\x9E\xD1\x82\xD0\xBB...' for column 't' at row 4 +Warning 1366 Incorrect string value: '\xD0\x9D\xD0\xB5 \xD0...' for column 't' at row 5 +Warning 1366 Incorrect string value: '\xD0\xB8 \xD0\xB1\xD1...' for column 't' at row 6 SELECT t, collation(t) FROM t1 WHERE MATCH t AGAINST ('Osnabrück'); t collation(t) aus Osnabrück latin1_german2_ci diff --git a/mysql-test/r/strict_autoinc_1myisam.result b/mysql-test/r/strict_autoinc_1myisam.result index 5d3c2698cda..90a69bedc74 100644 --- a/mysql-test/r/strict_autoinc_1myisam.result +++ b/mysql-test/r/strict_autoinc_1myisam.result @@ -1,3 +1,4 @@ +drop table if exists t1; set @org_mode=@@sql_mode; create table t1 ( diff --git a/mysql-test/r/strict_autoinc_2innodb.result b/mysql-test/r/strict_autoinc_2innodb.result index f1936ff4de3..8a5243173e4 100644 --- a/mysql-test/r/strict_autoinc_2innodb.result +++ b/mysql-test/r/strict_autoinc_2innodb.result @@ -1,3 +1,4 @@ +drop table if exists t1; set @org_mode=@@sql_mode; create table t1 ( diff --git a/mysql-test/r/strict_autoinc_3heap.result b/mysql-test/r/strict_autoinc_3heap.result index aa0be270ac5..7dc48357c46 100644 --- a/mysql-test/r/strict_autoinc_3heap.result +++ b/mysql-test/r/strict_autoinc_3heap.result @@ -1,3 +1,4 @@ +drop table if exists t1; set @org_mode=@@sql_mode; create table t1 ( diff --git a/mysql-test/r/strict_autoinc_4bdb.result b/mysql-test/r/strict_autoinc_4bdb.result index 73683b645e2..89c4d2537f5 100644 --- a/mysql-test/r/strict_autoinc_4bdb.result +++ b/mysql-test/r/strict_autoinc_4bdb.result @@ -1,3 +1,4 @@ +drop table if exists t1; set @org_mode=@@sql_mode; create table t1 ( diff --git a/mysql-test/r/strict_autoinc_5ndb.result b/mysql-test/r/strict_autoinc_5ndb.result index d0d62d05b32..715f9a4b98b 100644 --- a/mysql-test/r/strict_autoinc_5ndb.result +++ b/mysql-test/r/strict_autoinc_5ndb.result @@ -1,3 +1,4 @@ +drop table if exists t1; set @org_mode=@@sql_mode; create table t1 ( diff --git a/sql/field.cc b/sql/field.cc index 4860f6ea3da..50b16e70cf2 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5799,37 +5799,148 @@ void Field_datetime::sql_type(String &res) const ** A string may be varchar or binary ****************************************************************************/ +/* + Report "not well formed" or "cannot convert" error + after storing a character string info a field. + + SYNOPSIS + check_string_copy_error() + field - Field + well_formed_error_pos - where not well formed data was first met + cannot_convert_error_pos - where a not-convertable character was first met + end - end of the string + + NOTES + As of version 5.0 both cases return the same error: + + "Invalid string value: 'xxx' for column 't' at row 1" + + Future versions will possibly introduce a new error message: + + "Cannot convert character string: 'xxx' for column 't' at row 1" + + RETURN + FALSE - If errors didn't happen + TRUE - If an error happened +*/ + +static bool +check_string_copy_error(Field_str *field, + const char *well_formed_error_pos, + const char *cannot_convert_error_pos, + const char *end) +{ + const char *pos, *end_orig; + char tmp[64], *t; + + if (!(pos= well_formed_error_pos) && + !(pos= cannot_convert_error_pos)) + return FALSE; + + end_orig= end; + set_if_smaller(end, pos + 6); + + for (t= tmp; pos < end; pos++) + { + if (((unsigned char) *pos) >= 0x20 && + ((unsigned char) *pos) <= 0x7F) + { + *t++= *pos; + } + else + { + *t++= '\\'; + *t++= 'x'; + *t++= _dig_vec_upper[((unsigned char) *pos) >> 4]; + *t++= _dig_vec_upper[((unsigned char) *pos) & 15]; + } + } + if (end_orig > end) + { + *t++= '.'; + *t++= '.'; + *t++= '.'; + } + *t= '\0'; + push_warning_printf(field->table->in_use, + field->table->in_use->abort_on_warning ? + MYSQL_ERROR::WARN_LEVEL_ERROR : + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, + ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + "string", tmp, field->field_name, + (ulong) field->table->in_use->row_count); + return TRUE; +} + + + +/* + Send a truncation warning or a truncation error + after storing a too long character string info a field. + + SYNOPSIS + report_data_too_long() + field - Field + + RETURN + N/A +*/ + +inline void +report_data_too_long(Field_str *field) +{ + if (field->table->in_use->abort_on_warning) + field->set_warning(MYSQL_ERROR::WARN_LEVEL_ERROR, ER_DATA_TOO_LONG, 1); + else + field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); +} + + +/* + Test if the given string contains important data: + not spaces for character string, + or any data for binary string. + + SYNOPSIS + test_if_important_data() + cs Character set + str String to test + strend String end + + RETURN + FALSE - If string does not have important data + TRUE - If string has some important data +*/ + +static bool +test_if_important_data(CHARSET_INFO *cs, const char *str, const char *strend) +{ + if (cs != &my_charset_bin) + str+= cs->cset->scan(cs, str, strend, MY_SEQ_SPACES); + return (str < strend); +} + + /* Copy a string and fill with space */ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) { - int error= 0, well_formed_error; - uint32 not_used; - char buff[STRING_BUFFER_USUAL_SIZE]; - String tmpstr(buff,sizeof(buff), &my_charset_bin); uint copy_length; + const char *well_formed_error_pos; + const char *cannot_convert_error_pos; + const char *from_end_pos; /* See the comment for Field_long::store(long long) */ DBUG_ASSERT(table->in_use == current_thd); - /* Convert character set if necessary */ - if (String::needs_conversion(length, cs, field_charset, ¬_used)) - { - uint conv_errors; - tmpstr.copy(from, length, cs, field_charset, &conv_errors); - from= tmpstr.ptr(); - length= tmpstr.length(); - if (conv_errors) - error= 2; - } - - /* Make sure we don't break a multibyte sequence or copy malformed data. */ - copy_length= field_charset->cset->well_formed_len(field_charset, - from,from+length, - field_length/ - field_charset->mbmaxlen, - &well_formed_error); - memmove(ptr, from, copy_length); + copy_length= well_formed_copy_nchars(field_charset, + ptr, field_length, + cs, from, length, + field_length / field_charset->mbmaxlen, + &well_formed_error_pos, + &cannot_convert_error_pos, + &from_end_pos); /* Append spaces if the string was shorter than the field. */ if (copy_length < field_length) @@ -5837,32 +5948,23 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) field_length-copy_length, field_charset->pad_char); + if (check_string_copy_error(this, well_formed_error_pos, + cannot_convert_error_pos, from + length)) + return 2; + /* Check if we lost any important data (anything in a binary string, or any non-space in others). */ - if ((copy_length < length) && table->in_use->count_cuted_fields) + if ((from_end_pos < from + length) && table->in_use->count_cuted_fields) { - if (binary()) - error= 2; - else + if (test_if_important_data(field_charset, from_end_pos, from + length)) { - const char *end=from+length; - from+= copy_length; - from+= field_charset->cset->scan(field_charset, from, end, - MY_SEQ_SPACES); - if (from != end) - error= 2; + report_data_too_long(this); + return 2; } } - if (error) - { - if (table->in_use->abort_on_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_ERROR, ER_DATA_TOO_LONG, 1); - else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); - } - return error; + return 0; } @@ -6195,58 +6297,35 @@ Field *Field_string::new_field(MEM_ROOT *root, struct st_table *new_table, int Field_varstring::store(const char *from,uint length,CHARSET_INFO *cs) { - uint32 not_used, copy_length; - char buff[STRING_BUFFER_USUAL_SIZE]; - String tmpstr(buff,sizeof(buff), &my_charset_bin); - int error_code= 0, well_formed_error; - enum MYSQL_ERROR::enum_warning_level level= MYSQL_ERROR::WARN_LEVEL_WARN; + uint copy_length; + const char *well_formed_error_pos; + const char *cannot_convert_error_pos; + const char *from_end_pos; + + copy_length= well_formed_copy_nchars(field_charset, + ptr + length_bytes, field_length, + cs, from, length, + field_length / field_charset->mbmaxlen, + &well_formed_error_pos, + &cannot_convert_error_pos, + &from_end_pos); - /* Convert character set if necessary */ - if (String::needs_conversion(length, cs, field_charset, ¬_used)) - { - uint conv_errors; - tmpstr.copy(from, length, cs, field_charset, &conv_errors); - from= tmpstr.ptr(); - length= tmpstr.length(); - if (conv_errors) - error_code= WARN_DATA_TRUNCATED; - } - /* - Make sure we don't break a multibyte sequence - as well as don't copy a malformed data. - */ - copy_length= field_charset->cset->well_formed_len(field_charset, - from,from+length, - field_length/ - field_charset->mbmaxlen, - &well_formed_error); - memmove(ptr + length_bytes, from, copy_length); if (length_bytes == 1) *ptr= (uchar) copy_length; else int2store(ptr, copy_length); + if (check_string_copy_error(this, well_formed_error_pos, + cannot_convert_error_pos, from + length)) + return 2; + // Check if we lost something other than just trailing spaces - if ((copy_length < length) && table->in_use->count_cuted_fields && - !error_code) + if ((from_end_pos < from + length) && table->in_use->count_cuted_fields) { - if (!binary()) - { - const char *end= from + length; - from+= copy_length; - from+= field_charset->cset->scan(field_charset, from, end, MY_SEQ_SPACES); - /* If we lost only spaces then produce a NOTE, not a WARNING */ - if (from == end) - level= MYSQL_ERROR::WARN_LEVEL_NOTE; - } - error_code= WARN_DATA_TRUNCATED; - } - if (error_code) - { - if (level == MYSQL_ERROR::WARN_LEVEL_WARN && - table->in_use->abort_on_warning) - error_code= ER_DATA_TOO_LONG; - set_warning(level, error_code, 1); + if (test_if_important_data(field_charset, from_end_pos, from + length)) + report_data_too_long(this); + else /* If we lost only spaces then produce a NOTE, not a WARNING */ + set_warning(MYSQL_ERROR::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1); return 2; } return 0; @@ -6828,68 +6907,70 @@ void Field_blob::put_length(char *pos, uint32 length) int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) { - int error= 0, well_formed_error; + uint copy_length, new_length; + const char *well_formed_error_pos; + const char *cannot_convert_error_pos; + const char *from_end_pos, *tmp; + char buff[STRING_BUFFER_USUAL_SIZE]; + String tmpstr(buff,sizeof(buff), &my_charset_bin); + if (!length) { bzero(ptr,Field_blob::pack_length()); + return 0; } - else - { - bool was_conversion; - char buff[STRING_BUFFER_USUAL_SIZE]; - String tmpstr(buff,sizeof(buff), &my_charset_bin); - uint copy_length; - uint32 not_used; - /* Convert character set if necessary */ - if ((was_conversion= String::needs_conversion(length, cs, field_charset, - ¬_used))) - { - uint conv_errors; - if (tmpstr.copy(from, length, cs, field_charset, &conv_errors)) - { - /* Fatal OOM error */ - bzero(ptr,Field_blob::pack_length()); - return -1; - } - from= tmpstr.ptr(); - length= tmpstr.length(); - if (conv_errors) - error= 2; - } - - copy_length= max_data_length(); - /* - copy_length is OK as last argument to well_formed_len as this is never - used to limit the length of the data. The cut of long data is done with - the 'min()' call below. - */ - copy_length= field_charset->cset->well_formed_len(field_charset, - from,from + - min(length, copy_length), - copy_length, - &well_formed_error); - if (copy_length < length) - error= 2; - Field_blob::store_length(copy_length); - if (was_conversion || table->copy_blobs || copy_length <= MAX_FIELD_WIDTH) - { // Must make a copy - if (from != value.ptr()) // For valgrind - { - value.copy(from,copy_length,charset()); - from=value.ptr(); - } - } - bmove(ptr+packlength,(char*) &from,sizeof(char*)); - } - if (error) + if (from == value.ptr()) { - if (table->in_use->abort_on_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_ERROR, ER_DATA_TOO_LONG, 1); - else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + uint32 dummy_offset; + if (!String::needs_conversion(length, cs, field_charset, &dummy_offset)) + { + Field_blob::store_length(length); + bmove(ptr+packlength,(char*) &from,sizeof(char*)); + return 0; + } + if (tmpstr.copy(from, length, cs)) + goto oom_error; + from= tmpstr.ptr(); } + + new_length= min(max_data_length(), field_charset->mbmaxlen * length); + if (value.alloc(new_length)) + goto oom_error; + + /* + "length" is OK as "nchars" argument to well_formed_copy_nchars as this + is never used to limit the length of the data. The cut of long data + is done with the new_length value. + */ + copy_length= well_formed_copy_nchars(field_charset, + (char*) value.ptr(), new_length, + cs, from, length, + length, + &well_formed_error_pos, + &cannot_convert_error_pos, + &from_end_pos); + + Field_blob::store_length(copy_length); + tmp= value.ptr(); + bmove(ptr+packlength,(char*) &tmp,sizeof(char*)); + + if (check_string_copy_error(this, well_formed_error_pos, + cannot_convert_error_pos, from + length)) + return 2; + + if (copy_length < length) + { + report_data_too_long(this); + return 2; + } + return 0; + +oom_error: + /* Fatal OOM error */ + bzero(ptr,Field_blob::pack_length()); + return -1; } diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 7aaca809113..85ff1fddc45 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -854,6 +854,162 @@ outp: } +/* + copy a string, + with optional character set conversion, + with optional left padding (for binary -> UCS2 conversion) + + SYNOPSIS + well_formed_copy_nhars() + to Store result here + to_length Maxinum length of "to" string + to_cs Character set of "to" string + from Copy from here + from_length Length of from string + from_cs From character set + nchars Copy not more that nchars characters + well_formed_error_pos Return position when "from" is not well formed + or NULL otherwise. + cannot_convert_error_pos Return position where a not convertable + character met, or NULL otherwise. + from_end_pos Return position where scanning of "from" + string stopped. + NOTES + + RETURN + length of bytes copied to 'to' +*/ + + +uint32 +well_formed_copy_nchars(CHARSET_INFO *to_cs, + char *to, uint to_length, + CHARSET_INFO *from_cs, + const char *from, uint from_length, + uint nchars, + const char **well_formed_error_pos, + const char **cannot_convert_error_pos, + const char **from_end_pos) +{ + uint res; + + if ((to_cs == &my_charset_bin) || + (from_cs == &my_charset_bin) || + (to_cs == from_cs) || + my_charset_same(from_cs, to_cs)) + { + if (to_length < to_cs->mbminlen || !nchars) + { + *from_end_pos= from; + *cannot_convert_error_pos= NULL; + *well_formed_error_pos= NULL; + return 0; + } + + if (to_cs == &my_charset_bin) + { + res= min(min(nchars, to_length), from_length); + memmove(to, from, res); + *from_end_pos= from + res; + *well_formed_error_pos= NULL; + *cannot_convert_error_pos= NULL; + } + else + { + int well_formed_error; + uint from_offset; + + if ((from_offset= (from_length % to_cs->mbminlen)) && + (from_cs == &my_charset_bin)) + { + /* + Copying from BINARY to UCS2 needs to prepend zeros sometimes: + INSERT INTO t1 (ucs2_column) VALUES (0x01); + 0x01 -> 0x0001 + */ + uint pad_length= to_cs->mbminlen - from_offset; + bzero(to, pad_length); + memmove(to + pad_length, from, from_offset); + nchars--; + from+= from_offset; + from_length-= from_offset; + to+= to_cs->mbminlen; + to_length-= to_cs->mbminlen; + } + + set_if_smaller(from_length, to_length); + res= to_cs->cset->well_formed_len(to_cs, from, from + from_length, + nchars, &well_formed_error); + memmove(to, from, res); + *from_end_pos= from + res; + *well_formed_error_pos= well_formed_error ? from + res : NULL; + *cannot_convert_error_pos= NULL; + if (from_offset) + res+= to_cs->mbminlen; + } + } + else + { + int cnvres; + my_wc_t wc; + int (*mb_wc)(struct charset_info_st *, my_wc_t *, + const uchar *, const uchar *)= from_cs->cset->mb_wc; + int (*wc_mb)(struct charset_info_st *, my_wc_t, + uchar *s, uchar *e)= to_cs->cset->wc_mb; + const uchar *from_end= (const uchar*) from + from_length; + uchar *to_end= (uchar*) to + to_length; + char *to_start= to; + *well_formed_error_pos= NULL; + *cannot_convert_error_pos= NULL; + + for ( ; nchars; nchars--) + { + const char *from_prev= from; + if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0) + from+= cnvres; + else if (cnvres == MY_CS_ILSEQ) + { + if (!*well_formed_error_pos) + *well_formed_error_pos= from; + from++; + wc= '?'; + } + else if (cnvres > MY_CS_TOOSMALL) + { + /* + A correct multibyte sequence detected + But it doesn't have Unicode mapping. + */ + if (!*cannot_convert_error_pos) + *cannot_convert_error_pos= from; + from+= (-cnvres); + wc= '?'; + } + else + break; // Not enough characters + +outp: + if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0) + to+= cnvres; + else if (cnvres == MY_CS_ILUNI && wc != '?') + { + if (!*cannot_convert_error_pos) + *cannot_convert_error_pos= from_prev; + wc= '?'; + goto outp; + } + else + break; + } + *from_end_pos= from; + res= to - to_start; + } + return (uint32) res; +} + + + + void String::print(String *str) { char *st= (char*)Ptr, *end= st+str_length; diff --git a/sql/sql_string.h b/sql/sql_string.h index 0659f684afe..09b8478adf8 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -32,6 +32,14 @@ String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, const char *from, uint32 from_length, CHARSET_INFO *from_cs, uint *errors); +uint32 well_formed_copy_nchars(CHARSET_INFO *to_cs, + char *to, uint to_length, + CHARSET_INFO *from_cs, + const char *from, uint from_length, + uint nchars, + const char **well_formed_error_pos, + const char **cannot_convert_error_pos, + const char **from_end_pos); class String { From e463ee942152e22c77d52403dbfb772648d4f35d Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 30 Oct 2006 14:40:15 +0400 Subject: [PATCH 5/9] Bug#20404: SHOW CREATE TABLE fails with Turkish I Problem: SHOW CREATE TABLE printed garbage in table name for tables having TURKISH I (i.e. LATIN CAPITABLE LETTER I WITH DOT ABOVE) when lower-case-table-name=1. Reason: In some cases during lower/upper conversion in utf8, the result string can be shorter the original string (including the above letter). Old implementation of caseup_str() and casedn_str() didn't handle the result length properly, assuming that length cannot change. This fix changes the result type of cs->cset->casedn_str() and cs->cset->caseup_str() from VOID to UINT, to return the result length, as well as put '\0' terminator on a proper place. Also, my_caseup_str_utf8() and my_casedn_str_utf8() were rewritten not to use strlen() for performance purposes. It was done with help of adding of new functions - my_utf8_uni_no_range() and my_uni_utf8_no_range() - for null terminated strings. include/m_ctype.h: Changeing return type from void to int for caseup_str() and casedn_str() mysql-test/r/lowercase_table.result: Adding test case mysql-test/t/lowercase_table.test: Adding test case sql/sql_parse.cc: Set table->table.length to result of my_casedn_str(). strings/ctype-bin.c: Changeing return type from void to int for caseup_str() and casedn_str() strings/ctype-mb.c: Changeing return type from void to int for caseup_str() and casedn_str() strings/ctype-simple.c: Changeing return type from void to int for caseup_str() and casedn_str() strings/ctype-ucs2.c: Changeing return type from void to int for caseup_str() and casedn_str() strings/ctype-utf8.c: Changeing return type from void to int for caseup_str() and casedn_str(). Optimization, to get rid of strlen(): Adding my_utf8_uni_no_range() and my_uni_utf8_no_range() - for null terninated strings. --- include/m_ctype.h | 12 +-- mysql-test/r/lowercase_table.result | 24 +++++ mysql-test/t/lowercase_table.test | 20 +++++ sql/sql_parse.cc | 2 +- strings/ctype-bin.c | 3 +- strings/ctype-mb.c | 24 ++--- strings/ctype-simple.c | 18 ++-- strings/ctype-ucs2.c | 8 +- strings/ctype-utf8.c | 135 ++++++++++++++++++++++++++-- 9 files changed, 213 insertions(+), 33 deletions(-) diff --git a/include/m_ctype.h b/include/m_ctype.h index 35d2889bdd5..ffecadd9836 100644 --- a/include/m_ctype.h +++ b/include/m_ctype.h @@ -178,8 +178,8 @@ typedef struct my_charset_handler_st unsigned char *s,unsigned char *e); /* Functions for case and sort convertion */ - void (*caseup_str)(struct charset_info_st *, char *); - void (*casedn_str)(struct charset_info_st *, char *); + uint (*caseup_str)(struct charset_info_st *, char *); + uint (*casedn_str)(struct charset_info_st *, char *); uint (*caseup)(struct charset_info_st *, char *src, uint srclen, char *dst, uint dstlen); uint (*casedn)(struct charset_info_st *, char *src, uint srclen, @@ -311,8 +311,8 @@ extern uint my_instr_simple(struct charset_info_st *, /* Functions for 8bit */ -extern void my_caseup_str_8bit(CHARSET_INFO *, char *); -extern void my_casedn_str_8bit(CHARSET_INFO *, char *); +extern uint my_caseup_str_8bit(CHARSET_INFO *, char *); +extern uint my_casedn_str_8bit(CHARSET_INFO *, char *); extern uint my_caseup_8bit(CHARSET_INFO *, char *src, uint srclen, char *dst, uint dstlen); extern uint my_casedn_8bit(CHARSET_INFO *, char *src, uint srclen, @@ -399,8 +399,8 @@ int my_mbcharlen_8bit(CHARSET_INFO *, uint c); /* Functions for multibyte charsets */ -extern void my_caseup_str_mb(CHARSET_INFO *, char *); -extern void my_casedn_str_mb(CHARSET_INFO *, char *); +extern uint my_caseup_str_mb(CHARSET_INFO *, char *); +extern uint my_casedn_str_mb(CHARSET_INFO *, char *); extern uint my_caseup_mb(CHARSET_INFO *, char *src, uint srclen, char *dst, uint dstlen); extern uint my_casedn_mb(CHARSET_INFO *, char *src, uint srclen, diff --git a/mysql-test/r/lowercase_table.result b/mysql-test/r/lowercase_table.result index 7705961d08d..6df3cf61ddb 100644 --- a/mysql-test/r/lowercase_table.result +++ b/mysql-test/r/lowercase_table.result @@ -84,3 +84,27 @@ create table t2 like T1; drop table t1, t2; show tables; Tables_in_test +set names utf8; +drop table if exists İ,İİ; +create table İ (s1 int); +show create table İ; +Table Create Table +İ CREATE TABLE `i` ( + `s1` int(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +show tables; +Tables_in_test +i +drop table İ; +create table İİ (s1 int); +show create table İİ; +Table Create Table +İİ CREATE TABLE `ii` ( + `s1` int(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +show tables; +Tables_in_test +ii +drop table İİ; +set names latin1; +End of 5.0 tests diff --git a/mysql-test/t/lowercase_table.test b/mysql-test/t/lowercase_table.test index 96437bc7636..31513f1bd06 100644 --- a/mysql-test/t/lowercase_table.test +++ b/mysql-test/t/lowercase_table.test @@ -85,3 +85,23 @@ drop table t1, t2; show tables; # End of 4.1 tests + + +# +# Bug#20404: SHOW CREATE TABLE fails with Turkish I +# +set names utf8; +--disable_warnings +drop table if exists İ,İİ; +--enable_warnings +create table İ (s1 int); +show create table İ; +show tables; +drop table İ; +create table İİ (s1 int); +show create table İİ; +show tables; +drop table İİ; +set names latin1; + +--echo End of 5.0 tests diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index c62c286cfdb..3cb2f5dc1db 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6177,7 +6177,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->alias= alias_str; if (lower_case_table_names && table->table.length) - my_casedn_str(files_charset_info, table->table.str); + table->table.length= my_casedn_str(files_charset_info, table->table.str); ptr->table_name=table->table.str; ptr->table_name_length=table->table.length; ptr->lock_type= lock_type; diff --git a/strings/ctype-bin.c b/strings/ctype-bin.c index 0bd5a1fda76..3e8b05580f6 100644 --- a/strings/ctype-bin.c +++ b/strings/ctype-bin.c @@ -211,9 +211,10 @@ static int my_strnncollsp_8bit_bin(CHARSET_INFO * cs __attribute__((unused)), /* This function is used for all conversion functions */ -static void my_case_str_bin(CHARSET_INFO *cs __attribute__((unused)), +static uint my_case_str_bin(CHARSET_INFO *cs __attribute__((unused)), char *str __attribute__((unused))) { + return 0; } static uint my_case_bin(CHARSET_INFO *cs __attribute__((unused)), diff --git a/strings/ctype-mb.c b/strings/ctype-mb.c index 394111be3bc..3ef245015d7 100644 --- a/strings/ctype-mb.c +++ b/strings/ctype-mb.c @@ -21,40 +21,44 @@ #ifdef USE_MB -void my_caseup_str_mb(CHARSET_INFO * cs, char *str) +uint my_caseup_str_mb(CHARSET_INFO * cs, char *str) { register uint32 l; - register uchar *map=cs->to_upper; + register uchar *map= cs->to_upper; + char *str_orig= str; while (*str) { /* Pointing after the '\0' is safe here. */ - if ((l=my_ismbchar(cs, str, str + cs->mbmaxlen))) - str+=l; + if ((l= my_ismbchar(cs, str, str + cs->mbmaxlen))) + str+= l; else { - *str=(char) map[(uchar)*str]; + *str= (char) map[(uchar)*str]; str++; } } + return str - str_orig; } -void my_casedn_str_mb(CHARSET_INFO * cs, char *str) +uint my_casedn_str_mb(CHARSET_INFO * cs, char *str) { register uint32 l; - register uchar *map=cs->to_lower; + register uchar *map= cs->to_lower; + char *str_orig= str; while (*str) { /* Pointing after the '\0' is safe here. */ - if ((l=my_ismbchar(cs, str, str + cs->mbmaxlen))) - str+=l; + if ((l= my_ismbchar(cs, str, str + cs->mbmaxlen))) + str+= l; else { - *str=(char) map[(uchar)*str]; + *str= (char) map[(uchar)*str]; str++; } } + return str - str_orig; } uint my_caseup_mb(CHARSET_INFO * cs, char *src, uint srclen, diff --git a/strings/ctype-simple.c b/strings/ctype-simple.c index e40a1948dcf..7484f3c0d92 100644 --- a/strings/ctype-simple.c +++ b/strings/ctype-simple.c @@ -188,20 +188,26 @@ int my_strnncollsp_simple(CHARSET_INFO * cs, const uchar *a, uint a_length, } -void my_caseup_str_8bit(CHARSET_INFO * cs,char *str) +uint my_caseup_str_8bit(CHARSET_INFO * cs,char *str) { - register uchar *map=cs->to_upper; - while ((*str = (char) map[(uchar) *str]) != 0) + register uchar *map= cs->to_upper; + char *str_orig= str; + while ((*str= (char) map[(uchar) *str]) != 0) str++; + return str - str_orig; } -void my_casedn_str_8bit(CHARSET_INFO * cs,char *str) + +uint my_casedn_str_8bit(CHARSET_INFO * cs,char *str) { - register uchar *map=cs->to_lower; - while ((*str = (char) map[(uchar)*str]) != 0) + register uchar *map= cs->to_lower; + char *str_orig= str; + while ((*str= (char) map[(uchar) *str]) != 0) str++; + return str - str_orig; } + uint my_caseup_8bit(CHARSET_INFO * cs, char *src, uint srclen, char *dst __attribute__((unused)), uint dstlen __attribute__((unused))) diff --git a/strings/ctype-ucs2.c b/strings/ctype-ucs2.c index 4a60220f73e..3c69a314b45 100644 --- a/strings/ctype-ucs2.c +++ b/strings/ctype-ucs2.c @@ -159,13 +159,13 @@ static void my_hash_sort_ucs2(CHARSET_INFO *cs, const uchar *s, uint slen, } -static void my_caseup_str_ucs2(CHARSET_INFO * cs __attribute__((unused)), +static uint my_caseup_str_ucs2(CHARSET_INFO * cs __attribute__((unused)), char * s __attribute__((unused))) { + return 0; } - static uint my_casedn_ucs2(CHARSET_INFO *cs, char *src, uint srclen, char *dst __attribute__((unused)), uint dstlen __attribute__((unused))) @@ -188,9 +188,11 @@ static uint my_casedn_ucs2(CHARSET_INFO *cs, char *src, uint srclen, return srclen; } -static void my_casedn_str_ucs2(CHARSET_INFO *cs __attribute__((unused)), + +static uint my_casedn_str_ucs2(CHARSET_INFO *cs __attribute__((unused)), char * s __attribute__((unused))) { + return 0; } diff --git a/strings/ctype-utf8.c b/strings/ctype-utf8.c index ae2c04fb068..a21fe4961ec 100644 --- a/strings/ctype-utf8.c +++ b/strings/ctype-utf8.c @@ -2045,6 +2045,52 @@ static int my_utf8_uni(CHARSET_INFO *cs __attribute__((unused)), return MY_CS_ILSEQ; } + +/* + The same as above, but without range check + for example, for a null-terminated string +*/ +static int my_utf8_uni_no_range(CHARSET_INFO *cs __attribute__((unused)), + my_wc_t * pwc, const uchar *s) +{ + unsigned char c; + + c= s[0]; + if (c < 0x80) + { + *pwc = c; + return 1; + } + + if (c < 0xc2) + return MY_CS_ILSEQ; + + if (c < 0xe0) + { + if (!((s[1] ^ 0x80) < 0x40)) + return MY_CS_ILSEQ; + + *pwc = ((my_wc_t) (c & 0x1f) << 6) | (my_wc_t) (s[1] ^ 0x80); + return 2; + } + + if (c < 0xf0) + { + if (!((s[1] ^ 0x80) < 0x40 && + (s[2] ^ 0x80) < 0x40 && + (c >= 0xe1 || s[1] >= 0xa0))) + return MY_CS_ILSEQ; + + *pwc= ((my_wc_t) (c & 0x0f) << 12) | + ((my_wc_t) (s[1] ^ 0x80) << 6) | + (my_wc_t) (s[2] ^ 0x80); + + return 3; + } + return MY_CS_ILSEQ; +} + + static int my_uni_utf8 (CHARSET_INFO *cs __attribute__((unused)) , my_wc_t wc, uchar *r, uchar *e) { @@ -2091,6 +2137,34 @@ static int my_uni_utf8 (CHARSET_INFO *cs __attribute__((unused)) , } +/* + The same as above, but without range check. +*/ +static int my_uni_utf8_no_range(CHARSET_INFO *cs __attribute__((unused)), + my_wc_t wc, uchar *r) +{ + int count; + + if (wc < 0x80) + count= 1; + else if (wc < 0x800) + count= 2; + else if (wc < 0x10000) + count= 3; + else + return MY_CS_ILUNI; + + switch (count) + { + /* Fall through all cases!!! */ + case 3: r[2]= (uchar) (0x80 | (wc & 0x3f)); wc= wc >> 6; wc |= 0x800; + case 2: r[1]= (uchar) (0x80 | (wc & 0x3f)); wc= wc >> 6; wc |= 0xc0; + case 1: r[0]= (uchar) wc; + } + return count; +} + + static uint my_caseup_utf8(CHARSET_INFO *cs, char *src, uint srclen, char *dst, uint dstlen) { @@ -2141,10 +2215,26 @@ static void my_hash_sort_utf8(CHARSET_INFO *cs, const uchar *s, uint slen, } -static void my_caseup_str_utf8(CHARSET_INFO * cs, char * s) +static uint my_caseup_str_utf8(CHARSET_INFO *cs, char *src) { - uint len= (uint) strlen(s); - my_caseup_utf8(cs, s, len, s, len); + my_wc_t wc; + int srcres, dstres; + char *dst= src, *dst0= src; + MY_UNICASE_INFO **uni_plane= cs->caseinfo; + DBUG_ASSERT(cs->caseup_multiply == 1); + + while (*src && + (srcres= my_utf8_uni_no_range(cs, &wc, (uchar *) src)) > 0) + { + int plane= (wc>>8) & 0xFF; + wc= uni_plane[plane] ? uni_plane[plane][wc & 0xFF].toupper : wc; + if ((dstres= my_uni_utf8_no_range(cs, wc, (uchar*) dst)) <= 0) + break; + src+= srcres; + dst+= dstres; + } + *dst= '\0'; + return (uint) (dst - dst0); } @@ -2170,10 +2260,43 @@ static uint my_casedn_utf8(CHARSET_INFO *cs, char *src, uint srclen, return (uint) (dst - dst0); } -static void my_casedn_str_utf8(CHARSET_INFO *cs, char * s) + +static uint my_casedn_str_utf8(CHARSET_INFO *cs, char *src) { - uint len= (uint) strlen(s); - my_casedn_utf8(cs, s, len, s, len); + my_wc_t wc; + int srcres, dstres; + char *dst= src, *dst0= src; + MY_UNICASE_INFO **uni_plane= cs->caseinfo; + DBUG_ASSERT(cs->casedn_multiply == 1); + + while (*src && + (srcres= my_utf8_uni_no_range(cs, &wc, (uchar *) src)) > 0) + { + int plane= (wc>>8) & 0xFF; + wc= uni_plane[plane] ? uni_plane[plane][wc & 0xFF].tolower : wc; + if ((dstres= my_uni_utf8_no_range(cs, wc, (uchar*) dst)) <= 0) + break; + src+= srcres; + dst+= dstres; + } + + /* + In rare cases lower string can be shorter than + the original string, for example: + + "U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE" + (which is 0xC4B0 in utf8, i.e. two bytes) + + is converted into + + "U+0069 LATIN SMALL LETTER I" + (which is 0x69 in utf8, i.e. one byte) + + So, we need to put '\0' terminator after converting. + */ + + *dst= '\0'; + return (uint) (dst - dst0); } From c755a7a1c48b236c57f996fbc3c07ccbc952424c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 1 Nov 2006 12:30:01 +0400 Subject: [PATCH 6/9] Bug#22877 replication character sets get out of sync using replicate-wild-ignore-table Problem: changes in character set variables before an action on an replication-ignored table makes slave to forget new variable values. Fix: initialize one_shot variables only when 4.1 -> 5.x replication is running. mysql-test/r/rpl_ignore_table.result: Adding test case mysql-test/t/rpl_ignore_table-slave.opt: Don't replicate tables with names starting with "tmptbl" mysql-test/t/rpl_ignore_table.test: Adding test case sql/sql_parse.cc: Reset one_shot variables only if we do 4.1->5.x replication. In other cases we cannot do that: resetting thd->variables out of sync with st_relay_log_info::cached_charset, which makes st_relay_log_info::cached_charset_compare() not to notice character set related variables changes afterwards. --- BitKeeper/etc/collapsed | 1 + mysql-test/r/rpl_ignore_table.result | 16 ++++++++++++++++ mysql-test/t/rpl_ignore_table-slave.opt | 2 +- mysql-test/t/rpl_ignore_table.test | 23 +++++++++++++++++++++++ sql/sql_parse.cc | 18 +++++++++++++++++- 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/BitKeeper/etc/collapsed b/BitKeeper/etc/collapsed index fd33e4fc902..e9b9c6d81e0 100644 --- a/BitKeeper/etc/collapsed +++ b/BitKeeper/etc/collapsed @@ -3,3 +3,4 @@ 44edb86b1iE5knJ97MbliK_3lCiAXA 44f33f3aj5KW5qweQeekY1LU0E9ZCg 4513d8e4Af4dQWuk13sArwofRgFDQw +4538a7b0EbDHHkWPbIwxO6ZIDdg6Dg diff --git a/mysql-test/r/rpl_ignore_table.result b/mysql-test/r/rpl_ignore_table.result index 356a9dcb2f8..136cf5cc5eb 100644 --- a/mysql-test/r/rpl_ignore_table.result +++ b/mysql-test/r/rpl_ignore_table.result @@ -14,3 +14,19 @@ SELECT * FROM t4; a DROP TABLE t1; DROP TABLE t4; +DROP TABLE IF EXISTS t5; +CREATE TABLE t5 ( +word varchar(50) collate utf8_unicode_ci NOT NULL default '' +) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +SET @@session.character_set_client=33,@@session.collation_connection=192; +CREATE TEMPORARY TABLE tmptbl504451f4258$1 (id INT NOT NULL) ENGINE=MEMORY; +INSERT INTO t5 (word) VALUES ('TEST’'); +SELECT HEX(word) FROM t5; +HEX(word) +54455354E28099 +SELECT HEX(word) FROM t5; +HEX(word) +54455354E28099 +SELECT * FROM tmptbl504451f4258$1; +ERROR 42S02: Table 'test.tmptbl504451f4258$1' doesn't exist +DROP TABLE t5; diff --git a/mysql-test/t/rpl_ignore_table-slave.opt b/mysql-test/t/rpl_ignore_table-slave.opt index cb49119bfcb..3aabbb2e0f5 100644 --- a/mysql-test/t/rpl_ignore_table-slave.opt +++ b/mysql-test/t/rpl_ignore_table-slave.opt @@ -1 +1 @@ ---replicate-ignore-table=test.t1 --replicate-ignore-table=test.t2 --replicate-ignore-table=test.t3 +--replicate-ignore-table=test.t1 --replicate-ignore-table=test.t2 --replicate-ignore-table=test.t3 --replicate-wild-ignore-table=%.tmptbl% diff --git a/mysql-test/t/rpl_ignore_table.test b/mysql-test/t/rpl_ignore_table.test index bc651779208..b875075f71c 100644 --- a/mysql-test/t/rpl_ignore_table.test +++ b/mysql-test/t/rpl_ignore_table.test @@ -26,3 +26,26 @@ SELECT * FROM t4; connection master; DROP TABLE t1; DROP TABLE t4; + + +# +# bug#22877 replication character sets get out of sync +# using replicate-wild-ignore-table +# +--disable_warnings +DROP TABLE IF EXISTS t5; +--enable_warnings +CREATE TABLE t5 ( + word varchar(50) collate utf8_unicode_ci NOT NULL default '' +) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +SET @@session.character_set_client=33,@@session.collation_connection=192; +CREATE TEMPORARY TABLE tmptbl504451f4258$1 (id INT NOT NULL) ENGINE=MEMORY; +INSERT INTO t5 (word) VALUES ('TEST’'); +SELECT HEX(word) FROM t5; +sync_slave_with_master; +connection slave; +SELECT HEX(word) FROM t5; +--error 1146 +SELECT * FROM tmptbl504451f4258$1; +connection master; +DROP TABLE t5; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 18d048df393..ede21011bcd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2480,7 +2480,23 @@ mysql_execute_command(THD *thd) { /* we warn the slave SQL thread */ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - reset_one_shot_variables(thd); + if (thd->one_shot_set) + { + /* + It's ok to check thd->one_shot_set here: + + The charsets in a MySQL 5.0 slave can change by both a binlogged + SET ONE_SHOT statement and the event-internal charset setting, + and these two ways to change charsets do not seems to work + together. + + At least there seems to be problems in the rli cache for + charsets if we are using ONE_SHOT. Note that this is normally no + problem because either the >= 5.0 slave reads a 4.1 binlog (with + ONE_SHOT) *or* or 5.0 binlog (without ONE_SHOT) but never both." + */ + reset_one_shot_variables(thd); + } DBUG_RETURN(0); } } From d9360eae3b57c7ef4b1a14b270d06c8423df3bfb Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 7 Nov 2006 12:45:48 +0400 Subject: [PATCH 7/9] Bug#23451 GROUP_CONCAT truncates a multibyte utf8 character Problem: GROUP_CONCAT on a multi-byte column can truncate in the middle of a multibyte character when applying group_concat_max_len limit. It produces an invalid multi-byte character in the result string. The second, easier version - reusing old "warning_for_row" flag, instead of introducing of "result_is_full" - which was added in the previous commit. mysql-test/r/func_gconcat.result: Adding test case mysql-test/t/func_gconcat.test: Adding test case sql/item_sum.cc: Adding well_formed_len() call not to cut in the middle of a multi-byte character. --- mysql-test/r/func_gconcat.result | 62 +++++++++++++++++++++++++++++++- mysql-test/t/func_gconcat.test | 24 +++++++++++++ sql/item_sum.cc | 20 +++++++++-- 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/func_gconcat.result b/mysql-test/r/func_gconcat.result index d4a46bfd79f..fa8a1e2cf6d 100644 --- a/mysql-test/r/func_gconcat.result +++ b/mysql-test/r/func_gconcat.result @@ -599,12 +599,72 @@ count(distinct (f1+1)) 1 3 drop table t1; +set names utf8; +create table t1 +( +x text character set utf8 not null, +y integer not null +); +insert into t1 values (repeat('a', 1022), 0), (repeat(_utf8 0xc3b7, 4), 0); +set group_concat_max_len= 1022 + 10; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1032 1031 1027 aaaaaaa,÷÷÷÷ C3B7C3B7C3B7 +set group_concat_max_len= 1022 + 9; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1031 1031 1027 aaaaaaa,÷÷÷÷ C3B7C3B7C3B7 +set group_concat_max_len= 1022 + 8; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1030 1029 1026 aaaaaaaa,÷÷÷ C3B7C3B7C3B7 +set group_concat_max_len= 1022 + 7; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1029 1029 1026 aaaaaaaa,÷÷÷ C3B7C3B7C3B7 +set group_concat_max_len= 1022 + 6; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1028 1027 1025 aaaaaaaaa,÷÷ 612CC3B7C3B7 +set group_concat_max_len= 1022 + 5; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1027 1027 1025 aaaaaaaaa,÷÷ 612CC3B7C3B7 +set group_concat_max_len= 1022 + 4; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1026 1025 1024 aaaaaaaaaa,÷ 6161612CC3B7 +set group_concat_max_len= 1022 + 3; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1025 1025 1024 aaaaaaaaaa,÷ 6161612CC3B7 +set group_concat_max_len= 1022 + 2; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1024 1023 1023 aaaaaaaaaaa, 61616161612C +set group_concat_max_len= 1022 + 1; +select @x:=group_concat(x) from t1 group by y; +select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); +@@group_concat_max_len length(@x) char_length(@x) right(@x,12) right(HEX(@x),12) +1023 1023 1023 aaaaaaaaaaa, 61616161612C +drop table t1; +set group_concat_max_len=1024; +set names latin1; create table t1 (f1 int unsigned, f2 varchar(255)); insert into t1 values (1,repeat('a',255)),(2,repeat('b',255)); select f2,group_concat(f1) from t1 group by f2; Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr def test t1 t1 f2 f2 253 255 255 Y 0 0 8 -def group_concat(f1) 252 400 1 Y 128 0 63 +def group_concat(f1) 252 1024 1 Y 128 0 63 f2 group_concat(f1) aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 2 diff --git a/mysql-test/t/func_gconcat.test b/mysql-test/t/func_gconcat.test index d51d88d50ef..aebceeedcc3 100644 --- a/mysql-test/t/func_gconcat.test +++ b/mysql-test/t/func_gconcat.test @@ -390,6 +390,30 @@ select f1, group_concat(f1+1) from t1 group by f1 with rollup; select count(distinct (f1+1)) from t1 group by f1 with rollup; drop table t1; +# +# Bug#23451 GROUP_CONCAT truncates a multibyte utf8 character +# +set names utf8; +create table t1 +( + x text character set utf8 not null, + y integer not null +); +insert into t1 values (repeat('a', 1022), 0), (repeat(_utf8 0xc3b7, 4), 0); +let $1= 10; +while ($1) +{ + eval set group_concat_max_len= 1022 + $1; + --disable_result_log + select @x:=group_concat(x) from t1 group by y; + --enable_result_log + select @@group_concat_max_len, length(@x), char_length(@x), right(@x,12), right(HEX(@x),12); + dec $1; +} +drop table t1; +set group_concat_max_len=1024; +set names latin1; + # # Bug#14169 type of group_concat() result changed to blob if tmp_table was used # diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 0b9b10d05d4..4c346357240 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -1664,6 +1664,7 @@ int dump_leaf_key(byte* key, uint32 count __attribute__((unused)), { char buff[MAX_FIELD_WIDTH]; String tmp((char *)&buff,sizeof(buff),default_charset_info), tmp2; + uint old_length= item->result.length(); if (item->no_appended) item->no_appended= FALSE; @@ -1702,8 +1703,22 @@ int dump_leaf_key(byte* key, uint32 count __attribute__((unused)), /* stop if length of result more than group_concat_max_len */ if (item->result.length() > item->group_concat_max_len) { + int well_formed_error; + CHARSET_INFO *cs= item->collation.collation; + const char *ptr= item->result.ptr(); + uint add_length; + /* + It's ok to use item->result.length() as the fourth argument + as this is never used to limit the length of the data. + Cut is done with the third argument. + */ + add_length= cs->cset->well_formed_len(cs, + ptr + old_length, + ptr + item->group_concat_max_len, + item->result.length(), + &well_formed_error); + item->result.length(old_length + add_length); item->count_cut_values++; - item->result.length(item->group_concat_max_len); item->warning_for_row= TRUE; return 1; } @@ -1893,8 +1908,7 @@ bool Item_func_group_concat::add() we can dump the row here in case of GROUP_CONCAT(DISTINCT...) instead of doing tree traverse later. */ - if (result.length() <= group_concat_max_len && - !warning_for_row && + if (!warning_for_row && (!tree_mode || (el->count == 1 && distinct && !arg_count_order))) dump_leaf_key(table->record[0], 1, this); From a39b8a064ed1cd200e34be4ee0af078da7b2412c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 8 Nov 2006 22:08:50 +0400 Subject: [PATCH 8/9] after merge fix for bug 23451. sql/item_sum.cc: after merge fix. --- sql/item_sum.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 000a8ce4b10..f1e0001c994 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -3038,7 +3038,7 @@ int dump_leaf_key(byte* key, element_count count __attribute__((unused)), { int well_formed_error; CHARSET_INFO *cs= item->collation.collation; - const char *ptr= item->result.ptr(); + const char *ptr= result->ptr(); uint add_length; /* It's ok to use item->result.length() as the fourth argument @@ -3047,10 +3047,10 @@ int dump_leaf_key(byte* key, element_count count __attribute__((unused)), */ add_length= cs->cset->well_formed_len(cs, ptr + old_length, - ptr + item->group_concat_max_len, - item->result.length(), + ptr + item->max_length, + result->length(), &well_formed_error); - item->result.length(old_length + add_length); + result->length(old_length + add_length); item->count_cut_values++; item->warning_for_row= TRUE; return 1; @@ -3310,7 +3310,8 @@ bool Item_func_group_concat::setup(THD *thd) DBUG_RETURN(TRUE); /* We'll convert all blobs to varchar fields in the temporary table */ - tmp_table_param->convert_blob_length= max_length; + tmp_table_param->convert_blob_length= max_length * + collation.collation->mbmaxlen; /* Push all not constant fields to the list and create a temp table */ always_null= 0; for (uint i= 0; i < arg_count_field; i++) From 7614eb0d1db4819ec6f1c3c59196d1dcb0d0c8d5 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 9 Nov 2006 14:41:34 +0400 Subject: [PATCH 9/9] Bug#21505 Create view - illegal mix of collation for operation 'UNION' The problem was that any VIEW columns had always implicit derivation. Fix: derivation is now copied from the original expression given in VIEW definition. For example: - a VIEW column which comes from a string constant in CREATE VIEW definition have now coercible derivation. - a VIEW column having COLLATE clause in CREATE VIEW definition have now explicit derivation. mysql-test/r/ctype_utf8.result: Adding test case mysql-test/t/ctype_utf8.test: Adding test case sql/field.cc: Copying derivation from item to field. sql/field.h: Adding derivation and methods to get/set it into Field. sql/item.cc: Copying derivation from field to item. sql/item.h: Moving "enum Derivation" declaration from item.h to mysql_priv.h sql/mysql_priv.h: Moving "enum Derivation" declaration from item.h to mysql_priv.h sql/sql_select.cc: Copying derivation from item to field in create_tmp_field_from_item() and create_tmp_field(). --- mysql-test/r/ctype_utf8.result | 26 ++++++++++++++++++++++++++ mysql-test/t/ctype_utf8.test | 24 ++++++++++++++++++++++++ sql/field.cc | 1 + sql/field.h | 7 +++++++ sql/item.cc | 2 +- sql/item.h | 12 ------------ sql/mysql_priv.h | 11 +++++++++++ sql/sql_select.cc | 5 ++++- 8 files changed, 74 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/ctype_utf8.result b/mysql-test/r/ctype_utf8.result index d1a3839d6e9..4eb74510c93 100644 --- a/mysql-test/r/ctype_utf8.result +++ b/mysql-test/r/ctype_utf8.result @@ -1536,6 +1536,32 @@ set @a:=null; execute my_stmt using @a; a b drop table if exists t1; +drop table if exists t1; +drop view if exists v1, v2; +set names utf8; +create table t1(col1 varchar(12) character set utf8 collate utf8_unicode_ci); +insert into t1 values('t1_val'); +create view v1 as select 'v1_val' as col1; +select coercibility(col1), collation(col1) from v1; +coercibility(col1) collation(col1) +4 utf8_general_ci +create view v2 as select col1 from v1 union select col1 from t1; +select coercibility(col1), collation(col1)from v2; +coercibility(col1) collation(col1) +2 utf8_unicode_ci +2 utf8_unicode_ci +drop view v1, v2; +create view v1 as select 'v1_val' collate utf8_swedish_ci as col1; +select coercibility(col1), collation(col1) from v1; +coercibility(col1) collation(col1) +0 utf8_swedish_ci +create view v2 as select col1 from v1 union select col1 from t1; +select coercibility(col1), collation(col1) from v2; +coercibility(col1) collation(col1) +0 utf8_swedish_ci +0 utf8_swedish_ci +drop view v1, v2; +drop table t1; CREATE TABLE t1 ( colA int(11) NOT NULL, colB varchar(255) character set utf8 NOT NULL, diff --git a/mysql-test/t/ctype_utf8.test b/mysql-test/t/ctype_utf8.test index 812965d648a..71259dab0c9 100644 --- a/mysql-test/t/ctype_utf8.test +++ b/mysql-test/t/ctype_utf8.test @@ -1228,6 +1228,30 @@ set @a:=null; execute my_stmt using @a; drop table if exists t1; + +# +# Bug#21505 Create view - illegal mix of collation for operation 'UNION' +# +--disable_warnings +drop table if exists t1; +drop view if exists v1, v2; +--enable_warnings +set names utf8; +create table t1(col1 varchar(12) character set utf8 collate utf8_unicode_ci); +insert into t1 values('t1_val'); +create view v1 as select 'v1_val' as col1; +select coercibility(col1), collation(col1) from v1; +create view v2 as select col1 from v1 union select col1 from t1; +select coercibility(col1), collation(col1)from v2; +drop view v1, v2; +create view v1 as select 'v1_val' collate utf8_swedish_ci as col1; +select coercibility(col1), collation(col1) from v1; +create view v2 as select col1 from v1 union select col1 from t1; +select coercibility(col1), collation(col1) from v2; +drop view v1, v2; +drop table t1; + + # # Bug#19960: Inconsistent results when joining # InnoDB tables using partial UTF8 indexes diff --git a/sql/field.cc b/sql/field.cc index 745e48460b5..f04efc66dfd 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1399,6 +1399,7 @@ Field_str::Field_str(char *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, field_charset=charset; if (charset->state & MY_CS_BINSORT) flags|=BINARY_FLAG; + field_derivation= DERIVATION_IMPLICIT; } diff --git a/sql/field.h b/sql/field.h index b79c2bf77a8..0f845f6c2f9 100644 --- a/sql/field.h +++ b/sql/field.h @@ -302,6 +302,9 @@ public: virtual CHARSET_INFO *sort_charset(void) const { return charset(); } virtual bool has_charset(void) const { return FALSE; } virtual void set_charset(CHARSET_INFO *charset) { } + virtual enum Derivation derivation(void) const + { return DERIVATION_IMPLICIT; } + virtual void set_derivation(enum Derivation derivation) { } bool set_warning(MYSQL_ERROR::enum_warning_level, unsigned int code, int cuted_increment); bool check_int(const char *str, int length, const char *int_end, @@ -373,6 +376,7 @@ public: class Field_str :public Field { protected: CHARSET_INFO *field_charset; + enum Derivation field_derivation; public: Field_str(char *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, @@ -387,6 +391,9 @@ public: uint size_of() const { return sizeof(*this); } CHARSET_INFO *charset(void) const { return field_charset; } void set_charset(CHARSET_INFO *charset) { field_charset=charset; } + enum Derivation derivation(void) const { return field_derivation; } + virtual void set_derivation(enum Derivation derivation_arg) + { field_derivation= derivation_arg; } bool binary() const { return field_charset == &my_charset_bin; } uint32 max_length() { return field_length; } friend class create_field; diff --git a/sql/item.cc b/sql/item.cc index d56ca95093b..f3081551299 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1622,7 +1622,7 @@ void Item_field::set_field(Field *field_par) db_name= field_par->table->s->db; alias_name_used= field_par->table->alias_name_used; unsigned_flag=test(field_par->flags & UNSIGNED_FLAG); - collation.set(field_par->charset(), DERIVATION_IMPLICIT); + collation.set(field_par->charset(), field_par->derivation()); fixed= 1; } diff --git a/sql/item.h b/sql/item.h index 0cfb0b01fd8..b5490f9e814 100644 --- a/sql/item.h +++ b/sql/item.h @@ -27,19 +27,7 @@ class Item_field; /* "Declared Type Collation" A combination of collation and its derivation. -*/ -enum Derivation -{ - DERIVATION_IGNORABLE= 5, - DERIVATION_COERCIBLE= 4, - DERIVATION_SYSCONST= 3, - DERIVATION_IMPLICIT= 2, - DERIVATION_NONE= 1, - DERIVATION_EXPLICIT= 0 -}; - -/* Flags for collation aggregation modes: MY_COLL_ALLOW_SUPERSET_CONV - allow conversion to a superset MY_COLL_ALLOW_COERCIBLE_CONV - allow conversion of a coercible value diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 165bedb3659..683f111e4f3 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -96,6 +96,17 @@ extern CHARSET_INFO *system_charset_info, *files_charset_info ; extern CHARSET_INFO *national_charset_info, *table_alias_charset; +enum Derivation +{ + DERIVATION_IGNORABLE= 5, + DERIVATION_COERCIBLE= 4, + DERIVATION_SYSCONST= 3, + DERIVATION_IMPLICIT= 2, + DERIVATION_NONE= 1, + DERIVATION_EXPLICIT= 0 +}; + + typedef struct my_locale_st { const char *name; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 75cfff4cbb6..abadd0697ac 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8560,6 +8560,7 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, item->collation.collation); else new_field= item->make_string_field(table); + new_field->set_derivation(item->collation.derivation); break; case DECIMAL_RESULT: new_field= new Field_new_decimal(item->max_length, maybe_null, item->name, @@ -8735,7 +8736,9 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, (make_copy_field ? 0 : copy_func), modify_item, convert_blob_length); case Item::TYPE_HOLDER: - return ((Item_type_holder *)item)->make_field_by_type(table); + result= ((Item_type_holder *)item)->make_field_by_type(table); + result->set_derivation(item->collation.derivation); + return result; default: // Dosen't have to be stored return 0; }