From 7d183320b0e9b271efe6017986b904b694d32310 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 3 Nov 2005 18:24:12 +0100 Subject: [PATCH 01/10] Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Version for 4.0. It fixes two problems: 1. The cause of the bug was that we did not check the table version for the HANDLER ... READ commands. We did not notice when a table was replaced by a new one. This can happen during ALTER TABLE, REPAIR TABLE, and OPTIMIZE TABLE (there might be more cases). I call the fix for this problem "the primary bug fix". 2. mysql_ha_flush() was not always called with a locked LOCK_open. Though the function comment clearly said it must. I changed the code so that the locking is done when required. I call the fix for this problem "the secondary fix". mysql-test/r/handler.result: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The test result. mysql-test/t/handler.test: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The test case. sql/mysql_priv.h: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed a definition for the secondary fix. sql/sql_base.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed function calls for the secondary fix. sql/sql_class.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed a function call for the secondary fix. sql/sql_handler.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The first two diffs make the primary bug fix. The rest is for the secondary fix. sql/sql_table.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed function calls for the secondary fix. --- mysql-test/r/handler.result | 18 ++++++++++++++ mysql-test/t/handler.test | 29 ++++++++++++++++++++++ sql/mysql_priv.h | 3 ++- sql/sql_base.cc | 7 +++--- sql/sql_class.cc | 2 +- sql/sql_handler.cc | 49 ++++++++++++++++++++++++++++++++++--- sql/sql_table.cc | 6 ++--- 7 files changed, 102 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/handler.result b/mysql-test/r/handler.result index 5af153930d5..3a61236ea14 100644 --- a/mysql-test/r/handler.result +++ b/mysql-test/r/handler.result @@ -447,3 +447,21 @@ drop table t2; drop table t3; drop table t4; drop table t5; +create table t1 (c1 int); +insert into t1 values (1); +handler t1 open; +handler t1 read first; +c1 +1 +send the below to another connection, do not wait for the result + optimize table t1; +proceed with the normal connection +handler t1 read next; +c1 +1 +handler t1 close; +read the result from the other connection +Table Op Msg_type Msg_text +test.t1 optimize status OK +proceed with the normal connection +drop table t1; diff --git a/mysql-test/t/handler.test b/mysql-test/t/handler.test index 53fe8c0a059..d91587b8070 100644 --- a/mysql-test/t/handler.test +++ b/mysql-test/t/handler.test @@ -339,3 +339,32 @@ drop table t2; drop table t3; drop table t4; drop table t5; + +# +# Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash +# +create table t1 (c1 int); +insert into t1 values (1); +# client 1 +handler t1 open; +handler t1 read first; +# client 2 +connect (con2,localhost,root,,); +connection con2; +--exec echo send the below to another connection, do not wait for the result +send optimize table t1; +--sleep 1 +# client 1 +--exec echo proceed with the normal connection +connection default; +handler t1 read next; +handler t1 close; +# client 2 +--exec echo read the result from the other connection +connection con2; +reap; +# client 1 +--exec echo proceed with the normal connection +connection default; +drop table t1; + diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 9b9edd905ad..526b79cd73a 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -547,7 +547,8 @@ int mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen= 0); int mysql_ha_close(THD *thd, TABLE_LIST *tables); int mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List *,enum ha_rkey_function,Item *,ha_rows,ha_rows); -int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags); +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags, + bool is_locked); /* mysql_ha_flush mode_flags bits */ #define MYSQL_HA_CLOSE_FINAL 0x00 #define MYSQL_HA_REOPEN_ON_USAGE 0x01 diff --git a/sql/sql_base.cc b/sql/sql_base.cc index bc2ad9fff50..4f52904a61e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -390,7 +390,8 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, thd->proc_info="Flushing tables"; close_old_data_files(thd,thd->open_tables,1,1); - mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL); + mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL, + TRUE); bool found=1; /* Wait until all threads has closed all the tables we had locked */ DBUG_PRINT("info", ("Waiting for others threads to close their open tables")); @@ -863,7 +864,7 @@ TABLE *open_table(THD *thd,const char *db,const char *table_name, } /* close handler tables which are marked for flush */ - mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE); for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ; table && table->in_use ; @@ -1262,7 +1263,7 @@ bool wait_for_tables(THD *thd) { thd->some_tables_deleted=0; close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); - mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE); if (!table_is_used(thd->open_tables,1)) break; (void) pthread_cond_wait(&COND_refresh,&LOCK_open); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 9dd75b32d5d..66d23ada163 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -217,7 +217,7 @@ void THD::cleanup(void) close_thread_tables(this); } mysql_ha_flush(this, (TABLE_LIST*) 0, - MYSQL_HA_CLOSE_FINAL | MYSQL_HA_FLUSH_ALL); + MYSQL_HA_CLOSE_FINAL | MYSQL_HA_FLUSH_ALL, FALSE); hash_free(&handler_tables_hash); close_temporary_tables(this); hash_free(&user_vars); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index fcdb2aeb668..28e94d1a477 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -357,6 +357,7 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, ha_rows select_limit,ha_rows offset_limit) { TABLE_LIST *hash_tables; + TABLE **table_ptr; TABLE *table; int err; int keyno=-1; @@ -379,6 +380,27 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' tab %p", hash_tables->db, hash_tables->real_name, hash_tables->alias, table)); + /* Table might have been flushed. */ + if (table && (table->version != refresh_version)) + { + /* + We must follow the thd->handler_tables chain, as we need the + address of the 'next' pointer referencing this table + for close_thread_table(). + */ + for (table_ptr= &(thd->handler_tables); + *table_ptr && (*table_ptr != table); + table_ptr= &(*table_ptr)->next) + {} + VOID(pthread_mutex_lock(&LOCK_open)); + if (close_thread_table(thd, table_ptr)) + { + /* Tell threads waiting for refresh that something has happened */ + VOID(pthread_cond_broadcast(&COND_refresh)); + } + VOID(pthread_mutex_unlock(&LOCK_open)); + table= hash_tables->table= NULL; + } if (!table) { /* @@ -593,6 +615,7 @@ err0: MYSQL_HA_REOPEN_ON_USAGE mark for reopen. MYSQL_HA_FLUSH_ALL flush all tables, not only those marked for flush. + is_locked If LOCK_open is locked. DESCRIPTION The list of HANDLER tables may be NULL, in which case all HANDLER @@ -600,7 +623,6 @@ err0: If 'tables' is NULL and MYSQL_HA_FLUSH_ALL is not set, all HANDLER tables marked for flush are closed. Broadcasts a COND_refresh condition, for every table closed. - The caller must lock LOCK_open. NOTE Since mysql_ha_flush() is called when the base table has to be closed, @@ -610,10 +632,12 @@ err0: 0 ok */ -int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags, + bool is_locked) { TABLE_LIST *tmp_tables; TABLE **table_ptr; + bool did_lock= FALSE; DBUG_ENTER("mysql_ha_flush"); DBUG_PRINT("enter", ("tables: %p mode_flags: 0x%02x", tables, mode_flags)); @@ -637,6 +661,12 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) (*table_ptr)->table_cache_key, (*table_ptr)->real_name, (*table_ptr)->table_name)); + /* The first time it is required, lock for close_thread_table(). */ + if (! did_lock && ! is_locked) + { + VOID(pthread_mutex_lock(&LOCK_open)); + did_lock= TRUE; + } mysql_ha_flush_table(thd, table_ptr, mode_flags); continue; } @@ -655,6 +685,12 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) if ((mode_flags & MYSQL_HA_FLUSH_ALL) || ((*table_ptr)->version != refresh_version)) { + /* The first time it is required, lock for close_thread_table(). */ + if (! did_lock && ! is_locked) + { + VOID(pthread_mutex_lock(&LOCK_open)); + did_lock= TRUE; + } mysql_ha_flush_table(thd, table_ptr, mode_flags); continue; } @@ -662,6 +698,10 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) } } + /* Release the lock if it was taken by this function. */ + if (did_lock) + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(0); } @@ -693,8 +733,8 @@ static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags) table->table_name, mode_flags)); if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, - (byte*) (*table_ptr)->table_name, - strlen((*table_ptr)->table_name) + 1))) + (byte*) table->table_name, + strlen(table->table_name) + 1))) { if (! (mode_flags & MYSQL_HA_REOPEN_ON_USAGE)) { @@ -708,6 +748,7 @@ static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags) } } + safe_mutex_assert_owner(&LOCK_open); if (close_thread_table(thd, table_ptr)) { /* Tell threads waiting for refresh that something has happened */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 4c269e6830f..987d12ccb40 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -179,7 +179,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, { char *db=table->db; uint flags; - mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, TRUE); if (!close_temporary_table(thd, db, table->real_name)) { tmp_table_deleted=1; @@ -1239,7 +1239,7 @@ static int mysql_admin_table(THD* thd, TABLE_LIST* tables, if (send_fields(thd, field_list, 1)) DBUG_RETURN(-1); - mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL, FALSE); for (table = tables; table; table = table->next) { char table_name[NAME_LEN*2+2]; @@ -1500,7 +1500,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } used_fields=create_info->used_fields; - mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL, FALSE); if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) DBUG_RETURN(-1); From 5412ee4f299a4a2d71db1b7e5f3e62132032fb00 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 7 Nov 2005 12:16:49 +0100 Subject: [PATCH 02/10] Bug#14616 - Freshly imported table returns error 124 when using LIMIT Initialized usable_keys from table->keys_in_use instead of ~0 in test_if_skip_sort_order(). It was possible that a disabled index was used for sorting. mysql-test/r/myisam.result: Bug#14616 - Freshly imported table returns error 124 when using LIMIT The test result. mysql-test/t/myisam.test: Bug#14616 - Freshly imported table returns error 124 when using LIMIT The test case. --- mysql-test/r/myisam.result | 10 ++++++++++ mysql-test/t/myisam.test | 12 ++++++++++++ sql/sql_select.cc | 8 ++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result index c55bacdd371..e6df3499eb5 100644 --- a/mysql-test/r/myisam.result +++ b/mysql-test/r/myisam.result @@ -462,3 +462,13 @@ select count(*) from t1 where a is null; count(*) 2 drop table t1; +create table t1 ( +c1 varchar(32), +key (c1) +) engine=myisam; +alter table t1 disable keys; +insert into t1 values ('a'), ('b'); +select c1 from t1 order by c1 limit 1; +c1 +a +drop table t1; diff --git a/mysql-test/t/myisam.test b/mysql-test/t/myisam.test index 57b64e30bac..a502002d30e 100644 --- a/mysql-test/t/myisam.test +++ b/mysql-test/t/myisam.test @@ -446,3 +446,15 @@ explain select count(*) from t1 where a is null; select count(*) from t1 where a is null; drop table t1; +# +# Bug#14616 - Freshly imported table returns error 124 when using LIMIT +# +create table t1 ( + c1 varchar(32), + key (c1) +) engine=myisam; +alter table t1 disable keys; +insert into t1 values ('a'), ('b'); +select c1 from t1 order by c1 limit 1; +drop table t1; + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 46f0139a608..6dd68a60f88 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -6003,8 +6003,12 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, key_map usable_keys; DBUG_ENTER("test_if_skip_sort_order"); - /* Check which keys can be used to resolve ORDER BY */ - usable_keys= ~(key_map) 0; + /* + Check which keys can be used to resolve ORDER BY. + We must not try to use disabled keys. + */ + usable_keys= table->keys_in_use; + for (ORDER *tmp_order=order; tmp_order ; tmp_order=tmp_order->next) { if ((*tmp_order->item)->type() != Item::FIELD_ITEM) From 675226728c40b2b9c477b7628a867292b243351d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 15 Nov 2005 18:01:30 +0100 Subject: [PATCH 03/10] Bug#14616 - Freshly imported table returns error 124 when using LIMIT After merge fix. --- mysql-test/r/myisam.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result index 352cc2fabb9..d2d0417f6e6 100644 --- a/mysql-test/r/myisam.result +++ b/mysql-test/r/myisam.result @@ -498,7 +498,6 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 system NULL NULL NULL NULL 1 Using temporary 1 SIMPLE t2 index NULL PRIMARY 4 NULL 2 Using index; Distinct drop table t1,t2; -drop table t1; create table t1 ( c1 varchar(32), key (c1) @@ -508,6 +507,7 @@ insert into t1 values ('a'), ('b'); select c1 from t1 order by c1 limit 1; c1 a +drop table t1; CREATE TABLE t1 (`a` int(11) NOT NULL default '0', `b` int(11) NOT NULL default '0', UNIQUE KEY `a` USING RTREE (`a`,`b`)) ENGINE=MyISAM; Got one of the listed errors create table t1 (a int, b varchar(200), c text not null) checksum=1; From 013b3d8ab33b052d504a2e3fa63a85c919c649cc Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 15 Nov 2005 21:57:02 +0100 Subject: [PATCH 04/10] Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Version for 5.0. It fixes three problems: 1. The cause of the bug was that we did not check the table version for the HANDLER ... READ commands. We did not notice when a table was replaced by a new one. This can happen during ALTER TABLE, REPAIR TABLE, and OPTIMIZE TABLE (there might be more cases). I call the fix for this problem "the primary bug fix". 2. mysql_ha_flush() was not always called with a locked LOCK_open. Though the function comment clearly said it must. I changed the code so that the locking is done when required. I call the fix for this problem "the secondary fix". 3. In 5.0 (not in 4.1 or 4.0) DROP TABLE had a possible deadlock flaw in concur with FLUSH TABLES WITH READ LOCK. I call the fix for this problem "the 5.0 addendum fix". include/my_pthread.h: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Added a new macro for the 5.0 addendum fix. mysql-test/r/handler.result: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The test result. mysql-test/t/handler.test: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The test case. sql/lock.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed a comment which did confuse me and which is not fully correct anymore after the 5.0 addendum fix. Added an assertion which would fire without the 5.0 addendum fix. sql/mysql_priv.h: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed a definition for the secondary fix. sql/sql_base.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed function calls for the secondary fix. sql/sql_class.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash Changed a function call for the secondary fix. sql/sql_handler.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The first two diffs make the primary bug fix. The rest is for the secondary fix. sql/sql_table.cc: Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash The first diff (four changed places) make the 5.0 addendum fix. The other three are changed function calls for the secondary fix. --- include/my_pthread.h | 8 +++- mysql-test/r/handler.result | 37 ++++++++++++++++++ mysql-test/t/handler.test | 75 +++++++++++++++++++++++++++++++++++++ sql/lock.cc | 18 +++++++-- sql/mysql_priv.h | 3 +- sql/sql_base.cc | 7 ++-- sql/sql_class.cc | 2 +- sql/sql_handler.cc | 50 +++++++++++++++++++++++-- sql/sql_table.cc | 23 +++++++----- 9 files changed, 200 insertions(+), 23 deletions(-) diff --git a/include/my_pthread.h b/include/my_pthread.h index ee2c801ff6e..6f60a6df2c1 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -536,9 +536,15 @@ void safe_mutex_end(FILE *file); #define pthread_cond_timedwait(A,B,C) safe_cond_timedwait((A),(B),(C),__FILE__,__LINE__) #define pthread_mutex_trylock(A) pthread_mutex_lock(A) #define pthread_mutex_t safe_mutex_t -#define safe_mutex_assert_owner(mp) DBUG_ASSERT((mp)->count > 0 && pthread_equal(pthread_self(),(mp)->thread)) +#define safe_mutex_assert_owner(mp) \ + DBUG_ASSERT((mp)->count > 0 && \ + pthread_equal(pthread_self(), (mp)->thread)) +#define safe_mutex_assert_not_owner(mp) \ + DBUG_ASSERT(! (mp)->count || \ + ! pthread_equal(pthread_self(), (mp)->thread)) #else #define safe_mutex_assert_owner(mp) +#define safe_mutex_assert_not_owner(mp) #endif /* SAFE_MUTEX */ /* READ-WRITE thread locking */ diff --git a/mysql-test/r/handler.result b/mysql-test/r/handler.result index 072d4582cbc..133683fb273 100644 --- a/mysql-test/r/handler.result +++ b/mysql-test/r/handler.result @@ -445,3 +445,40 @@ drop table t2; drop table t3; drop table t4; drop table t5; +create table t1 (c1 int); +insert into t1 values (1); +handler t1 open; +handler t1 read first; +c1 +1 +send the below to another connection, do not wait for the result + optimize table t1; +proceed with the normal connection +handler t1 read next; +c1 +1 +handler t1 close; +read the result from the other connection +Table Op Msg_type Msg_text +test.t1 optimize status OK +proceed with the normal connection +drop table t1; +create table t1 (c1 int); +insert into t1 values (14397); +flush tables with read lock; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +send the below to another connection, do not wait for the result + drop table t1; +proceed with the normal connection +select * from t1; +c1 +14397 +unlock tables; +read the result from the other connection +proceed with the normal connection +select * from t1; +ERROR 42S02: Table 'test.t1' doesn't exist +drop table if exists t1; +Warnings: +Note 1051 Unknown table 't1' diff --git a/mysql-test/t/handler.test b/mysql-test/t/handler.test index 1bb9b1d3504..f3e14c3cd2b 100644 --- a/mysql-test/t/handler.test +++ b/mysql-test/t/handler.test @@ -347,4 +347,79 @@ drop table t3; drop table t4; drop table t5; +# +# Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash +# +create table t1 (c1 int); +insert into t1 values (1); +# client 1 +handler t1 open; +handler t1 read first; +# client 2 +connect (con2,localhost,root,,); +connection con2; +--exec echo send the below to another connection, do not wait for the result +send optimize table t1; +--sleep 1 +# client 1 +--exec echo proceed with the normal connection +connection default; +handler t1 read next; +handler t1 close; +# client 2 +--exec echo read the result from the other connection +connection con2; +reap; +# client 1 +--exec echo proceed with the normal connection +connection default; +drop table t1; + # End of 4.1 tests + +# +# Addendum to Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash +# Show that DROP TABLE can no longer deadlock against +# FLUSH TABLES WITH READ LOCK. This is a 5.0 issue. +# +create table t1 (c1 int); +insert into t1 values (14397); +flush tables with read lock; +# The thread with the global read lock cannot drop the table itself: +--error 1223 +drop table t1; +# +# client 2 +# We need a second connection to try the drop. +# The drop waits for the global read lock to go away. +# Without the addendum fix it locked LOCK_open before entering the wait loop. +connection con2; +--exec echo send the below to another connection, do not wait for the result +send drop table t1; +--sleep 1 +# +# client 1 +# Now we need something that wants LOCK_open. A simple table access which +# opens the table does the trick. +--exec echo proceed with the normal connection +connection default; +# This would hang on LOCK_open without the 5.0 addendum fix. +select * from t1; +# Release the read lock. This should make the DROP go through. +unlock tables; +# +# client 2 +# Read the result of the drop command. +connection con2; +--exec echo read the result from the other connection +reap; +# +# client 1 +# Now back to normal operation. The table should not exist any more. +--exec echo proceed with the normal connection +connection default; +--error 1146 +select * from t1; +# Just to be sure and not confuse the next test case writer. +drop table if exists t1; + diff --git a/sql/lock.cc b/sql/lock.cc index f4c4a781e45..fe8dcb3aa5e 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -815,10 +815,13 @@ static void print_lock_error(int error, const char *table) access to them is protected with a mutex LOCK_global_read_lock - (XXX: one should never take LOCK_open if LOCK_global_read_lock is taken, - otherwise a deadlock may occur - see mysql_rm_table. Other mutexes could - be a problem too - grep the code for global_read_lock if you want to use - any other mutex here) + (XXX: one should never take LOCK_open if LOCK_global_read_lock is + taken, otherwise a deadlock may occur. Other mutexes could be a + problem too - grep the code for global_read_lock if you want to use + any other mutex here) Also one must not hold LOCK_open when calling + wait_if_global_read_lock(). When the thread with the global read lock + tries to close its tables, it needs to take LOCK_open in + close_thread_table(). How blocking of threads by global read lock is achieved: that's advisory. Any piece of code which should be blocked by global read lock must @@ -937,6 +940,13 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, DBUG_ENTER("wait_if_global_read_lock"); LINT_INIT(old_message); + /* + Assert that we do not own LOCK_open. If we would own it, other + threads could not close their tables. This would make a pretty + deadlock. + */ + safe_mutex_assert_not_owner(&LOCK_open); + (void) pthread_mutex_lock(&LOCK_global_read_lock); if ((need_exit_cond= must_wait)) { diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 1719253a458..2a65b99585a 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -890,7 +890,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen); bool mysql_ha_close(THD *thd, TABLE_LIST *tables); bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List *,enum ha_rkey_function,Item *,ha_rows,ha_rows); -int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags); +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags, + bool is_locked); /* mysql_ha_flush mode_flags bits */ #define MYSQL_HA_CLOSE_FINAL 0x00 #define MYSQL_HA_REOPEN_ON_USAGE 0x01 diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 1e7fce9001f..771a70ffebb 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -311,7 +311,8 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, thd->proc_info="Flushing tables"; close_old_data_files(thd,thd->open_tables,1,1); - mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL); + mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL, + TRUE); bool found=1; /* Wait until all threads has closed all the tables we had locked */ DBUG_PRINT("info", @@ -1238,7 +1239,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, /* close handler tables which are marked for flush */ if (thd->handler_tables) - mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE); for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ; table && table->in_use ; @@ -1642,7 +1643,7 @@ bool wait_for_tables(THD *thd) { thd->some_tables_deleted=0; close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); - mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE); if (!table_is_used(thd->open_tables,1)) break; (void) pthread_cond_wait(&COND_refresh,&LOCK_open); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index fc9df020b6c..7ffe60ec2b2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -376,7 +376,7 @@ void THD::cleanup(void) close_thread_tables(this); } mysql_ha_flush(this, (TABLE_LIST*) 0, - MYSQL_HA_CLOSE_FINAL | MYSQL_HA_FLUSH_ALL); + MYSQL_HA_CLOSE_FINAL | MYSQL_HA_FLUSH_ALL, FALSE); hash_free(&handler_tables_hash); delete_dynamic(&user_var_events); hash_free(&user_vars); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index cc45a7001cd..07f4de26707 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -336,6 +336,7 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, ha_rows select_limit_cnt, ha_rows offset_limit_cnt) { TABLE_LIST *hash_tables; + TABLE **table_ptr; TABLE *table; MYSQL_LOCK *lock; List list; @@ -368,6 +369,28 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' tab %p", hash_tables->db, hash_tables->table_name, hash_tables->alias, table)); + /* Table might have been flushed. */ + if (table && (table->s->version != refresh_version)) + { + /* + We must follow the thd->handler_tables chain, as we need the + address of the 'next' pointer referencing this table + for close_thread_table(). + */ + for (table_ptr= &(thd->handler_tables); + *table_ptr && (*table_ptr != table); + table_ptr= &(*table_ptr)->next) + {} + (*table_ptr)->file->ha_index_or_rnd_end(); + VOID(pthread_mutex_lock(&LOCK_open)); + if (close_thread_table(thd, table_ptr)) + { + /* Tell threads waiting for refresh that something has happened */ + VOID(pthread_cond_broadcast(&COND_refresh)); + } + VOID(pthread_mutex_unlock(&LOCK_open)); + table= hash_tables->table= NULL; + } if (!table) { /* @@ -594,6 +617,7 @@ err0: MYSQL_HA_REOPEN_ON_USAGE mark for reopen. MYSQL_HA_FLUSH_ALL flush all tables, not only those marked for flush. + is_locked If LOCK_open is locked. DESCRIPTION The list of HANDLER tables may be NULL, in which case all HANDLER @@ -601,7 +625,6 @@ err0: If 'tables' is NULL and MYSQL_HA_FLUSH_ALL is not set, all HANDLER tables marked for flush are closed. Broadcasts a COND_refresh condition, for every table closed. - The caller must lock LOCK_open. NOTE Since mysql_ha_flush() is called when the base table has to be closed, @@ -611,10 +634,12 @@ err0: 0 ok */ -int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags, + bool is_locked) { TABLE_LIST *tmp_tables; TABLE **table_ptr; + bool did_lock= FALSE; DBUG_ENTER("mysql_ha_flush"); DBUG_PRINT("enter", ("tables: %p mode_flags: 0x%02x", tables, mode_flags)); @@ -640,6 +665,12 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) (*table_ptr)->s->db, (*table_ptr)->s->table_name, (*table_ptr)->alias)); + /* The first time it is required, lock for close_thread_table(). */ + if (! did_lock && ! is_locked) + { + VOID(pthread_mutex_lock(&LOCK_open)); + did_lock= TRUE; + } mysql_ha_flush_table(thd, table_ptr, mode_flags); continue; } @@ -658,6 +689,12 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) if ((mode_flags & MYSQL_HA_FLUSH_ALL) || ((*table_ptr)->s->version != refresh_version)) { + /* The first time it is required, lock for close_thread_table(). */ + if (! did_lock && ! is_locked) + { + VOID(pthread_mutex_lock(&LOCK_open)); + did_lock= TRUE; + } mysql_ha_flush_table(thd, table_ptr, mode_flags); continue; } @@ -665,6 +702,10 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) } } + /* Release the lock if it was taken by this function. */ + if (did_lock) + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(0); } @@ -696,8 +737,8 @@ static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags) table->alias, mode_flags)); if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, - (byte*) (*table_ptr)->alias, - strlen((*table_ptr)->alias) + 1))) + (byte*) table->alias, + strlen(table->alias) + 1))) { if (! (mode_flags & MYSQL_HA_REOPEN_ON_USAGE)) { @@ -712,6 +753,7 @@ static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags) } (*table_ptr)->file->ha_index_or_rnd_end(); + safe_mutex_assert_owner(&LOCK_open); if (close_thread_table(thd, table_ptr)) { /* Tell threads waiting for refresh that something has happened */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c0748abf333..0d6ef9bb767 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -103,23 +103,28 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, /* mark for close and remove all cached entries */ - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - VOID(pthread_mutex_lock(&LOCK_open)); - if (!drop_temporary) { if ((error= wait_if_global_read_lock(thd, 0, 1))) { my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->table_name); - goto err; + DBUG_RETURN(TRUE); } else need_start_waiters= TRUE; } + + /* + Acquire LOCK_open after wait_if_global_read_lock(). If we would hold + LOCK_open during wait_if_global_read_lock(), other threads could not + close their tables. This would make a pretty deadlock. + */ + thd->mysys_var->current_mutex= &LOCK_open; + thd->mysys_var->current_cond= &COND_refresh; + VOID(pthread_mutex_lock(&LOCK_open)); + error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); -err: pthread_mutex_unlock(&LOCK_open); pthread_mutex_lock(&thd->mysys_var->mutex); @@ -232,7 +237,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, char *db=table->db; db_type table_type= DB_TYPE_UNKNOWN; - mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, TRUE); if (!close_temporary_table(thd, db, table->table_name)) { tmp_table_deleted=1; @@ -2171,7 +2176,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL, FALSE); for (table= tables; table; table= table->next_local) { char table_name[NAME_LEN*2+2]; @@ -3128,7 +3133,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, new_db= db; used_fields=create_info->used_fields; - mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL); + mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL, FALSE); /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ if (alter_info->tablespace_op != NO_TABLESPACE_OP) DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, From 563e5c8d7961c79106fcf3b83656942ece4b4cd4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Nov 2005 11:52:09 +0100 Subject: [PATCH 05/10] ndb - bug#14007 4.1 [re-commit for LenZ merge] mysql-test/r/ndb_charset.result: bug#14007 test [re-commit] mysql-test/t/ndb_charset.test: bug#14007 test [re-commit] ndb/include/kernel/AttributeDescriptor.hpp: bug#14007 4.1 need getSizeInBytes [re-commit] ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp: bug#14007 4.1 *** do not AUTOmerge to 5.0 *** [re-commit] --- mysql-test/r/ndb_charset.result | 22 +++++++++++++----- mysql-test/t/ndb_charset.test | 19 ++++++++------- ndb/include/kernel/AttributeDescriptor.hpp | 9 ++++++++ ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp | 23 ++++++++++++++++++- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/mysql-test/r/ndb_charset.result b/mysql-test/r/ndb_charset.result index 00bc36a7c0d..b8d881fca83 100644 --- a/mysql-test/r/ndb_charset.result +++ b/mysql-test/r/ndb_charset.result @@ -190,12 +190,22 @@ p a 6 AAA drop table t1; create table t1 ( -a varchar(10) primary key -) engine=ndb; -insert into t1 values ('jonas % '); -replace into t1 values ('jonas % '); -replace into t1 values ('jonas % '); +a char(10) primary key +) engine=ndbcluster default charset=latin1; +insert into t1 values ('aaabb'); select * from t1; a -jonas % +aaabb +replace into t1 set a = 'AAABB'; +select * from t1; +a +AAABB +replace into t1 set a = 'aAaBb'; +select * from t1; +a +aAaBb +replace into t1 set a = 'aaabb'; +select * from t1; +a +aaabb drop table t1; diff --git a/mysql-test/t/ndb_charset.test b/mysql-test/t/ndb_charset.test index 89f1ed17cfb..a885427f593 100644 --- a/mysql-test/t/ndb_charset.test +++ b/mysql-test/t/ndb_charset.test @@ -159,14 +159,17 @@ select * from t1 where a = 'AaA' order by p; select * from t1 where a = 'AAA' order by p; drop table t1; -# bug +# bug#14007 create table t1 ( - a varchar(10) primary key -) engine=ndb; -insert into t1 values ('jonas % '); -replace into t1 values ('jonas % '); -replace into t1 values ('jonas % '); + a char(10) primary key +) engine=ndbcluster default charset=latin1; + +insert into t1 values ('aaabb'); +select * from t1; +replace into t1 set a = 'AAABB'; +select * from t1; +replace into t1 set a = 'aAaBb'; +select * from t1; +replace into t1 set a = 'aaabb'; select * from t1; drop table t1; - -# End of 4.1 tests diff --git a/ndb/include/kernel/AttributeDescriptor.hpp b/ndb/include/kernel/AttributeDescriptor.hpp index 071d45e2607..9d7de21d904 100644 --- a/ndb/include/kernel/AttributeDescriptor.hpp +++ b/ndb/include/kernel/AttributeDescriptor.hpp @@ -36,6 +36,7 @@ private: static Uint32 getType(const Uint32 &); static Uint32 getSize(const Uint32 &); + static Uint32 getSizeInBytes(const Uint32 &); static Uint32 getSizeInWords(const Uint32 &); static Uint32 getArrayType(const Uint32 &); static Uint32 getArraySize(const Uint32 &); @@ -79,6 +80,7 @@ private: #define AD_SIZE_SHIFT (4) #define AD_SIZE_MASK (7) +#define AD_SIZE_IN_BYTES_SHIFT (3) #define AD_SIZE_IN_WORDS_OFFSET (31) #define AD_SIZE_IN_WORDS_SHIFT (5) @@ -185,6 +187,13 @@ AttributeDescriptor::getSize(const Uint32 & desc){ return (desc >> AD_SIZE_SHIFT) & AD_SIZE_MASK; } +inline +Uint32 +AttributeDescriptor::getSizeInBytes(const Uint32 & desc){ + return (getArraySize(desc) << getSize(desc)) + >> AD_SIZE_IN_BYTES_SHIFT; +} + inline Uint32 AttributeDescriptor::getSizeInWords(const Uint32 & desc){ diff --git a/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp b/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp index cbb165c3eb1..7b642f90a17 100644 --- a/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp +++ b/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp @@ -700,6 +700,27 @@ Dbtup::checkUpdateOfPrimaryKey(Uint32* updateBuffer, Tablerec* const regTabPtr) Uint32 attrDescriptorIndex = regTabPtr->tabDescriptor + (attributeId << ZAD_LOG_SIZE); Uint32 attrDescriptor = tableDescriptor[attrDescriptorIndex].tabDescr; Uint32 attributeOffset = tableDescriptor[attrDescriptorIndex + 1].tabDescr; + + Uint32 xfrmBuffer[1 + MAX_KEY_SIZE_IN_WORDS * 1]; // strxfrm_multiply == 1 + Uint32 charsetFlag = AttributeOffset::getCharsetFlag(attributeOffset); + if (charsetFlag) { + Uint32 csPos = AttributeOffset::getCharsetPos(attributeOffset); + CHARSET_INFO* cs = regTabPtr->charsetArray[csPos]; + Uint32 sizeInBytes = AttributeDescriptor::getSizeInBytes(attrDescriptor); + Uint32 sizeInWords = AttributeDescriptor::getSizeInWords(attrDescriptor); + const uchar* srcPtr = (uchar*)&updateBuffer[1]; + uchar* dstPtr = (uchar*)&xfrmBuffer[1]; + Uint32 n = + (*cs->coll->strnxfrm)(cs, dstPtr, sizeInBytes, srcPtr, sizeInBytes); + // pad with blanks (unlikely) and zeroes to match NDB API behaviour + while (n < sizeInBytes) + dstPtr[n++] = 0x20; + while (n < 4 * sizeInWords) + dstPtr[n++] = 0; + xfrmBuffer[0] = ahIn.m_value; + updateBuffer = xfrmBuffer; + } + ReadFunction f = regTabPtr->readFunctionArray[attributeId]; AttributeHeader::init(&attributeHeader, attributeId, 0); @@ -707,7 +728,7 @@ Dbtup::checkUpdateOfPrimaryKey(Uint32* updateBuffer, Tablerec* const regTabPtr) tMaxRead = MAX_KEY_SIZE_IN_WORDS; bool tmp = tXfrmFlag; - tXfrmFlag = false; + tXfrmFlag = true; ndbrequire((this->*f)(&keyReadBuffer[0], ahOut, attrDescriptor, attributeOffset)); tXfrmFlag = tmp; ndbrequire(tOutBufIndex == ahOut->getDataSize()); From 8a661e77ea3759eb0bdc0fd1a0caecc708593732 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Nov 2005 14:09:06 +0200 Subject: [PATCH 06/10] Issuing error about presence of commit/rollback statements in stored functions and triggers added to SP parsing procedure (BUG#13627) The crash mentioned in original bug report is already prevented by one of previous patches (fix for bug #13343 "CREATE|etc TRIGGER|VIEW|USER don't commit the transaction (inconsistency)"), this patch only improve error returning. mysql-test/r/sp-error.result: Test that statements which implicitly commit transaction mysql-test/t/sp-error.test: Test that statements which implicitly commit transaction sql/sp_head.cc: We set the new flag about commit/rollback statements presence sql/sp_head.h: The new flag about commit/rollback presence added A comment fixed sql/sql_yacc.yy: Removed commit/rollback-statement-present errors spread by this file, only one check left which check flags of a SP --- mysql-test/r/sp-error.result | 103 ++++++++++++++++++++++++++ mysql-test/t/sp-error.test | 138 +++++++++++++++++++++++++++++++++++ sql/sp_head.cc | 39 ++++++++++ sql/sp_head.h | 13 +++- sql/sql_yacc.yy | 57 +-------------- 5 files changed, 293 insertions(+), 57 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index fabbf13a96b..2f4b420a2ae 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -872,6 +872,109 @@ names foo4 drop procedure bug13510_3| drop procedure bug13510_4| +drop function if exists bug_13627_f| +CREATE TABLE t1 (a int)| +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN DROP TRIGGER test1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN DROP TRIGGER test1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN load table t1 from master; END | +ERROR 0A000: LOAD TABLE is not allowed in stored procedures +CREATE FUNCTION bug_13627_f() returns int BEGIN load table t1 from master; return 1; END | +ERROR 0A000: LOAD TABLE is not allowed in stored procedures +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create table t2 (a int); END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN create table t2 (a int); return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create index t1_i on t1 (a); END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN create index t1_i on t1 (a); return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN alter table t1 add column b int; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN alter table t1 add column b int; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN rename table t1 to t2; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN rename table t1 to t2; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN truncate table t1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN truncate table t1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop table t1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN drop table t1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop index t1_i on t1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN drop index t1_i on t1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN unlock tables; END | +ERROR 0A000: UNLOCK is not allowed in stored procedures +CREATE FUNCTION bug_13627_f() returns int BEGIN unlock tables; return 1; END | +ERROR 0A000: UNLOCK is not allowed in stored procedures +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN LOCK TABLE t1 READ; END | +ERROR 0A000: LOCK is not allowed in stored procedures +CREATE FUNCTION bug_13627_f() returns int BEGIN LOCK TABLE t1 READ; return 1; END | +ERROR 0A000: LOCK is not allowed in stored procedures +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create database mysqltest; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN create database mysqltest; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop database mysqltest; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN drop database mysqltest; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create user 'mysqltest_1'; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN create user 'mysqltest_1'; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop user 'mysqltest_1'; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN drop user 'mysqltest_1'; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN rename user 'mysqltest_2' to 'mysqltest_1'; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN rename user 'mysqltest_2' to 'mysqltest_1'; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create view v1 as select 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN create view v1 as select 1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN alter view v1 as select 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN alter view v1 as select 1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop view v1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE FUNCTION bug_13627_f() returns int BEGIN drop view v1; return 1; END | +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create trigger tr2 before insert on t1 for each row do select 1; END | +ERROR 2F003: Can't create a TRIGGER from within another stored routine +CREATE FUNCTION bug_13627_f() returns int BEGIN create trigger tr2 before insert on t1 for each row do select 1; return 1; END | +ERROR 2F003: Can't create a TRIGGER from within another stored routine +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop function bug_13627_f; END | +ERROR HY000: Can't drop or alter a FUNCTION from within another stored routine +CREATE FUNCTION bug_13627_f() returns int BEGIN drop function bug_13627_f; return 1; END | +ERROR HY000: Can't drop or alter a FUNCTION from within another stored routine +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create function f2 () returns int return 1; END | +ERROR 2F003: Can't create a FUNCTION from within another stored routine +CREATE FUNCTION bug_13627_f() returns int BEGIN create function f2 () returns int return 1; return 1; END | +ERROR 2F003: Can't create a FUNCTION from within another stored routine +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW +BEGIN +CREATE TEMPORARY TABLE t2 (a int); +DROP TEMPORARY TABLE t2; +END | +CREATE FUNCTION bug_13627_f() returns int +BEGIN +CREATE TEMPORARY TABLE t2 (a int); +DROP TEMPORARY TABLE t2; +return 1; +END | +drop table t1| +drop function bug_13627_f| create database mysqltest1; use mysqltest1; drop database mysqltest1; diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 3c1efe73c18..f16562227f3 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -1263,6 +1263,144 @@ call bug13510_4()| drop procedure bug13510_3| drop procedure bug13510_4| + + +# +# Test that statements which implicitly commit transaction are prohibited +# in stored function and triggers. Attempt to create function or trigger +# containing such statement should produce error (includes test for +# bug #13627). +# +--disable_warnings +drop function if exists bug_13627_f| +--enable_warnings + +CREATE TABLE t1 (a int)| +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN DROP TRIGGER test1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN DROP TRIGGER test1; return 1; END | + +-- error ER_SP_BADSTATEMENT +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN load table t1 from master; END | +-- error ER_SP_BADSTATEMENT +CREATE FUNCTION bug_13627_f() returns int BEGIN load table t1 from master; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create table t2 (a int); END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN create table t2 (a int); return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create index t1_i on t1 (a); END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN create index t1_i on t1 (a); return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN alter table t1 add column b int; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN alter table t1 add column b int; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN rename table t1 to t2; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN rename table t1 to t2; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN truncate table t1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN truncate table t1; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop table t1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN drop table t1; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop index t1_i on t1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN drop index t1_i on t1; return 1; END | + +-- error ER_SP_BADSTATEMENT +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN unlock tables; END | +-- error ER_SP_BADSTATEMENT +CREATE FUNCTION bug_13627_f() returns int BEGIN unlock tables; return 1; END | + +-- error ER_SP_BADSTATEMENT +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN LOCK TABLE t1 READ; END | +-- error ER_SP_BADSTATEMENT +CREATE FUNCTION bug_13627_f() returns int BEGIN LOCK TABLE t1 READ; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create database mysqltest; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN create database mysqltest; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop database mysqltest; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN drop database mysqltest; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create user 'mysqltest_1'; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN create user 'mysqltest_1'; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop user 'mysqltest_1'; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN drop user 'mysqltest_1'; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN rename user 'mysqltest_2' to 'mysqltest_1'; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN rename user 'mysqltest_2' to 'mysqltest_1'; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create view v1 as select 1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN create view v1 as select 1; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN alter view v1 as select 1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN alter view v1 as select 1; return 1; END | + +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop view v1; END | +-- error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION bug_13627_f() returns int BEGIN drop view v1; return 1; END | + +-- error ER_SP_NO_RECURSIVE_CREATE +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create trigger tr2 before insert on t1 for each row do select 1; END | +-- error ER_SP_NO_RECURSIVE_CREATE +CREATE FUNCTION bug_13627_f() returns int BEGIN create trigger tr2 before insert on t1 for each row do select 1; return 1; END | + +-- error ER_SP_NO_DROP_SP +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN drop function bug_13627_f; END | +-- error ER_SP_NO_DROP_SP +CREATE FUNCTION bug_13627_f() returns int BEGIN drop function bug_13627_f; return 1; END | + +-- error ER_SP_NO_RECURSIVE_CREATE +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN create function f2 () returns int return 1; END | +-- error ER_SP_NO_RECURSIVE_CREATE +CREATE FUNCTION bug_13627_f() returns int BEGIN create function f2 () returns int return 1; return 1; END | + +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW + BEGIN + CREATE TEMPORARY TABLE t2 (a int); + DROP TEMPORARY TABLE t2; + END | +CREATE FUNCTION bug_13627_f() returns int + BEGIN + CREATE TEMPORARY TABLE t2 (a int); + DROP TEMPORARY TABLE t2; + return 1; + END | + +drop table t1| +drop function bug_13627_f| + delimiter ;| # diff --git a/sql/sp_head.cc b/sql/sp_head.cc index facd984cc50..e3cdc909048 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -120,6 +120,45 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_DEALLOCATE_PREPARE: flags= sp_head::CONTAINS_DYNAMIC_SQL; break; + case SQLCOM_CREATE_TABLE: + if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + flags= 0; + else + flags= sp_head::HAS_COMMIT_OR_ROLLBACK; + break; + case SQLCOM_DROP_TABLE: + if (lex->drop_temporary) + flags= 0; + else + flags= sp_head::HAS_COMMIT_OR_ROLLBACK; + break; + case SQLCOM_CREATE_INDEX: + case SQLCOM_CREATE_DB: + case SQLCOM_CREATE_VIEW: + case SQLCOM_CREATE_TRIGGER: + case SQLCOM_CREATE_USER: + case SQLCOM_ALTER_TABLE: + case SQLCOM_BEGIN: + case SQLCOM_RENAME_TABLE: + case SQLCOM_RENAME_USER: + case SQLCOM_DROP_INDEX: + case SQLCOM_DROP_DB: + case SQLCOM_DROP_USER: + case SQLCOM_DROP_VIEW: + case SQLCOM_DROP_TRIGGER: + case SQLCOM_TRUNCATE: + case SQLCOM_COMMIT: + case SQLCOM_ROLLBACK: + case SQLCOM_LOAD_MASTER_DATA: + case SQLCOM_LOCK_TABLES: + case SQLCOM_CREATE_PROCEDURE: + case SQLCOM_CREATE_SPFUNCTION: + case SQLCOM_ALTER_PROCEDURE: + case SQLCOM_ALTER_FUNCTION: + case SQLCOM_DROP_PROCEDURE: + case SQLCOM_DROP_FUNCTION: + flags= sp_head::HAS_COMMIT_OR_ROLLBACK; + break; default: flags= 0; break; diff --git a/sql/sp_head.h b/sql/sp_head.h index d1a122fd410..8c2d58a696e 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -115,10 +115,13 @@ public: MULTI_RESULTS= 8, // Is set if a procedure with SELECT(s) CONTAINS_DYNAMIC_SQL= 16, // Is set if a procedure with PREPARE/EXECUTE IS_INVOKED= 32, // Is set if this sp_head is being used - HAS_SET_AUTOCOMMIT_STMT = 64 // Is set if a procedure with 'set autocommit' + HAS_SET_AUTOCOMMIT_STMT= 64,// Is set if a procedure with 'set autocommit' + /* Is set if a procedure with COMMIT (implicit or explicit) | ROLLBACK */ + HAS_COMMIT_OR_ROLLBACK= 128 }; - int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE + /* TYPE_ENUM_FUNCTION, TYPE_ENUM_PROCEDURE or TYPE_ENUM_TRIGGER */ + int m_type; uint m_flags; // Boolean attributes of a stored routine enum enum_field_types m_returns; // For FUNCTIONs only Field::geometry_type m_geom_returns; @@ -292,6 +295,12 @@ public: my_error(ER_SP_NO_RETSET, MYF(0), where); else if (m_flags & HAS_SET_AUTOCOMMIT_STMT) my_error(ER_SP_CANT_SET_AUTOCOMMIT, MYF(0)); + else if (m_type != TYPE_ENUM_PROCEDURE && + (m_flags & sp_head::HAS_COMMIT_OR_ROLLBACK)) + { + my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); + return TRUE; + } return test(m_flags & (CONTAINS_DYNAMIC_SQL|MULTI_RESULTS|HAS_SET_AUTOCOMMIT_STMT)); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 55002def5e9..339091ed4e8 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1159,11 +1159,6 @@ create: | CREATE opt_unique_or_fulltext INDEX_SYM ident key_alg ON table_ident { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, $7, NULL, TL_OPTION_UPDATING)) @@ -3299,11 +3294,6 @@ alter: { THD *thd= YYTHD; LEX *lex= thd->lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_ALTER_TABLE; lex->name= 0; lex->duplicates= DUP_ERROR; @@ -3614,11 +3604,6 @@ start: START_SYM TRANSACTION_SYM start_transaction_opts { LEX *lex= Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_BEGIN; lex->start_transaction_opt= $3; } @@ -3803,13 +3788,7 @@ opt_no_write_to_binlog: rename: RENAME table_or_tables { - LEX *lex= Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } - lex->sql_command=SQLCOM_RENAME_TABLE; + Lex->sql_command= SQLCOM_RENAME_TABLE; } table_to_table_list {} @@ -5946,21 +5925,10 @@ drop: lex->sql_command = SQLCOM_DROP_TABLE; lex->drop_temporary= $2; lex->drop_if_exists= $4; - if (!lex->drop_temporary && lex->sphead && - lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } } | DROP INDEX_SYM ident ON table_ident {} { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_DROP_INDEX; lex->alter_info.drop_list.empty(); lex->alter_info.drop_list.push_back(new Alter_drop(Alter_drop::KEY, @@ -6006,13 +5974,7 @@ drop: } | DROP VIEW_SYM if_exists table_list opt_restrict { - THD *thd= YYTHD; - LEX *lex= thd->lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } + LEX *lex= Lex; lex->sql_command= SQLCOM_DROP_VIEW; lex->drop_if_exists= $3; } @@ -8652,11 +8614,6 @@ begin: BEGIN_SYM { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command = SQLCOM_BEGIN; lex->start_transaction_opt= 0; } @@ -8689,11 +8646,6 @@ commit: COMMIT_SYM opt_work opt_chain opt_release { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_COMMIT; lex->tx_chain= $3; lex->tx_release= $4; @@ -8704,11 +8656,6 @@ rollback: ROLLBACK_SYM opt_work opt_chain opt_release { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - YYABORT; - } lex->sql_command= SQLCOM_ROLLBACK; lex->tx_chain= $3; lex->tx_release= $4; From 74dcfd251b0508e713e49105c806b0ab5a248fd8 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Nov 2005 13:26:26 +0100 Subject: [PATCH 07/10] ndb - bug#14007 5.0 *** does not automerge into 5.1 *** mysql-test/r/ndb_charset.result: bug#14007 5.0 mysql-test/t/ndb_charset.test: bug#14007 5.0 ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp: bug#14007 5.0 ndb/src/kernel/vm/SimulatedBlock.cpp: bug#14007 5.0 ndb/src/kernel/vm/SimulatedBlock.hpp: bug#14007 5.0 --- mysql-test/r/ndb_charset.result | 20 +++- mysql-test/t/ndb_charset.test | 15 ++- ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp | 22 ++--- ndb/src/kernel/vm/SimulatedBlock.cpp | 92 ++++++++++--------- ndb/src/kernel/vm/SimulatedBlock.hpp | 6 +- 5 files changed, 87 insertions(+), 68 deletions(-) diff --git a/mysql-test/r/ndb_charset.result b/mysql-test/r/ndb_charset.result index 500b0497890..3763e20e59a 100644 --- a/mysql-test/r/ndb_charset.result +++ b/mysql-test/r/ndb_charset.result @@ -306,11 +306,21 @@ count(*) drop table t1; create table t1 ( a char(10) primary key -) engine=ndb; -insert into t1 values ('jonas % '); -replace into t1 values ('jonas % '); -replace into t1 values ('jonas % '); +) engine=ndbcluster default charset=latin1; +insert into t1 values ('aaabb'); select * from t1; a -jonas % +aaabb +replace into t1 set a = 'AAABB'; +select * from t1; +a +AAABB +replace into t1 set a = 'aAaBb'; +select * from t1; +a +aAaBb +replace into t1 set a = 'aaabb'; +select * from t1; +a +aaabb drop table t1; diff --git a/mysql-test/t/ndb_charset.test b/mysql-test/t/ndb_charset.test index fb43e1831f3..5941e5750db 100644 --- a/mysql-test/t/ndb_charset.test +++ b/mysql-test/t/ndb_charset.test @@ -237,13 +237,18 @@ drop table t1; #select a,b,length(a),length(b) from t1 where a='c' and b='c'; #drop table t1; -# bug +# bug#14007 create table t1 ( a char(10) primary key -) engine=ndb; -insert into t1 values ('jonas % '); -replace into t1 values ('jonas % '); -replace into t1 values ('jonas % '); +) engine=ndbcluster default charset=latin1; + +insert into t1 values ('aaabb'); +select * from t1; +replace into t1 set a = 'AAABB'; +select * from t1; +replace into t1 set a = 'aAaBb'; +select * from t1; +replace into t1 set a = 'aaabb'; select * from t1; drop table t1; diff --git a/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp b/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp index db916b2d4d2..8a55777ac05 100644 --- a/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp +++ b/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp @@ -685,22 +685,16 @@ Dbtup::checkUpdateOfPrimaryKey(Uint32* updateBuffer, Tablerec* const regTabPtr) Uint32 attrDescriptor = tableDescriptor[attrDescriptorIndex].tabDescr; Uint32 attributeOffset = tableDescriptor[attrDescriptorIndex + 1].tabDescr; - Uint32 xfrmBuffer[1 + MAX_KEY_SIZE_IN_WORDS * 1]; // strxfrm_multiply == 1 + Uint32 xfrmBuffer[1 + MAX_KEY_SIZE_IN_WORDS * MAX_XFRM_MULTIPLY]; Uint32 charsetFlag = AttributeOffset::getCharsetFlag(attributeOffset); if (charsetFlag) { - Uint32 csPos = AttributeOffset::getCharsetPos(attributeOffset); - CHARSET_INFO* cs = regTabPtr->charsetArray[csPos]; - Uint32 sizeInBytes = AttributeDescriptor::getSizeInBytes(attrDescriptor); - Uint32 sizeInWords = AttributeDescriptor::getSizeInWords(attrDescriptor); - const uchar* srcPtr = (uchar*)&updateBuffer[1]; - uchar* dstPtr = (uchar*)&xfrmBuffer[1]; - Uint32 n = - (*cs->coll->strnxfrm)(cs, dstPtr, sizeInBytes, srcPtr, sizeInBytes); - // pad with blanks (unlikely) and zeroes to match NDB API behaviour - while (n < sizeInBytes) - dstPtr[n++] = 0x20; - while (n < 4 * sizeInWords) - dstPtr[n++] = 0; + Uint32 csIndex = AttributeOffset::getCharsetPos(attributeOffset); + CHARSET_INFO* cs = regTabPtr->charsetArray[csIndex]; + Uint32 srcPos = 0; + Uint32 dstPos = 0; + xfrm_attr(attrDescriptor, cs, &updateBuffer[1], srcPos, + &xfrmBuffer[1], dstPos, MAX_KEY_SIZE_IN_WORDS * MAX_XFRM_MULTIPLY); + ahIn.setDataSize(dstPos); xfrmBuffer[0] = ahIn.m_value; updateBuffer = xfrmBuffer; } diff --git a/ndb/src/kernel/vm/SimulatedBlock.cpp b/ndb/src/kernel/vm/SimulatedBlock.cpp index d708052ca4e..4625cd43640 100644 --- a/ndb/src/kernel/vm/SimulatedBlock.cpp +++ b/ndb/src/kernel/vm/SimulatedBlock.cpp @@ -1868,55 +1868,61 @@ SimulatedBlock::xfrm_key(Uint32 tab, const Uint32* src, while (i < noOfKeyAttr) { const KeyDescriptor::KeyAttr& keyAttr = desc->keyAttr[i]; - - Uint32 srcBytes = - AttributeDescriptor::getSizeInBytes(keyAttr.attributeDescriptor); - Uint32 srcWords = (srcBytes + 3) / 4; - Uint32 dstWords = ~0; - uchar* dstPtr = (uchar*)&dst[dstPos]; - const uchar* srcPtr = (const uchar*)&src[srcPos]; - CHARSET_INFO* cs = keyAttr.charsetInfo; - - if (cs == NULL) - { - jam(); - memcpy(dstPtr, srcPtr, srcWords << 2); - dstWords = srcWords; - } - else - { - jam(); - Uint32 typeId = - AttributeDescriptor::getType(keyAttr.attributeDescriptor); - Uint32 lb, len; - bool ok = NdbSqlUtil::get_var_length(typeId, srcPtr, srcBytes, lb, len); - ndbrequire(ok); - Uint32 xmul = cs->strxfrm_multiply; - if (xmul == 0) - xmul = 1; - /* - * Varchar is really Char. End spaces do not matter. To get - * same hash we blank-pad to maximum length via strnxfrm. - * TODO use MySQL charset-aware hash function instead - */ - Uint32 dstLen = xmul * (srcBytes - lb); - ndbrequire(dstLen <= ((dstSize - dstPos) << 2)); - int n = NdbSqlUtil::strnxfrm_bug7284(cs, dstPtr, dstLen, srcPtr + lb, len); - ndbrequire(n != -1); - while ((n & 3) != 0) - { - dstPtr[n++] = 0; - } - dstWords = (n >> 2); - } - dstPos += dstWords; - srcPos += srcWords; + Uint32 dstWords = + xfrm_attr(keyAttr.attributeDescriptor, keyAttr.charsetInfo, + src, srcPos, dst, dstPos, dstSize); keyPartLen[i++] = dstWords; } return dstPos; } +Uint32 +SimulatedBlock::xfrm_attr(Uint32 attrDesc, CHARSET_INFO* cs, + const Uint32* src, Uint32 & srcPos, + Uint32* dst, Uint32 & dstPos, Uint32 dstSize) const +{ + Uint32 srcBytes = AttributeDescriptor::getSizeInBytes(attrDesc); + Uint32 srcWords = (srcBytes + 3) / 4; + Uint32 dstWords = ~0; + uchar* dstPtr = (uchar*)&dst[dstPos]; + const uchar* srcPtr = (const uchar*)&src[srcPos]; + + if (cs == NULL) + { + jam(); + memcpy(dstPtr, srcPtr, srcWords << 2); + dstWords = srcWords; + } + else + { + jam(); + Uint32 typeId = AttributeDescriptor::getType(attrDesc); + Uint32 lb, len; + bool ok = NdbSqlUtil::get_var_length(typeId, srcPtr, srcBytes, lb, len); + ndbrequire(ok); + Uint32 xmul = cs->strxfrm_multiply; + if (xmul == 0) + xmul = 1; + /* + * Varchar end-spaces are ignored in comparisons. To get same hash + * we blank-pad to maximum length via strnxfrm. + */ + Uint32 dstLen = xmul * (srcBytes - lb); + ndbrequire(dstLen <= ((dstSize - dstPos) << 2)); + int n = NdbSqlUtil::strnxfrm_bug7284(cs, dstPtr, dstLen, srcPtr + lb, len); + ndbrequire(n != -1); + while ((n & 3) != 0) + { + dstPtr[n++] = 0; + } + dstWords = (n >> 2); + } + dstPos += dstWords; + srcPos += srcWords; + return dstWords; +} + Uint32 SimulatedBlock::create_distr_key(Uint32 tableId, Uint32 *data, diff --git a/ndb/src/kernel/vm/SimulatedBlock.hpp b/ndb/src/kernel/vm/SimulatedBlock.hpp index ce77fa916d8..b7bd8c57ee8 100644 --- a/ndb/src/kernel/vm/SimulatedBlock.hpp +++ b/ndb/src/kernel/vm/SimulatedBlock.hpp @@ -395,8 +395,12 @@ protected: * @return length */ Uint32 xfrm_key(Uint32 tab, const Uint32* src, - Uint32 *dst, Uint32 dstLen, + Uint32 *dst, Uint32 dstSize, Uint32 keyPartLen[MAX_ATTRIBUTES_IN_INDEX]) const; + + Uint32 xfrm_attr(Uint32 attrDesc, CHARSET_INFO* cs, + const Uint32* src, Uint32 & srcPos, + Uint32* dst, Uint32 & dstPos, Uint32 dstSize) const; /** * From dcf5d348cc74d9ad8963903e9b614c950a7e109a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Nov 2005 15:17:08 +0100 Subject: [PATCH 08/10] bug#14433 - archive uses wrong ref_length mysql-test/t/func_group.test: re-enable the test --- mysql-test/t/disabled.def | 1 - mysql-test/t/func_group.test | 4 ++-- sql/ha_archive.cc | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index eedf4b30e73..fe95a543fb5 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -15,4 +15,3 @@ rpl_relayrotate : Unstable test case, bug#12429 rpl_until : Unstable test case, bug#12429 rpl_deadlock : Unstable test case, bug#12429 kill : Unstable test case, bug#9712 -archive_gis : The test fails on 32bit Linux diff --git a/mysql-test/t/func_group.test b/mysql-test/t/func_group.test index 9237205eeb5..c667f90940c 100644 --- a/mysql-test/t/func_group.test +++ b/mysql-test/t/func_group.test @@ -2,8 +2,6 @@ # simple test of all group functions # ---source include/have_innodb.inc - --disable_warnings drop table if exists t1,t2; --enable_warnings @@ -545,10 +543,12 @@ DROP TABLE t1; # Bug #12882 min/max inconsistent on empty table # +--disable_warnings create table t1m (a int) engine=myisam; create table t1i (a int) engine=innodb; create table t2m (a int) engine=myisam; create table t2i (a int) engine=innodb; +--enable_warnings insert into t2m values (5); insert into t2i values (5); diff --git a/sql/ha_archive.cc b/sql/ha_archive.cc index c4801de5fb2..1e8fc582eb8 100644 --- a/sql/ha_archive.cc +++ b/sql/ha_archive.cc @@ -233,7 +233,8 @@ ha_archive::ha_archive(TABLE *table_arg) buffer.set((char *)byte_buffer, IO_SIZE, system_charset_info); /* The size of the offset value we will use for position() */ - ref_length = sizeof(z_off_t); + ref_length = 2 << ((zlibCompileFlags() >> 6) & 3); + DBUG_ASSERT(ref_length <= sizeof(z_off_t)); } /* From cd1abd99db00f92311f5b3bba85fe6e997a29f0d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Nov 2005 15:58:17 +0100 Subject: [PATCH 09/10] Bug#14616 - Freshly imported table returns error 124 when using LIMIT After merge fix. --- sql/sql_select.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index ae40a81643f..9c7df461a3e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11132,7 +11132,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, Check which keys can be used to resolve ORDER BY. We must not try to use disabled keys. */ - usable_keys= table->keys_in_use; + usable_keys= table->s->keys_in_use; for (ORDER *tmp_order=order; tmp_order ; tmp_order=tmp_order->next) { From b67076102b62454e5560cd5a7a484366b47eb984 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Nov 2005 03:15:10 +0300 Subject: [PATCH 10/10] A fix and a test case for Bug#14077 "Failure to replicate a stored function with a cursor". Enable execution of SELECT queries in SP on slave. mysql-test/r/rpl_sp.result: Test results were fixed (Bug#14077). mysql-test/t/rpl_sp.test: Add a test case for Bug#14077 "Failure to replicate a stored function with a cursor". sql/sql_parse.cc: Do not rewrite SELECTs with DOs on slave: if this SELECT was for a stored routine cursor, slave must be able to execute the SELECT in order to open a cursor. At the moment the bug is present only in stored functions and stored procedures called from stored functions, because, due to stored procedure unfolding for replication, top level stored procedures are never executed on slave. --- mysql-test/r/rpl_sp.result | 25 +++++++++++++++++++++++++ mysql-test/t/rpl_sp.test | 36 ++++++++++++++++++++++++++++++++++++ sql/sql_parse.cc | 12 ------------ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/rpl_sp.result b/mysql-test/r/rpl_sp.result index ba840caf6c2..41bcfc7d72c 100644 --- a/mysql-test/r/rpl_sp.result +++ b/mysql-test/r/rpl_sp.result @@ -375,3 +375,28 @@ drop procedure foo; drop function fn1; drop database mysqltest1; drop user "zedjzlcsjhd"@127.0.0.1; +use test; +use test; +drop function if exists f1; +create function f1() returns int reads sql data +begin +declare var integer; +declare c cursor for select a from v1; +open c; +fetch c into var; +close c; +return var; +end| +create view v1 as select 1 as a; +create table t1 (a int); +insert into t1 (a) values (f1()); +select * from t1; +a +1 +drop view v1; +drop function f1; +select * from t1; +a +1 +drop table t1; +reset master; diff --git a/mysql-test/t/rpl_sp.test b/mysql-test/t/rpl_sp.test index e7a3afca9cb..386582f8f1b 100644 --- a/mysql-test/t/rpl_sp.test +++ b/mysql-test/t/rpl_sp.test @@ -360,4 +360,40 @@ connection master; drop function fn1; drop database mysqltest1; drop user "zedjzlcsjhd"@127.0.0.1; +use test; sync_slave_with_master; +use test; + +# +# Bug#14077 "Failure to replicate a stored function with a cursor": +# verify that stored routines with cursors work on slave. +# +connection master; +--disable_warnings +drop function if exists f1; +--enable_warnings +delimiter |; +create function f1() returns int reads sql data +begin + declare var integer; + declare c cursor for select a from v1; + open c; + fetch c into var; + close c; + return var; +end| +delimiter ;| +create view v1 as select 1 as a; +create table t1 (a int); +insert into t1 (a) values (f1()); +select * from t1; +drop view v1; +drop function f1; +sync_slave_with_master; +connection slave; +select * from t1; + +# cleanup +connection master; +drop table t1; +reset master; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index c19d54feda5..1e6810e0036 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2403,18 +2403,6 @@ mysql_execute_command(THD *thd) reset_one_shot_variables(thd); DBUG_RETURN(0); } -#ifndef TO_BE_DELETED - /* - This is a workaround to deal with the shortcoming in 3.23.44-3.23.46 - masters in RELEASE_LOCK() logging. We re-write SELECT RELEASE_LOCK() - as DO RELEASE_LOCK() - */ - if (lex->sql_command == SQLCOM_SELECT) - { - lex->sql_command = SQLCOM_DO; - lex->insert_list = &select_lex->item_list; - } -#endif } else #endif /* HAVE_REPLICATION */