From a31f655d82277fda1b89266e0dab849292434a02 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 18 Sep 2009 12:34:08 +0300 Subject: [PATCH 1/8] Bug #47106: Crash / segfault on adding EXPLAIN to a non-crashing query The fix for bug 46749 removed the check for OUTER_REF_TABLE_BIT and substituted it for a check on the presence of Item_ident::depended_from. Removing it altogether was wrong : OUTER_REF_TABLE_BIT should still be checked in addition to depended_from (because it's not set in all cases and doesn't contradict to the check of depended_from). Fixed by returning the old condition back as a compliment to the new one. --- mysql-test/r/subselect4.result | 31 +++++++++++++++++++++++++++++++ mysql-test/t/subselect4.test | 32 ++++++++++++++++++++++++++++++++ sql/sql_select.cc | 8 ++++---- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/subselect4.result b/mysql-test/r/subselect4.result index 68577cb2a4c..e863cbfb7a8 100644 --- a/mysql-test/r/subselect4.result +++ b/mysql-test/r/subselect4.result @@ -27,4 +27,35 @@ SELECT 1; 1 1 DROP TABLE t1,t2,t3; +# +# Bug #47106: Crash / segfault on adding EXPLAIN to a non-crashing +# query +# +CREATE TABLE t1 ( +a INT, +b INT, +PRIMARY KEY (a), +KEY b (b) +); +INSERT INTO t1 VALUES (1, 1), (2, 1); +CREATE TABLE t2 LIKE t1; +INSERT INTO t2 SELECT * FROM t1; +CREATE TABLE t3 LIKE t1; +INSERT INTO t3 SELECT * FROM t1; +# Should not crash. +# Should have 1 impossible where and 2 dependent subqs. +EXPLAIN +SELECT +(SELECT 1 FROM t1,t2 WHERE t2.b > t3.b) +FROM t3 WHERE 1 = 0 GROUP BY 1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +2 DEPENDENT SUBQUERY t1 index NULL PRIMARY 4 NULL 2 Using index +2 DEPENDENT SUBQUERY t2 index b b 5 NULL 2 Using where; Using index +# should return 0 rows +SELECT +(SELECT 1 FROM t1,t2 WHERE t2.b > t3.b) +FROM t3 WHERE 1 = 0 GROUP BY 1; +(SELECT 1 FROM t1,t2 WHERE t2.b > t3.b) +DROP TABLE t1,t2,t3; End of 5.0 tests. diff --git a/mysql-test/t/subselect4.test b/mysql-test/t/subselect4.test index ff4cdf3c439..440eca22828 100644 --- a/mysql-test/t/subselect4.test +++ b/mysql-test/t/subselect4.test @@ -28,5 +28,37 @@ SELECT 1; DROP TABLE t1,t2,t3; +--echo # +--echo # Bug #47106: Crash / segfault on adding EXPLAIN to a non-crashing +--echo # query +--echo # + +CREATE TABLE t1 ( + a INT, + b INT, + PRIMARY KEY (a), + KEY b (b) +); +INSERT INTO t1 VALUES (1, 1), (2, 1); + +CREATE TABLE t2 LIKE t1; +INSERT INTO t2 SELECT * FROM t1; + +CREATE TABLE t3 LIKE t1; +INSERT INTO t3 SELECT * FROM t1; + +--echo # Should not crash. +--echo # Should have 1 impossible where and 2 dependent subqs. +EXPLAIN +SELECT + (SELECT 1 FROM t1,t2 WHERE t2.b > t3.b) +FROM t3 WHERE 1 = 0 GROUP BY 1; + +--echo # should return 0 rows +SELECT + (SELECT 1 FROM t1,t2 WHERE t2.b > t3.b) +FROM t3 WHERE 1 = 0 GROUP BY 1; + +DROP TABLE t1,t2,t3; --echo End of 5.0 tests. diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 84b5b61c941..76d6833de5c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3216,12 +3216,12 @@ add_key_equal_fields(KEY_FIELD **key_fields, uint and_level, @retval FALSE it's something else */ -inline static bool +static bool is_local_field (Item *field) { - field= field->real_item(); - return field->type() == Item::FIELD_ITEM && - !((Item_field *)field)->depended_from; + return field->real_item()->type() == Item::FIELD_ITEM + && !(field->used_tables() & OUTER_REF_TABLE_BIT) + && !((Item_field *)field->real_item())->depended_from; } From 40bd549b9b2412953608aec96e1be3711a0ce037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Bl=C3=A5udd?= Date: Thu, 24 Sep 2009 08:30:31 +0200 Subject: [PATCH 2/8] Bug#42850 race condition in my_thr_init.c - Create the "dummy" thread joinable and wait for it to exit before continuing in 'my_thread_global_init' - This way we know that the pthread library is initialized by one thread only --- mysys/my_thr_init.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mysys/my_thr_init.c b/mysys/my_thr_init.c index c446808c7da..64fc99952e9 100644 --- a/mysys/my_thr_init.c +++ b/mysys/my_thr_init.c @@ -107,10 +107,11 @@ my_bool my_thread_global_init(void) pthread_attr_t dummy_thread_attr; pthread_attr_init(&dummy_thread_attr); - pthread_attr_setdetachstate(&dummy_thread_attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setdetachstate(&dummy_thread_attr, PTHREAD_CREATE_JOINABLE); - pthread_create(&dummy_thread,&dummy_thread_attr, - nptl_pthread_exit_hack_handler, NULL); + if (pthread_create(&dummy_thread,&dummy_thread_attr, + nptl_pthread_exit_hack_handler, NULL) == 0) + (void)pthread_join(dummy_thread, NULL); } #endif /* TARGET_OS_LINUX */ From 7d9548d26b276c7932d0f366e6f9acd516c86257 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 25 Sep 2009 11:26:49 +0200 Subject: [PATCH 3/8] Bug#32430: 'show innodb status' causes errors Invalid (old?) table or database name in logs Problem was still not completely fixed, due to qouting. This is the server side only fix (in explain_filename), the change from filename_to_tablename to use explain_filename in the InnoDB code must be done before the bug is fixed. --- mysql-test/include/have_not_innodb_plugin.inc | 4 + mysql-test/r/not_true.require | 2 + mysql-test/r/partition_innodb_builtin.result | 39 ++++++++++ mysql-test/r/partition_innodb_plugin.result | 50 +++++++++++++ mysql-test/t/disabled.def | 3 +- mysql-test/t/partition_innodb_builtin.test | 67 +++++++++++++++++ mysql-test/t/partition_innodb_plugin.test | 75 +++++++++++++++++++ sql/mysql_priv.h | 5 +- sql/sql_table.cc | 58 +++++++------- 9 files changed, 271 insertions(+), 32 deletions(-) create mode 100644 mysql-test/include/have_not_innodb_plugin.inc create mode 100644 mysql-test/r/not_true.require create mode 100644 mysql-test/r/partition_innodb_builtin.result create mode 100644 mysql-test/r/partition_innodb_plugin.result create mode 100644 mysql-test/t/partition_innodb_builtin.test create mode 100644 mysql-test/t/partition_innodb_plugin.test diff --git a/mysql-test/include/have_not_innodb_plugin.inc b/mysql-test/include/have_not_innodb_plugin.inc new file mode 100644 index 00000000000..aaefbaf661c --- /dev/null +++ b/mysql-test/include/have_not_innodb_plugin.inc @@ -0,0 +1,4 @@ +disable_query_log; +--require r/not_true.require +select (PLUGIN_LIBRARY LIKE 'ha_innodb_plugin%') as `TRUE` from information_schema.plugins where PLUGIN_NAME='InnoDB'; +enable_query_log; diff --git a/mysql-test/r/not_true.require b/mysql-test/r/not_true.require new file mode 100644 index 00000000000..0032832f3d1 --- /dev/null +++ b/mysql-test/r/not_true.require @@ -0,0 +1,2 @@ +TRUE +NULL diff --git a/mysql-test/r/partition_innodb_builtin.result b/mysql-test/r/partition_innodb_builtin.result new file mode 100644 index 00000000000..384ce0790a4 --- /dev/null +++ b/mysql-test/r/partition_innodb_builtin.result @@ -0,0 +1,39 @@ +SET NAMES utf8; +CREATE TABLE `t``\""e` (a INT, PRIMARY KEY (a)) +ENGINE=InnoDB +PARTITION BY RANGE (a) +SUBPARTITION BY HASH (a) +(PARTITION `p0``\""e` VALUES LESS THAN (100) +(SUBPARTITION `sp0``\""e`, +SUBPARTITION `sp1``\""e`), +PARTITION `p1``\""e` VALUES LESS THAN (MAXVALUE) +(SUBPARTITION `sp2``\""e`, +SUBPARTITION `sp3``\""e`)); +INSERT INTO `t``\""e` VALUES (0), (2), (6), (10), (14), (18), (22); +START TRANSACTION; +# con1 +SET NAMES utf8; +START TRANSACTION; +# default connection +UPDATE `t``\""e` SET a = 16 WHERE a = 0; +# con1 +UPDATE `t``\""e` SET a = 8 WHERE a = 22; +UPDATE `t``\""e` SET a = 12 WHERE a = 0; +# default connection +UPDATE `t``\""e` SET a = 4 WHERE a = 22; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# First table reported in 'SHOW ENGINE InnoDB STATUS' +SHOW ENGINE InnoDB STATUS; +Type Name Status +InnoDB index `PRIMARY` of table `test`.`t``\""e` /* Partition `p0``\""e`, Subpartition `sp0``\""e` */ +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +SHOW ENGINE InnoDB STATUS; +Type Name Status +InnoDB index `PRIMARY` of table `test`.`t``\""e` /* Partition `p0``\""e`, Subpartition `sp0``\""e` */ +set @@sql_mode = @old_sql_mode; +# con1 +ROLLBACK; +# default connection +DROP TABLE `t``\""e`; +SET NAMES DEFAULT; diff --git a/mysql-test/r/partition_innodb_plugin.result b/mysql-test/r/partition_innodb_plugin.result new file mode 100644 index 00000000000..dd91eee316a --- /dev/null +++ b/mysql-test/r/partition_innodb_plugin.result @@ -0,0 +1,50 @@ +SET NAMES utf8; +CREATE TABLE `t``\""e` (a INT, PRIMARY KEY (a)) +ENGINE=InnoDB +PARTITION BY RANGE (a) +SUBPARTITION BY HASH (a) +(PARTITION `p0``\""e` VALUES LESS THAN (100) +(SUBPARTITION `sp0``\""e`, +SUBPARTITION `sp1``\""e`), +PARTITION `p1``\""e` VALUES LESS THAN (MAXVALUE) +(SUBPARTITION `sp2``\""e`, +SUBPARTITION `sp3``\""e`)); +INSERT INTO `t``\""e` VALUES (0), (2), (6), (10), (14), (18), (22); +START TRANSACTION; +# con1 +SET NAMES utf8; +START TRANSACTION; +# default connection +UPDATE `t``\""e` SET a = 16 WHERE a = 0; +# con1 +UPDATE `t``\""e` SET a = 8 WHERE a = 22; +UPDATE `t``\""e` SET a = 12 WHERE a = 0; +# default connection +SELECT lock_table, COUNT(*) FROM INFORMATION_SCHEMA.INNODB_LOCKS +GROUP BY lock_table; +lock_table COUNT(*) +`test`.`t``\""e` /* Partition `p0``\""e`, Subpartition `sp0``\""e` */ 2 +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +SELECT lock_table, COUNT(*) FROM INFORMATION_SCHEMA.INNODB_LOCKS +GROUP BY lock_table; +lock_table COUNT(*) +"test"."t`\""""e" /* Partition "p0`\""""e", Subpartition "sp0`\""""e" */ 2 +set @@sql_mode = @old_sql_mode; +UPDATE `t``\""e` SET a = 4 WHERE a = 22; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# First table reported in 'SHOW ENGINE InnoDB STATUS' +SHOW ENGINE InnoDB STATUS; +Type Name Status +InnoDB index `PRIMARY` of table `test`.`t``\""e` /* Partition `p0``\""e`, Subpartition `sp0``\""e` */ +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +SHOW ENGINE InnoDB STATUS; +Type Name Status +InnoDB index `PRIMARY` of table `test`.`t``\""e` /* Partition `p0``\""e`, Subpartition `sp0``\""e` */ +set @@sql_mode = @old_sql_mode; +# con1 +ROLLBACK; +# default connection +DROP TABLE `t``\""e`; +SET NAMES DEFAULT; diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index 5436b7166f4..6613b6da415 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -13,4 +13,5 @@ kill : Bug#37780 2008-12-03 HHunger need some changes to be innodb_bug39438 : Bug#42383 2009-01-28 lsoares "This fails in embedded and on windows. Note that this test is not run on windows and on embedded in PB for main trees currently" query_cache_28249 : Bug#43861 2009-03-25 main.query_cache_28249 fails sporadically init_connect : Bug#44920 2009-07-06 pcrews MTR not processing master.opt input properly on Windows. *Must be done this way due to the nature of the bug* - +partition_innodb_builtin : Bug#32430 2009-09-25 mattiasj Waiting for push of Innodb changes +partition_innodb_plugin : Bug#32430 2009-09-25 mattiasj Waiting for push of Innodb changes diff --git a/mysql-test/t/partition_innodb_builtin.test b/mysql-test/t/partition_innodb_builtin.test new file mode 100644 index 00000000000..a9be41c7455 --- /dev/null +++ b/mysql-test/t/partition_innodb_builtin.test @@ -0,0 +1,67 @@ +--source include/have_partition.inc +--source include/have_innodb.inc +--source include/have_not_innodb_plugin.inc + +# +# Bug#32430 - show engine innodb status causes errors +# +SET NAMES utf8; +CREATE TABLE `t``\""e` (a INT, PRIMARY KEY (a)) +ENGINE=InnoDB +PARTITION BY RANGE (a) +SUBPARTITION BY HASH (a) +(PARTITION `p0``\""e` VALUES LESS THAN (100) + (SUBPARTITION `sp0``\""e`, + SUBPARTITION `sp1``\""e`), + PARTITION `p1``\""e` VALUES LESS THAN (MAXVALUE) + (SUBPARTITION `sp2``\""e`, + SUBPARTITION `sp3``\""e`)); +INSERT INTO `t``\""e` VALUES (0), (2), (6), (10), (14), (18), (22); +START TRANSACTION; +--echo # con1 +connect(con1,localhost,root,,); +SET NAMES utf8; +START TRANSACTION; +--echo # default connection +connection default; +UPDATE `t``\""e` SET a = 16 WHERE a = 0; +--echo # con1 +connection con1; +UPDATE `t``\""e` SET a = 8 WHERE a = 22; +let $id_1= `SELECT CONNECTION_ID()`; +SEND; +UPDATE `t``\""e` SET a = 12 WHERE a = 0; +--echo # default connection +connection default; +let $wait_timeout= 2; +let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE ID = $id_1 AND STATE = 'Searching rows for update'; +--source include/wait_condition.inc +#--echo # tested wait condition $wait_condition_reps times +--error ER_LOCK_DEADLOCK +UPDATE `t``\""e` SET a = 4 WHERE a = 22; +--echo # First table reported in 'SHOW ENGINE InnoDB STATUS' +# RECORD LOCKS space id 0 page no 50 n bits 80 index `PRIMARY` in \ +# Database `test`, Table `t1`, Partition `p0`, Subpartition `sp0` \ +# trx id 0 775 +# NOTE: replace_regex is very slow on match copy/past '(.*)' regex's +# on big texts, removing a lot of text before + after makes it much faster. +#/.*in (.*) trx.*/\1/ +--replace_regex /.*RECORD LOCKS space id [0-9]* page no [0-9]* n bits [0-9]* // / trx id .*// /.*index .* in // +SHOW ENGINE InnoDB STATUS; +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +# INNODB_LOCKS only exists in innodb_plugin +#SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; +--replace_regex /.*RECORD LOCKS space id [0-9]* page no [0-9]* n bits [0-9]* // / trx id .*// /.*index .* in // +SHOW ENGINE InnoDB STATUS; +set @@sql_mode = @old_sql_mode; +--echo # con1 +connection con1; +REAP; +ROLLBACK; +disconnect con1; +--echo # default connection +connection default; +DROP TABLE `t``\""e`; +SET NAMES DEFAULT; diff --git a/mysql-test/t/partition_innodb_plugin.test b/mysql-test/t/partition_innodb_plugin.test new file mode 100644 index 00000000000..fed8c96424a --- /dev/null +++ b/mysql-test/t/partition_innodb_plugin.test @@ -0,0 +1,75 @@ +--source include/have_partition.inc +--source include/have_innodb.inc +--source suite/innodb/include/have_innodb_plugin.inc + +# +# Bug#32430 - show engine innodb status causes errors +# +SET NAMES utf8; +CREATE TABLE `t``\""e` (a INT, PRIMARY KEY (a)) +ENGINE=InnoDB +PARTITION BY RANGE (a) +SUBPARTITION BY HASH (a) +(PARTITION `p0``\""e` VALUES LESS THAN (100) + (SUBPARTITION `sp0``\""e`, + SUBPARTITION `sp1``\""e`), + PARTITION `p1``\""e` VALUES LESS THAN (MAXVALUE) + (SUBPARTITION `sp2``\""e`, + SUBPARTITION `sp3``\""e`)); +INSERT INTO `t``\""e` VALUES (0), (2), (6), (10), (14), (18), (22); +START TRANSACTION; +--echo # con1 +connect(con1,localhost,root,,); +SET NAMES utf8; +START TRANSACTION; +--echo # default connection +connection default; +UPDATE `t``\""e` SET a = 16 WHERE a = 0; +--echo # con1 +connection con1; +UPDATE `t``\""e` SET a = 8 WHERE a = 22; +let $id_1= `SELECT CONNECTION_ID()`; +SEND; +UPDATE `t``\""e` SET a = 12 WHERE a = 0; +--echo # default connection +connection default; +let $wait_timeout= 2; +let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE ID = $id_1 AND STATE = 'Searching rows for update'; +--source include/wait_condition.inc +#--echo # tested wait condition $wait_condition_reps times +# INNODB_LOCKS only exists in innodb_plugin +--sorted_result +SELECT lock_table, COUNT(*) FROM INFORMATION_SCHEMA.INNODB_LOCKS +GROUP BY lock_table; +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +--sorted_result +SELECT lock_table, COUNT(*) FROM INFORMATION_SCHEMA.INNODB_LOCKS +GROUP BY lock_table; +set @@sql_mode = @old_sql_mode; +--error ER_LOCK_DEADLOCK +UPDATE `t``\""e` SET a = 4 WHERE a = 22; +--echo # First table reported in 'SHOW ENGINE InnoDB STATUS' +# RECORD LOCKS space id 0 page no 50 n bits 80 index `PRIMARY` in \ +# Database `test`, Table `t1`, Partition `p0`, Subpartition `sp0` \ +# trx id 0 775 +# NOTE: replace_regex is very slow on match copy/past '(.*)' regex's +# on big texts, removing a lot of text before + after makes it much faster. +#/.*in (.*) trx.*/\1/ +--replace_regex /.*RECORD LOCKS space id [0-9]* page no [0-9]* n bits [0-9]* // / trx id .*// /.*index .* in // +SHOW ENGINE InnoDB STATUS; +set @old_sql_mode = @@sql_mode; +set sql_mode = 'ANSI_QUOTES'; +--replace_regex /.*RECORD LOCKS space id [0-9]* page no [0-9]* n bits [0-9]* // / trx id .*// /.*index .* in // +SHOW ENGINE InnoDB STATUS; +set @@sql_mode = @old_sql_mode; +--echo # con1 +connection con1; +REAP; +ROLLBACK; +disconnect con1; +--echo # default connection +connection default; +DROP TABLE `t``\""e`; +SET NAMES DEFAULT; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 381a0313add..6fde16d3049 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2277,10 +2277,9 @@ enum enum_explain_filename_mode { EXPLAIN_ALL_VERBOSE= 0, EXPLAIN_PARTITIONS_VERBOSE, - EXPLAIN_PARTITIONS_AS_COMMENT, - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING + EXPLAIN_PARTITIONS_AS_COMMENT }; -uint explain_filename(const char *from, char *to, uint to_length, +uint explain_filename(THD* thd, const char *from, char *to, uint to_length, enum_explain_filename_mode explain_mode); uint filename_to_tablename(const char *from, char *to, uint to_length); uint tablename_to_filename(const char *from, char *to, uint to_length); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 41e76211dd8..5f718b25d60 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -70,15 +70,21 @@ static void wait_for_kill_signal(THD *thd) /** @brief Helper function for explain_filename + @param thd Thread handle + @param to_p Explained name in system_charset_info + @param end_p End of the to_p buffer + @param name Name to be converted + @param name_len Length of the name, in bytes */ -static char* add_identifier(char *to_p, const char * end_p, - const char* name, uint name_len, bool add_quotes) +static char* add_identifier(THD* thd, char *to_p, const char * end_p, + const char* name, uint name_len) { uint res; uint errors; const char *conv_name; char tmp_name[FN_REFLEN]; char conv_string[FN_REFLEN]; + int quote; DBUG_ENTER("add_identifier"); if (!name[name_len]) @@ -102,19 +108,21 @@ static char* add_identifier(char *to_p, const char * end_p, conv_name= conv_string; } - if (add_quotes && (end_p - to_p > 2)) + quote = thd ? get_quote_char_for_identifier(thd, conv_name, res - 1) : '"'; + + if (quote != EOF && (end_p - to_p > 2)) { - *(to_p++)= '`'; + *(to_p++)= (char) quote; while (*conv_name && (end_p - to_p - 1) > 0) { uint length= my_mbcharlen(system_charset_info, *conv_name); if (!length) length= 1; - if (length == 1 && *conv_name == '`') + if (length == 1 && *conv_name == (char) quote) { if ((end_p - to_p) < 3) break; - *(to_p++)= '`'; + *(to_p++)= (char) quote; *(to_p++)= *(conv_name++); } else if (((long) length) < (end_p - to_p)) @@ -125,7 +133,11 @@ static char* add_identifier(char *to_p, const char * end_p, else break; /* string already filled */ } - to_p= strnmov(to_p, "`", end_p - to_p); + if (end_p > to_p) { + *(to_p++)= (char) quote; + if (end_p > to_p) + *to_p= 0; /* terminate by NUL, but do not include it in the count */ + } } else to_p= strnmov(to_p, conv_name, end_p - to_p); @@ -145,6 +157,7 @@ static char* add_identifier(char *to_p, const char * end_p, diagnostic, error etc. when it would be useful to know what a particular file [and directory] means. Such as SHOW ENGINE STATUS, error messages etc. + @param thd Thread handle @param from Path name in my_charset_filename Null terminated in my_charset_filename, normalized to use '/' as directory separation character. @@ -161,13 +174,12 @@ static char* add_identifier(char *to_p, const char * end_p, [,[ Temporary| Renamed] Partition `p` [, Subpartition `sp`]] *| (| is really a /, and it is all in one line) - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING -> - same as above but no quotes are added. @retval Length of returned string */ -uint explain_filename(const char *from, +uint explain_filename(THD* thd, + const char *from, char *to, uint to_length, enum_explain_filename_mode explain_mode) @@ -281,14 +293,12 @@ uint explain_filename(const char *from, { to_p= strnmov(to_p, ER(ER_DATABASE_NAME), end_p - to_p); *(to_p++)= ' '; - to_p= add_identifier(to_p, end_p, db_name, db_name_len, 1); + to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len); to_p= strnmov(to_p, ", ", end_p - to_p); } else { - to_p= add_identifier(to_p, end_p, db_name, db_name_len, - (explain_mode != - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING)); + to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len); to_p= strnmov(to_p, ".", end_p - to_p); } } @@ -296,16 +306,13 @@ uint explain_filename(const char *from, { to_p= strnmov(to_p, ER(ER_TABLE_NAME), end_p - to_p); *(to_p++)= ' '; - to_p= add_identifier(to_p, end_p, table_name, table_name_len, 1); + to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len); } else - to_p= add_identifier(to_p, end_p, table_name, table_name_len, - (explain_mode != - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING)); + to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len); if (part_name) { - if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT || - explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING) + if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT) to_p= strnmov(to_p, " /* ", end_p - to_p); else if (explain_mode == EXPLAIN_PARTITIONS_VERBOSE) to_p= strnmov(to_p, " ", end_p - to_p); @@ -321,20 +328,15 @@ uint explain_filename(const char *from, } to_p= strnmov(to_p, ER(ER_PARTITION_NAME), end_p - to_p); *(to_p++)= ' '; - to_p= add_identifier(to_p, end_p, part_name, part_name_len, - (explain_mode != - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING)); + to_p= add_identifier(thd, to_p, end_p, part_name, part_name_len); if (subpart_name) { to_p= strnmov(to_p, ", ", end_p - to_p); to_p= strnmov(to_p, ER(ER_SUBPARTITION_NAME), end_p - to_p); *(to_p++)= ' '; - to_p= add_identifier(to_p, end_p, subpart_name, subpart_name_len, - (explain_mode != - EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING)); + to_p= add_identifier(thd, to_p, end_p, subpart_name, subpart_name_len); } - if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT || - explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT_NO_QUOTING) + if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT) to_p= strnmov(to_p, " */", end_p - to_p); } DBUG_PRINT("exit", ("to '%s'", to)); From 99bb6acb62262c2f62dbd864162241ad5ddb8c66 Mon Sep 17 00:00:00 2001 From: Martin Hansson Date: Mon, 28 Sep 2009 12:48:52 +0200 Subject: [PATCH 4/8] Bug#46958: Assertion in Diagnostics_area::set_ok_status, trigger, merge table The problem with break statements is that they have very local effects. Hence a break statement within the inner loop of a nested-loops join caused execution to proceed to the next table even though a serious error occurred. The problem was fixed by breaking out the inner loop into its own method. The change empowers all errors to terminate the execution. The errors that will now halt multi-DELETE execution altogether are - triggers returning errors - handler errors - server being killed --- mysql-test/r/delete.result | 45 +++++++++++ mysql-test/t/delete.test | 44 +++++++++++ sql/sql_class.h | 3 +- sql/sql_delete.cc | 156 +++++++++++++++++++++---------------- 4 files changed, 182 insertions(+), 66 deletions(-) diff --git a/mysql-test/r/delete.result b/mysql-test/r/delete.result index eb93c69d960..0124a7da35a 100644 --- a/mysql-test/r/delete.result +++ b/mysql-test/r/delete.result @@ -279,3 +279,48 @@ ERROR 42000: Incorrect number of arguments for FUNCTION test.f1; expected 0, got DROP TABLE t1; DROP FUNCTION f1; End of 5.0 tests +# +# Bug#46958: Assertion in Diagnostics_area::set_ok_status, trigger, +# merge table +# +CREATE TABLE t1 ( a INT ); +CREATE TABLE t2 ( a INT ); +CREATE TABLE t3 ( a INT ); +INSERT INTO t1 VALUES (1), (2); +INSERT INTO t2 VALUES (1), (2); +INSERT INTO t3 VALUES (1), (2); +CREATE TRIGGER tr1 BEFORE DELETE ON t2 +FOR EACH ROW INSERT INTO no_such_table VALUES (1); +DELETE t1, t2, t3 FROM t1, t2, t3; +ERROR 42S02: Table 'test.no_such_table' doesn't exist +SELECT * FROM t1; +a +SELECT * FROM t2; +a +1 +2 +SELECT * FROM t3; +a +1 +2 +DROP TABLE t1, t2, t3; +CREATE TABLE t1 ( a INT ); +CREATE TABLE t2 ( a INT ); +CREATE TABLE t3 ( a INT ); +INSERT INTO t1 VALUES (1), (2); +INSERT INTO t2 VALUES (1), (2); +INSERT INTO t3 VALUES (1), (2); +CREATE TRIGGER tr1 AFTER DELETE ON t2 +FOR EACH ROW INSERT INTO no_such_table VALUES (1); +DELETE t1, t2, t3 FROM t1, t2, t3; +ERROR 42S02: Table 'test.no_such_table' doesn't exist +SELECT * FROM t1; +a +SELECT * FROM t2; +a +2 +SELECT * FROM t3; +a +1 +2 +DROP TABLE t1, t2, t3; diff --git a/mysql-test/t/delete.test b/mysql-test/t/delete.test index 602e30687c8..d77f5eb128b 100644 --- a/mysql-test/t/delete.test +++ b/mysql-test/t/delete.test @@ -292,3 +292,47 @@ DROP TABLE t1; DROP FUNCTION f1; --echo End of 5.0 tests + +--echo # +--echo # Bug#46958: Assertion in Diagnostics_area::set_ok_status, trigger, +--echo # merge table +--echo # +CREATE TABLE t1 ( a INT ); +CREATE TABLE t2 ( a INT ); +CREATE TABLE t3 ( a INT ); + +INSERT INTO t1 VALUES (1), (2); +INSERT INTO t2 VALUES (1), (2); +INSERT INTO t3 VALUES (1), (2); + +CREATE TRIGGER tr1 BEFORE DELETE ON t2 +FOR EACH ROW INSERT INTO no_such_table VALUES (1); + +--error ER_NO_SUCH_TABLE +DELETE t1, t2, t3 FROM t1, t2, t3; + +SELECT * FROM t1; +SELECT * FROM t2; +SELECT * FROM t3; + +DROP TABLE t1, t2, t3; + +CREATE TABLE t1 ( a INT ); +CREATE TABLE t2 ( a INT ); +CREATE TABLE t3 ( a INT ); + +INSERT INTO t1 VALUES (1), (2); +INSERT INTO t2 VALUES (1), (2); +INSERT INTO t3 VALUES (1), (2); + +CREATE TRIGGER tr1 AFTER DELETE ON t2 +FOR EACH ROW INSERT INTO no_such_table VALUES (1); + +--error ER_NO_SUCH_TABLE +DELETE t1, t2, t3 FROM t1, t2, t3; + +SELECT * FROM t1; +SELECT * FROM t2; +SELECT * FROM t3; + +DROP TABLE t1, t2, t3; diff --git a/sql/sql_class.h b/sql/sql_class.h index c38eb17f191..b0128244030 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2907,7 +2907,8 @@ public: bool send_data(List &items); bool initialize_tables (JOIN *join); void send_error(uint errcode,const char *err); - int do_deletes(); + int do_deletes(); + int do_table_deletes(TABLE *table, bool ignore); bool send_eof(); virtual void abort(); }; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index d2f90fa9288..a81a5f4641f 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -860,22 +860,19 @@ void multi_delete::abort() -/* +/** Do delete from other tables. - Returns values: - 0 ok - 1 error + + @retval 0 ok + @retval 1 error + + @todo Is there any reason not use the normal nested-loops join? If not, and + there is no documentation supporting it, this method and callee should be + removed and there should be hooks within normal execution. */ int multi_delete::do_deletes() { - int local_error= 0, counter= 0, tmp_error; - bool will_batch; - /* - If the IGNORE option is used all non fatal errors will be translated - to warnings and we should not break the row-by-row iteration - */ - bool ignore= thd->lex->current_select->no_error; DBUG_ENTER("do_deletes"); DBUG_ASSERT(do_delete); @@ -886,79 +883,108 @@ int multi_delete::do_deletes() table_being_deleted= (delete_while_scanning ? delete_tables->next_local : delete_tables); - for (; table_being_deleted; + for (uint counter= 0; table_being_deleted; table_being_deleted= table_being_deleted->next_local, counter++) { - ha_rows last_deleted= deleted; TABLE *table = table_being_deleted->table; if (tempfiles[counter]->get(table)) + DBUG_RETURN(1); + + int local_error= + do_table_deletes(table, thd->lex->current_select->no_error); + + if (thd->killed && !local_error) + DBUG_RETURN(1); + + if (local_error == -1) // End of file + local_error = 0; + + if (local_error) + DBUG_RETURN(local_error); + } + DBUG_RETURN(0); +} + + +/** + Implements the inner loop of nested-loops join within multi-DELETE + execution. + + @param table The table from which to delete. + + @param ignore If used, all non fatal errors will be translated + to warnings and we should not break the row-by-row iteration. + + @return Status code + + @retval 0 All ok. + @retval 1 Triggers or handler reported error. + @retval -1 End of file from handler. +*/ +int multi_delete::do_table_deletes(TABLE *table, bool ignore) +{ + int local_error= 0; + READ_RECORD info; + ha_rows last_deleted= deleted; + DBUG_ENTER("do_deletes_for_table"); + init_read_record(&info, thd, table, NULL, 0, 1, FALSE); + /* + Ignore any rows not found in reference tables as they may already have + been deleted by foreign key handling + */ + info.ignore_not_found_rows= 1; + bool will_batch= !table->file->start_bulk_delete(); + while (!(local_error= info.read_record(&info)) && !thd->killed) + { + if (table->triggers && + table->triggers->process_triggers(thd, TRG_EVENT_DELETE, + TRG_ACTION_BEFORE, FALSE)) { - local_error=1; + local_error= 1; break; } - - READ_RECORD info; - init_read_record(&info, thd, table, NULL, 0, 1, FALSE); - /* - Ignore any rows not found in reference tables as they may already have - been deleted by foreign key handling - */ - info.ignore_not_found_rows= 1; - will_batch= !table->file->start_bulk_delete(); - while (!(local_error=info.read_record(&info)) && !thd->killed) + + local_error= table->file->ha_delete_row(table->record[0]); + if (local_error && !ignore) { + table->file->print_error(local_error, MYF(0)); + break; + } + + /* + Increase the reported number of deleted rows only if no error occurred + during ha_delete_row. + Also, don't execute the AFTER trigger if the row operation failed. + */ + if (!local_error) + { + deleted++; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_BEFORE, FALSE)) + TRG_ACTION_AFTER, FALSE)) { local_error= 1; break; } - - local_error= table->file->ha_delete_row(table->record[0]); - if (local_error && !ignore) - { - table->file->print_error(local_error,MYF(0)); - break; - } - - /* - Increase the reported number of deleted rows only if no error occurred - during ha_delete_row. - Also, don't execute the AFTER trigger if the row operation failed. - */ - if (!local_error) - { - deleted++; - if (table->triggers && - table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_AFTER, FALSE)) - { - local_error= 1; - break; - } - } } - if (will_batch && (tmp_error= table->file->end_bulk_delete())) - { - if (!local_error) - { - local_error= tmp_error; - table->file->print_error(local_error,MYF(0)); - } - } - if (last_deleted != deleted && !table->file->has_transactions()) - thd->transaction.stmt.modified_non_trans_table= TRUE; - end_read_record(&info); - if (thd->killed && !local_error) - local_error= 1; - if (local_error == -1) // End of file - local_error = 0; } + if (will_batch) + { + int tmp_error= table->file->end_bulk_delete(); + if (tmp_error && !local_error) + { + local_error= tmp_error; + table->file->print_error(local_error, MYF(0)); + } + } + if (last_deleted != deleted && !table->file->has_transactions()) + thd->transaction.stmt.modified_non_trans_table= TRUE; + + end_read_record(&info); + DBUG_RETURN(local_error); } - /* Send ok to the client From da5d0c90a4dc6da9eb43ad94f834c0ecf86a258d Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Mon, 28 Sep 2009 16:55:01 +0300 Subject: [PATCH 5/8] Fixed Sun Studio 8 compilation failures as suggested by Jorgen and reviewed by Svoj over e-mail. --- storage/myisam/mi_check.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/storage/myisam/mi_check.c b/storage/myisam/mi_check.c index 15eb28e6183..1c33ffa90f5 100644 --- a/storage/myisam/mi_check.c +++ b/storage/myisam/mi_check.c @@ -801,7 +801,7 @@ static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, { DBUG_DUMP("old",(uchar*) info->lastkey, info->lastkey_length); DBUG_DUMP("new",(uchar*) key, key_length); - DBUG_DUMP("new_in_page",(char*) old_keypos,(uint) (keypos-old_keypos)); + DBUG_DUMP("new_in_page",(uchar*) old_keypos,(uint) (keypos-old_keypos)); if (comp_flag & SEARCH_FIND && flag == 0) mi_check_print_error(param,"Found duplicated key at page %s",llstr(page,llbuff)); @@ -871,7 +871,7 @@ static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, llstr(page,llbuff),llstr(record,llbuff2), llstr(info->state->data_file_length,llbuff3))); DBUG_DUMP("key",(uchar*) key,key_length); - DBUG_DUMP("new_in_page",(char*) old_keypos,(uint) (keypos-old_keypos)); + DBUG_DUMP("new_in_page",(uchar*) old_keypos,(uint) (keypos-old_keypos)); goto err; } param->record_checksum+=(ha_checksum) record; @@ -1545,6 +1545,8 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info, if (info->s->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) param->testflag|=T_CALC_CHECKSUM; + DBUG_ASSERT(param->use_buffers < SIZE_T_MAX); + if (!param->using_global_keycache) VOID(init_key_cache(dflt_key_cache, param->key_cache_block_size, (size_t) param->use_buffers, 0, 0)); From 8f640b6239babacf2f7fc23b19b279a1c69f8b30 Mon Sep 17 00:00:00 2001 From: Jonathan Perkin Date: Mon, 28 Sep 2009 15:14:33 +0100 Subject: [PATCH 6/8] bug#30954: "configure" script in binary distributions considered harmfull Add --help option. --- support-files/binary-configure.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/support-files/binary-configure.sh b/support-files/binary-configure.sh index 884a8363e22..5e6d62f69a0 100644 --- a/support-files/binary-configure.sh +++ b/support-files/binary-configure.sh @@ -1,4 +1,28 @@ #!/bin/sh + +SCRIPT_NAME="`basename $0`" + +usage() +{ + echo "Usage: ${SCRIPT_NAME} [--help|-h]" + echo "" + echo "This script creates the MySQL system tables and starts the server." +} + +for arg do + case "$arg" in + --help|-h) + usage + exit 0 + ;; + *) + echo "${SCRIPT_NAME}: unknown option $arg" + usage + exit 2 + ;; + esac +done + if test ! -x ./scripts/mysql_install_db then echo "I didn't find the script './scripts/mysql_install_db'." From 4299943064130539cdba21ca9d045aa7424eb2ce Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Tue, 29 Sep 2009 07:23:38 +0500 Subject: [PATCH 7/8] Bug#47150 Assertion in Field_long::val_int() on MERGE + TRIGGER + multi-table UPDATE The bug is not related to MERGE table or TRIGGER. More correct description would be 'assertion on multi-table UPDATE + NATURAL JOIN + MERGEABLE VIEW'. On PREPARE stage(see test case) we call mark_common_columns() func which creates ON condition for NATURAL JOIN and sets appropriate table read_set bitmaps for fields which are used in ON condition. On EXECUTE stage mark_common_columns() is not called, we set necessary read_set bitmaps in setup_conds(). But 'B.f1' field is already processed and related item alredy fixed before setup_conds() as updated field and setup_conds can not set read_set bitmap because of that. The fix is to set read_set bitmap for appropriate table field even if Item_direct_view_ref item which represents a refernce to this field is fixed. --- mysql-test/r/join.result | 10 ++++++++++ mysql-test/t/join.test | 20 ++++++++++++++++++++ sql/item.cc | 21 +++++++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/join.result b/mysql-test/r/join.result index 736ecf1d90e..77f73532474 100644 --- a/mysql-test/r/join.result +++ b/mysql-test/r/join.result @@ -1064,3 +1064,13 @@ a b c d 128 NULL 128 NULL DROP TABLE IF EXISTS t1,t2; End of 5.0 tests. +CREATE TABLE t1 (f1 int); +CREATE TABLE t2 (f1 int); +INSERT INTO t2 VALUES (1); +CREATE VIEW v1 AS SELECT * FROM t2; +PREPARE stmt FROM 'UPDATE t2 AS A NATURAL JOIN v1 B SET B.f1 = 1'; +EXECUTE stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +DROP VIEW v1; +DROP TABLE t1, t2; diff --git a/mysql-test/t/join.test b/mysql-test/t/join.test index b5e30e63f54..1cd05c8cb65 100644 --- a/mysql-test/t/join.test +++ b/mysql-test/t/join.test @@ -729,4 +729,24 @@ SELECT * FROM t1 JOIN t2 ON b=c ORDER BY a; SELECT * FROM t1 JOIN t2 ON a=c ORDER BY a; DROP TABLE IF EXISTS t1,t2; + --echo End of 5.0 tests. + + +# +# Bug#47150 Assertion in Field_long::val_int() on MERGE + TRIGGER + multi-table UPDATE +# +CREATE TABLE t1 (f1 int); + +CREATE TABLE t2 (f1 int); +INSERT INTO t2 VALUES (1); +CREATE VIEW v1 AS SELECT * FROM t2; + +PREPARE stmt FROM 'UPDATE t2 AS A NATURAL JOIN v1 B SET B.f1 = 1'; +EXECUTE stmt; +EXECUTE stmt; + +DEALLOCATE PREPARE stmt; + +DROP VIEW v1; +DROP TABLE t1, t2; diff --git a/sql/item.cc b/sql/item.cc index 26df3a45971..86e4551e55b 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6331,9 +6331,26 @@ bool Item_direct_view_ref::fix_fields(THD *thd, Item **reference) /* view fild reference must be defined */ DBUG_ASSERT(*ref); /* (*ref)->check_cols() will be made in Item_direct_ref::fix_fields */ - if (!(*ref)->fixed && - ((*ref)->fix_fields(thd, ref))) + if ((*ref)->fixed) + { + Item *ref_item= (*ref)->real_item(); + if (ref_item->type() == Item::FIELD_ITEM) + { + /* + In some cases we need to update table read set(see bug#47150). + If ref item is FIELD_ITEM and fixed then field and table + have proper values. So we can use them for update. + */ + Field *fld= ((Item_field*) ref_item)->field; + DBUG_ASSERT(fld && fld->table); + if (thd->mark_used_columns == MARK_COLUMNS_READ) + bitmap_set_bit(fld->table->read_set, fld->field_index); + } + } + else if (!(*ref)->fixed && + ((*ref)->fix_fields(thd, ref))) return TRUE; + return Item_direct_ref::fix_fields(thd, reference); } From fc3740368aed05f95da167deda7544a91a904d3f Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 29 Sep 2009 07:58:42 -0300 Subject: [PATCH 8/8] Bug#45567: Fast ALTER TABLE broken for enum and set The problem was that appending values to the end of an existing ENUM or SET column was being treated as table data modification, preventing a immediately (fast) table alteration that occurs when only table metadata is being modified. The cause was twofold: adding a enumeration or set members to the end of the list of valid member values was not being considered a "compatible" table alteration, and for SET columns, the check was being done upon the max display length and not the underlying (pack) length of the field. The solution is to augment the function that checks wether two ENUM or SET fields are compatible -- by comparing the pack lengths and performing a limited comparison of the member values. --- mysql-test/r/alter_table.result | 62 +++++++++++++++++++++++++++ mysql-test/t/alter_table.test | 46 ++++++++++++++++++++ sql/field.cc | 75 ++++++++++++++++++++++++++------- sql/field.h | 8 +++- 4 files changed, 174 insertions(+), 17 deletions(-) diff --git a/mysql-test/r/alter_table.result b/mysql-test/r/alter_table.result index 5a115e9ea99..db7173d0b47 100644 --- a/mysql-test/r/alter_table.result +++ b/mysql-test/r/alter_table.result @@ -1268,4 +1268,66 @@ a b 4 b 5 a DROP TABLE t1; +# +# Bug#45567: Fast ALTER TABLE broken for enum and set +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a ENUM('a1','a2')); +INSERT INTO t1 VALUES ('a1'),('a2'); +# No copy: No modification +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +# No copy: Add new enumeration to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a3'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +# Copy: Modify and add new to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','xx','a5'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# Copy: Remove from the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','xx'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# Copy: Add new enumeration +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a0','xx'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# No copy: Add new enumerations to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a0','xx','a5','a6'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +DROP TABLE t1; +CREATE TABLE t1 (a SET('a1','a2')); +INSERT INTO t1 VALUES ('a1'),('a2'); +# No copy: No modification +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +# No copy: Add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a3'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +# Copy: Modify and add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','xx','a5'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# Copy: Remove from the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','xx'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# Copy: Add new member +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +# No copy: Add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx','a5','a6'); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +# Copy: Numerical incrase (pack lenght) +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx','a5','a6','a7','a8','a9','a10'); +affected rows: 2 +info: Records: 2 Duplicates: 0 Warnings: 0 +DROP TABLE t1; End of 5.1 tests diff --git a/mysql-test/t/alter_table.test b/mysql-test/t/alter_table.test index ae48d5a8736..17549745203 100644 --- a/mysql-test/t/alter_table.test +++ b/mysql-test/t/alter_table.test @@ -1000,4 +1000,50 @@ ALTER TABLE t1 MODIFY b ENUM('a', 'z', 'b', 'c') NOT NULL; SELECT * FROM t1; DROP TABLE t1; +--echo # +--echo # Bug#45567: Fast ALTER TABLE broken for enum and set +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a ENUM('a1','a2')); +INSERT INTO t1 VALUES ('a1'),('a2'); +--enable_info +--echo # No copy: No modification +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2'); +--echo # No copy: Add new enumeration to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a3'); +--echo # Copy: Modify and add new to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','xx','a5'); +--echo # Copy: Remove from the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','xx'); +--echo # Copy: Add new enumeration +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a0','xx'); +--echo # No copy: Add new enumerations to the end +ALTER TABLE t1 MODIFY COLUMN a ENUM('a1','a2','a0','xx','a5','a6'); +--disable_info +DROP TABLE t1; + +CREATE TABLE t1 (a SET('a1','a2')); +INSERT INTO t1 VALUES ('a1'),('a2'); +--enable_info +--echo # No copy: No modification +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2'); +--echo # No copy: Add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a3'); +--echo # Copy: Modify and add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','xx','a5'); +--echo # Copy: Remove from the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','xx'); +--echo # Copy: Add new member +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx'); +--echo # No copy: Add new to the end +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx','a5','a6'); +--echo # Copy: Numerical incrase (pack lenght) +ALTER TABLE t1 MODIFY COLUMN a SET('a1','a2','a0','xx','a5','a6','a7','a8','a9','a10'); +--disable_info +DROP TABLE t1; + --echo End of 5.1 tests diff --git a/sql/field.cc b/sql/field.cc index 9f46552d5bd..0df9b0fc2e4 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -8832,6 +8832,24 @@ bool Field::eq_def(Field *field) } +/** + Compare the first t1::count type names. + + @return TRUE if the type names of t1 match those of t2. FALSE otherwise. +*/ + +static bool compare_type_names(CHARSET_INFO *charset, TYPELIB *t1, TYPELIB *t2) +{ + for (uint i= 0; i < t1->count; i++) + if (my_strnncoll(charset, + (const uchar*) t1->type_names[i], + t1->type_lengths[i], + (const uchar*) t2->type_names[i], + t2->type_lengths[i])) + return FALSE; + return TRUE; +} + /** @return returns 1 if the fields are equally defined @@ -8839,32 +8857,57 @@ bool Field::eq_def(Field *field) bool Field_enum::eq_def(Field *field) { + TYPELIB *values; + if (!Field::eq_def(field)) - return 0; - return compare_enum_values(((Field_enum*) field)->typelib); -} + return FALSE; + values= ((Field_enum*) field)->typelib; -bool Field_enum::compare_enum_values(TYPELIB *values) -{ + /* Definition must be strictly equal. */ if (typelib->count != values->count) return FALSE; - for (uint i= 0; i < typelib->count; i++) - if (my_strnncoll(field_charset, - (const uchar*) typelib->type_names[i], - typelib->type_lengths[i], - (const uchar*) values->type_names[i], - values->type_lengths[i])) - return FALSE; - return TRUE; + + return compare_type_names(field_charset, typelib, values); } +/** + Check whether two fields can be considered 'equal' for table + alteration purposes. Fields are equal if they retain the same + pack length and if new members are added to the end of the list. + + @return IS_EQUAL_YES if fields are compatible. + IS_EQUAL_NO otherwise. +*/ + uint Field_enum::is_equal(Create_field *new_field) { - if (!Field_str::is_equal(new_field)) - return 0; - return compare_enum_values(new_field->interval); + TYPELIB *values= new_field->interval; + + /* + The fields are compatible if they have the same flags, + type, charset and have the same underlying length. + */ + if (compare_str_field_flags(new_field, flags) || + new_field->sql_type != real_type() || + new_field->charset != field_charset || + new_field->pack_length != pack_length()) + return IS_EQUAL_NO; + + /* + Changing the definition of an ENUM or SET column by adding a new + enumeration or set members to the end of the list of valid member + values only alters table metadata and not table data. + */ + if (typelib->count > values->count) + return IS_EQUAL_NO; + + /* Check whether there are modification before the end. */ + if (! compare_type_names(field_charset, typelib, new_field->interval)) + return IS_EQUAL_NO; + + return IS_EQUAL_YES; } diff --git a/sql/field.h b/sql/field.h index a9299256f88..7a9b69eff40 100644 --- a/sql/field.h +++ b/sql/field.h @@ -472,6 +472,13 @@ public: /* maximum possible display length */ virtual uint32 max_display_length()= 0; + /** + Whether a field being created is compatible with a existing one. + + Used by the ALTER TABLE code to evaluate whether the new definition + of a table is compatible with the old definition so that it can + determine if data needs to be copied over (table data change). + */ virtual uint is_equal(Create_field *new_field); /* convert decimal to longlong with overflow check */ longlong convert_decimal2longlong(const my_decimal *val, bool unsigned_flag, @@ -1862,7 +1869,6 @@ public: CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; } private: int do_save_field_metadata(uchar *first_byte); - bool compare_enum_values(TYPELIB *values); uint is_equal(Create_field *new_field); };